@@ -0,0 +1,114 @@ | |||
Language: Cpp | |||
# BasedOnStyle: LLVM | |||
AccessModifierOffset: -2 | |||
AlignAfterOpenBracket: Align | |||
AlignConsecutiveAssignments: false | |||
AlignConsecutiveDeclarations: false | |||
AlignEscapedNewlines: DontAlign | |||
AlignOperands: true | |||
AlignTrailingComments: false | |||
AllowAllParametersOfDeclarationOnNextLine: true | |||
AllowShortBlocksOnASingleLine: false | |||
AllowShortCaseLabelsOnASingleLine: false | |||
AllowShortFunctionsOnASingleLine: All | |||
AllowShortIfStatementsOnASingleLine: false | |||
AllowShortLoopsOnASingleLine: false | |||
AlwaysBreakAfterDefinitionReturnType: None | |||
AlwaysBreakAfterReturnType: None | |||
AlwaysBreakBeforeMultilineStrings: false | |||
AlwaysBreakTemplateDeclarations: Yes | |||
BinPackArguments: true | |||
BinPackParameters: true | |||
BreakBeforeBraces: Allman | |||
BraceWrapping: | |||
AfterClass: true | |||
AfterControlStatement: true | |||
AfterEnum: true | |||
AfterFunction: true | |||
AfterNamespace: true | |||
AfterObjCDeclaration: true | |||
AfterStruct: true | |||
AfterUnion: true | |||
AfterExternBlock: true | |||
BeforeCatch: true | |||
BeforeElse: true | |||
IndentBraces: true | |||
SplitEmptyFunction: false | |||
SplitEmptyRecord: false | |||
SplitEmptyNamespace: false | |||
BreakBeforeBinaryOperators: None | |||
BreakBeforeInheritanceComma: false | |||
BreakInheritanceList: BeforeColon | |||
BreakBeforeTernaryOperators: true | |||
BreakConstructorInitializersBeforeComma: false | |||
BreakConstructorInitializers: BeforeColon | |||
BreakAfterJavaFieldAnnotations: false | |||
BreakStringLiterals: true | |||
ColumnLimit: 120 | |||
CommentPragmas: '^ [NOTE|WARNING|TODO|FIXME]:' | |||
CompactNamespaces: false | |||
ConstructorInitializerAllOnOneLineOrOnePerLine: false | |||
ConstructorInitializerIndentWidth: 4 | |||
ContinuationIndentWidth: 4 | |||
Cpp11BracedListStyle: true | |||
DerivePointerAlignment: false | |||
DisableFormat: false | |||
FixNamespaceComments: false | |||
ForEachMacros: | |||
- foreach | |||
- Q_FOREACH | |||
- BOOST_FOREACH | |||
IncludeBlocks: Preserve | |||
IncludeCategories: | |||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/' | |||
Priority: 2 | |||
- Regex: '^(<|"(gtest|gmock|isl|json)/)' | |||
Priority: 3 | |||
- Regex: '.*' | |||
Priority: 1 | |||
IncludeIsMainRegex: '(Test)?$' | |||
IndentCaseLabels: false | |||
IndentPPDirectives: None | |||
IndentWidth: 2 | |||
IndentWrappedFunctionNames: false | |||
JavaScriptQuotes: Leave | |||
JavaScriptWrapImports: true | |||
KeepEmptyLinesAtTheStartOfBlocks: true | |||
MacroBlockBegin: '' | |||
MacroBlockEnd: '' | |||
MaxEmptyLinesToKeep: 1 | |||
NamespaceIndentation: All | |||
ObjCBinPackProtocolList: Auto | |||
ObjCBlockIndentWidth: 2 | |||
ObjCSpaceAfterProperty: false | |||
ObjCSpaceBeforeProtocolList: true | |||
PenaltyBreakAssignment: 2 | |||
PenaltyBreakBeforeFirstCallParameter: 19 | |||
PenaltyBreakComment: 300 | |||
PenaltyBreakFirstLessLess: 120 | |||
PenaltyBreakString: 1000 | |||
PenaltyBreakTemplateDeclaration: 10 | |||
PenaltyExcessCharacter: 1000000 | |||
PenaltyReturnTypeOnItsOwnLine: 60 | |||
PointerAlignment: Left | |||
ReflowComments: true | |||
SortIncludes: false | |||
SortUsingDeclarations: false | |||
SpaceAfterCStyleCast: false | |||
SpaceAfterTemplateKeyword: false | |||
SpaceBeforeAssignmentOperators: true | |||
SpaceBeforeCpp11BracedList: false | |||
SpaceBeforeCtorInitializerColon: true | |||
SpaceBeforeInheritanceColon: true | |||
SpaceBeforeParens: Never | |||
SpaceBeforeRangeBasedForLoopColon: false | |||
SpaceInEmptyParentheses: false | |||
SpacesBeforeTrailingComments: 1 | |||
SpacesInAngles: false | |||
SpacesInContainerLiterals: false | |||
SpacesInCStyleCastParentheses: false | |||
SpacesInParentheses: false | |||
SpacesInSquareBrackets: false | |||
Standard: Cpp11 | |||
TabWidth: 2 | |||
UseTab: Never |
@@ -0,0 +1,189 @@ | |||
#CLion | |||
ws | |||
.idea | |||
#Kdev | |||
*.kdev* | |||
# | |||
bin | |||
*.swp | |||
# ---> C | |||
# Object files | |||
*.o | |||
*.ko | |||
*.obj | |||
*.elf | |||
# Precompiled Headers | |||
*.gch | |||
*.pch | |||
# Libraries | |||
*.lib | |||
*.a | |||
*.la | |||
*.lo | |||
# Shared objects (inc. Windows DLLs) | |||
*.dll | |||
*.so | |||
*.so.* | |||
*.dylib | |||
# Executables | |||
*.exe | |||
*.out | |||
*.app | |||
*.i*86 | |||
*.x86_64 | |||
*.hex | |||
# Debug files | |||
*.dSYM/ | |||
# ---> C++ | |||
# Compiled Object files | |||
*.slo | |||
*.lo | |||
*.o | |||
*.obj | |||
# Precompiled Headers | |||
*.gch | |||
*.pch | |||
# Compiled Dynamic libraries | |||
*.so | |||
*.dylib | |||
*.dll | |||
# Fortran module files | |||
*.mod | |||
# Compiled Static libraries | |||
*.lai | |||
*.la | |||
*.a | |||
*.lib | |||
# Executables | |||
*.exe | |||
*.out | |||
*.app | |||
# ---> Node | |||
# Logs | |||
logs | |||
*.log | |||
npm-debug.log* | |||
# Runtime data | |||
pids | |||
*.pid | |||
*.seed | |||
# Directory for instrumented libs generated by jscoverage/JSCover | |||
lib-cov | |||
# Coverage directory used by tools like istanbul | |||
coverage | |||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | |||
.grunt | |||
# node-waf configuration | |||
.lock-wscript | |||
# Compiled binary addons (http://nodejs.org/api/addons.html) | |||
build/Release | |||
# Dependency directory | |||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git | |||
node_modules | |||
# ---> Qt | |||
# C++ objects and libs | |||
*.slo | |||
*.lo | |||
*.o | |||
*.a | |||
*.la | |||
*.lai | |||
*.so | |||
*.dll | |||
*.dylib | |||
# Qt-es | |||
/.qmake.cache | |||
/.qmake.stash | |||
*.pro.user | |||
*.pro.user.* | |||
*.qbs.user | |||
*.qbs.user.* | |||
*.moc | |||
moc_*.cpp | |||
qrc_*.cpp | |||
ui_*.h | |||
Makefile.* | |||
*-build-* | |||
# QtCreator | |||
*.autosave | |||
#QtCtreator Qml | |||
*.qmlproject.user | |||
*.qmlproject.user.* | |||
# ---> Ruby | |||
*.gem | |||
*.rbc | |||
/.config | |||
/coverage/ | |||
/InstalledFiles | |||
/pkg/ | |||
/spec/reports/ | |||
/spec/examples.txt | |||
/test/tmp/ | |||
/test/version_tmp/ | |||
/tmp/ | |||
## Specific to RubyMotion: | |||
.dat* | |||
.repl_history | |||
build/ | |||
## Documentation cache and generated files: | |||
/.yardoc/ | |||
/_yardoc/ | |||
/doc/ | |||
/rdoc/ | |||
## Environment normalisation: | |||
/.bundle/ | |||
/vendor/bundle | |||
/lib/bundler/man/ | |||
# for a library or gem, you might want to ignore these files since the code is | |||
# intended to run in multiple environments; otherwise, check them in: | |||
# Gemfile.lock | |||
# .ruby-version | |||
# .ruby-gemset | |||
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: | |||
.rvmrc | |||
# ---> Rust | |||
# Compiled files | |||
*.o | |||
*.so | |||
*.rlib | |||
*.dll | |||
# Executables | |||
*.exe | |||
# Generated by Cargo | |||
/target/ | |||
.vs |
@@ -0,0 +1,4 @@ | |||
[submodule "ws"] | |||
path = ws | |||
url = https://github.com/abbycin/ws | |||
branch = dev |
@@ -0,0 +1,35 @@ | |||
cmake_minimum_required(VERSION 3.15) | |||
project(ws CXX) | |||
add_library(ws INTERFACE) | |||
target_include_directories(ws INTERFACE ${PROJECT_SOURCE_DIR}) | |||
set(CMAKE_CXX_STANDARD 17) | |||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |||
set(CMAKE_CXX_EXTENSIONS OFF) | |||
if("${CMAKE_CXX_COMPILER}" MATCHES "clang") | |||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") | |||
endif() | |||
set(CMAKE_VERBOSE_MAKEFILE OFF) | |||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) | |||
find_package(Threads) | |||
list(APPEND LIBS Threads::Threads) | |||
list(APPEND INC ${PROJECT_SOURCE_DIR}) | |||
if(NOT ASIO_PATH) | |||
set(ASIO_PATH /opt/asio-1.12.2/include) | |||
endif() | |||
if(NOT EXISTS ${ASIO_PATH}) | |||
message(FATAL_ERROR "you must supply asio directory which contains asio.hpp by using -DASIO_PATH=/path/to/asio") | |||
else() | |||
list(APPEND INC "${ASIO_PATH}") | |||
add_definitions(-DASIO_STANDALONE -DASIO_HAS_STD_CHRONO) | |||
endif() | |||
include_directories(${INC}) | |||
add_executable(chat common.h chat.cpp) | |||
target_link_libraries(chat ${LIBS}) |
@@ -0,0 +1,226 @@ | |||
/*********************************************** | |||
File Name: push_server.cpp | |||
Author: Abby Cin | |||
Mail: abbytsing@gmail.com | |||
Created Time: 4/24/20 10:04 PM | |||
***********************************************/ | |||
#include "common.h" | |||
#include "ws/websocket.h" | |||
#include <iostream> | |||
#include <set> | |||
using sock_t = ws::stream<asio::ip::tcp::socket>; | |||
class PushHandler; | |||
using PushHandlerPtr = std::shared_ptr<PushHandler>; | |||
using PushHandlerWPtr = std::weak_ptr<PushHandler>; | |||
class WsServer | |||
{ | |||
public: | |||
WsServer(asio::ip::tcp::endpoint ep, int n); | |||
~WsServer() | |||
{ | |||
if(is_running_) | |||
{ | |||
stop(); | |||
} | |||
} | |||
void run(); | |||
void accept(); | |||
void stop(); | |||
void insert(PushHandlerPtr conn); | |||
void push(const ws::Buffer& buf); | |||
private: | |||
bool is_running_{false}; | |||
std::mutex mtx_{}; | |||
IoContextPool pool_; | |||
std::unique_ptr<asio::ip::tcp::acceptor> acceptor_; | |||
std::unique_ptr<asio::signal_set> signals_; | |||
std::list<PushHandlerWPtr> clients_; | |||
}; | |||
class PushHandler : public std::enable_shared_from_this<PushHandler> | |||
{ | |||
public: | |||
explicit PushHandler(asio::io_context& ioc, WsServer* server) : sock_{ioc}, server_{server} {} | |||
~PushHandler() | |||
{ | |||
std::cerr << __func__ << '\n'; | |||
} | |||
static std::shared_ptr<PushHandler> create_instance(asio::io_context& ioc, WsServer* server) | |||
{ | |||
return std::make_shared<PushHandler>(ioc, server); | |||
} | |||
asio::ip::tcp::socket& socket() { return sock_.next_layer(); } | |||
void run() | |||
{ | |||
sock_.set_ping_msg("are you ok?", std::chrono::seconds(5)); | |||
auto self = shared_from_this(); | |||
sock_.accept([self, this](http::header& h, const std::error_code& ec) { | |||
if(ec) | |||
{ | |||
std::cerr << "upgrade: " << ec.message() << '\n'; | |||
} | |||
else | |||
{ | |||
do_read(); | |||
} | |||
}); | |||
} | |||
bool alive() { return alive_; } | |||
void send(const ws::Buffer& buf) | |||
{ | |||
auto self = shared_from_this(); | |||
sock_.write_text(buf, [self, this](const std::error_code& ec, size_t) { | |||
if(ec) | |||
{ | |||
std::cerr << "send error: " << ec.message() << '\n'; | |||
alive_ = false; | |||
sock_.force_close(); | |||
} | |||
}); | |||
} | |||
private: | |||
bool alive_{true}; | |||
sock_t sock_; | |||
WsServer* server_; | |||
void do_read() | |||
{ | |||
if(alive_) | |||
{ | |||
auto self = shared_from_this(); | |||
sock_.read([self, this](const std::error_code& ec, const ws::Buffer& buf) { | |||
if(ec) | |||
{ | |||
std::cerr << "read: " << ec.message() << "; " << buf << '\n'; | |||
sock_.force_close(); | |||
} | |||
else | |||
{ | |||
server_->push(buf); | |||
do_read(); | |||
} | |||
}); | |||
} | |||
} | |||
}; | |||
WsServer::WsServer(asio::ip::tcp::endpoint ep, int n) : pool_{n}, acceptor_{}, signals_{} | |||
{ | |||
signals_.reset(new asio::signal_set{pool_.get_context()}); | |||
signals_->add(SIGINT); | |||
signals_->add(SIGTERM); | |||
signals_->async_wait([this](const asio::error_code& e, int s) { | |||
if(e) | |||
{ | |||
std::cerr << e.message() << '\n'; | |||
} | |||
else | |||
{ | |||
std::cerr << "received signal " << s << ", exit...\n"; | |||
} | |||
this->stop(); | |||
}); | |||
acceptor_.reset(new asio::ip::tcp::acceptor{pool_.get_context()}); | |||
acceptor_->open(ep.protocol()); | |||
acceptor_->set_option(asio::socket_base::reuse_address(true)); | |||
#ifdef __linux__ | |||
using reuse_port = asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT>; | |||
acceptor_->set_option(reuse_port(true)); | |||
#endif | |||
acceptor_->bind(ep); | |||
acceptor_->listen(); | |||
}; | |||
void WsServer::run() | |||
{ | |||
accept(); | |||
is_running_ = true; | |||
pool_.run(); | |||
} | |||
void WsServer::accept() | |||
{ | |||
acceptor_->async_accept([this](const std::error_code& e, asio::ip::tcp::socket sock) { | |||
if(e) | |||
{ | |||
std::cerr << e.message() << '\n'; | |||
return; | |||
} | |||
auto conn = PushHandler::create_instance(pool_.get_context(), this); | |||
conn->socket() = std::move(sock); | |||
insert(conn); | |||
conn->run(); | |||
accept(); | |||
}); | |||
} | |||
void WsServer::push(const ws::Buffer& buf) | |||
{ | |||
std::lock_guard<std::mutex> lg{mtx_}; | |||
// `buf` will be invalid after `read`, since it's a view of internal read buffer | |||
std::string data{buf.peek(), buf.readable_size()}; | |||
for(auto iter = clients_.begin(); iter != clients_.end();) | |||
{ | |||
auto c = iter->lock(); | |||
if(c) | |||
{ | |||
if(c->alive()) | |||
{ | |||
c->send(ws::buffer(data)); | |||
} | |||
++iter; | |||
} | |||
else | |||
{ | |||
iter = clients_.erase(iter); | |||
} | |||
} | |||
} | |||
void WsServer::insert(PushHandlerPtr conn) | |||
{ | |||
std::lock_guard<std::mutex> lg{mtx_}; | |||
clients_.push_back(conn); | |||
} | |||
void WsServer::stop() | |||
{ | |||
if(is_running_) | |||
{ | |||
std::error_code ec; | |||
acceptor_->cancel(ec); | |||
pool_.stop(); | |||
is_running_ = false; | |||
clients_.clear(); | |||
} | |||
} | |||
int main(int argc, char* argv[]) | |||
{ | |||
int n = 1; | |||
if(argc == 2) | |||
{ | |||
n = std::stoi(argv[1]); | |||
} | |||
WsServer server{asio::ip::tcp::endpoint{asio::ip::address_v4::any(), 8889}, n}; | |||
server.run(); | |||
} |
@@ -0,0 +1,70 @@ | |||
/********************************************************* | |||
File Name: common.h | |||
Author: Abby Cin | |||
Mail: abbytsing@gmail.com | |||
Created Time: Fri 06 Mar 2020 08:22:37 PM CST | |||
**********************************************************/ | |||
#ifndef WS_COMMON_H | |||
#define WS_COMMON_H | |||
#include <thread> | |||
#include <vector> | |||
#include "asio.hpp" | |||
class IoContextPool | |||
{ | |||
public: | |||
IoContextPool(int n = std::thread::hardware_concurrency()) | |||
{ | |||
for(int i = 0; i < n; ++i) | |||
{ | |||
auto ctx = new asio::io_context{}; | |||
work_.emplace_back(new asio::io_context::work{*ctx}); | |||
ctx_.emplace_back(ctx); | |||
} | |||
} | |||
~IoContextPool() { stop(); } | |||
asio::io_context& get_context() | |||
{ | |||
if(cur_ == ctx_.size()) | |||
{ | |||
cur_ = 0; | |||
} | |||
return *ctx_[cur_++]; | |||
} | |||
void run() | |||
{ | |||
for(size_t i = 0; i < ctx_.size(); ++i) | |||
{ | |||
worker_.emplace_back(new std::thread{[](std::shared_ptr<asio::io_context> ioc) { ioc->run(); }, ctx_[i]}); | |||
} | |||
for(auto& t: worker_) | |||
{ | |||
try | |||
{ | |||
t->join(); | |||
} | |||
catch(...) | |||
{ | |||
} | |||
} | |||
} | |||
void stop() | |||
{ | |||
for(auto& c: ctx_) | |||
{ | |||
c->stop(); | |||
} | |||
} | |||
private: | |||
size_t cur_{0}; | |||
std::vector<std::shared_ptr<asio::io_context>> ctx_{}; | |||
std::vector<std::shared_ptr<asio::io_context::work>> work_{}; // work_ should destroy before ctx_ | |||
std::vector<std::shared_ptr<std::thread>> worker_{}; | |||
}; | |||
#endif // WS_COMMON_H |