提交 ba88f0b5 编写于 作者: Y youngwolf 提交者: youngowlf

Rename the name of classes and header files according to ascs, relocate header...

Rename the name of classes and header files according to ascs, relocate header files according to ascs, macros been kept.
Drop standard edition, use ascs instead.
Support unstripped message.
Truly support asio 1.11.
Optimize send buffer if possible (asio 1.11).
Add two demos for concurrent test.
上级 0b1cacb1
...@@ -9,5 +9,3 @@ Makefile ...@@ -9,5 +9,3 @@ Makefile
*.project *.project
*.workspace *.workspace
.* .*
!.gitignore
...@@ -2,7 +2,7 @@ st_asio_wrapper ...@@ -2,7 +2,7 @@ st_asio_wrapper
=============== ===============
Overview Overview
- -
st_asio_wrapper is an asynchronous c/s framework based on Boost.Asio, besides all benefits brought by Boost and Boost.Asio, it also contain: </br> st_asio_wrapper is an asynchronous c/s framework based on Boost.Asio, besides all benefits brought by Boost and Boost.Asio, it also contains: </br>
1. Based on message just like UDP with several couple of build-in packer and unpacker;</br> 1. Based on message just like UDP with several couple of build-in packer and unpacker;</br>
2. Support packer and unpacker customization, and replacing packer and unpacker at run-time;</br> 2. Support packer and unpacker customization, and replacing packer and unpacker at run-time;</br>
3. Automatically reconnect to the server after link broken;</br> 3. Automatically reconnect to the server after link broken;</br>
...@@ -11,51 +11,47 @@ st_asio_wrapper is an asynchronous c/s framework based on Boost.Asio, besides al ...@@ -11,51 +11,47 @@ st_asio_wrapper is an asynchronous c/s framework based on Boost.Asio, besides al
6. Support message buffer;</br> 6. Support message buffer;</br>
7. Support ssl;</br> 7. Support ssl;</br>
8. Support TCP/UDP.</br> 8. Support TCP/UDP.</br>
Quick start: Quick start:
- -
###server: ### server:
Derive your own socket from `st_server_socket`, you must at least re-write the `on_msg` or `on_msg_handle` virtual function and handle messages in it;</br> Derive your own socket from `server_socket_base`, you must at least re-write the `on_msg` or `on_msg_handle` virtual function and handle messages in it;</br>
Create a `st_service_pump` object, create a `st_server` object, call `st_service_pump::start_service`;</br> Create a `service_pump` object, create a `server_base` object, call `service_pump::start_service`;</br>
Call `st_server_socket::send_msg` when you have messages need to send.</br> Call `server_socket_base::send_msg` when you have messages need to send.</br>
###client: ### client:
Derive your own socket from `st_connector`, you must at least re-write the `on_msg` or `on_msg_handle` virtual function and handle messages in it;</br> Derive your own socket from `client_socket_base`, you must at least re-write the `on_msg` or `on_msg_handle` virtual function and handle messages in it;</br>
Create a `st_service_pump` object, create a `st_tcp_client` object, set server address via `st_connector::set_server_addr`, call `st_service_pump::start_service`;</br> Create a `service_pump` object, create a `tcp::client_base` object, set server address via `client_socket_base::set_server_addr`, call `service_pump::start_service`;</br>
Call `st_connector::send_msg` when you have messages need to send.</br> Call `client_socket_base::send_msg` when you have messages need to send.</br>
Directory structure: Directory structure:
- -
Directory `compatible_edition` has the same structure as `st_asio_wrapper`, it is so called compatible edition, because it doesn't used any C++0x features, so it can be compiled by old compilers which don't support C++0x.</br> All source codes are placed in directory `include`, other directories hold demos, documents are placed in directory `doc`.</br>
All source codes are placed in directory `include`, other directories hold demos (except `compatible_edition`).</br>
Demos: Demos:
- -
###asio_server: ### echo_server:
Demonstrate how to implement tcp servers, it cantains two servers, one is the simplest server (normal server), which just send characters from keyboard to all `asio_clients`, and receive messages from all `asio_clients` (then display them); the other is echo server, which send every received message from `test_clients` back.</br> Demonstrate how to implement tcp servers, it cantains two servers, one is the simplest server (normal server), which just send characters from keyboard to all clients (from `client` demo), and receive messages from all clients (from `client` demo), then display them; the other is echo server, which send every received message from `echo_client` demo back.</br>
###asio_client: ### client:
Demonstrate how to implement tcp client, it simply send characters from keyboard to `asio_server`, and receive messages from `asio_server` (then display them).</br> Demonstrate how to implement tcp client, it simply send characters from keyboard to normal server in `echo_server`, and receive messages from normal server in `echo_server`, then display them.</br>
###test_client: ### echo_client:
Used to test the performance of `echo server`.</br> Used to test `st_asio_wrapper`'s performance (whith `echo server`).</br>
###file_server: ### file_server:
A file transfer server.</br> A file transfer server.</br>
###file_client: ### file_client:
A file transfer client, use `get <file name1> [file name2] [...]` to fetch files from `file_server`.</br> A file transfer client, use `get <file name1> [file name2] [...]` to fetch files from `file_server`.</br>
###udp_client: ### udp_client:
Demonstrate how to implement UDP communication.</br> Demonstrate how to implement UDP communication.</br>
###ssl_test: ### ssl_test:
Demonstrate how to implement TCP communication with ssl.</br> Demonstrate how to implement TCP communication with ssl.</br>
Compiler requirement: Compiler requirement:
- -
Normal edition need Visual C++ 10.0, GCC 4.6 or Clang 3.1 at least;</br> No special limitations, just need you to compile boost successfully.</br>
Compatible edition does not have any limitations, just need you to compile boost successfully.</br>
Boost requirement: Boost requirement:
- -
1.49 or highter, earlier edition maybe can work, but I'm not sure.</br> 1.49 or highter, earlier edition maybe can work, but I'm not sure.</br>
Debug environment:
-
win10 vc2008/vc2015 32/64 bit</br>
Win7 vc2010 32/64 bit</br>
Debian 7/8 32/64 bit</br>
Ubuntu 16 64 bit</br>
Fedora 23 64 bit</br>
FreeBSD 10 32/64 bit</br>
email: mail2tao@163.com email: mail2tao@163.com
- -
Community on QQ: 198941541 Community on QQ: 198941541
......
#include <iostream>
//configuration
#define ST_ASIO_SERVER_PORT 9527
#define ST_ASIO_DELAY_CLOSE 1 //this demo not used object pool and doesn't need life cycle management,
//so, define this to avoid hooks for async call (and slightly improve efficiency),
//any value which is bigger than zero is okay.
#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
#define ST_ASIO_CUSTOM_LOG
#define ST_ASIO_DEFAULT_UNPACKER non_copy_unpacker
//#define ST_ASIO_DEFAULT_UNPACKER stream_unpacker
//the following three macros demonstrate how to support huge msg(exceed 65535 - 2).
//huge msg will consume huge memory, for example, if we want to support 1M msg size, because every st_tcp_socket has a
//private unpacker which has a fixed buffer with at lest 1M size, so just for unpackers, 1K st_tcp_socket will consume 1G memory.
//if we consider the send buffer and recv buffer, the buffer's default max size is 1K, so, every st_tcp_socket
//can consume 2G(2 * 1M * 1K) memory when performance testing(both send buffer and recv buffer are full).
//generally speaking, if there are 1K clients connected to the server, the server can consume
//1G(occupied by unpackers) + 2G(occupied by msg buffer) * 1K = 2049G memory theoretically.
//please note that the server also need to define at least ST_ASIO_HUGE_MSG and ST_ASIO_MSG_BUFFER_SIZE macros too.
//#define ST_ASIO_HUGE_MSG
//#define ST_ASIO_MSG_BUFFER_SIZE (1024 * 1024)
//#define ST_ASIO_MAX_MSG_NUM 8 //reduce msg buffer size to reduce memory occupation
#define ST_ASIO_HEARTBEAT_INTERVAL 5 //if use stream_unpacker, heartbeat messages will be treated as normal messages,
//because stream_unpacker doesn't support heartbeat
//configuration
//demonstrate how to use custom log system:
//use your own code to replace the following all_out_helper2 macros, then you can record logs according to your wishes.
//custom log should be defined(or included) before including any st_asio_wrapper header files except st_asio_wrapper_base.h
//notice: please don't forget to define the ST_ASIO_CUSTOM_LOG macro.
#include "../include/st_asio_wrapper_base.h"
using namespace st_asio_wrapper;
class unified_out
{
public:
static void fatal_out(const char* fmt, ...) {all_out_helper2("fatal");}
static void error_out(const char* fmt, ...) {all_out_helper2("error");}
static void warning_out(const char* fmt, ...) {all_out_helper2("warning");}
static void info_out(const char* fmt, ...) {all_out_helper2("info");}
static void debug_out(const char* fmt, ...) {all_out_helper2("debug");}
};
#include "../include/ext/st_asio_wrapper_tcp.h"
using namespace st_asio_wrapper::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define RECONNECT_COMMAND "reconnect"
int main(int argc, const char* argv[])
{
printf("usage: %s [<port=%d> [ip=%s]]\n", argv[0], ST_ASIO_SERVER_PORT + 100, ST_ASIO_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
st_service_pump sp;
st_tcp_sclient client(sp);
// argv[2] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4
if (argc > 2)
client.set_server_addr(atoi(argv[1]), argv[2]);
else if (argc > 1)
client.set_server_addr(atoi(argv[1]), ST_ASIO_SERVER_IP);
else
client.set_server_addr(ST_ASIO_SERVER_PORT + 100, ST_ASIO_SERVER_IP);
sp.start_service();
while(sp.is_running())
{
std::string str;
std::cin >> str;
if (QUIT_COMMAND == str)
sp.stop_service();
else if (RESTART_COMMAND == str)
{
sp.stop_service();
sp.start_service();
}
else if (RECONNECT_COMMAND == str)
client.graceful_shutdown(true);
else
client.sync_send_msg(str);
// client.safe_send_msg(str);
}
return 0;
}
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{F1789FAC-53CB-4ACF-920C-829D93758315}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>asio_client</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib64;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib64;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile />
<PrecompiledHeaderOutputFile />
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>
</PrecompiledHeaderOutputFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="asio_client.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
\ No newline at end of file
module = asio_client
include ../config.mk
#include <iostream>
//configuration
#define ST_ASIO_SERVER_PORT 9527
#define ST_ASIO_REUSE_OBJECT //use objects pool
//#define ST_ASIO_FREE_OBJECT_INTERVAL 60 //it's useless if ST_ASIO_REUSE_OBJECT macro been defined
//#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
#define ST_ASIO_ENHANCED_STABILITY
//#define ST_ASIO_FULL_STATISTIC //full statistic will slightly impact efficiency
//#define ST_ASIO_USE_STEADY_TIMER
//#define ST_ASIO_USE_SYSTEM_TIMER
//#define ST_ASIO_MAX_MSG_NUM 16
//if there's a huge number of links, please reduce messge buffer via ST_ASIO_MAX_MSG_NUM macro.
//please think about if we have 512 links, how much memory we can accupy at most with default ST_ASIO_MAX_MSG_NUM?
//it's 2 * 1024 * 1024 * 512 = 1G
//use the following macro to control the type of packer and unpacker
#define PACKER_UNPACKER_TYPE 0
//0-default packer and unpacker, head(length) + body
//1-replaceable packer and unpacker, head(length) + body
//2-fixed length packer and unpacker
//3-prefix and/or suffix packer and unpacker
#if 1 == PACKER_UNPACKER_TYPE
#define ST_ASIO_DEFAULT_PACKER replaceable_packer<>
#define ST_ASIO_DEFAULT_UNPACKER replaceable_unpacker<>
#elif 2 == PACKER_UNPACKER_TYPE
#undef ST_ASIO_HEARTBEAT_INTERVAL
#define ST_ASIO_HEARTBEAT_INTERVAL 0 //not support heartbeat
#define ST_ASIO_DEFAULT_PACKER fixed_length_packer
#define ST_ASIO_DEFAULT_UNPACKER fixed_length_unpacker
#elif 3 == PACKER_UNPACKER_TYPE
#undef ST_ASIO_HEARTBEAT_INTERVAL
#define ST_ASIO_HEARTBEAT_INTERVAL 0 //not support heartbeat
#define ST_ASIO_DEFAULT_PACKER prefix_suffix_packer
#define ST_ASIO_DEFAULT_UNPACKER prefix_suffix_unpacker
#endif
//configuration
#include "../include/ext/st_asio_wrapper_tcp.h"
using namespace st_asio_wrapper;
using namespace st_asio_wrapper::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define LIST_ALL_CLIENT "list_all_client"
#define LIST_STATUS "status"
//demonstrate how to use custom packer
//under the default behavior, each st_tcp_socket has their own packer, and cause memory waste
//at here, we make each echo_socket use the same global packer for memory saving
//notice: do not do this for unpacker, because unpacker has member variables and can't share each other
auto global_packer(boost::make_shared<ST_ASIO_DEFAULT_PACKER>());
//about congestion control
//
//in 1.3, congestion control has been removed (no post_msg nor post_native_msg anymore), this is because
//without known the business (or logic), framework cannot always do congestion control properly.
//now, users should take the responsibility to do congestion control, there're two ways:
//
//1. for receiver, if you cannot handle msgs timely, which means the bottleneck is in your business,
// you should open/close congestion control intermittently;
// for sender, send msgs in on_msg_send() or use sending buffer limitation (like safe_send_msg(..., false)),
// but must not in service threads, please note.
//
//2. for sender, if responses are available (like pingpong test), send msgs in on_msg()/on_msg_handle(),
// but this will reduce IO throughput because SOCKET's sliding window is not fully used, pleae note.
//
//asio_server chose method #1
//demonstrate how to control the type of st_server_socket_base::server from template parameter
class i_echo_server : public i_server
{
public:
virtual void test() = 0;
};
class echo_socket : public st_server_socket_base<ST_ASIO_DEFAULT_PACKER, ST_ASIO_DEFAULT_UNPACKER, i_echo_server>
{
public:
echo_socket(i_echo_server& server_) : st_server_socket_base(server_)
{
packer(global_packer);
#if 2 == PACKER_UNPACKER_TYPE
boost::dynamic_pointer_cast<ST_ASIO_DEFAULT_UNPACKER>(unpacker())->fixed_length(1024);
#elif 3 == PACKER_UNPACKER_TYPE
boost::dynamic_pointer_cast<ST_ASIO_DEFAULT_UNPACKER>(unpacker())->prefix_suffix("begin", "end");
#endif
}
public:
//because we use objects pool(REUSE_OBJECT been defined), so, strictly speaking, this virtual
//function must be rewrote, but we don't have member variables to initialize but invoke father's
//reset() directly, so, it can be omitted, but we keep it for possibly future using
virtual void reset() {st_server_socket_base::reset();}
protected:
virtual void on_recv_error(const boost::system::error_code& ec)
{
//the type of st_server_socket_base::server now can be controlled by derived class(echo_socket),
//which is actually i_echo_server, so, we can invoke i_echo_server::test virtual function.
server.test();
st_server_socket_base::on_recv_error(ec);
}
//msg handling: send the original msg back(echo server)
//congestion control, method #1, the peer needs its own congestion control too.
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
//this virtual function doesn't exists if ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER been defined
virtual bool on_msg(out_msg_type& msg)
{
auto re = send_msg(msg.data(), msg.size());
if (!re)
{
//cannot handle (send it back) this msg timely, begin congestion control
//'msg' will be put into receiving buffer, and be dispatched via on_msg_handle() in the future
congestion_control(true);
//unified_out::warning_out("open congestion control."); //too many prompts will affect efficiency
}
return re;
}
virtual bool on_msg_handle(out_msg_type& msg)
{
auto re = send_msg(msg.data(), msg.size());
if (re)
{
//successfully handled the only one msg in receiving buffer, end congestion control
//subsequent msgs will be dispatched via on_msg() again.
congestion_control(false);
//unified_out::warning_out("close congestion control."); //too many prompts will affect efficiency
}
return re;
}
#else
//if we used receiving buffer, congestion control will become much simpler, like this:
virtual bool on_msg_handle(out_msg_type& msg) {return send_msg(msg.data(), msg.size());}
#endif
//msg handling end
};
class echo_server : public st_server_base<echo_socket, st_object_pool<echo_socket>, i_echo_server>
{
public:
echo_server(st_service_pump& service_pump_) : st_server_base(service_pump_) {}
statistic get_statistic()
{
statistic stat;
do_something_to_all([&stat](object_ctype& item) {stat += item->get_statistic();});
return stat;
}
//from i_echo_server, pure virtual function, we must implement it.
virtual void test() {/*puts("in echo_server::test()");*/}
};
#if ST_ASIO_HEARTBEAT_INTERVAL > 0
typedef st_server_socket_base<packer, unpacker> normal_socket;
#else
//demonstrate how to open heartbeat function without defining macro ST_ASIO_HEARTBEAT_INTERVAL
class normal_socket : public st_server_socket_base<packer, unpacker>
{
public:
normal_socket(i_server& server_) : st_server_socket_base(server_) {}
protected:
virtual bool do_start()
{
//demo asio_client needs heartbeat (macro ST_ASIO_HEARTBEAT_INTERVAL been defined), pleae note that the interval (here is 5) must be equal to
//macro ST_ASIO_HEARTBEAT_INTERVAL defined in demo asio_client, and macro ST_ASIO_HEARTBEAT_MAX_ABSENCE must has the same value as demo asio_client's.
start_heartbeat(5);
return st_server_socket_base::do_start();
}
};
#endif
int main(int argc, const char* argv[])
{
printf("usage: %s [<service thread number=1> [<port=%d> [ip=0.0.0.0]]]\n", argv[0], ST_ASIO_SERVER_PORT);
puts("normal server's port will be 100 larger.");
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
st_service_pump sp;
//only need a simple server? you can directly use st_server or st_server_base, because of normal_socket,
//this server cannot support fixed_length_packer/fixed_length_unpacker and prefix_suffix_packer/prefix_suffix_unpacker,
//the reason is these packer and unpacker need additional initializations that normal_socket not implemented,
//see echo_socket's constructor for more details.
st_server_base<normal_socket> server_(sp);
echo_server echo_server_(sp); //echo server
if (argc > 3)
{
server_.set_server_addr(atoi(argv[2]) + 100, argv[3]);
echo_server_.set_server_addr(atoi(argv[2]), argv[3]);
}
else if (argc > 2)
{
server_.set_server_addr(atoi(argv[2]) + 100);
echo_server_.set_server_addr(atoi(argv[2]));
}
else
server_.set_server_addr(ST_ASIO_SERVER_PORT + 100);
auto thread_num = 1;
if (argc > 1)
thread_num = std::min(16, std::max(thread_num, atoi(argv[1])));
#if 3 == PACKER_UNPACKER_TYPE
global_packer->prefix_suffix("begin", "end");
#endif
sp.start_service(thread_num);
while(sp.is_running())
{
std::string str;
std::cin >> str;
if (QUIT_COMMAND == str)
sp.stop_service();
else if (RESTART_COMMAND == str)
{
sp.stop_service();
sp.start_service(thread_num);
}
else if (LIST_STATUS == str)
{
printf("normal server, link #: " ST_ASIO_SF ", invalid links: " ST_ASIO_SF "\n", server_.size(), server_.invalid_object_size());
printf("echo server, link #: " ST_ASIO_SF ", invalid links: " ST_ASIO_SF "\n", echo_server_.size(), echo_server_.invalid_object_size());
puts("");
puts(echo_server_.get_statistic().to_string().data());
}
else if (LIST_ALL_CLIENT == str)
{
puts("clients from normal server:");
server_.list_all_object();
puts("clients from echo server:");
echo_server_.list_all_object();
}
else
{
// /*
//broadcast series functions call pack_msg for each client respectively, because clients may used different protocols(so different type of packers, of course)
server_.sync_broadcast_msg(str.data(), str.size() + 1);
//send \0 character too, because demo asio_client used basic_buffer as its msg type, it will not append \0 character automatically as std::string does,
//so need \0 character when printing it.
// */
/*
//broadcast series functions call pack_msg for each client respectively, because clients may used different protocols(so different type of packers, of course)
server_.broadcast_msg(str.data(), str.size() + 1);
//send \0 character too, because demo asio_client used basic_buffer as its msg type, it will not append \0 character automatically as std::string does,
//so need \0 character when printing it.
*/
/*
//if all clients used the same protocol, we can pack msg one time, and send it repeatedly like this:
packer p;
auto msg = p.pack_msg(str.data(), str.size() + 1);
//send \0 character too, because demo asio_client used basic_buffer as its msg type, it will not append \0 character automatically as std::string does,
//so need \0 character when printing it.
if (!msg.empty())
server_.do_something_to_all([&msg](st_server_base<normal_server_socket>::object_ctype& item) {item->direct_send_msg(msg);});
*/
/*
//if demo asio_client is using stream_unpacker
if (!str.empty())
server_.do_something_to_all([&str](st_server_base<normal_server_socket>::object_ctype& item) {item->direct_send_msg(str);});
*/
}
}
return 0;
}
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{CBD5B85C-0054-4CC7-93D6-3CC4AA72081C}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>asio_server</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib64;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>C:\project\boost_1_62_0;$(IncludePath)</IncludePath>
<LibraryPath>C:\project\boost_1_62_0\stage\lib64;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="asio_server.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
\ No newline at end of file
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
//#define ST_ASIO_DEFAULT_UNPACKER stream_unpacker //#define ST_ASIO_DEFAULT_UNPACKER stream_unpacker
//the following three macros demonstrate how to support huge msg(exceed 65535 - 2). //the following three macros demonstrate how to support huge msg(exceed 65535 - 2).
//huge msg will consume huge memory, for example, if we want to support 1M msg size, because every st_tcp_socket has a //huge msg will consume huge memory, for example, if we want to support 1M msg size, because every tcp::socket has a
//private unpacker which has a fixed buffer with at lest 1M size, so just for unpackers, 1K st_tcp_socket will consume 1G memory. //private unpacker which has a fixed buffer with at lest 1M size, so just for unpackers, 1K tcp::socket will consume 1G memory.
//if we consider the send buffer and recv buffer, the buffer's default max size is 1K, so, every st_tcp_socket //if we consider the send buffer and recv buffer, the buffer's default max size is 1K, so, every tcp::socket
//can consume 2G(2 * 1M * 1K) memory when performance testing(both send buffer and recv buffer are full). //can consume 2G(2 * 1M * 1K) memory when performance testing(both send buffer and recv buffer are full).
//generally speaking, if there are 1K clients connected to the server, the server can consume //generally speaking, if there are 1K clients connected to the server, the server can consume
//1G(occupied by unpackers) + 2G(occupied by msg buffer) * 1K = 2049G memory theoretically. //1G(occupied by unpackers) + 2G(occupied by msg buffer) * 1K = 2049G memory theoretically.
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
//demonstrate how to use custom log system: //demonstrate how to use custom log system:
//use your own code to replace the following all_out_helper2 macros, then you can record logs according to your wishes. //use your own code to replace the following all_out_helper2 macros, then you can record logs according to your wishes.
//custom log should be defined(or included) before including any st_asio_wrapper header files except st_asio_wrapper_base.h //custom log should be defined(or included) before including any st_asio_wrapper header files except base.h
//notice: please don't forget to define the ST_ASIO_CUSTOM_LOG macro. //notice: please don't forget to define the ST_ASIO_CUSTOM_LOG macro.
#include "../include/st_asio_wrapper_base.h" #include "../include/base.h"
using namespace st_asio_wrapper; using namespace st_asio_wrapper;
class unified_out class unified_out
...@@ -44,8 +44,8 @@ public: ...@@ -44,8 +44,8 @@ public:
static void debug_out(const char* fmt, ...) {all_out_helper2("debug");} static void debug_out(const char* fmt, ...) {all_out_helper2("debug");}
}; };
#include "../include/ext/st_asio_wrapper_tcp.h" #include "../include/ext/tcp.h"
using namespace st_asio_wrapper::ext; using namespace st_asio_wrapper::ext::tcp;
#define QUIT_COMMAND "quit" #define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart" #define RESTART_COMMAND "restart"
...@@ -59,8 +59,8 @@ int main(int argc, const char* argv[]) ...@@ -59,8 +59,8 @@ int main(int argc, const char* argv[])
else else
puts("type " QUIT_COMMAND " to end."); puts("type " QUIT_COMMAND " to end.");
st_service_pump sp; service_pump sp;
st_tcp_sclient client(sp); single_client client(sp);
// argv[2] = "::1" //ipv6 // argv[2] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4 // argv[2] = "127.0.0.1" //ipv4
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
<VisualStudioProject <VisualStudioProject
ProjectType="Visual C++" ProjectType="Visual C++"
Version="9.00" Version="9.00"
Name="asio_client" Name="client"
ProjectGUID="{88097F72-F14C-4144-9832-22D33A1946CA}" ProjectGUID="{88097F72-F14C-4144-9832-22D33A1946CA}"
RootNamespace="asio_client" RootNamespace="client"
Keyword="Win32Proj" Keyword="Win32Proj"
TargetFrameworkVersion="196613" TargetFrameworkVersion="196613"
> >
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<Tool <Tool
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="0" Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true" MinimalRebuild="true"
BasicRuntimeChecks="3" BasicRuntimeChecks="3"
RuntimeLibrary="3" RuntimeLibrary="3"
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
<Tool <Tool
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="0" Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true" MinimalRebuild="true"
BasicRuntimeChecks="3" BasicRuntimeChecks="3"
RuntimeLibrary="3" RuntimeLibrary="3"
...@@ -189,7 +189,7 @@ ...@@ -189,7 +189,7 @@
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="2" Optimization="2"
EnableIntrinsicFunctions="true" EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2" RuntimeLibrary="2"
EnableFunctionLevelLinking="true" EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0" UsePrecompiledHeader="0"
...@@ -264,7 +264,7 @@ ...@@ -264,7 +264,7 @@
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="2" Optimization="2"
EnableIntrinsicFunctions="true" EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2" RuntimeLibrary="2"
EnableFunctionLevelLinking="true" EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0" UsePrecompiledHeader="0"
...@@ -321,7 +321,7 @@ ...@@ -321,7 +321,7 @@
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
> >
<File <File
RelativePath=".\asio_client.cpp" RelativePath=".\client.cpp"
> >
</File> </File>
</Filter> </Filter>
......
module = udp_test module = client
include ../config.mk include ../config.mk
module = asio_server
include ../config.mk
# If your compiler cannot find boost, please specify it explicitly like this:
#boost_include_dir = -I/usr/local/include/
#boost_lib_dir = -L/usr/local/lib/
cflag = -Wall -fexceptions -std=c++98
ifeq (${MAKECMDGOALS}, debug)
cflag += -g -DDEBUG
dir = debug
else
cflag += -O -DNDEBUG
lflag = -s
dir = release
endif
kernel = ${shell uname -s}
ifeq (${kernel}, SunOS)
cflag += -pthreads ${ext_cflag} ${boost_include_dir}
lflag += -pthreads -lsocket -lnsl ${boost_lib_dir} -lboost_system -lboost_thread ${ext_libs}
else
ifeq (${kernel}, FreeBSD)
cflag += -pthread ${ext_cflag} ${boost_include_dir}
lflag += -pthread ${boost_lib_dir} -lboost_system -lboost_thread -lboost_atomic ${ext_libs}
else # here maybe still have other kernels need to be separated out
cflag += -pthread ${ext_cflag} ${boost_include_dir}
lflag += -pthread ${boost_lib_dir} -lboost_system -lboost_thread ${ext_libs}
endif
endif
target = ${dir}/${module}
sources = ${shell ls *.cpp}
objects = ${patsubst %.cpp,${dir}/%.o,${sources}}
deps = ${patsubst %.o,%.d,${objects}}
${shell mkdir -p ${dir}}
release debug : ${target}
-include ${deps}
${target} : ${objects}
${CXX} -o $@ $^ ${lflag}
${objects} : ${dir}/%.o : %.cpp
${CXX} ${cflag} -E -MMD -w -MT '$@' -MF ${subst .cpp,.d,${dir}/$<} $< 1>/dev/null
${CXX} ${cflag} -c $< -o $@
.PHONY : clean
clean:
-rm -rf debug release
#include <iostream>
#include <boost/tokenizer.hpp>
//configuration
#define ST_ASIO_SERVER_PORT 5051
#define ST_ASIO_DELAY_CLOSE 5 //define this to avoid hooks for async call (and slightly improve efficiency)
//#define ST_ASIO_INPUT_QUEUE non_lock_queue
//we cannot use non_lock_queue, because we also send messages (talking messages) out of st_asio_wrapper::st_socket::on_msg_send().
#define ST_ASIO_DEFAULT_UNPACKER replaceable_unpacker<>
#define ST_ASIO_RECV_BUFFER_TYPE std::vector<boost::asio::mutable_buffer> //scatter-gather buffer, it's very useful under certain situations (for example, ring buffer).
#define ST_ASIO_SCATTERED_RECV_BUFFER //used by unpackers, not belongs to st_asio_wrapper
//configuration
#include "file_client.h"
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define REQUEST_FILE "get"
#if BOOST_VERSION >= 105300
boost::atomic_ushort completed_client_num;
#else
st_atomic<unsigned short> completed_client_num;
#endif
int link_num = 1;
fl_type file_size;
int main(int argc, const char* argv[])
{
puts("this is a file transfer client.");
printf("usage: %s [<port=%d> [<ip=%s> [link num=1]]]\n", argv[0], ST_ASIO_SERVER_PORT, ST_ASIO_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
st_service_pump sp;
file_client client(sp);
if (argc > 3)
link_num = std::min(256, std::max(atoi(argv[3]), 1));
for (int i = 0; i < link_num; ++i)
{
// argv[2] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4
if (argc > 2)
client.add_socket(atoi(argv[1]), argv[2])->set_index(i);
else if (argc > 1)
client.add_socket(atoi(argv[1]))->set_index(i);
else
client.add_socket()->set_index(i);
}
sp.start_service();
while(sp.is_running())
{
std::string str;
std::getline(std::cin, str);
if (QUIT_COMMAND == str)
sp.stop_service();
else if (RESTART_COMMAND == str)
{
sp.stop_service();
sp.start_service();
}
else if (str.size() > sizeof(REQUEST_FILE) && !strncmp(REQUEST_FILE, str.data(), sizeof(REQUEST_FILE) - 1) && isspace(str[sizeof(REQUEST_FILE) - 1]))
{
str.erase(0, sizeof(REQUEST_FILE));
boost::char_separator<char> sep(" \t");
boost::tokenizer<boost::char_separator<char> > tok(str, sep);
for (BOOST_AUTO(iter, tok.begin()); iter != tok.end(); ++iter)
{
completed_client_num = 0;
file_size = 0;
printf("transfer %s begin.\n", iter->data());
if (client.find(0)->get_file(*iter))
{
client.do_something_to_all(boost::lambda::if_then(0U != boost::lambda::bind((boost::uint_fast64_t (file_socket::*)() const) &file_socket::id, *boost::lambda::_1),
boost::lambda::bind(&file_socket::get_file, *boost::lambda::_1, *iter)));
client.start();
while (completed_client_num != (unsigned short) link_num)
boost::this_thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50));
client.stop(*iter);
}
else
printf("transfer %s failed!\n", iter->data());
}
}
else
client.at(0)->talk(str);
}
return 0;
}
#ifndef FILE_CLIENT_H_
#define FILE_CLIENT_H_
#include <boost/timer/timer.hpp>
#include "../include/ext/st_asio_wrapper_tcp.h"
using namespace st_asio_wrapper;
using namespace st_asio_wrapper::ext;
#include "unpacker.h"
#if BOOST_VERSION >= 105300
extern boost::atomic_ushort completed_client_num;
#else
extern st_atomic<unsigned short> completed_client_num;
#endif
extern int link_num;
extern fl_type file_size;
class file_socket : public base_socket, public st_client_socket
{
public:
file_socket(boost::asio::io_service& io_service_) : st_client_socket(io_service_), index(-1) {}
virtual ~file_socket() {clear();}
//reset all, be ensure that there's no any operations performed on this file_socket when invoke it
virtual void reset() {clear(); st_client_socket::reset();}
void set_index(int index_) {index = index_;}
fl_type get_rest_size() const
{
BOOST_AUTO(up, boost::dynamic_pointer_cast<const data_unpacker>(unpacker()));
return up ? up->get_rest_size() : 0;
}
operator fl_type() const {return get_rest_size();}
bool get_file(const std::string& file_name)
{
assert(!file_name.empty());
if (TRANS_IDLE != state)
return false;
if (0 == id())
file = fopen(file_name.data(), "w+b");
else
file = fopen(file_name.data(), "r+b");
if (NULL == file)
{
printf("can't create file %s.\n", file_name.data());
return false;
}
std::string order("\0", ORDER_LEN);
order += file_name;
state = TRANS_PREPARE;
send_msg(order, true);
return true;
}
void talk(const std::string& str)
{
if (TRANS_IDLE == state && !str.empty())
{
std::string order("\2", ORDER_LEN);
order += str;
send_msg(order, true);
}
}
protected:
//msg handling
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
//we always handle messages in on_msg(), so we don't care the type of input queue and input container at all.
virtual bool on_msg(out_msg_type& msg) {handle_msg(msg); return true;}
#endif
//we will change unpacker at runtime, this operation can only be done in on_msg(), reset() or constructor,
//so we must guarantee all messages to be handled in on_msg()
//virtual bool on_msg_handle(out_msg_type& msg) {handle_msg(msg); return true;}
//msg handling end
virtual void on_connect()
{
boost::uint_fast64_t id = index;
char buffer[ORDER_LEN + sizeof(boost::uint_fast64_t)];
*buffer = 3; //head
memcpy(boost::next(buffer, ORDER_LEN), &id, sizeof(boost::uint_fast64_t));
send_msg(buffer, sizeof(buffer), true);
st_client_socket::on_connect();
}
private:
void clear()
{
state = TRANS_IDLE;
if (NULL != file)
{
fclose(file);
file = NULL;
}
unpacker(boost::make_shared<ST_ASIO_DEFAULT_UNPACKER>());
}
void trans_end() {clear(); ++completed_client_num;}
void handle_msg(out_msg_ctype& msg)
{
if (TRANS_BUSY == state)
{
assert(msg.empty());
trans_end();
return;
}
else if (msg.size() <= ORDER_LEN)
{
printf("wrong order length: " ST_ASIO_SF ".\n", msg.size());
return;
}
switch (*msg.data())
{
case 0:
if (ORDER_LEN + DATA_LEN == msg.size() && NULL != file && TRANS_PREPARE == state)
{
fl_type length;
memcpy(&length, boost::next(msg.data(), ORDER_LEN), DATA_LEN);
if (-1 == length)
{
if (0 == index)
puts("get file failed!");
trans_end();
}
else
{
if (0 == index)
file_size = length;
fl_type my_length = length / link_num;
fl_type offset = my_length * index;
if (link_num - 1 == index)
my_length = length - offset;
if (my_length > 0)
{
char buffer[ORDER_LEN + OFFSET_LEN + DATA_LEN];
*buffer = 1; //head
memcpy(boost::next(buffer, ORDER_LEN), &offset, OFFSET_LEN);
memcpy(boost::next(buffer, ORDER_LEN + OFFSET_LEN), &my_length, DATA_LEN);
state = TRANS_BUSY;
send_msg(buffer, sizeof(buffer), true);
fseeko(file, offset, SEEK_SET);
unpacker(boost::make_shared<data_unpacker>(file, my_length));
}
else
trans_end();
}
}
break;
case 2:
if (0 == index)
printf("server says: %s\n", boost::next(msg.data(), ORDER_LEN));
break;
default:
break;
}
}
private:
int index;
};
class file_client : public st_tcp_client_base<file_socket>
{
public:
static const tid TIMER_BEGIN = st_tcp_client_base<file_socket>::TIMER_END;
static const tid UPDATE_PROGRESS = TIMER_BEGIN;
static const tid TIMER_END = TIMER_BEGIN + 10;
file_client(st_service_pump& service_pump_) : st_tcp_client_base<file_socket>(service_pump_) {}
void start()
{
begin_time.start();
set_timer(UPDATE_PROGRESS, 50, boost::bind(&file_client::update_progress_handler, this, _1, -1));
}
void stop(const std::string& file_name)
{
stop_timer(UPDATE_PROGRESS);
update_progress_handler(UPDATE_PROGRESS, 0);
double used_time = (double) begin_time.elapsed().wall / 1000000000;
printf("\ntransfer %s end, speed: %f MBps.\n", file_name.data(), file_size / used_time / 1024 / 1024);
}
fl_type get_total_rest_size()
{
fl_type total_rest_size = 0;
do_something_to_all(total_rest_size += *boost::lambda::_1);
// do_something_to_all(total_rest_size += boost::lambda::bind(&file_socket::get_rest_size, *boost::lambda::_1));
return total_rest_size;
}
private:
bool update_progress_handler(tid id, unsigned last_percent)
{
assert(UPDATE_PROGRESS == id);
fl_type total_rest_size = get_total_rest_size();
unsigned new_percent = (unsigned) ((file_size - total_rest_size) * 100 / file_size);
if (last_percent != new_percent)
{
printf("\r%u%%", new_percent);
fflush(stdout);
ST_THIS update_timer_info(id, 50, boost::bind(&file_client::update_progress_handler, this, _1, new_percent));
}
return total_rest_size > 0;
}
protected:
boost::timer::cpu_timer begin_time;
};
#endif //#ifndef FILE_CLIENT_H_
module = file_client
ext_cflag = -D_FILE_OFFSET_BITS=64
ext_libs = -lboost_timer -lboost_chrono
include ../config.mk
#ifndef UNPACKER_H_
#define UNPACKER_H_
#include "../include/st_asio_wrapper_base.h"
using namespace st_asio_wrapper;
#include "../file_server/common.h"
class data_unpacker : public i_unpacker<replaceable_buffer>
{
public:
data_unpacker(FILE* file, fl_type data_len) : _file(file), _data_len(data_len)
{
assert(NULL != _file);
buffer = new char[boost::asio::detail::default_max_transfer_size];
assert(NULL != buffer);
}
~data_unpacker() {delete[] buffer;}
fl_type get_rest_size() const {return _data_len;}
virtual void reset() {_file = NULL; delete[] buffer; buffer = NULL; _data_len = 0;}
virtual bool parse_msg(size_t bytes_transferred, container_type& msg_can)
{
assert(_data_len >= (fl_type) bytes_transferred && bytes_transferred > 0);
_data_len -= bytes_transferred;
if (bytes_transferred != fwrite(buffer, 1, bytes_transferred, _file))
{
printf("fwrite(" ST_ASIO_SF ") error!\n", bytes_transferred);
return false;
}
if (0 == _data_len)
msg_can.emplace_back();
return true;
}
virtual size_t completion_condition(const boost::system::error_code& ec, size_t bytes_transferred) {return ec ? 0 : boost::asio::detail::default_max_transfer_size;}
virtual buffer_type prepare_next_recv()
{
size_t buffer_len = _data_len > boost::asio::detail::default_max_transfer_size ? boost::asio::detail::default_max_transfer_size : (size_t) _data_len;
#ifdef ST_ASIO_SCATTERED_RECV_BUFFER
return buffer_type(1, boost::asio::buffer(buffer, buffer_len));
#else
return boost::asio::buffer(buffer, buffer_len);
#endif
}
protected:
FILE* _file;
char* buffer;
fl_type _data_len;
};
#endif //UNPACKER_H_
#ifndef COMMON_H_
#define COMMON_H_
#include <stdio.h>
#ifdef _MSC_VER
#define fseeko _fseeki64
#define ftello _ftelli64
#define fl_type __int64
#else
#define fl_type off_t
#endif
#define ORDER_LEN sizeof(char)
#define OFFSET_LEN sizeof(fl_type)
#define DATA_LEN OFFSET_LEN
/*
protocol:
head(1 byte) + body
if head equal to:
0: body is a filename
request the file length, client->server->client
return: same head + file length(8 bytes)
1: body is file offset(8 bytes) + data length(8 bytes)
request the file content, client->server->client
return: file content(no-protocol), repeat until all data requested by client been sent(client only need to request one time)
2: body is talk content
talk, client->server. please note that server cannot talk to client, this is because server never knows whether
it is going to transmit a file or not.
return: n/a
3: body is object id(8 bytes)
change file server's object ids, demonstrate how to use macro ST_ASIO_RESTORE_OBJECT.
return: n/a
*/
class base_socket
{
public:
base_socket() : state(TRANS_IDLE), file(NULL) {}
protected:
enum TRANS_STATE {TRANS_IDLE, TRANS_PREPARE, TRANS_BUSY};
TRANS_STATE state;
FILE* file;
};
#endif // COMMON_H_
#ifndef FILE_BUFFER_H_
#define FILE_BUFFER_H_
#include "../include/st_asio_wrapper_base.h"
using namespace st_asio_wrapper;
#include "common.h"
class file_buffer : public i_buffer
{
public:
file_buffer(FILE* file, fl_type data_len) : _file(file), _data_len(data_len)
{
assert(NULL != _file);
buffer = new char[boost::asio::detail::default_max_transfer_size];
assert(NULL != buffer);
read();
}
~file_buffer() {delete[] buffer;}
public:
virtual bool empty() const {return 0 == buffer_len;}
virtual size_t size() const {return buffer_len;}
virtual const char* data() const {return buffer;}
void read()
{
if (0 == _data_len)
buffer_len = 0;
else
{
buffer_len = _data_len > boost::asio::detail::default_max_transfer_size ? boost::asio::detail::default_max_transfer_size : (size_t) _data_len;
_data_len -= buffer_len;
if (buffer_len != fread(buffer, 1, buffer_len, _file))
{
printf("fread(" ST_ASIO_SF ") error!\n", buffer_len);
buffer_len = 0;
}
}
}
protected:
FILE* _file;
char* buffer;
size_t buffer_len;
fl_type _data_len;
};
#endif //FILE_BUFFER_H_
#include <iostream>
//configuration
#define ST_ASIO_SERVER_PORT 5051
#define ST_ASIO_RESTORE_OBJECT
#define ST_ASIO_ENHANCED_STABILITY
#define ST_ASIO_WANT_MSG_SEND_NOTIFY
#define ST_ASIO_INPUT_QUEUE non_lock_queue
//file_server / file_client is a responsive system, before file_server send each message (except talking message,
//but file_server only receive talking message, not send talking message proactively), the previous message has been
//sent to file_client, so sending buffer will always be empty, which means we will never operate sending buffer concurrently,
//so need no locks.
#define ST_ASIO_DEFAULT_PACKER replaceable_packer<>
#define ST_ASIO_RECV_BUFFER_TYPE std::vector<boost::asio::mutable_buffer> //scatter-gather buffer, it's very useful under certain situations (for example, ring buffer).
#define ST_ASIO_SCATTERED_RECV_BUFFER //used by unpackers, not belongs to st_asio_wrapper
//configuration
#include "file_socket.h"
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define LIST_ALL_CLIENT "list_all_client"
int main(int argc, const char* argv[])
{
puts("this is a file transfer server.");
printf("usage: %s [<port=%d> [ip=0.0.0.0]]\n", argv[0], ST_ASIO_SERVER_PORT);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
st_service_pump sp;
st_server_base<file_socket> file_server_(sp);
if (argc > 2)
file_server_.set_server_addr(atoi(argv[1]), argv[2]);
else if (argc > 1)
file_server_.set_server_addr(atoi(argv[1]));
sp.start_service();
while(sp.is_running())
{
std::string str;
std::getline(std::cin, str);
if (QUIT_COMMAND == str)
sp.stop_service();
else if (RESTART_COMMAND == str)
{
sp.stop_service();
sp.start_service();
}
else if (LIST_ALL_CLIENT == str)
file_server_.list_all_object();
}
return 0;
}
//configuration
#define ST_ASIO_SERVER_PORT 5051
#define ST_ASIO_RESTORE_OBJECT
#define ST_ASIO_ENHANCED_STABILITY
#define ST_ASIO_WANT_MSG_SEND_NOTIFY
#define ST_ASIO_INPUT_QUEUE non_lock_queue
//file_server / file_client is a responsive system, before file_server send each message (except talking message,
//but file_server only receive talking message, not send talking message proactively), the previous message has been
//sent to file_client, so sending buffer will always be empty, which means we will never operate sending buffer concurrently,
//so need no locks.
#define ST_ASIO_DEFAULT_PACKER replaceable_packer<>
#define ST_ASIO_RECV_BUFFER_TYPE std::vector<boost::asio::mutable_buffer> //scatter-gather buffer, it's very useful under certain situations (for example, ring buffer).
#define ST_ASIO_SCATTERED_RECV_BUFFER //used by unpackers, not belongs to st_asio_wrapper
//configuration
#include "file_socket.h"
file_socket::file_socket(i_server& server_) : st_server_socket(server_) {}
file_socket::~file_socket() {trans_end();}
void file_socket::reset() {trans_end(); st_server_socket::reset();}
void file_socket::take_over(boost::shared_ptr<st_server_socket> socket_ptr)
{
BOOST_AUTO(raw_socket_ptr, boost::dynamic_pointer_cast<file_socket>(socket_ptr)); //socket_ptr actually is a pointer of file_socket
printf("restore user data from invalid object (" ST_ASIO_LLF ").\n", raw_socket_ptr->id());
}
//this works too, but brings warnings with -Woverloaded-virtual option.
//void file_socket::take_over(boost::shared_ptr<file_socket> socket_ptr) {printf("restore user data from invalid object (" ST_ASIO_LLF ").\n", socket_ptr->id());}
//msg handling
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
//we can handle msg very fast, so we don't use recv buffer
bool file_socket::on_msg(out_msg_type& msg) {handle_msg(msg); return true;}
#endif
bool file_socket::on_msg_handle(out_msg_type& msg) {handle_msg(msg); return true;}
//msg handling end
#ifdef ST_ASIO_WANT_MSG_SEND_NOTIFY
void file_socket::on_msg_send(in_msg_type& msg)
{
BOOST_AUTO(buffer, dynamic_cast<file_buffer*>(msg.raw_buffer()));
if (NULL != buffer)
{
buffer->read();
if (buffer->empty())
trans_end();
else
direct_send_msg(msg, true);
}
}
#endif
void file_socket::trans_end()
{
state = TRANS_IDLE;
if (NULL != file)
{
fclose(file);
file = NULL;
}
}
void file_socket::handle_msg(out_msg_ctype& msg)
{
if (msg.size() <= ORDER_LEN)
{
printf("wrong order length: " ST_ASIO_SF ".\n", msg.size());
return;
}
switch (*msg.data())
{
case 0:
if (TRANS_IDLE == state)
{
trans_end();
char buffer[ORDER_LEN + DATA_LEN];
*buffer = 0; //head
file = fopen(boost::next(msg.data(), ORDER_LEN), "rb");
if (NULL != file)
{
fseeko(file, 0, SEEK_END);
fl_type length = ftello(file);
memcpy(boost::next(buffer, ORDER_LEN), &length, DATA_LEN);
state = TRANS_PREPARE;
}
else
{
memset(boost::next(buffer, ORDER_LEN), -1, DATA_LEN);
printf("can't not open file %s!\n", boost::next(msg.data(), ORDER_LEN));
}
send_msg(buffer, sizeof(buffer), true);
}
break;
case 1:
if (TRANS_PREPARE == state && NULL != file && ORDER_LEN + OFFSET_LEN + DATA_LEN == msg.size())
{
fl_type offset;
memcpy(&offset, boost::next(msg.data(), ORDER_LEN), OFFSET_LEN);
fl_type length;
memcpy(&length, boost::next(msg.data(), ORDER_LEN + OFFSET_LEN), DATA_LEN);
if (offset >= 0 && length > 0 && offset + length <= ftello(file))
{
state = TRANS_BUSY;
fseeko(file, offset, SEEK_SET);
in_msg_type msg(new file_buffer(file, length));
direct_send_msg(msg, true);
}
}
break;
case 2:
printf("client says: %s\n", boost::next(msg.data(), ORDER_LEN));
break;
case 3:
if (ORDER_LEN + sizeof(boost::uint_fast64_t) == msg.size())
{
boost::uint_fast64_t id;
memcpy(&id, boost::next(msg.data(), ORDER_LEN), sizeof(boost::uint_fast64_t));
server.restore_socket(ST_THIS shared_from_this(), id);
}
default:
break;
}
}
#ifndef FILE_SOCKET_H_
#define FILE_SOCKET_H_
#include "../include/ext/st_asio_wrapper_tcp.h"
using namespace st_asio_wrapper::ext;
#include "file_buffer.h"
class file_socket : public base_socket, public st_server_socket
{
public:
file_socket(i_server& server_);
virtual ~file_socket();
public:
//because we don't use objects pool(we don't defined ST_ASIO_REUSE_OBJECT), so this virtual function will
//not be invoked, and can be omitted, but we keep it for possibly future using
virtual void reset();
virtual void take_over(boost::shared_ptr<st_server_socket> socket_ptr); //move socket_ptr into this socket
// virtual void take_over(boost::shared_ptr<file_socket> socket_ptr); //this works too, but brings warnings with -Woverloaded-virtual option.
protected:
//msg handling
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
//we can handle msg very fast, so we don't use recv buffer
virtual bool on_msg(out_msg_type& msg);
#endif
virtual bool on_msg_handle(out_msg_type& msg);
//msg handling end
#ifdef ST_ASIO_WANT_MSG_SEND_NOTIFY
virtual void on_msg_send(in_msg_type& msg);
#endif
private:
void trans_end();
void handle_msg(out_msg_ctype& msg);
};
#endif //#ifndef FILE_SOCKET_H_
module = file_server
ext_cflag = -D_FILE_OFFSET_BITS=64
include ../config.mk
/*
* st_asio_wrapper_client.h
*
* Created on: 2016-7-30
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* client related conveniences.
*/
#ifndef ST_ASIO_WRAPPER_EXT_CLIENT_H_
#define ST_ASIO_WRAPPER_EXT_CLIENT_H_
#include "st_asio_wrapper_tcp.h"
#endif /* ST_ASIO_WRAPPER_EXT_CLIENT_H_ */
/*
* st_asio_wrapper_server.h
*
* Created on: 2016-9-7
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* server related conveniences.
*/
#ifndef ST_ASIO_WRAPPER_EXT_SERVER_H_
#define ST_ASIO_WRAPPER_EXT_SERVER_H_
#include "st_asio_wrapper_tcp.h"
#endif /* ST_ASIO_WRAPPER_EXT_SERVER_H_ */
/*
* st_asio_wrapper_ssl.h
*
* Created on: 2016-7-30
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* ssl related conveniences.
*/
#ifndef ST_ASIO_WRAPPER_EXT_SSL_H_
#define ST_ASIO_WRAPPER_EXT_SSL_H_
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_ssl.h"
#ifndef ST_ASIO_DEFAULT_PACKER
#define ST_ASIO_DEFAULT_PACKER packer
#endif
#ifndef ST_ASIO_DEFAULT_UNPACKER
#define ST_ASIO_DEFAULT_UNPACKER unpacker
#endif
namespace st_asio_wrapper { namespace ext {
typedef st_ssl_client_socket_base<ST_ASIO_DEFAULT_PACKER, ST_ASIO_DEFAULT_UNPACKER> st_ssl_client_socket;
typedef st_ssl_client_socket st_ssl_connector;
typedef st_ssl_tcp_sclient_base<st_ssl_client_socket> st_ssl_tcp_sclient;
typedef st_ssl_tcp_client_base<st_ssl_client_socket> st_ssl_tcp_client;
typedef st_ssl_server_socket_base<ST_ASIO_DEFAULT_PACKER, ST_ASIO_DEFAULT_UNPACKER> st_ssl_server_socket;
typedef st_ssl_server_base<st_ssl_server_socket> st_ssl_server;
}} //namespace
#endif /* ST_ASIO_WRAPPER_EXT_SSL_H_ */
/*
* st_asio_wrapper_tcp.h
*
* Created on: 2016-7-30
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* TCP related conveniences.
*/
#ifndef ST_ASIO_WRAPPER_EXT_TCP_H_
#define ST_ASIO_WRAPPER_EXT_TCP_H_
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_client_socket.h"
#include "../st_asio_wrapper_tcp_client.h"
#include "../st_asio_wrapper_server_socket.h"
#include "../st_asio_wrapper_server.h"
#ifndef ST_ASIO_DEFAULT_PACKER
#define ST_ASIO_DEFAULT_PACKER packer
#endif
#ifndef ST_ASIO_DEFAULT_UNPACKER
#define ST_ASIO_DEFAULT_UNPACKER unpacker
#endif
namespace st_asio_wrapper { namespace ext {
typedef st_client_socket_base<ST_ASIO_DEFAULT_PACKER, ST_ASIO_DEFAULT_UNPACKER> st_client_socket;
typedef st_client_socket st_connector;
typedef st_tcp_sclient_base<st_client_socket> st_tcp_sclient;
typedef st_tcp_client_base<st_client_socket> st_tcp_client;
typedef st_server_socket_base<ST_ASIO_DEFAULT_PACKER, ST_ASIO_DEFAULT_UNPACKER> st_server_socket;
typedef st_server_base<st_server_socket> st_server;
}} //namespace
#endif /* ST_ASIO_WRAPPER_EXT_TCP_H_ */
/*
* st_asio_wrapper_udp.h
*
* Created on: 2016-9-7
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* udp related conveniences.
*/
#ifndef ST_ASIO_WRAPPER_EXT_UDP_H_
#define ST_ASIO_WRAPPER_EXT_UDP_H_
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_udp_socket.h"
#include "../st_asio_wrapper_udp_client.h"
#ifndef ST_ASIO_DEFAULT_PACKER
#define ST_ASIO_DEFAULT_PACKER packer
#endif
#ifndef ST_ASIO_DEFAULT_UDP_UNPACKER
#define ST_ASIO_DEFAULT_UDP_UNPACKER udp_unpacker
#endif
namespace st_asio_wrapper { namespace ext {
typedef st_udp_socket_base<ST_ASIO_DEFAULT_PACKER, ST_ASIO_DEFAULT_UDP_UNPACKER> st_udp_socket;
typedef st_udp_sclient_base<st_udp_socket> st_udp_sclient;
typedef st_udp_client_base<st_udp_socket> st_udp_client;
}} //namespace
#endif /* ST_ASIO_WRAPPER_EXT_UDP_H_ */
/*
* st_asio_wrapper.h
*
* Created on: 2012-10-21
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* this head file contain some macros used to verify the compiler and update logs etc.
* license: www.boost.org/LICENSE_1_0.txt
*
* change log:
* See st_asio_wrapper.h in standard edition.
*
*/
#ifndef ST_ASIO_WRAPPER_H_
#define ST_ASIO_WRAPPER_H_
#define ST_ASIO_WRAPPER_VER 10401 //[x]xyyzz -> [x]x.[y]y.[z]z
#define ST_ASIO_WRAPPER_VERSION "1.4.1"
//boost and compiler check
#ifdef _MSC_VER
#if _MSC_VER >= 1700
#pragma message("Your compiler is Visual C++ 11.0 or higher, you can use the standard edition to gain some performance improvement.")
#endif
#elif defined(__GNUC__)
#ifdef __clang__
#if __clang_major__ > 3 || __clang_major__ == 3 && __clang_minor__ >= 1
#warning Your compiler is Clang 3.1 or higher, you can use the standard edition to gain some performance improvement.
#endif
#elif __GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6
#warning Your compiler is GCC 4.6 or higher, you can use the standard edition to gain some performance improvement.
#endif
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__cplusplus) && __cplusplus >= 201103L
#warning st_asio_wrapper(compatible edition) does not need any c++11 features.
#endif
#else
#error st_asio_wrapper only support Visual C++, GCC and Clang.
#endif
#if BOOST_VERSION < 104900
#error st_asio_wrapper only support boost 1.49 or higher.
#endif
//boost and compiler check
#endif /* ST_ASIO_WRAPPER_H_ */
ST_MAKE = ${MAKE_COMMAND}
ifeq (${MAKECMDGOALS}, debug)
ST_MAKE += debug
else
ifeq (${MAKECMDGOALS}, clean)
ST_MAKE += clean
endif
endif
release debug clean :
cd asio_server && ${ST_MAKE}
cd asio_client && ${ST_MAKE}
cd test_client && ${ST_MAKE}
cd file_server && ${ST_MAKE}
cd file_client && ${ST_MAKE}
cd udp_test && ${ST_MAKE}
cd ssl_test && ${ST_MAKE}
cd pingpong_server && ${ST_MAKE}
cd pingpong_client && ${ST_MAKE}
module = pingpong_server
include ../config.mk
-----BEGIN DH PARAMETERS-----
MIGHAoGBAInZBFOTUOmomMh1AzuvBR92YWBhvS+UQBfL1OzVwYCT4YgVILy8zuc2
cwN3VGAjCkiATALve/UG8zUNVSLAqfSCOXwbphAZ2KqbBw6DDTk5BbzwbaabuF+a
0kTmGGq2K9N3dc9pAD6KDModu7N4yVECcmqYpHVme17lR0U+vBqbAgEC
-----END DH PARAMETERS-----
-----BEGIN CERTIFICATE-----
MIICjTCCAfYCCQDXfmozhhvEbjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
Q04xDjAMBgNVBAgMBUNoaW5hMRAwDgYDVQQHDAdDaGVuZ0R1MREwDwYDVQQKDAhT
VF9DdXJ2ZTERMA8GA1UECwwIU1RfQ3VydmUxEjAQBgNVBAMMCXlvdW5nd29sZjEf
MB0GCSqGSIb3DQEJARYQbWFpbDJ0YW9AMTYzLmNvbTAeFw0xNTA2MzAwNjU0NDFa
Fw0yNTA2MjcwNjU0NDFaMIGKMQswCQYDVQQGEwJDTjEOMAwGA1UECAwFQ2hpbmEx
EDAOBgNVBAcMB0NoZW5nRHUxETAPBgNVBAoMCFNUX0N1cnZlMREwDwYDVQQLDAhT
VF9DdXJ2ZTESMBAGA1UEAwwJeW91bmd3b2xmMR8wHQYJKoZIhvcNAQkBFhBtYWls
MnRhb0AxNjMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2aX/0UkLH
wcRW6Ls1devwIa7NmA8wyZ9zPv6QBOYq7yK65jSH5992Kq8gZx2Wp+onTLBlopFZ
iWICtHy0C41HUzPRr8+HZPItBXWPmuJAYUFkb5CPvWatiYZ29kXJpHG+ij22+One
eB24PPrYMMYLey/LAyelHDWMfX4S7nSVGQIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
AKxATWCmpWgxRNXTch20uFiTW2Csq2/Io8R9nTSDh0/BHs40aJMI7lv/7s5hUlX5
sxqr1EQ8uvaDD1fKGJT1yRTkMjRBdmW5UY119twr1pRqIVQ6J/1EsZ6eIcatwqHl
j4ImJfXJ4/YpzNbmffi7PkfU24/goHFUAH3bbG3zNekn
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC2aX/0UkLHwcRW6Ls1devwIa7NmA8wyZ9zPv6QBOYq7yK65jSH
5992Kq8gZx2Wp+onTLBlopFZiWICtHy0C41HUzPRr8+HZPItBXWPmuJAYUFkb5CP
vWatiYZ29kXJpHG+ij22+OneeB24PPrYMMYLey/LAyelHDWMfX4S7nSVGQIDAQAB
AoGBAJXsxk8gYWTffKlV5bC9+CrI9Gxko2+c9+H4D7B9ZlQ5clkdJmXhjYgI8yF1
vxr5bfnZDU6rvD1ULms8vxYYPIfUJbgyws2BNh4YkA0XO9yWa7j8McOmWuI1pmdO
zoyP030Eh4/pHK059zISjiNfp/nf0PyWxilft053j1UUUF2RAkEA5RB5QAT5Gtt4
gPqhUDfWKfImdle4Ob6xOYYL8Z21/nLh3Pu+lEdEtxdDP9Hd8OFJCtIaV1IPQAFb
/J+Wwvz9tQJBAMvcpz9EVyseHiabD2lNItNYnfjd5XtO3JrwJS/ZXBTTk2yuWgv6
GXpJAf2q57JCT/ffl3R3qwnXDmPD1YQZ+FUCQQCUeq4tukuChjrBWDSSW+89t1IJ
KeknvR0BzYsWbgVXTz5q0OADG4NGLxUPXElB57ZCBli+u1lZQshOo1/FOtp1AkBQ
bPGyXQTCpvQgKOZQlbTaikhwSSJinj8cBQe2s9rHAJ1VsAcoWHe6rZjSeZ1EzzJ7
SMOYmClrDWPI6haPMiutAkBY2pIP1JDQXWaNaSE4hP2vgIZBeJsB8waV8oo0Ycxj
pkm4rsv2WP1BVX9E5sqC4dP2rKJgY0XRoDjU0aZX/so2
-----END RSA PRIVATE KEY-----
-----BEGIN DH PARAMETERS-----
MIGHAoGBANqE9bWxbxmNIIpvq6zpjmWiwB5u+0ACDHlQ7zROVz4XUsyj4SGHupuK
P9+Gn55yAgoUHf567wWuVQBLqCobjfAEbAfZPnlhtLgt0avFwc+XGrylZWr2VV01
naJiaJWK8cRlrPktggJR6EBoqu485WhE0IDL0lZarIV92JML8evbAgEC
-----END DH PARAMETERS-----
-----BEGIN CERTIFICATE-----
MIICjTCCAfYCCQDs5HqjGPDbZzANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
Q04xDjAMBgNVBAgMBUNoaW5hMRAwDgYDVQQHDAdDaGVuZ0R1MREwDwYDVQQKDAhT
VF9DdXJ2ZTERMA8GA1UECwwIU1RfQ3VydmUxEjAQBgNVBAMMCXlvdW5nd29sZjEf
MB0GCSqGSIb3DQEJARYQbWFpbDJ0YW9AMTYzLmNvbTAeFw0xNTA2MzAwNjUyMzha
Fw0yNTA2MjcwNjUyMzhaMIGKMQswCQYDVQQGEwJDTjEOMAwGA1UECAwFQ2hpbmEx
EDAOBgNVBAcMB0NoZW5nRHUxETAPBgNVBAoMCFNUX0N1cnZlMREwDwYDVQQLDAhT
VF9DdXJ2ZTESMBAGA1UEAwwJeW91bmd3b2xmMR8wHQYJKoZIhvcNAQkBFhBtYWls
MnRhb0AxNjMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP76qWFVJ+
OGf6x3sBCmooHh82qDo51kx8030/XsAyUcjCUQX+k7ZZlE1eh1bQxPiEj7xVhxyN
9yizWw+qxZ7IujyPbeNiDjjQjrWKioUUoQ1XyWqjCC09piQSPAIakhXUEHhaUgk5
t4Asx7vAlMEDjCu+DJzgz+WL7J0wEZ0yKwIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
ADKOXcNOZb7JK1KcCo0qirQFM4q6L1RMaoFXaJqiW++gd6a4zslnKZS8tkciMKKl
yYu7di5MU807aMX9fVTnFVrfAVAUsg5itSxD7qO/EY1CMMOUVk30MqQkpFueZ3NN
ap6FGARVVbrA5VlhhFiRitLBu0hmQsTWvHe/NDeMyBXE
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDP76qWFVJ+OGf6x3sBCmooHh82qDo51kx8030/XsAyUcjCUQX+
k7ZZlE1eh1bQxPiEj7xVhxyN9yizWw+qxZ7IujyPbeNiDjjQjrWKioUUoQ1XyWqj
CC09piQSPAIakhXUEHhaUgk5t4Asx7vAlMEDjCu+DJzgz+WL7J0wEZ0yKwIDAQAB
AoGARv6lV6jgCYhouA7zyF8t75SZQ6gceTLZ2qS61rAbHxMdYHsZeNomIF+qKuQ6
l/0ofNo2i10eOrpwgwxMRRpEI9cBOeo3bUb5lCgX9Ck5CNS4p9vUp6HZuIxACaAj
gDkqYULiDCzQfy76y7IRwqWLOyj+aOIDwGU7F5sgple644ECQQDwvW4/tVCqbJqD
oETs9fgXorxGlSv8OsVm8tvr5OX8SuygTMiYb/47a3UudLPQgancIUp86gqqbS8t
D8RZs4OZAkEA3R3q9Q1Oo7JF8yXKIfBKDRszk7bycRGPs2yjItddz2yyBJaqiq74
T2SuTIQLT2RpG3mignc5n+SVPZTovUx+YwJANdvLfIlOALLHZsdGZFUrKe6IfKoG
6yki3MNcnEvbgDQq+Dn1btUvZY/G6ZvGVtGLdOolxgh+Vl6S/VvXq9HTkQJBANhY
nHqWQZpjJJPqJqMcU6gNNGLUPtSopRyeL+Q4Kt538JoSvNyio+wyi2PsUpCkoz6x
vXK/qs8qPWeamjoVbRcCQQDEB9X9MVqJmkgO5P3MRTmm0CX0Ud/pyBgnqnzQ+lwN
GYEAzbAUGF+JSx+tDC1pj0LpxdVVpMrGonx3M/3BMeMP
-----END RSA PRIVATE KEY-----
module = ssl_test
ext_libs = -lcrypto -lssl
include ../config.mk
#include <iostream>
//configuration
#define ST_ASIO_SERVER_PORT 9527
#define ST_ASIO_REUSE_OBJECT //use objects pool
#define ST_ASIO_REUSE_SSL_STREAM
//#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
#define ST_ASIO_ENHANCED_STABILITY
//#define ST_ASIO_DEFAULT_PACKER replaceable_packer<>
//#define ST_ASIO_DEFAULT_UNPACKER replaceable_unpacker<>
#define ST_ASIO_HEARTBEAT_INTERVAL 5 //SSL has supported heartbeat because we used user data instead of OOB to implement
//heartbeat since 1.2.0
//configuration
#include "../include/ext/st_asio_wrapper_ssl.h"
using namespace st_asio_wrapper;
using namespace st_asio_wrapper::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define RECONNECT_COMMAND "reconnect"
#define SHOW_ALL_LINKS "show_all_links"
#define SHUTDOWN_LINK "shutdown"
int main(int argc, const char* argv[])
{
puts("Directories 'certs' and 'client_certs' must available in current directory.");
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
st_service_pump sp;
st_ssl_server server_(sp, boost::asio::ssl::context::sslv23_server);
server_.context().set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
server_.context().set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::context::verify_fail_if_no_peer_cert);
server_.context().load_verify_file("client_certs/server.crt");
server_.context().use_certificate_chain_file("certs/server.crt");
server_.context().use_private_key_file("certs/server.key", boost::asio::ssl::context::pem);
server_.context().use_tmp_dh_file("certs/dh1024.pem");
///*
//method #1
st_ssl_tcp_client client_(sp, boost::asio::ssl::context::sslv23_client);
client_.context().set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
client_.context().set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::context::verify_fail_if_no_peer_cert);
client_.context().load_verify_file("certs/server.crt");
client_.context().use_certificate_chain_file("client_certs/server.crt");
client_.context().use_private_key_file("client_certs/server.key", boost::asio::ssl::context::pem);
client_.context().use_tmp_dh_file("client_certs/dh1024.pem");
//please config the ssl context before creating any clients.
client_.add_socket();
client_.add_socket();
//*/
/*
//method #2
//to use st_ssl_tcp_sclient, we must construct ssl context first.
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23_client);
ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
ctx.set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::context::verify_fail_if_no_peer_cert);
ctx.load_verify_file("certs/server.crt");
ctx.use_certificate_chain_file("client_certs/server.crt");
ctx.use_private_key_file("client_certs/server.key", boost::asio::ssl::context::pem);
ctx.use_tmp_dh_file("client_certs/dh1024.pem");
st_ssl_tcp_sclient client_(sp, ctx);
*/
sp.start_service();
while(sp.is_running())
{
std::string str;
std::cin >> str;
if (QUIT_COMMAND == str)
{
sp.stop_service(&client_);
sp.stop_service();
}
else if (SHOW_ALL_LINKS == str)
{
puts("server:");
printf("link #: " ST_ASIO_SF ", invalid links: " ST_ASIO_SF "\n", server_.size(), server_.invalid_object_size());
server_.list_all_object();
//if you used st_ssl_tcp_sclient, comment out following codes.
puts("\nclient:");
printf("link #: " ST_ASIO_SF ", valid links: " ST_ASIO_SF ", invalid links: " ST_ASIO_SF "\n", client_.size(), client_.valid_size(), client_.invalid_object_size());
client_.list_all_object();
}
#ifndef ST_ASIO_REUSE_SSL_STREAM
else if (RESTART_COMMAND == str || RECONNECT_COMMAND == str)
puts("please define macro ST_ASIO_REUSE_SSL_STREAM to test this feature.");
else if (SHUTDOWN_LINK == str)
// server_.at(0)->graceful_shutdown();
// server_.at(0)->graceful_shutdown(true);
// server_.at(0)->force_shutdown();
client_.graceful_shutdown(client_.at(0));
// client_.graceful_shutdown(client_.at(0), false);
// client_.force_shutdown(client_.at(0));
// client_.graceful_shutdown(); //if you used st_ssl_tcp_sclient
// client_.graceful_shutdown(false, false); //if you used st_ssl_tcp_sclient
// client_.force_shutdown(); //if you used st_ssl_tcp_sclient
#else
else if (RESTART_COMMAND == str)
{
client_.force_shutdown(true); //important, or client will not be able to reconnect to the server
// client_.force_shutdown(true); //if you used single_client
sp.stop_service(&client_);
sp.stop_service();
sp.start_service();
}
else if (RECONNECT_COMMAND == str)
// server_.graceful_shutdown();
client_.graceful_shutdown(true);
else if (SHUTDOWN_LINK == str)
//async shutdown, client will reconnect to the server
// server_.at(0)->graceful_shutdown();
// server_.at(0)->force_shutdown();
//sync shutdown, client will reconnect to the server
// server_.at(0)->graceful_shutdown(true);
//sync shutdown and reconnect to the server
client_.at(0)->graceful_shutdown(true);
// client_.at(0)->force_shutdown(true);
// client_.graceful_shutdown(true); //if you used st_ssl_tcp_sclient
// client_.force_shutdown(true); //if you used st_ssl_tcp_sclient
//async shutdown and reconnect to the server
// client_.at(0)->graceful_shutdown(true, false);
// client_.graceful_shutdown(true, false); //if you used st_ssl_tcp_sclient
//sync shutdown and not reconnect to the server
// client_.at(0)->graceful_shutdown();
// client_.at(0)->force_shutdown();
// client_.graceful_shutdown(client_.at(0));
// client_.force_shutdown(client_.at(0));
// client_.graceful_shutdown(); //if you used st_ssl_tcp_sclient
// client_.force_shutdown(); //if you used st_ssl_tcp_sclient
//async shutdown and not reconnect to the server
// client_.at(0)->graceful_shutdown(false, false);
// client_.graceful_shutdown(client_.at(0), false);
// client_.graceful_shutdown(false, false); //if you used st_ssl_tcp_sclient
#endif
else
server_.broadcast_msg(str);
}
return 0;
}
性能测试环境搭建:
开启asio_server这个demo,当成性能测试服务端,注意asio_server里面演示了两种服务器,一种是普通服务器,一种是echo
服务器,它们会同时启动(端口不一样),asio_client将连接普通服务器,test_client将连接echo服务器;
开启test_client,当成性能测试客户端;测试结果由test_client来提供并显示;
test_client的运行格式可以通过运行 test_client -h 来获得。
其中link_num是连接数量,最小1,最大4096,是否能成功开启这么多的套接字与系统配置有关,比如ubuntu,可以通过ulimit修改,
如果运行test_client不输入参数,则默认16连接,1个线程。
当所有连接都建立起来之后,输入参数:
消息数 消息长度 填充(字符)模式(0广播 1随机)
比如 100000 1024 0,意思是,每条连接发送10万条消息,每条消息长度(不得小于sizeof(size_t))1024,消息内容是字符0。
关于消息内容,再说一句,其实它最前面有sizeof(size_t)个字符是一个序号,用于检测数据的合法性,也就是说这个虽然是性能
测试,也附带测试了一下数据的正确性。sizeof(size_t)个字符之后才是前面说的填充字符,大家一看源代码就明白了。
如果模式为0,则对所有连接都发送“消息数”个消息,总共发送“连接数*消息数”个消息;
如果模式为1,则总共只发送“消息数”个消息,每个消息都随机的选择一个连接来发送。
除“消息数”之外,其它参数可省略,默认值以次是:1024 0 0
测试结束之后,可再次输入命令,再一次测试。
测试之前,可输入+/-number来增减连接,比如输入“+16”,则增加16条连接。
消息具体流程:
客户端发送数据到服务端,服务端再把数据返回回去,当所有连接的所有数据都发送完毕,并且所有数据也接收完毕,则测试结束;
服务端在这个过程中不会做任何投机取巧的事,比如把收到的数据不解包就直接发送,这样可以省去解包和打包,
相反,它会按照正规流程来做(这才是实际使用中的情形),即对收到的数据解包,再打包发送回去。
测试报告:
包括总耗时(精确到毫秒)和流量,其中流量只计算了单边流量,所以要乘以2才是总流量。
注:如果未定义ST_ASIO_WANT_MSG_SEND_NOTIFY宏,则所有连接上的消息发送,都在主线程中进行(仅仅指send_msg等,真正的消息发送永远是异步且在service线程中进行),
所以修改一下test_client,适当的增加一些线程来发送数据,应该效率更高;如果定义了ST_ASIO_WANT_MSG_SEND_NOTIFY宏,则所有工作均在service线程中进行,
否则main函数负责消息发送,发完之后sleep直到测试结束。
#include <iostream>
//configuration
#define ST_ASIO_DELAY_CLOSE 1 //this demo not used object pool and doesn't need life cycle management,
//so, define this to avoid hooks for async call (and slightly improve efficiency),
//any value which is bigger than zero is okay.
//#define ST_ASIO_DEFAULT_PACKER replaceable_packer<>
//#define ST_ASIO_DEFAULT_UDP_UNPACKER replaceable_udp_unpacker<>
#define ST_ASIO_HEARTBEAT_INTERVAL 5 //neither udp_unpacker nor replaceable_udp_unpacker support heartbeat message,
//so heartbeat will be treated as normal message.
//configuration
#include "../include/ext/st_asio_wrapper_udp.h"
using namespace st_asio_wrapper;
using namespace st_asio_wrapper::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
int main(int argc, const char* argv[])
{
printf("usage: %s <my port> <peer port> [peer ip=127.0.0.1]\n", argv[0]);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else if (argc < 3)
return 1;
else
puts("type " QUIT_COMMAND " to end.");
st_service_pump sp;
st_udp_sclient client(sp);
client.set_local_addr((unsigned short) atoi(argv[1])); //for multicast, do not bind to a specific IP, just port is enough
client.set_peer_addr((unsigned short) atoi(argv[2]), argc >= 4 ? argv[3] : "127.0.0.1");
sp.start_service();
//for multicast, join it after start_service():
// client.lowest_layer().set_option(boost::asio::ip::multicast::join_group(boost::asio::ip::address::from_string("x.x.x.x")));
//if you must join it before start_service():
// client.lowest_layer().open(ST_ASIO_UDP_DEFAULT_IP_VERSION);
// client.lowest_layer().set_option(boost::asio::ip::multicast::join_group(boost::asio::ip::address::from_string("x.x.x.x")));
// sp.start_service();
while(sp.is_running())
{
std::string str;
std::cin >> str;
if (QUIT_COMMAND == str)
sp.stop_service();
else if (RESTART_COMMAND == str)
{
sp.stop_service();
sp.start_service();
}
else
client.direct_sync_send_msg(str); //to send to different endpoints, use overloads that take a const boost::asio::ip::udp::endpoint& parameter
// client.sync_send_native_msg(str); //to send to different endpoints, use overloads that take a const boost::asio::ip::udp::endpoint& parameter
// client.safe_send_native_msg(str); //to send to different endpoints, use overloads that take a const boost::asio::ip::udp::endpoint& parameter
}
return 0;
}
#include <iostream> #include <iostream>
#include <boost/timer/timer.hpp> #include <boost/timer/timer.hpp>
#include <boost/tokenizer.hpp>
//configuration //configuration
#define ST_ASIO_SERVER_PORT 9527 #define ST_ASIO_SERVER_PORT 9527
#define ST_ASIO_MAX_OBJECT_NUM 102400
#define ST_ASIO_REUSE_OBJECT //use objects pool #define ST_ASIO_REUSE_OBJECT //use objects pool
#define ST_ASIO_DELAY_CLOSE 5 //define this to avoid hooks for async call (and slightly improve efficiency) #define ST_ASIO_DELAY_CLOSE 5 //define this to avoid hooks for async call (and slightly improve efficiency)
//#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER //#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
//#define ST_ASIO_WANT_MSG_SEND_NOTIFY #define ST_ASIO_MSG_BUFFER_SIZE 1024
#define ST_ASIO_MSG_BUFFER_SIZE 65536 #define ST_ASIO_INPUT_QUEUE non_lock_queue //we will never operate sending buffer concurrently, so need no locks
#define ST_ASIO_INPUT_QUEUE non_lock_queue //we will never operate sending buffer concurrently, so need no locks.
#define ST_ASIO_DEFAULT_UNPACKER stream_unpacker //non-protocol
//configuration //configuration
#include "../include/ext/st_asio_wrapper_tcp.h" #include "../include/ext/tcp.h"
using namespace st_asio_wrapper; using namespace st_asio_wrapper;
using namespace st_asio_wrapper::ext; using namespace st_asio_wrapper::tcp;
using namespace st_asio_wrapper::ext::tcp;
#define QUIT_COMMAND "quit" #define QUIT_COMMAND "quit"
#define LIST_STATUS "status" #define LIST_STATUS "status"
boost::timer::cpu_timer begin_time; class echo_socket : public client_socket
#if BOOST_VERSION >= 105300
boost::atomic_ushort completed_session_num;
#else
st_atomic<unsigned short> completed_session_num;
#endif
//about congestion control
//
//in 1.3, congestion control has been removed (no post_msg nor post_native_msg anymore), this is because
//without known the business (or logic), framework cannot always do congestion control properly.
//now, users should take the responsibility to do congestion control, there're two ways:
//
//1. for receiver, if you cannot handle msgs timely, which means the bottleneck is in your business,
// you should open/close congestion control intermittently;
// for sender, send msgs in on_msg_send() or use sending buffer limitation (like safe_send_msg(..., false)),
// but must not in service threads, please note.
//
//2. for sender, if responses are available (like pingpong test), send msgs in on_msg()/on_msg_handle(),
// but this will reduce IO throughput because SOCKET's sliding window is not fully used, pleae note.
//
//pingpong_client will choose method #1 if defined ST_ASIO_WANT_MSG_SEND_NOTIFY, otherwise #2
//BTW, if pingpong_client chose method #2, then pingpong_server can work properly without any congestion control,
//which means pingpong_server can send msgs back with can_overflow parameter equal to true, and memory occupation
//will be under control.
class echo_socket : public st_client_socket
{ {
public: public:
echo_socket(boost::asio::io_service& io_service_) : st_client_socket(io_service_) {} echo_socket(boost::asio::io_service& io_service_) : client_socket(io_service_), msg_len(ST_ASIO_MSG_BUFFER_SIZE - ST_ASIO_HEAD_LEN) {unpacker()->stripped(false);}
void begin(size_t msg_num, const char* msg, size_t msg_len) void begin(size_t msg_len_) {msg_len = msg_len_;}
void check_delay(float max_delay) {if (is_connected() && (double) last_send_time.elapsed().wall / 1000000000 > max_delay) force_shutdown();}
protected:
virtual void on_connect()
{ {
total_bytes = msg_len; boost::asio::ip::tcp::no_delay option(true);
total_bytes *= msg_num; lowest_layer().set_option(option);
send_bytes = recv_bytes = 0;
send_native_msg(msg, msg_len); char* buff = new char[msg_len];
} memset(buff, 'Y', msg_len); //what should we send?
protected: last_send_time.stop();
virtual void on_connect() {boost::asio::ip::tcp::no_delay option(true); lowest_layer().set_option(option); st_client_socket::on_connect();} last_send_time.start();
send_msg(buff, msg_len, true);
delete[] buff;
client_socket::on_connect();
}
//msg handling //msg handling
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER #ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
...@@ -70,50 +52,23 @@ protected: ...@@ -70,50 +52,23 @@ protected:
#endif #endif
virtual bool on_msg_handle(out_msg_type& msg) {handle_msg(msg); return true;} virtual bool on_msg_handle(out_msg_type& msg) {handle_msg(msg); return true;}
#ifdef ST_ASIO_WANT_MSG_SEND_NOTIFY
//congestion control, method #1, the peer needs its own congestion control too.
virtual void on_msg_send(in_msg_type& msg)
{
send_bytes += msg.size();
if (send_bytes < total_bytes)
direct_send_msg(msg, true);
}
private:
void handle_msg(out_msg_ctype& msg)
{
recv_bytes += msg.size();
if (recv_bytes >= total_bytes && 0 == --completed_session_num)
begin_time.stop();
}
#else
private: private:
//congestion control, method #2, the peer totally doesn't have to consider congestion control.
void handle_msg(out_msg_type& msg) void handle_msg(out_msg_type& msg)
{ {
if (0 == total_bytes) last_send_time.stop();
return; last_send_time.start();
direct_send_msg(msg, true);
recv_bytes += msg.size();
if (recv_bytes >= total_bytes)
{
total_bytes = 0;
if (0 == --completed_session_num)
begin_time.stop();
}
else
direct_send_msg(msg, true);
} }
#endif
private: private:
boost::uint64_t total_bytes, send_bytes, recv_bytes; size_t msg_len;
boost::timer::cpu_timer last_send_time;
}; };
class echo_client : public st_tcp_client_base<echo_socket> class echo_client : public client_base<echo_socket>
{ {
public: public:
echo_client(st_service_pump& service_pump_) : st_tcp_client_base<echo_socket>(service_pump_) {} echo_client(service_pump& service_pump_) : client_base<echo_socket>(service_pump_) {}
statistic get_statistic() statistic get_statistic()
{ {
...@@ -123,12 +78,19 @@ public: ...@@ -123,12 +78,19 @@ public:
return stat; return stat;
} }
void begin(size_t msg_num, const char* msg, size_t msg_len) {do_something_to_all(boost::bind(&echo_socket::begin, _1, msg_num, msg, msg_len));} void begin(float max_delay, size_t msg_len)
{
do_something_to_all(boost::bind(&echo_socket::begin, _1, msg_len));
set_timer(TIMER_END, 5000, (boost::lambda::bind(&echo_client::check_delay, this, max_delay), true));
}
void check_delay(float max_delay) {do_something_to_all(boost::bind(&echo_socket::check_delay, _1, max_delay));}
}; };
int main(int argc, const char* argv[]) int main(int argc, const char* argv[])
{ {
printf("usage: %s [<service thread number=1> [<port=%d> [<ip=%s> [link num=16]]]]\n", argv[0], ST_ASIO_SERVER_PORT, ST_ASIO_SERVER_IP); printf("usage: %s [<message size=" ST_ASIO_SF "> [<max delay=%f (seconds)> [<service thread number=1> [<port=%d> [<ip=%s> [link num=16]]]]]]\n",
argv[0], ST_ASIO_MSG_BUFFER_SIZE - ST_ASIO_HEAD_LEN, 1.f, ST_ASIO_SERVER_PORT, ST_ASIO_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h"))) if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0; return 0;
else else
...@@ -136,23 +98,23 @@ int main(int argc, const char* argv[]) ...@@ -136,23 +98,23 @@ int main(int argc, const char* argv[])
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
size_t link_num = 16; size_t link_num = 16;
if (argc > 4) if (argc > 6)
link_num = std::min(ST_ASIO_MAX_OBJECT_NUM, std::max(atoi(argv[4]), 1)); link_num = std::min(ST_ASIO_MAX_OBJECT_NUM, std::max(atoi(argv[6]), 1));
printf("exec: echo_client with " ST_ASIO_SF " links\n", link_num); printf("exec: echo_client with " ST_ASIO_SF " links\n", link_num);
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
st_service_pump sp; service_pump sp;
echo_client client(sp); echo_client client(sp);
// argv[2] = "::1" //ipv6 // argv[5] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4 // argv[5] = "127.0.0.1" //ipv4
std::string ip = argc > 3 ? argv[3] : ST_ASIO_SERVER_IP; std::string ip = argc > 5 ? argv[5] : ST_ASIO_SERVER_IP;
unsigned short port = argc > 2 ? atoi(argv[2]) : ST_ASIO_SERVER_PORT; unsigned short port = argc > 4 ? atoi(argv[4]) : ST_ASIO_SERVER_PORT;
int thread_num = 1; int thread_num = 1;
if (argc > 1) if (argc > 3)
thread_num = std::min(16, std::max(thread_num, atoi(argv[1]))); thread_num = std::min(16, std::max(thread_num, atoi(argv[3])));
//add one thread will seriously impact IO throughput when doing performance benchmark, this is because the business logic is very simple (send original messages back, //add one thread will seriously impact IO throughput when doing performance benchmark, this is because the business logic is very simple (send original messages back,
//or just add up total message size), under this scenario, just one service thread without receiving buffer will obtain the best IO throughput. //or just add up total message size), under this scenario, just one service thread without receiving buffer will obtain the best IO throughput.
//the server has such behavior too. //the server has such behavior too.
...@@ -160,6 +122,15 @@ int main(int argc, const char* argv[]) ...@@ -160,6 +122,15 @@ int main(int argc, const char* argv[])
for (size_t i = 0; i < link_num; ++i) for (size_t i = 0; i < link_num; ++i)
client.add_socket(port, ip); client.add_socket(port, ip);
float max_delay = 1.f;
if (argc > 2)
max_delay = std::max(.1f, (float) atof(argv[2]));
size_t msg_len = ST_ASIO_MSG_BUFFER_SIZE - ST_ASIO_HEAD_LEN;
if (argc > 1)
msg_len = std::max((size_t) 1, std::min(msg_len, (size_t) atoi(argv[1])));
client.begin(max_delay, msg_len);
sp.start_service(thread_num); sp.start_service(thread_num);
while(sp.is_running()) while(sp.is_running())
{ {
...@@ -173,37 +144,6 @@ int main(int argc, const char* argv[]) ...@@ -173,37 +144,6 @@ int main(int argc, const char* argv[])
puts(""); puts("");
puts(client.get_statistic().to_string().data()); puts(client.get_statistic().to_string().data());
} }
else if (!str.empty())
{
size_t msg_num = 1024;
size_t msg_len = 1024; //must greater than or equal to sizeof(size_t)
char msg_fill = '0';
boost::char_separator<char> sep(" \t");
boost::tokenizer<boost::char_separator<char> > tok(str, sep);
BOOST_AUTO(iter, tok.begin());
if (iter != tok.end()) msg_num = std::max((size_t) atoi(iter++->data()), (size_t) 1);
if (iter != tok.end()) msg_len = std::min((size_t) ST_ASIO_MSG_BUFFER_SIZE, std::max((size_t) atoi(iter++->data()), (size_t) 1));
if (iter != tok.end()) msg_fill = *iter++->data();
printf("test parameters after adjustment: " ST_ASIO_SF " " ST_ASIO_SF " %c\n", msg_num, msg_len, msg_fill);
puts("performance test begin, this application will have no response during the test!");
completed_session_num = (unsigned short) link_num;
char* init_msg = new char[msg_len];
memset(init_msg, msg_fill, msg_len);
client.begin(msg_num, init_msg, msg_len);
begin_time.start();
while (0 != completed_session_num)
boost::this_thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50));
boost::uint64_t total_msg_bytes = link_num; total_msg_bytes *= msg_len; total_msg_bytes *= msg_num;
double used_time = (double) begin_time.elapsed().wall / 1000000000;
printf("finished in %f seconds, speed: %f(*2) MBps.\n", used_time, total_msg_bytes / used_time / 1024 / 1024);
delete[] init_msg;
}
} }
return 0; return 0;
......
<?xml version="1.0" encoding="gb2312"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9.00"
Name="concurrent_client"
ProjectGUID="{56B85EA8-2114-4EEE-8077-BC910DF06D6E}"
RootNamespace="concurrent_client"
Keyword="Win32Proj"
TargetFrameworkVersion="196613"
>
<Platforms>
<Platform
Name="Win32"
/>
<Platform
Name="x64"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="2"
GenerateDebugInformation="true"
SubSystem="1"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="2"
GenerateDebugInformation="true"
SubSystem="1"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
WholeProgramOptimization="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="1"
GenerateDebugInformation="true"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
WholeProgramOptimization="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="1"
GenerateDebugInformation="true"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Դļ"
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\concurrent_client.cpp"
>
</File>
</Filter>
<Filter
Name="ͷļ"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
</Filter>
<Filter
Name="Դļ"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>
module = pingpong_client module = concurrent_client
ext_libs = -lboost_timer -lboost_chrono ext_libs = -lboost_timer -lboost_chrono
include ../config.mk include ../config.mk
......
...@@ -3,98 +3,40 @@ ...@@ -3,98 +3,40 @@
//configuration //configuration
#define ST_ASIO_SERVER_PORT 9527 #define ST_ASIO_SERVER_PORT 9527
#define ST_ASIO_MAX_OBJECT_NUM 102400
#define ST_ASIO_REUSE_OBJECT //use objects pool #define ST_ASIO_REUSE_OBJECT //use objects pool
#define ST_ASIO_DELAY_CLOSE 5 //define this to avoid hooks for async call (and slightly improve efficiency) #define ST_ASIO_DELAY_CLOSE 5 //define this to avoid hooks for async call (and slightly improve efficiency)
//#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER //#define ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
#define ST_ASIO_MSG_BUFFER_SIZE 65536 #define ST_ASIO_MSG_BUFFER_SIZE 1024
#define ST_ASIO_INPUT_QUEUE non_lock_queue #define ST_ASIO_INPUT_QUEUE non_lock_queue //we will never operate sending buffer concurrently, so need no locks
//if pingpong_client only send message in on_msg() or on_msg_handle(), which means a responsive system, a real pingpong test,
//then, before pingpong_server send each message, the previous message has been sent to pingpong_client,
//so sending buffer will always be empty, which means we will never operate sending buffer concurrently, so need no locks.
//
//if pingpong_client send message in on_msg_send(), then using non_lock_queue as input queue in pingpong_server will lead
//undefined behavior, please note.
#define ST_ASIO_DEFAULT_UNPACKER stream_unpacker //non-protocol
//configuration //configuration
#include "../include/ext/st_asio_wrapper_tcp.h" #include "../include/ext/tcp.h"
using namespace st_asio_wrapper; using namespace st_asio_wrapper;
using namespace st_asio_wrapper::ext; using namespace st_asio_wrapper::tcp;
using namespace st_asio_wrapper::ext::tcp;
#ifdef _MSC_VER
#define atoll _atoi64
#endif
#define QUIT_COMMAND "quit" #define QUIT_COMMAND "quit"
#define LIST_STATUS "status" #define LIST_STATUS "status"
//about congestion control class echo_socket : public server_socket
//
//in 1.3, congestion control has been removed (no post_msg nor post_native_msg anymore), this is because
//without known the business (or logic), framework cannot always do congestion control properly.
//now, users should take the responsibility to do congestion control, there're two ways:
//
//1. for receiver, if you cannot handle msgs timely, which means the bottleneck is in your business,
// you should open/close congestion control intermittently;
// for sender, send msgs in on_msg_send() or use sending buffer limitation (like safe_send_msg(..., false)),
// but must not in service threads, please note.
//
//2. for sender, if responses are available (like pingpong test), send msgs in on_msg()/on_msg_handle(),
// but this will reduce IO throughput because SOCKET's sliding window is not fully used, pleae note.
//
//pingpong_server chose method #1
//BTW, if pingpong_client chose method #2, then pingpong_server can work properly without any congestion control,
//which means pingpong_server can send msgs back with can_overflow parameter equal to true, and memory occupation
//will be under control.
class echo_socket : public st_server_socket
{ {
public: public:
echo_socket(i_server& server_) : st_server_socket(server_) {} echo_socket(i_server& server_) : server_socket(server_) {unpacker()->stripped(false);}
protected: protected:
//msg handling: send the original msg back(echo server) //msg handling: send the original msg back(echo server)
//congestion control, method #1, the peer needs its own congestion control too. #ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER //this virtual function doesn't exist if ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER been defined
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER virtual bool on_msg(out_msg_type& msg) {direct_send_msg(msg, true); return true;}
//this virtual function doesn't exists if ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER been defined
virtual bool on_msg(out_msg_type& msg)
{
bool re = direct_send_msg(msg);
if (!re)
{
//cannot handle (send it back) this msg timely, begin congestion control
//'msg' will be put into receiving buffer, and be dispatched via on_msg_handle() in the future
congestion_control(true);
//unified_out::warning_out("open congestion control."); //too many prompts will affect efficiency
}
return re;
}
virtual bool on_msg_handle(out_msg_type& msg)
{
bool re = direct_send_msg(msg);
if (re)
{
//successfully handled the only one msg in receiving buffer, end congestion control
//subsequent msgs will be dispatched via on_msg() again.
congestion_control(false);
//unified_out::warning_out("close congestion control."); //too many prompts will affect efficiency
}
return re;
}
#else
//if we used receiving buffer, congestion control will become much simpler, like this:
virtual bool on_msg_handle(out_msg_type& msg) {return direct_send_msg(msg);}
#endif #endif
virtual bool on_msg_handle(out_msg_type& msg) {direct_send_msg(msg, true); return true;}
//msg handling end //msg handling end
}; };
class echo_server : public st_server_base<echo_socket> class echo_server : public server_base<echo_socket>
{ {
public: public:
echo_server(st_service_pump& service_pump_) : st_server_base<echo_socket>(service_pump_) {} echo_server(service_pump& service_pump_) : server_base<echo_socket>(service_pump_) {}
statistic get_statistic() statistic get_statistic()
{ {
...@@ -116,7 +58,7 @@ int main(int argc, const char* argv[]) ...@@ -116,7 +58,7 @@ int main(int argc, const char* argv[])
else else
puts("type " QUIT_COMMAND " to end."); puts("type " QUIT_COMMAND " to end.");
st_service_pump sp; service_pump sp;
echo_server echo_server_(sp); echo_server echo_server_(sp);
if (argc > 3) if (argc > 3)
......
<?xml version="1.0" encoding="gb2312"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9.00"
Name="concurrent_server"
ProjectGUID="{EDCF2EE4-C212-4B2C-B535-528838F4816C}"
RootNamespace="concurrent_server"
Keyword="Win32Proj"
TargetFrameworkVersion="196613"
>
<Platforms>
<Platform
Name="Win32"
/>
<Platform
Name="x64"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="2"
GenerateDebugInformation="true"
SubSystem="1"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="2"
GenerateDebugInformation="true"
SubSystem="1"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
WholeProgramOptimization="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="1"
GenerateDebugInformation="true"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1"
WholeProgramOptimization="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="1"
GenerateDebugInformation="true"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Դļ"
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\concurrent_server.cpp"
>
</File>
</Filter>
<Filter
Name="ͷļ"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
</Filter>
<Filter
Name="Դļ"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>
module = asio_client module = concurrent_server
include ../config.mk include ../config.mk
...@@ -3,15 +3,16 @@ ...@@ -3,15 +3,16 @@
#boost_include_dir = -I/usr/local/include/ #boost_include_dir = -I/usr/local/include/
#boost_lib_dir = -L/usr/local/lib/ #boost_lib_dir = -L/usr/local/lib/
cflag = -Wall -fexceptions -std=c++11 cflag = -Wall -fexceptions -std=c++98
ifeq (${MAKECMDGOALS}, debug) ifeq (${MAKECMDGOALS}, debug)
cflag += -g -DDEBUG cflag += -g -DDEBUG
dir = debug dir = debug
else else
cflag += -O2 -DNDEBUG cflag += -O -DNDEBUG
lflag = -s lflag = -s
dir = release dir = release
endif endif
cflag += -DBOOST_ASIO_NO_DEPRECATED
kernel = ${shell uname -s} kernel = ${shell uname -s}
ifeq (${kernel}, SunOS) ifeq (${kernel}, SunOS)
......

包括了公共函数、类、接口等。
1. 接口
i_server:
用于在server_socket_base中调用server_base,server_socket_base使用,server_base实现,
如果想增加更多的功能,则继承自i_server实现自己的接口,继承自server_base并实现这个新接口,最后继承自server_socket_base以使用这个新接口。
这里只解释一下del_socket和restore_socket接口:
关闭一个连接时,server_socket_base调用这个方法,i_server的实现者(server就是一个i_server的实现者)一般来说应该把这个连接从自己的
客户端对象池里面删除,调用这个方法的最佳时期是在on_recv_error里面。
注意,socket_ptr参数看起来是一个object,其实它的类型是server_base中的Socket模板参数,用object完全是为了不让i_server带一个类似于
Socket的模板参数,带上之后会有模板参数的循环依赖的问题,如果哪位同仁可以解决此问题,麻烦告诉我一声。
i_server的实现者在使用socket_ptr参数之前,会转换成Socket类型指针,请看server_base的实现。
restore_socket接口:
当connector在断开后重新连接上服务器之后,在服务端必须会得到一个全新的server_socket_base,我们可以直接把之前的server_socket_base直接恢复到
这个新的server_socket_base之中(其它了SOCKET句柄不一样之外,其它都一样),并且保持id不变。为此你需要定义ST_ASIO_RESTORE_OBJECT宏,这个
宏的意思跟ST_ASIO_REUSE_OBJECT类似,只是不会在创建server_socket_base是重用,而是在调用restore_socket时重用,那么第二个server_socket_base如何
知道第一个server_socket_base是哪一个呢?答案是这个功能需要客户端支持,客户端需要在重连接成功之后,发送必要的数据以便让server_socket_base知道其
前身的id,然后从前身恢复。注意,恢复的过程也需要你去实现,方法是重写server_socket_base::take_over。
i_buffer:
如果想要在运行时替换打包解包器,你的缓存必须实现这个接口,然后以auto_buffer或者shared_buffer作为消息类型。
i_packer:
所有打包器必须实现这个接口,tcp和udp共用。每次发送消息的时候,只要不是直接发送的消息,都会调用pack_msg来打包。
i_unpacker:
tcp解包器必须实现这个接口。每次开始接收消息之后,会调用prepare_next_recv以得到一个缓存,当收到一些数据之后,asio会调用completion_condition,
如果返回0,说明已经成功的接收到了至少一个消息,于是st_asio_wrapper会接着调用parse_msg来解析消息,解析完了之后再次调用prepare_next_recv进入一个新的循环。
注意,解析完消息之后,缓存里面可能还剩余部分数据(消息被分包),第二次prepare_next_recv返回的缓存,不能覆盖未解析完的数据(如果采用固定缓存的解包器,通常的做法
是把未解析完的数据移动到缓存最前面),否则数据会被覆盖。如果定义了ST_ASIO_SCATTERED_RECV_BUFFER宏,则解包器支持scatter-gather缓存,即一次提供多个不连续的
缓存用于接收消息,如果你使用了ring buffer,相信你知道我在说什么。
i_udp_unpacker:
udp解包器必须实现这个接口。
2. 类
st_atomic:
boost::atomic的代替品,因为boost::atomic在boost 1.53版本中才引入,而st_asio_wrapper库最低支持boost 1.49,注意它是通过boost::shared_mutex
来实现线程安全的,效率不如原子操作高。
scope_atomic_lock:
功能与boost::scope_lock一致,只是它是lock-free的,用原子操作代替了mutex操作(如果用st_aotmic则除外)。
dummy_packer:
仅仅提供发送消息的类型以便通过编译,无法真正的打包,所以只能通过direct_send_msg等发送消息。
auto_buffer:
如果想要运行时替换打包解包器,则打包解包器必须以auto_buffer或者shared_buffer作为消息类型(你写个类似的也可,但没有必要,如果需要更多的功能可以用继承)。
这个类是不允许被复制的,用户只负责分配内存。
shared_buffer:
同auto_buffer,除了允许被复制(表面上的被复制,不是深度复制,我们要尽量避免深度复制,std::string就深度复制)。
dummy_packer:
这个类只提供消息类型的定义,不做真正的打包,所以用户必须以native方式发送消息,或者用direct_send_msg等发送消息。
udp_msg:
udp消息,其实就是在tcp消息上加了一个对端地址。
statistic:
性能统计,如果定义了ASCS_FULL_STATISTIC,则统计数量和时间(对性能有些影响),否则只统计数量。
消息发送相关的统计
uint_fast64_t send_msg_sum; 成功发送到asio的消息条数
uint_fast64_t send_byte_sum; 成功发送到asio的消息总字节数
stat_duration send_delay_sum; 从消息发送(send_(native_)msg,不包括打包时间)到真正发送(asio::async_write)的延迟时间
stat_duration send_time_sum; 从真正的消息发送(asio::async_write)到发送完成(发送到SOCKET缓存),这一项体现了你的网络吞吐率,注意吞吐率低不代表是你的问题,也有可能是接收方慢了。
消息接收相关统计
uint_fast64_t recv_msg_sum; 收到的消息条数
uint_fast64_t recv_byte_sum; 收到的消息总字节数
stat_duration dispatch_dealy_sum; 从消息解包完成之后,到on_msg_handle的时间延迟,如果这项偏大,可能是因为service线程总不够用
stat_duration recv_idle_sum; 暂停消息接收的总时间,在接收缓存满,消息派发被暂停或者拥塞控制时,都将会暂停消息接收。
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
stat_duration handle_time_1_sum; 调用on_msg花费的总时间
#endif
stat_duration handle_time_2_sum; 调用on_msg_handle花费的总时间
obj_with_begin_time:
可包装任何对象,并且加上一个时间(用于时间统计)。
log_formater:
log打印函数,只是打印到屏幕,如果需要更详细的功能需要自己实现。
unified_out:
调用log_formater打印log,如果定义ASCS_NO_UNIFIED_OUT,所有log打印函数将变成空函数,什么也不做。
3. 函数
do_something_to_all:
对容器(不得是类map容器,比如map,unordered_map等)中的所有对象执行一个动作,动作由一个函数对象(std::function)决定。
可以选择带锁和不带锁,如果带锁,加的是共享锁,请注意。
do_something_to_one:
同do_something_to_all,如果对某一个对象执行的动作返回true,则跳过所有剩下的对象,如果全部对象都返回false,则等于do_something_to_all,但效率上稍慢。

包括了容器相关的类和方法等。
类:
list:
boost::container::list的别名,只是省略了第二个参数,
dummy_lockable:
实现了lock()和unlock()接口,但什么也不做。
lockable:
实现了lock()和unlock()接口,分别对std::shared_mutex执行加锁和解锁操作。
template<typename T, typename Container, typename Lockable>
class queue : public Container, public Lockable
一个队列基类,可选择容器(list和deque,或者是有相同接口的定义容器)和锁类型(dummy_lockable和lockable,或者是有相同接口的定义锁)。
如果你想用自己的容器,那么这个容器必须提供必要的接口,具体需要哪些接口,我的建议是,先用上你的容器,然后由编译器告诉你缺少什么接口,
接口需要做的工作从名字便知(比如push_back,pop_front等)。
template<typename T, typename Container> using non_lock_queue = queue<T, Container, dummy_lockable>;
无锁不安全队列(在某些特定的业务逻辑下,队列无需加锁,由业务保证其线程安全性,比如绝对的一应一答式逻辑)。
template<typename T, typename Container> using lock_queue = queue<T, Container, lockable>;
有锁安全队列。
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
ST_SERVICE_THREAD_NUM被改成了ST_ASIO_SERVICE_THREAD_NUM ST_SERVICE_THREAD_NUM被改成了ST_ASIO_SERVICE_THREAD_NUM
size_t_format被改成了ST_ASIO_SF size_t_format被改成了ST_ASIO_SF
每个直接或者间接从st_timer继承的类中,都定义了TIMER_BEGIN和TIMER_END两个静态常量, 每个直接或者间接从timer继承的类中,都定义了TIMER_BEGIN和TIMER_END两个静态常量,
用户自定义定时器ID必须从父类的TIMER_END开始。 用户自定义定时器ID必须从父类的TIMER_END开始。
其它所有文档请参看ascs的开发文档,主要的不同点仅三个:
1. 最外层的命名空间是st_asio_wrapper而非ascs;
2. 类atomic仅在st_asio_wrapper中存在;
3. 类cpu_timer仅在ascs中存在;
\ No newline at end of file

#ifdef ASCS_HUGE_MSG
#define ASCS_HEAD_TYPE uint32_t
#define ASCS_HEAD_H2N htonl
#else
#define ASCS_HEAD_TYPE uint16_t
#define ASCS_HEAD_H2N htons
#endif
#define ASCS_HEAD_LEN (sizeof(ASCS_HEAD_TYPE))
namespace st_asio_wrapper { namespace ext {
打包器的helper类,目前只有一个方法,就是判断要打包的消息,在打包后,是否超过了最大长度
class packer_helper
{
public:
static size_t msg_size_check(size_t pre_len, const char* const pstr[], const size_t len[], size_t num);
};
默认的打包器,如果你需要的消息格式与默认打包器的消息格式不相同,那么显然的,你需要实现自己的打包器;
默认打包器的消息格式是:长度(2字节)+ 消息内容,所以其支持的消息长度为1至(65535-2),如果想突破这个限制,
可以定义HUGE_MSG宏,那么可以支持到4G长度。
注意,大消息会占用你相像不到的大内存,比如你的消息长度为1M(看起来不算大),那么由于每个tcp::socket都有一个解包器,
每个解包器都必须有一个至少1M大小的接收缓存,那么1024个tcp::socket,光是解包器缓存就占用1G内存,再考虑默认的接收发送缓存
分别可最大存放1024个消息,那么每个tcp::socket在接收发送缓存都满的情况下,可占用2 * 1024 * 1M = 2G内存,那么1024个tcp::socket
就将占用2T内存。所以推荐的作法是,如果你的消息很长,那么一定要尽量减小接收发送缓存的长度,比如设置为1个消息。
class packer : public i_packer<std::string>;
当你想在运行时替换打包器的话,可以把这个打包器设置为默认打包器,这个打包器返回replaceable_buffer对象,由于replaceable_buffer对象
保存了一个i_buffer指针,所以只要是实现了i_buffer接口的对象,都能赋予replaceable_buffer,具体请参看replaceable_buffer及i_buffer的定义。
template<typename T = replaceable_buffer>
class replaceable_packer : public i_packer<T>;
固定长度的打包器。
class fixed_length_packer : public packer;
带固定头和固定尾的打包器,头可以为空,尾不能为空。
class prefix_suffix_packer : public i_packer<std::string>;
}} //namespace

st_client主要是实现st_service_pump::i_service接口,以便st_service_pump能启动它,用在tcp客户端和udp两端。
st_sclient只包括一个套接字,如果你需要一系列套接字,则使用st_client。
namespace st_asio_wrapper
{
只支持一条连接的客户端(或者说一个套接字),tcp和udp通用
template<typename Socket>
class st_sclient : public st_service_pump::i_service, public Socket
{
public:
st_sclient(st_service_pump& service_pump_);
protected:
virtual void init();
virtual void uninit();
实现i_service的纯虚接口,供st_service_pump在start_service/stop_service时调用,这两个接口其实就是实现了开始和结束逻辑,开始意味着什么由Socket决定,
这个我们在前面讲st_socket、st_tcp_socket、st_udp_socket、st_connector和st_server_socket的时候,已经说过多次了。
};
支持多条连接的客户端(或者说多个套接字),tcp和udp通用,其实它更像一个容器,只是在st_object_pool上扩展了一些helper函数
template<typename Socket, typename Pool>
class st_client : public Pool
{
protected:
st_client(st_service_pump& service_pump_);
template<typename Arg>
st_client(st_service_pump& service_pump_, Arg arg);
后者由ssl使用。
virtual void init();
实现i_service的纯虚接口,供st_service_pump在start_service时调用,跟st_sclient::init功能一样,只是对所有客户端都做一个“开始”操作。
public:
bool add_socket(typename Pool::object_ctype& socket_ptr, bool reset = true);
添加一个连接到对象池,并调用socket_ptr->start(),如果reset为true,那么在这之前还会先调用socket_ptr->reset()。
using Pool::create_object;
对客户端开放对象创建功能。
};
} //namespace

#ifndef ST_ASIO_SERVER_IP
#define ST_ASIO_SERVER_IP "127.0.0.1"
#endif
#ifndef ST_ASIO_SERVER_PORT
#define ST_ASIO_SERVER_PORT 5050
#endif
#ifndef ST_ASIO_RECONNECT_INTERVAL
#define ST_ASIO_RECONNECT_INTERVAL 500 //millisecond(s)
异步连接,asio返回失败之后,暂停一定时间继续重试(即再次调用asnc_connect)。
#endif
namespace st_asio_wrapper
{
带连接功能的st_tcp_socket,算是一个真正的客户端了
template <typename Packer, typename Unpacker, typename Socket = asio::ip::tcp::socket,
template<typename, typename> class InQueue = ASCS_INPUT_QUEUE, template<typename> class InContainer = ASCS_INPUT_CONTAINER,
template<typename, typename> class OutQueue = ASCS_OUTPUT_QUEUE, template<typename> class OutContainer = ASCS_OUTPUT_CONTAINER>
class st_connector_base : public st_tcp_socket_base<Socket, Packer, Unpacker, InQueue, InContainer, OutQueue, OutContainer>
{
public:
st_connector_base(boost::asio::io_service& io_service_);
template<typename Arg>
st_connector_base(boost::asio::io_service& io_service_, Arg& arg);
ssl使用。
public:
virtual void reset();
重置所有,st_object_pool在重用时会调用。st_connector的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
virtual bool obsoleted();
在调用父类同名函数的基础上,增加了对是否重连接的判断,如果需要重连接则返回假。
void set_server_addr(unsigned short port, const std::string& ip);
const boost::asio::ip::tcp::endpoint& get_server_addr() const;
设置服务端地址用于连接之用,需要在do_start之前被调用。
void disconnect(bool reconnect = false);
直接调用force_shutdown。
void force_shutdown(bool reconnect = false);
强制关闭————调用父类的shutdown,如果reconnect为true,则关闭之后,马上重新连接服务器。
void graceful_shutdown(bool reconnect = false, bool sync = true);
优雅关闭,调用父类的graceful_shutdown函数,reconnect参数的意义同上,sync参数直接传递给父类。
在on_msg中,请以sync为false调用该函数,在其它所有service线程中,推荐也用sync为false调用该函数。
void show_info(const char* head, const char* tail) const;
在head和tail中间,显示自己的地址(IP加端口)。
void show_info(const char* head, const char* tail, const boost::system::error_code& ec) const;
同上,但加上了显示ec.message.data()的内容。
protected:
virtual bool do_start();
开始,这里开始的意义是,如果连接未成功,则开始连接服务器,如果连接已经成功,则开始接收数据。
virtual int prepare_reconnect(const boost::system::error_code& ec);
连接失败时回调,返回大于等于零则继续在相应毫秒之后重试,返回负数则放弃。
virtual void on_connect();
连接成功时回调,用户重写它以得到连接成功事件。
virtual void on_unpack_error();
解包错误,默认的行为是关闭连接,可以重写以自定义行为。
virtual void on_recv_error(const error_code& ec);
接收错误,默认的行为是关闭连接,可以重写以自定义行为。
virtual void on_async_shutdown_error();
异步关闭连接失败时回调。
virtual bool on_heartbeat_error();
心跳检测超时时回调。
virtual void on_close();
如果需要重连接,则执行重连接,所以用户在重写它的时候,如果需要重连接逻辑,还得调用st_connector_base::on_close,
要么就得自己实现。
bool prepare_next_reconnect(const boost::system::error_code& ec);
如果允许(io_service仍然在运行且prepare_reconnect返回大于等于0),启动定时器以延时一小段时间之后重新连接服务器。
bool check_heartbeat(int interval);
发送和读取心跳包,将做断线检测。
如果定义了ASCS_HEARTBEAT_INTERVAL,将自动调用这个函数,否则你也可以以自己的逻辑来调用它。
private:
bool async_shutdown_handler(st_timer::tid id, ssize_t loop_num);
异步优雅关闭(shutdown)超时定时器。
void connect_handler(const error_code& ec);
连接成功或者失败后由asio回调。
protected:
boost::asio::ip::tcp::endpoint server_addr;
服务器地址。
bool need_reconnect;
是否重新连接(当连接断开后)。
};
} //namespace

#ifndef ST_ASIO_MAX_OBJECT_NUM
#define ST_ASIO_MAX_OBJECT_NUM 4096
#endif
//#define ST_ASIO_RESTORE_OBJECT
//ST_ASIO_RESTORE_OBJECT has the same effects as macro ST_ASIO_REUSE_OBJECT (it will overwrite the latter), except:
//reuse will not happen when create new connections, but just happen when invoke i_server::restore_socket.
//you may ask, for what purpose we introduced this feature?
//consider following situation:
//if a specific link is down, and the client has reconnected to the server, on the server side, how does the new st_server_socket_base
//restore all user data (because you don't want to nor need to reestablish them) and keep its id?
//before this feature been introduced, it's almost impossible.
//according to above explanation, we know that:
//1. like object pool, only objects in invalid_object_can can be restored;
//2. client need to inform st_server_socket_base the former id (or something else which can be used to calculate the former id
// on the server side) after reconnected to the server;
//3. this feature needs user's support (send former id to server side on client side, invoke i_server::restore_socket in st_server_socket_base);
//4. do not define this macro on client side nor for UDP.
#ifndef ST_ASIO_REUSE_OBJECT
#ifndef ST_ASIO_FREE_OBJECT_INTERVAL
#define ST_ASIO_FREE_OBJECT_INTERVAL 10
如果未开启对象池,将自动开启一个定时器,用于定时释放(从内存中删除)失效的对象(失效对象是指obsoleted()返回true,
智能指针只有一个引用并且超时了的对象,超时请参看ST_ASIO_OBSOLETED_OBJECT_LIFE_TIME宏),用这个宏指定定时器间隔,单位为秒。
如果开启了对象池,将不会自动开启这个定时器,但使用者可以自己定义这个宏以强迫st_object_pool开启这个定时器(建议间隔
就不能再是10秒了,应该更长一点,比如1分钟),然后失效对象将会被关闭(调用对象的close()函数以释放SOCKET句柄)而不是被释放。
其实这个定时器就是以不同的参数调用free_object()函数而已,所以用户也可以自己开定时器来更灵活的控制如果释放或者关闭对象。
#endif
#endif
//#define ST_ASIO_CLEAR_OBJECT_INTERVAL 60
自动清除(从对象池移出到临时链表)失效对象,这个功能有什么用呢?
如果在连接断开时,你没有或者不想去从对象池中删除,那么可以让st_object_pool周期性的查找失效连接,
然后移出对象池,这样一次遍历可以操作多条连接,对于短连接效率可能会更高,对于长连接,建议每次连接断开时,调用
st_server::del_client()马上清除这条连接。用这个宏指定定时器间隔,单位为秒。
namespace st_asio_wrapper
{
对象池类,用于管理正常的和失效的连接,对象重用,定时删除已经关闭的连接等;
注意,只能管理由st_socket派生的对象
template<typename Object>
class st_object_pool: public st_service_pump::i_service, public st_timer
{
public:
typedef boost::shared_ptr<Object> object_type;
typedef const object_type object_ctype;
hash表需要的对象比较类。
struct st_object_hasher
{
public:
size_t operator()(object_ctype& object_ptr) const;
size_t operator()(uint_fast64_t id) const;
};
struct st_object_equal
{
public:
bool operator()(object_ctype& left, object_ctype& right) const;
bool operator()(uint_fast64_t id, object_ctype& right) const;
};
typedef boost::unordered::unordered_set<object_type, st_object_hasher, st_object_equal> container_type;
protected:
st_object_pool(st_service_pump& service_pump_);
void start();
开始,根据宏开启一些定时器,比如如果未定义REUSE_OBJECT,则开启一个定时器用于定时查找已经关闭的连接。
void stop();
结束,关闭所有定时器。
bool add_object(object_ctype& object_ptr);
添加一个对象,注意对象总数不能超过ST_ASIO_MAX_OBJECT_NUM。
bool del_object(object_ctype& object_ptr);
清除一个对象(从对象池移出到临时链表,并不真正的从内存中释放该对象)。
virtual void on_create(object_ctype& object_ptr);
每创建(包括重用)一个对象之后,回调这个函数,用户可以做一些在对象池层面上的逻辑,比如额外的初始化工作等。
void init_object(object_ctype& object_ptr);
内部使用,目前只是给对象赋一个唯一的ID。
object_type change_object_id(object_ctype& object_ptr, uint_fast64_t id)
将无效对象(其id等于id)恢复到object_ptr,如果成功,object_ptr的take_over函数将被回调,同时object_ptr的id已经被修改为id。
#if defined(ST_ASIO_REUSE_OBJECT) && !defined(ST_ASIO_RESTORE_OBJECT)
object_type reuse_object();
查找可重用的对象,如果没有,返回空的智能指针。能被重用的对象必需是:
1. 已经从对象池移到了临时链表里面;
2. 对象的引用记数(对象是一个shared_ptr)必须是1;
3. 对象的obsoleted函数必须返回true。
#endif
template<typename Arg>
object_type create_object(Arg& arg);
template<typename Arg1, typename Arg2>
object_type create_object(Arg1& arg1, Arg2& arg2);
如果定义了ST_ASIO_REUSE_OBJECT且未定义T_ASIO_RESTORE_OBJECT宏,则先调用reuse_object尝试重用对象,如果没有对象可被重用,
则创建一个新的,最后都是调用init_object。
object_type create_object();
helper函数,以i_server::sp为参数调用create_obbject。
public:
container_type& container();
用于配置unordered_set,比如设置负载因子,预分配空间等。注意必须在service_pump启动之前调用,因为没有锁相应的mutex。
size_t max_size() const;
void max_size(size_t _max_size);
对象池最大容量,可运行时修改。占用内存是动态分配的,只有有效的对象(包括等待被重用的对象)会占用内存。
size_t size();
对象池中的对象个数,不包括被移除到临时链表的对象。
object_type find(uint_fast64_t id);
根据id查找有效对象。
object_type at(size_t index);
获取指定位置的有效对象(连接池中的对象),位置序号从0开始,复杂度O(n)。
size_t invalid_object_size();
获取无效对象总数(临时链表里面的对象),无效对象要么定时被删除,要么等待被重用,由宏控制。
object_type invalid_object_find(uint_fast64_t id);
根据id查找无效对象,复杂度O(n)。
object_type invalid_object_at(size_t index);
获取指定位置的无效对象,位置序号从0开始,复杂度O(n)。
object_type invalid_object_pop(uint_fast64_t id);
根据id查找无效对象,并且从容器中删除,复杂度O(n)。
void list_all_object();
列出所有有效对象。
void clear_obsoleted_object(container_type& objects);
删除对象池里面的所有无效对象(移出到临时链表)。
void free_object(size_t num = -1);
释放指定数量的无效对象,如果对象重用开启,则无效对象永远不会释放(而是等待被重用),在某些情况下,你可能不需要
这么多等待被重用的无效对象,可以用这个函数来释放一些无效对象。
template<typename _Predicate> void do_something_to_all(const _Predicate& __pred);
template<typename _Predicate> void do_something_to_one(const _Predicate& __pred);
对调用一个对象调用__pred,只操作有效对象。
protected:
boost::atomic_uint_fast64_t cur_id;
当前已经分配到哪个id了,用于为每一个通过st_object_pool::create_object创建的对象分配一个唯一的id。
container_type object_can;
boost::shared_mutex object_can_mutex;
size_t max_size_;
存放有效对象(hash表)。
boost::container::list<invalid_object> invalid_object_can;
boost::shared_mutex invalid_object_can_mutex;
存放无效对象(对象池,双向链表)。
};
} //namespace

#ifndef ST_ASIO_SERVER_PORT
#define ST_ASIO_SERVER_PORT 5050
服务器监听端口。
#endif
#ifndef ST_ASIO_ASYNC_ACCEPT_NUM
#define ST_ASIO_ASYNC_ACCEPT_NUM 16
同时投递多少个async_accept。
#endif
#ifndef ST_ASIO_TCP_DEFAULT_IP_VERSION
#define ST_ASIO_TCP_DEFAULT_IP_VERSION boost::asio::ip::tcp::v4()
为监听套接字绑定地址时,在不指定ip的情况下,指定ip地址的版本(v4还是v6),如果指定了ip,则ip地址的版本可以从ip中推导出来。
#endif
namespace st_asio_wrapper
{
服务端服务器类,主要功能是监听和接受连接,连接管理继承于Pool
template<typename Socket, typename Pool = st_object_pool<Socket>, typename Server = i_server>
class st_server_base : public Server, public Pool
{
public:
st_server_base(st_service_pump& service_pump_);
template<typename Arg>
st_server_base(st_service_pump& service_pump_, Arg arg);
ssl使用。
void set_server_addr(unsigned short port, const std::string& ip = std::string());
const boost::asio::ip::tcp::endpoint& get_server_addr() const;
设置获取监听地址。
void stop_listen();
停止监听。
bool is_listening() const;
是否正在监听。
virtual st_service_pump& get_service_pump();
virtual const st_service_pump& get_service_pump() const;
virtual bool del_socket(const boost::shared_ptr<st_object>& socket_ptr);
virtual bool restore_socket(const boost::shared_ptr<st_object>& socket_ptr, uint_fast64_t id);
实现i_server中的四个纯虚接口。
del_socket的功能主要是调用st_object_pool的del_object函数,并输出一些信息,主要用于连接断开时调用,也可用于主动断开连接,
但是用st_socket的force_close或者graceful_close更理合理一些。
restore_socket的功能主要是id的重用和st_server_socket_base对象的恢复(在st_connector重新连接之后)。
void broadcast_msg(const std::string& str, bool can_overflow = false);
void broadcast_msg(const char* pstr, size_t len, bool can_overflow = false);
void broadcast_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void broadcast_native_msg(const std::string& str, bool can_overflow = false);
void broadcast_native_msg(const char* pstr, size_t len, bool can_overflow = false);
void broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void safe_broadcast_msg(const std::string& str, bool can_overflow = false);
void safe_broadcast_msg(const char* pstr, size_t len, bool can_overflow = false);
void safe_broadcast_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void safe_broadcast_native_msg(const std::string& str, bool can_overflow = false);
void safe_broadcast_native_msg(const char* pstr, size_t len, bool can_overflow = false);
void safe_broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void sync_broadcast_msg(const std::string& str, bool can_overflow = false);
void sync_broadcast_msg(const char* pstr, size_t len, bool can_overflow = false);
void sync_broadcast_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void sync_broadcast_native_msg(const std::string& str, bool can_overflow = false);
void sync_broadcast_native_msg(const char* pstr, size_t len, bool can_overflow = false);
void sync_broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
对每一个连接调用st_tcp_socket中的同名函数。
void disconnect(typename Pool::object_ctype& socket_ptr);
void disconnect();
void force_shutdown(typename Pool::object_ctype& socket_ptr);
void force_shutdown();
void graceful_shutdown(typename Pool::object_ctype& socket_ptr, bool sync = false);
void graceful_shutdown();
先从对象池中删除socket_ptr(del_object),然后调用socket_ptr的同名函数。如果不带socket_ptr,则对所有有效对象执行同名函数(不会先执行del_object)。
protected:
virtual void init();
virtual void uninit();
实现i_service纯虚接口,在启动和关闭service的时候,由st_service_pump调用。
virtual bool on_accept(typename Pool::object_ctype& socket_ptr);
一个连接成功被接受后调用这个函数,如果返回true,连接将被st_object_pool管理;如果返回false,那么可能意味着你不想要这个连接
(你应该在返回false之前关闭这个st_socket)或者你想自己管理这个连接(你应该在返回false之前调用socket_ptr->start以开始数据接收),
总之st_ojbect_pool不再维护这个连接。
virtual bool on_accept_error(const boost::system::error_code& ec, typename Pool::object_ctype& socket_ptr);
在接受连接失败时回调这个函数,如果你想忽略这个错误且马上继续接受新连接,则返回true;如果你想忽略这个错误且等一段时间之后再继续接受新连接,
则启动一个定时器并返回fale,当定时器到达时,调用start_next_accept()函数;如果你想永远终止再接受新连接,则不要重写这个虚函数,如果你
非要重写,那么在你的代码后面调用stop_listen()且返回false。
void start_next_accept();
开始下一次异步接受连接,抽象成一个方法是因为两个地方需要调用,一是init内,一accept_handler内。
使用者也可以随时调用这个方法,如果你想增加一些异步accept的话(参看ST_ASIO_ASYNC_ACCEPT_NUM宏)。
protected:
bool add_socket(typename Pool::object_ctype& socket_ptr);
添加一条连接到对象池(调用st_object_pool::add_object),如果成功,打印一些连接建议相关的信息。
void accept_handler(const error_code& ec, typename Pool::object_ctype& socket_ptr);
异步接受到连接时asio回调,如果出错(ec为真),将调用on_accept_error(),否则继续异步接受连接(start_next_accept),
如果on_accept_error()返回true,也将继续异步接受连接(start_next_accept)。
protected:
boost::asio::ip::tcp::endpoint server_addr;
监听地址。
boost::asio::ip::tcp::acceptor acceptor;
异步连接接受器。
};
} //namespace

namespace st_asio_wrapper
{
服务端套接字类
template<typename Packer, typename Unpacker, typename Server = i_server, typename Socket = boost::asio::ip::tcp::socket,
template<typename, typename> class InQueue = ASCS_INPUT_QUEUE, template<typename> class InContainer = ASCS_INPUT_CONTAINER,
template<typename, typename> class OutQueue = ASCS_OUTPUT_QUEUE, template<typename> class OutContainer = ASCS_OUTPUT_CONTAINER>
class st_server_socket_base : public st_tcp_socket_base<Socket, Packer, Unpacker, InQueue, InContainer, OutQueue, OutContainer>,
public boost::enable_shared_from_this<st_server_socket_base<Packer, Unpacker, Server, Socket, InQueue, InContainer, OutQueue, OutContainer>>
{
public:
st_server_socket_base(Server& server_);
template<typename Arg>
st_server_socket_base(Server& server_, Arg& arg);
ssl使用。
virtual void reset();
重置所有,st_object_pool在重用时会调用。st_server_socket的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
virtual void take_over(boost::shared_ptr<st_server_socket_base> socket_ptr);
恢复对象socket_ptr到这个st_server_socket,所以你的用户数据最好是指针(智能指针),这样就不需要拷贝了,否则必须执行深拷贝。
void disconnect();
直接调用force_shutdown。
void force_shutdown();
强制退出————调用父类的同名函数。
void graceful_shutdown(bool sync = true);
优雅关闭————调用父类的同名函数。
在on_msg中,请以sync为false调用该函数,在其它所有service线程中,推荐也用sync为false调用该函数。
void show_info(const char* head, const char* tail) const;
在head和tail中间,显示对方的地址(IP加端口)。
void show_info(const char* head, const char* tail, const boost::system::error_code& ec) const;
同上,但加上了显示ec.message.data()的内容。
protected:
virtual bool do_start();
开始,这里开始的意义是,马上开始接收数据。
virtual void on_unpack_error();
解包错误,默认的行为是关闭连接,可以重写以自定义行为。
virtual void on_recv_error(const error_code& ec);
连接断开,默认的行为是调用i_server::del_client,可以重写以自定义行为。
virtual void on_async_shutdown_error();
异步关闭出错时回调。
virtual bool on_heartbeat_error();
心跳超时时回调。
protected:
Server& server;
用于操控st_server,st_server在创建st_server_socket的时候,会把自己的引用通过构造函数传入。
};
} //namespace

#ifndef ST_ASIO_SERVICE_THREAD_NUM
#define ST_ASIO_SERVICE_THREAD_NUM 8
同时开启多少个线程执行boost::asio::io_service::run函数。
#endif
namespace st_asio_wrapper
{
包装io_service,用于启动st_asio_wrapper里面的service(实现了i_service的所有对象)。
class st_service_pump : public boost::asio::io_service
{
public:
class i_service
{
protected:
i_service(st_service_pump& service_pump_);
virtual ~i_service();
public:
void start_service();
启动service(调用init),如果service已经启动,则调用没有任何作用。
void stop_service();
停止service(调用uninit),如果service已经停止,则调用没有任何作用。
bool is_started() const;
判断service是否已经启动。
void id(int id);
int id() const;
设置/获取service的id,用不用id或者怎么使用id,由使用者决定,st_asio_wrapper目前未使用service id,
如果你需要查找service,则最好启用它。
void user_data(void* data_);
void* user_data() const;
用户数据,用不用或者怎么使用请发挥想象,st_asio_wrapper库本身并不使用这个值,也不管理其生命周期。
st_service_pump& get_service_pump();
const st_service_pump& get_service_pump() const;
获取st_service_pump对象。
protected:
virtual void init() = 0;
virtual void uninit() = 0;
继承者实现,在启动/停止service的时候,st_service_pump会调用。
protected:
st_service_pump& sp;
private:
bool started;
int id_;
void* data;
};
public:
typedef i_service* object_type;
typedef const object_type object_ctype;
typedef boost::container::list<object_type> container_type;
st_service_pump();
i_service* find(int id);
根据id查找service。
void remove(object_type i_service_);
void remove(int id);
删除指定的service(调用stop_and_free)。
void clear();
删除所有service(调用stop_and_free)。
void start_service(int thread_num = ST_ASIO_SERVICE_THREAD_NUM);
void stop_service();
启动/停止service(调用end_service),thread_num是线程数量(用于调用io_service::run)。
void start_service(object_type i_service_, int thread_num = ST_ASIO_SERVICE_THREAD_NUM);
void stop_service(object_type i_service_);
如果某个service是在前面那个start_service之后才添加的,则调用这个启动它,否则功能完全同前面那个start_service,这也是为什么带了
thread_num这个看似无关的参数。
stop_service停止指定的service,跟前面的stop_service一样,差别仅是前面的停止所有service。
void run_service(int thread_num = ST_ASIO_SERVICE_THREAD_NUM);
void end_service();
run_service同start_service,但是会阻塞,直到所有线程(调用io_service::run的线程)都退出,所以end_service必须在另外的线程中调用。
请注意,结束以run_service启动的service pump,必须用end_service,end_service调用之后马上返回,service pump是否结束以run_service
是否返回为准,而不是end_service的返回。
bool is_running() const;
等效于 !io_service::stopped()
bool is_service_started() const;
是否已经调用过了start_service(int thread_num)。
void add_service_thread(int thread_num);
运行时增加service线程,注意只能增加不能减少。
protected:
void do_service(int thread_num);
真正开启service,其实就是调用每一个service的start_service接口,并开启指定数据的线程来执行run函数。
void wait_service();
等待do_service创建的所有线程结束。
void stop_and_free(object_type i_service_);
调用stop_service接口,并传给free函数释放i_service_。
virtual void free(object_type i_service_);
默认什么也不做,如果你需要,请重写这个函数。
#ifdef ST_ASIO_ENHANCED_STABILITY
virtual bool on_exception(const std::exception& e);
如果io_service::run抛出异常,则会调用这个函数,如果返回真,则忽略异常再次调用io_service::run。
size_t run(boost::system::error_code& ec);
调用io_service::run,并在外面包一层try catch。
#endif
template<typename _Predicate> void do_something_to_all(const _Predicate& __pred);
template<typename _Predicate> void do_something_to_one(const _Predicate& __pred);
对所有service对象执行__pred。
private:
void add(object_type i_service_);
添加一个service,由i_service使用。
protected:
container_type service_can;
boost::shared_mutex service_can_mutex;
boost::thread_group service_threads;
bool started;
};
} //namespace

#ifndef ST_ASIO_DELAY_CLOSE
#define ST_ASIO_DELAY_CLOSE 0 //seconds, guarantee 100% safety when reusing or freeing this st_socket
#endif
static_assert(ST_ASIO_DELAY_CLOSE >= 0, "delay close duration must be bigger than or equal to zero.");
#ifndef ASCS_HEARTBEAT_INTERVAL
#define ASCS_HEARTBEAT_INTERVAL 0 //second(s)
#endif
发送心跳包间隔,在此间隔中,如果有消息被发送,将不会发送心跳包,直到下一个间隔再发。心跳包通过调用打包器的pack_heartbeat函数得到。
如果想关闭心跳包,定义这个宏为0即可,此时你仍然可以以自己的逻辑来发送接收心跳包,并判断连接的有效性,只需要你调用check_heartbeat或者
start_heartbeat即可,前者检测(然后如果未超时的话,将发送一个心跳包)一次,后者定时检查。
#ifndef ASCS_HEARTBEAT_MAX_ABSENCE
#define ASCS_HEARTBEAT_MAX_ABSENCE 3 //times of ASCS_HEARTBEAT_INTERVAL
#endif
static_assert(ASCS_HEARTBEAT_MAX_ABSENCE > 0, "heartbeat absence must be bigger than zero.");
在ASCS_HEARTBEAT_INTERVAL * ASCS_HEARTBEAT_MAX_ABSENCE秒之内,如果没有收到任何数据,连接被认为是意外中断。
namespace st_asio_wrapper
{
template<typename Socket, typename Packer, typename Unpacker, typename InMsgType, typename OutMsgType,
template<typename, typename> class InQueue, template<typename> class InContainer,
template<typename, typename> class OutQueue, template<typename> class OutContainer>
class st_socket: public st_timer
{
protected:
typedef obj_with_begin_time<InMsgType> in_msg;
typedef obj_with_begin_time<OutMsgType> out_msg;
typedef InQueue<in_msg, InContainer<in_msg>> in_container_type;
typedef OutQueue<out_msg, OutContainer<out_msg>> out_container_type;
st_socket(boost::asio::io_service& io_service_);
template<typename Arg>
st_socket(boost::asio::io_service& io_service_, Arg& arg);
ssl使用。
void first_init();
构造时调用,仅仅是为了节省代码量而已,因为我们有两个构造函数都将调用它。
void reset();
被重用是调用。
void clear_buffer();
清空所有buffer。
public:
typedef obj_with_begin_time<InMsgType> in_msg;
typedef obj_with_begin_time<OutMsgType> out_msg;
typedef InQueue<in_msg, InContainer<in_msg>> in_container_type;
typedef OutQueue<out_msg, OutContainer<out_msg>> out_container_type;
uint_fast64_t id() const;
bool is_equal_to(uint_fast64_t id) const;
获取id。
Socket& next_layer();
const Socket& next_layer() const;
typename Socket::lowest_layer_type& lowest_layer();
const typename Socket::lowest_layer_type& lowest_layer() const;
底层对象,它应该是一个boost::asio::ip::tcp::socket,boost::asio::ip::udp::socket或者
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>对象及其从它们继承的对象。
最底层对象其实就是调用底层对象的lowest_layer(),我们真正要读写的其实就是最底层对象。
virtual bool obsoleted();
判断本对象是否可以被重用或者被释放。
virtual bool is_ready() = 0;
是否可以开始发送和接收数据。
virtual void send_heartbeat() = 0;
发送心跳包,由子类实现。
bool started() const;
是否已经开始,已经开始意思是已经调用过start()了,多次调用start()会有严重的包乱顺问题,好在我在某个版本
增加了防止多次调用start()的功能,之前靠用户保证,现在st_asio_wrapper库可以保证,即使用户多次调用也没问题。
void start();
开始,开始的动作由子类实现,本函数只是简单的判断start是否已经被调用过了,如果没有,则调用do_start函数(纯虚)。
bool send_msg();
发送缓存里面的消息,如果当前已经在发送消息了,则调用本函数无任何作用。
st_socket内部有消息发送缓存,当连接未建立的时候,用户仍然可以发送消息(注意缓存满问题),这些消息会缓存起来,
当连接建立之后,会自动开始真正的发送消息,这个功能就是调用这个函数实现的。
void start_heartbeat(int interval, int max_absence = ST_ASIO_HEARTBEAT_MAX_ABSENCE);
开始定时发检测和送心跳包(未超时时)。
bool check_heartbeat(int interval, int max_absence = ST_ASIO_HEARTBEAT_MAX_ABSENCE);
检测心跳包,如果未超时的话将发送一个心跳包。
bool is_sending_msg() const;
是否正在发送数据。
bool is_dispatching_msg() const;
是否正在派发数据。
void congestion_control(bool enable);
bool congestion_control() const;
拥塞控制。
如果你在处理消息的过程中,发现无法处理当前这条消息(比如处理消息产生的回应消息会造成发送缓存溢出,或者其它必要条件还未准备好,
比如你要读取数据库,但数据库连接还未建立起来),那么你怎么办呢?第一是等,但一但阻塞在on_msg或者on_msg_handle,就阻塞了一个
service线程,service线程是非常有限的资源,除非你开了很多很多的service线程(这会严重影响效率,增加线程切换开销)。第二你可以开启拥塞控制,
然后在on_msg或者on_msg_handle里面返回false,代表无法处理当前这条消息。
具体来说,如果你在on_msg里面开启拥塞控制,那么马上停止通过on_msg派发剩下的消息,当前这条消息(你返回false的这条)进入接收缓存,并通过
on_msg_handle来派发,在on_msg_handle里面,如果你发现仍然处理不了,则继续返回false,直到你能够处理它,并在处理它之后(on_msg_handle里面)
关闭拥塞控制并返回true,那么剩下的消息将再次通过on_msg派发。
如果你在on_msg_handle里面处理消息,那返回false就是拥塞控制,不再需要调用congestion_control函数(调用也无效果)。
const struct statistic& get_statistic() const;
综合统计信息。
boost::shared_ptr<i_packer<typename Packer::msg_type>> packer();
boost::shared_ptr<const i_packer<typename Packer::msg_type>> packer() const;
void packer(const boost::shared_ptr<i_packer<typename Packer::msg_type>>& _packer_);
获取/修改打包器。
注意,运行时修改打包器是非线程安全的,它会与消息发送冲突,由于消息发送和打包器修改都是使用者触发的,所以如果有资源竞争,使用者
有义务解决冲突问题。不支持多线程一是为了效率,二是这个功能用得很少。
bool is_send_buffer_available();
判断消息发送缓存是否可用,即里面的消息数量是否小于ST_ASIO_MAX_MSG_NUM条,如果以can_overflow为true调用任何消息发送函数(如send_msg),
将马上成功而无论消息发送缓存是否可用,所以可能会造成消息发送缓存大小不可控。
bool direct_send_msg(const InMsgType& msg, bool can_overflow = false);
bool direct_send_msg(InMsgType&& msg, bool can_overflow = false);
直接发送消息(放入消息发送缓存)而不再调用i_packer::pack_msg函数,其实st_socket内部在发送消息时也是调用这个函数,只是在调用
之前先调用了i_packer::pack_msg而已。
size_t get_pending_send_msg_num();
size_t get_pending_recv_msg_num();
获取缓存里面的消息数量,其中post和send缓存里面的消息是打包过的;recv缓存里面的消息是解包过后的,下同。
void pop_first_pending_send_msg(InMsgType& msg);
void pop_first_pending_recv_msg(OutMsgType& msg);
弹出缓存中第一个包,如果得到一个空包(msg.empty()等于true),则说明缓存里面没有消息。
void pop_all_pending_send_msg(in_container_type& msg_list);
void pop_all_pending_recv_msg(out_container_type& msg_list);
弹出缓存中所有包,相当于清空了缓存。
protected:
virtual bool do_start() = 0;
子类重写,请看st_server_socket、st_connector、st_udp_socket和st_ssl_connector的实现。
virtual bool do_send_msg() = 0;
真正的消息发送(调用asio函数),具体怎么发请看st_tcp_socket和st_udp_socket的实现。
virtual void do_recv_msg() = 0;
真正的消息接收(调用asio函数),具体怎么发请看st_tcp_socket和st_udp_socket的实现。
virtual bool is_send_allowed() const;
是否允许发送消息,对于st_socket来说,只要未暂停消息发送,就是允许消息发送,子类重写这个函数实现自己的判断逻辑,然后加上
st_socket的判断结果,最终确认是否可发送数据。请看st_tcp_socket、st_connector和st_udp_socket的实现。
virtual void on_send_error(const error_code& ec);
virtual void on_recv_error(const boost::system::error_code& ec) = 0;
发送接收失败时回调,对于st_tcp_socket,如果需要连接断开事件,建议重写on_recv_error。
virtual bool on_heartbeat_error() = 0;
心跳包超时时回调。
virtual void on_close();
当对象真正被close之前,会调用这个回调,用户可以在这里面释放资源,在这之后,对象可能会被重用或者被释放。
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(OutMsgType& msg) = 0;
收到一条消息时回调,返回true表示消息被成功处理,返回false表示消息无法立即处理,于是进入接收缓存,通过on_msg_handle再次派发。
#endif
virtual bool on_msg_handle(OutMsgType& msg, bool link_down) = 0;
从接收缓存派发一条消息,返回true表示消息被成功处理,返回false表示消息无法立即处理,于是将暂停一小段时间之后继续重试(异步);
如果link_down,则无论返回true还是false,都将当成消息已经处理,将继续派发下一条消息(同步地),在连接断开时,会置link_down为真,
此时需要尽快的派发完所有剩余的消息。
#ifdef ST_ASIO_WANT_MSG_SEND_NOTIFY
virtual void on_msg_send(InMsgType& msg);
成功发送(消息写入底层socket缓存)一个消息之后回调,消息是打包过后的。
#endif
#ifdef ST_ASIO_WANT_ALL_MSG_SEND_NOTIFY
virtual void on_all_msg_send(InMsgType& msg);
当发送缓存由非空变为空的时候回调,消息是打包过后的。
#endif
void close();
开启close流程,由继承者调用。st_socket会定时检测自己是否可以安全的被重用或被释放(即所有异步调用都已结束,包括正常结束和非正常结束),
如果是,调用上面的on_close(), 然后st_object_pool将完全接管这个st_socket,以便在适当的时候重用或者释放它。
如果定义了ST_ASIO_DELAY_CLOSE宏且其值等于0,则st_socket将保证以上说的行为,如果没有定义,则简单地在ST_ASIO_DELAY_CLOSE秒后,调用on_close(),
然后同样的道理,st_object_pool将完全接管这个st_socket,以便在适当的时候重用或者释放它。
void handle_msg();
子类收到消息之后,调用这个函数来派发消息,它要么直接调用on_msg,要么把消息放入消息接收缓存,然后调用dispatch_msg,如果消息处理完毕(调用on_msg)
或者都放入了消息接收缓存,则调用do_recv_msg以继续接收数据。
void dispatch_msg();
派发消息(调用do_dispatch_msg)。
void do_dispatch_msg(bool need_lock);
调用io_service::post发出一个异步调用,调度到时回调msg_handler。
bool do_direct_send_msg(InMsgType&& msg);
将消息插入容器,内部使用。
private:
template<typename Object> friend class object_pool;
void id(uint_fast64_t id);
设置id,注意使用者不可设置id,只有st_socket的创建者(st_object_pool或者其继承者)才可设置id,除非这个st_socket没有被任何对象池管理。
bool timer_handler(tid id);
处理所有定时器
private:
template<typename Object> friend class st_object_pool;
void id(uint_fast64_t id) {_id = id;}
设置id,只有object_pool可以调用。
bool timer_handler(tid id);
定时器回调函数。
void msg_handler();
异步派发接收缓存里面的消息时,asio调用本函数,在这个函数里面将调用on_msg_handle,然后调用do_dispatch_msg或者dispatch_msg继续派发消息。
protected:
uint_fast64_t _id;
保存在对象池中时,作为key,所以必须唯一,对象池用hash作为容器以快速查找。
Socket next_layer_;
前面在next_layer里面解释过了。
out_msg last_dispatch_msg;
由于是异步发送和派发消息,这两个成员变量保证其在异步处理过程中的有效性。
std::shared_ptr<i_packer<typename Packer::msg_type>> packer_;
打包器。
in_container_type send_msg_buffer;
out_container_type recv_msg_buffer;
boost::container::list<out_msg> temp_msg_buffer;
收发缓存,访问temp_msg_buffer无需互斥,它只能在内部访问,作用是当收到消息之后,当消息无法存入接收缓存
(消息派发被暂停,或者正在拥塞控制),那么消息将被存放于temp_msg_buffer,并且不再继续接收消息,直到temp_msg_buffer
里面的消息全部被处理掉,或者移到了recv_msg_buffer,st_socket会周期性的做以上尝试。
volatile bool sending;
std::atomic_flag send_atomic;
volatile bool dispatching;
std::atomic_flag dispatch_atomic;
volatile bool congestion_controlling;
内部使用的一些状态,看名字应该能猜到其意思。
volatile bool started_; //has started or not
st_atomic_size_t start_atomic;
是否已经开始,开始的概念由子类具体实现,st_socket只是记录是否已经调用过start函数而已。
struct statistic stat;
typename statistic::stat_time recv_idle_begin_time;
时间消耗统计。
time_t last_send_time, last_recv_time;
上次读写时间,由于判断是否需要发送心跳包,和心跳包是否超时。
};
} //namespace

namespace st_asio_wrapper
{
仅支持一条连接的tcp客户端
template<typename Socket> using st_tcp_sclient_base = st_sclient<Socket>;
支持多条连接的tcp客户端
template<typename Socket, typename Pool = st_object_pool<Socket>>
class st_tcp_client_base : public st_client<Socket, Pool>
{
public:
st_tcp_client_base(st_service_pump& service_pump_);
template<typename Arg>
st_tcp_client_base(st_service_pump& service_pump_, Arg arg);
ssl使用。
size_t valid_size();
有效(已连接)的连接数量。
using st_client<Socket, Pool>::add_socket;
typename Pool::object_type add_socket();
创建或者重用一个对象,然后以reset为true调用父类的add_socket。注意未设地址,将使用SERVER_IP和SERVER_PORT
作为服务端地址,如果你要用别的地址,请用下面那个add_client。
typename Pool::object_type add_socket(unsigned short port, const std::string& ip = std::string());
创建或者重用一个对象,设置服务端地址,然后以reset为true调用父类的add_client。
void broadcast_msg(const std::string& str, bool can_overflow = false);
void broadcast_msg(const char* pstr, size_t len, bool can_overflow = false);
void broadcast_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void broadcast_native_msg(const std::string& str, bool can_overflow = false);
void broadcast_native_msg(const char* pstr, size_t len, bool can_overflow = false);
void broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void safe_broadcast_msg(const std::string& str, bool can_overflow = false);
void safe_broadcast_msg(const char* pstr, size_t len, bool can_overflow = false);
void safe_broadcast_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void safe_broadcast_native_msg(const std::string& str, bool can_overflow = false);
void safe_broadcast_native_msg(const char* pstr, size_t len, bool can_overflow = false);
void safe_broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void sync_broadcast_msg(const std::string& str, bool can_overflow = false);
void sync_broadcast_msg(const char* pstr, size_t len, bool can_overflow = false);
void sync_broadcast_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
void sync_broadcast_native_msg(const std::string& str, bool can_overflow = false);
void sync_broadcast_native_msg(const char* pstr, size_t len, bool can_overflow = false);
void sync_broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
对每一个连接调用st_tcp_socket中的同名函数。
void disconnect(typename Pool::object_ctype& client_ptr);
void disconnect(bool reconnect = false);
void force_shutdown(typename Pool::object_ctype& client_ptr);
void force_shutdown(bool reconnect = false);
void graceful_shutdown(typename Pool::object_ctype& client_ptr, bool sync = true);
void graceful_shutdown(bool reconnect = false, bool sync = true);
先从对象池中删除client_ptr,然后以默认参数调用client_ptr的同名函数。对于没有client_ptr参数的重载,不删除任何对象并对所有对象调用同名函数,参数直接传递。
protected:
virtual void uninit();
实现i_service的纯虚接口,由st_service_pump在stop_service时调用,跟st_sclient::uninit功能一样,只是对所有客户端都做一个“结束”操作。
};
} //namespace

#ifndef ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION
#define ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION 5 //seconds, maximum duration while graceful shutdown
#endif
static_assert(ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION > 0, "graceful shutdown duration must be bigger than zero.");
namespace st_asio_wrapper
{
tcp套接字类,实现tcp数据的收发
template <typename Socket, typename Packer, typename Unpacker,
template<typename, typename> class InQueue, template<typename> class InContainer,
template<typename, typename> class OutQueue, template<typename> class OutContainer>
class st_tcp_socket_base : public st_socket<Socket, Packer, Unpacker, typename Packer::msg_type, typename Unpacker::msg_type, InQueue, InContainer, OutQueue, OutContainer>
{
public:
typedef typename Packer::msg_type in_msg_type;
typedef typename Packer::msg_ctype in_msg_ctype;
typedef typename Unpacker::msg_type out_msg_type;
typedef typename Unpacker::msg_ctype out_msg_ctype;
protected:
enum shutdown_states {NONE, FORCE, GRACEFUL};
st_tcp_socket(boost::asio::io_service& io_service_);
template<typename Arg>
st_tcp_socket(boost::asio::io_service& io_service_, Arg& arg);
ssl使用。
void first_init();
构造时调用,仅仅是为了节省代码量而已,因为我们有两个构造函数都将调用它。
public:
virtual bool obsoleted();
在调用父类同名函数的基础上,增加了对是否正在关闭连接的判断,如果是,则返回假。
virtual bool is_ready();
是否可以数据收发。
virtual void send_heartbeat();
发送一个心跳包。
void reset();
重置所有(reset_state加clear_buffer,后者由父类实现)。
bool is_broken() const;
连接是否已经断开。
bool is_connected() const;
连接是否已经建立。
bool is_shutting_down() const;
是否正在优雅关闭套接字,此时不再发送消息(如果发送则会出错,因为已经关闭了自己的数据发送),但继续接收消息。
boost::shared_ptr<i_unpacker<out_msg_type>> unpacker();
boost::shared_ptr<const i_unpacker<out_msg_type>> unpacker() const;
void unpacker(const boost::shared_ptr<i_unpacker<out_msg_type>>& _unpacker_);
获取/修改解包器。
注意,运行时修改解包器是非线程安全的,而且只能在构造函数、子类的reset函数(虚的那个)和on_msg里面修改。不支持多线程一是为了
效率,二是支持了也必须要在前面说的那三个地方修改,而这三个地方不会有多线程问题,三是这个功能用得很少。
using st_socket<Socket, Packer, Unpacker>::send_msg;
bool send_msg(const std::string& str, bool can_overflow = false);
bool send_msg(const char* pstr, size_t len, bool can_overflow = false);
bool send_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
发送消息,前两个是helper函数,最后一个才是真正的发送消息(放入消息发送缓存);第一个调用第二个,第二个调用第三个。
bool send_native_msg(const std::string& str, bool can_overflow = false);
bool send_native_msg(const char* pstr, size_t len, bool can_overflow = false);
bool send_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
bool safe_send_msg(const std::string& str, bool can_overflow = false);
bool safe_send_msg(const char* pstr, size_t len, bool can_overflow = false);
bool safe_send_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同send_msg,只是在消息发送缓存溢出的时候会等待直到缓存可用;如果is_send_allowed返回false或者io_service已经停止,则马上放弃等待返回失败。
safe系列函数,在on_msg和om_msg_handle里面调用时需要特别谨慎,因为它会阻塞service线程。
bool safe_send_native_msg(const std::string& str, bool can_overflow = false);
bool safe_send_native_msg(const char* pstr, size_t len, bool can_overflow = false);
bool safe_send_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
bool sync_send_msg(const std::string& str, bool can_overflow = false);
bool sync_send_msg(const char* pstr, size_t len, bool can_overflow = false);
bool sync_send_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同步发送消息,前两个是helper函数,最后一个才是真正的发送消息(调用do_direct_sync_send_msg);第一个调用第二个,第二个调用第三个。
注意,只要在当前没有任何消息正在被发送时,才能同步发送消息。
bool sync_send_native_msg(const std::string& str, bool can_overflow = false);
bool sync_send_native_msg(const char* pstr, size_t len, bool can_overflow = false);
bool sync_send_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
size_t direct_sync_send_msg(in_msg_ctype& msg);
直接同步发送消息。注意,只要在当前没有任何消息正在被发送时,才能同步发送消息。
protected:
void force_shutdown();
void graceful_shutdown(bool sync);
第一个直接直接调用shutdown()。
第二个函数优雅关闭套接字,所谓优雅关闭,就是先关闭自己的数据发送,然后接收完遗留数据之后,才完全关闭套接字。当sync为假时,graceful_shutdown马上返回,
优雅关闭将在后台继承进行,当回调到on_recv_error的时候,关闭结束(有可能优雅关闭成功,有可能超时被强制关闭,超时由ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION宏决定)。
size_t do_sync_send_msg(in_msg_ctype& msg);
直接同步发送消息。不可直接调用,必须从sync_send_msg或者direct_sync_send_msg调用。
virtual bool do_send_msg();
马上开始消息发送,重写自st_socket的do_send_msg,由st_socket调用。
void do_recv_msg();
马上开始接收数据,由子类调用,因为st_tcp_socket不知道什么时候可以接收数据(比如是否连接成功等)。
virtual void on_unpack_error() = 0;
解包出错时回调。
virtual void on_async_shutdown_error() = 0;
异步关闭连接失败时架设。
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(out_msg_type& msg);
重写st_socket的on_msg,功能是打印消息到控制台,使用者重写这个函数以处理消息。
#endif
virtual bool on_msg_handle(msg_type& msg, bool link_down);
重写st_socket的on_msg_handle,功能是打印消息到控制台,使用者重写这个函数以处理消息。
private:
void shutdown();
关闭套接字,停止所有定时器,直接派发所有剩余消息,最后启动一个定时器,如果定义了ST_ASIO_ENHANCED_STABILITY宏,则这个将周期性的检测
当前套接字是否可以安全地被重用或释放,如果未定义,则简单的在ST_ASIO_DELAY_CLOSE秒后认为当前大量接字可被安全地重用或释放。
size_t completion_checker(const boost::system::error_code& ec, size_t bytes_transferred);
统计解包时间。
void recv_handler(const error_code& ec, size_t bytes_transferred);
收到数据时后asio回调。
void send_handler(const error_code& ec, size_t bytes_transferred);
成功发送消息(写入底层套接字)后由asio回调。
bool async_shutdown_handler(st_timer::tid id, size_t loop_num);
异步关闭连接的回调函数。
protected:
boost::container::list<typename super::in_msg> last_send_msg;
boost::shared_ptr<i_unpacker<out_msg_type>> unpacker_;
volatile link_status status;
};
} //namespace st_asio_wrapper

namespace st_asio_wrapper
{
定时器类
class st_timer
{
public:
#if defined(ST_ASIO_USE_STEADY_TIMER) || defined(ST_ASIO_USE_SYSTEM_TIMER)
#ifdef BOOST_ASIO_HAS_STD_CHRONO
typedef std::chrono::milliseconds milliseconds;
#else
typedef boost::chrono::milliseconds milliseconds;
#endif
#ifdef ST_ASIO_USE_STEADY_TIMER
typedef boost::asio::steady_timer timer_type;
#else
typedef boost::asio::system_timer timer_type;
#endif
#else
typedef boost::posix_time::milliseconds milliseconds;
typedef boost::asio::deadline_timer timer_type;
#endif
typedef unsigned char tid;
static const tid TIMER_END = 0;
继承者的定义器ID必须从父类的TIMER_END开始,然后最好也定义一个自己的TIMER_END,如果你这个类可能会被继承的话。
struct timer_info
{
enum timer_status {TIMER_FAKE, TIMER_OK, TIMER_CANCELED};
tid id;
timer_status status;
size_t milliseconds;
boost::function<bool (tid)> call_back;
在定时器到达后,call_back被回调,并根据返回值决定是否继续这个定时器(true即继续),同一个定时器,call_back的调用是顺序的。
boost::shared_ptr<timer_type> timer;
timer_info() : id(0), status(TIMER_FAKE), milliseconds(0) {}
};
定时器数据结构。
typedef const timer_info timer_cinfo;
typedef std::vector<timer_info> container_type;
st_timer(io_service& _io_service_);
void update_timer_info(tid id, size_t milliseconds, boost::function<bool(tid)>&& call_back, bool start = false);
void update_timer_info(tid id, size_t milliseconds, const boost::function<bool(tid)>& call_back, bool start = false)
更新timer,如果start为true,更新完了之后马上会开启或者重新开启这个timer。
注意,对同一个st_timer里面的同一个定时器操作并不是线程安全的。
void change_timer_interval(tid id, size_t interval);
只修改定时器间隔。
bool revive_timer(tid id);
复活定时器,其它不做任何修改。
void set_timer(tid id, size_t milliseconds, boost::function<bool(tid)>&& call_back);
void set_timer(tid id, size_t milliseconds, const boost::function<bool(tid)>& call_back);
开启定时器,定时器以id分区,如果定时器已经存在,则重新开始记时。这个函数其实就是以start为true调用update_timer_info而已。
object_type find_timer(tid id);
查找定义器。
bool start_timer(tid id);
开启一个已经存在的定义器。注意stop_timer之后,定义器还是存在的,只是未启动。
timer_info find_timer(tid id);
查找定义器,无论它是否有效。
bool is_timer(tid id) const;
判断指定定时器是否有效。
void stop_timer(tid id);
停止定时器,如果定时时间已经到达,且已经被post到io_server的队列中,则on_timer仍然会在未来某个时候被回调,这是asio的设计。
void stop_all_timer();
停止所有定时器。
void stop_all_timer(tid excepted_id);
停止所有定时器,除了excepted_id这个定时器。
template<typename _Predicate> void do_something_to_all(const _Predicate& __pred);
对所有定时器做一个操作,操作由__pred来定,st_asio_wrapper库只是调用__pred()。
template<typename _Predicate> void do_something_to_one(const _Predicate& __pred);
与do_something_to_all类似,只是当__pred()返回真时就不再继续循环处理后面的定时器了(如果你永远返回false,那就等于so_something_to_all),跟查找功能类似。
protected:
void start_timer(timer_info& ti);
内部使用的helper函数,真正的开启定时器(timer_type::async_wait)。
void stop_timer(timer_info& ti);
内部使用的helper函数,真正的结束定时器(调用timer_type::cancel)。
protected:
container_type timer_can;
private:
using st_object::io_service_;
隐藏io_service。
};
} //namespace

namespace st_asio_wrapper
{
仅支持一个套接字的udp服务
template<typename Socket> using st_udp_sclient_base = st_sclient<Socket>;
支持多个套接字的udp服务
template<typename Socket, typename Pool = st_object_pool<Socket>>
class st_udp_client_base : public st_client<Socke, Pool>
{
public:
st_udp_client_base(st_service_pump& service_pump_);
using st_client<Socket, Pool>::add_socket;
typename Pool::object_type add_socket(unsigned short port, const std::string& ip = std::string());
创建或者重用一个对象,然后以reset为true调用父类的同名函数。
void disconnect(typename Pool::object_ctype& socket_ptr);
void disconnect();
void force_shutdown(typename Pool::object_ctype& socket_ptr);
void force_shutdown();
void graceful_shutdown(typename Pool::object_ctype& socket_ptr);
void graceful_shutdown();
先从对象池中删除socket_ptr,然后调用client_ptr的同名函数。对于没有socket_ptr参数的重载,对所有对象调用同名函数。
protected:
virtual void uninit();
实现i_service的纯虚接口,由st_service_pump在stop_service时调用,跟st_sclient::uninit功能一样,只是对所有客户端都做一个“结束”操作。
};
} //namespace

#ifndef ST_ASIO_UDP_DEFAULT_IP_VERSION
#define ST_ASIO_UDP_DEFAULT_IP_VERSION boost::asio::ip::udp::v4()
#endif
绑定地址时,在不指定ip的情况下,指定ip地址的版本(v4还是v6),如果指定了ip,则ip地址的版本可以从ip中推导出来。
namespace st_asio_wrapper
{
udp套接字类,实现udp数据的收发
template <typename Packer, typename Unpacker, typename Socket = boost::asio::ip::udp::socket,
template<typename, typename> class InQueue = ASCS_INPUT_QUEUE, template<typename> class InContainer = ASCS_INPUT_CONTAINER,
template<typename, typename> class OutQueue = ASCS_OUTPUT_QUEUE, template<typename> class OutContainer = ASCS_OUTPUT_CONTAINER>
class st_udp_socket_base : public st_socket<Socket, Packer, Unpacker, udp_msg<typename Packer::msg_type>, udp_msg<typename Unpacker::msg_type>, InQueue, InContainer, OutQueue, OutContainer>
{
public:
typedef udp_msg<typename Packer::msg_type> in_msg_type;
typedef const in_msg_type in_msg_ctype;
typedef udp_msg<typename Unpacker::msg_type> out_msg_type;
typedef const out_msg_type out_msg_ctype;
public:
st_udp_socket_base(boost::asio::io_service& io_service_);
public:
virtual bool is_ready();
是否可以数据收发。
virtual void send_heartbeat();
发送一个心跳包。
virtual void reset();
重置所有,st_object_pool在重用时会调用。st_udp_socket的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
void set_local_addr(unsigned short port, const std::string& ip = std::string());
const boost::asio::ip::udp::endpoint& get_local_addr() const;
设置获取本端地址。
bool set_peer_addr(unsigned short port, const std::string& ip = std::string());
const boost::asio::ip::udp::endpoint& get_peer_addr() const;
设置获取对端地址。
void disconnect();
void force_close();
void graceful_close();
调用clean_up,这三个函数功能完全一样,为了与st_tcp_socket在接口上保持一致才设计成三个。
boost::shared_ptr<i_udp_unpacker<typename Packer::msg_type>> unpacker();
boost::shared_ptr<const i_udp_unpacker<typename Unpacker::msg_type>> unpacker() const;
void unpacker(const boost::shared_ptr<i_udp_unpacker<typename Unpacker::msg_type>>& _unpacker_);
获取/修改解包器。
注意,运行时修改解包器是非线程安全的,而且只能在构造函数、子类的reset函数(虚的那个)和on_msg里面修改。不支持多线程一是为了
效率,二是支持了也必须要在前面说的那三个地方修改,而这三个地方不会有多线程问题,三是这个功能用得很少。
using st_socket<Socket, Packer, Unpacker, in_msg_type, out_msg_type>::send_msg;
bool send_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool send_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool send_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
发送消息,前两个是helper函数,最后一个才是真正的发送消息(放入消息发送缓存);第一个调用第二个,第二个调用第三个。
bool send_native_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool send_native_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool send_native_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
bool safe_send_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool safe_send_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool safe_send_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同send_msg,只是在消息发送缓存溢出的时候会等待直到缓存可用;如果is_send_allowed返回false或者io_service已经停止,则马上放弃等待返回失败。
safe系列函数,在on_msg和om_msg_handle里面调用时需要特别谨慎,因为它会阻塞service线程。
bool safe_send_native_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool safe_send_native_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool safe_send_native_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
bool sync_send_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool sync_send_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool sync_send_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同步发送消息,前两个是helper函数,最后一个才是真正的发送消息(放入消息发送缓存);第一个调用第二个,第二个调用第三个。
bool sync_send_native_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool sync_send_native_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool sync_send_native_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
注,以上18个函数都还有一个重载版本,不需要提供peer_addr,但必须在之前调用过set_peer_addr函数,一像这下面这一对函数一样。
size_t direct_sync_send_msg(typename Packer::msg_ctype& msg);
size_t direct_sync_send_msg(const boost::asio::ip::udp::endpoint& peer_addr, typename Packer::msg_ctype& msg);
直接同步发送消息。注意,只要在当前没有任何消息正在被发送时,才能同步发送消息。
void show_info(const char* head, const char* tail);
打印日志,在head和tail中间加上本端ip和端口。
protected:
virtual bool do_start();
马上开始消息接收,重写自st_socket的do_start,由st_socket调用。
size_t do_sync_send_msg(typename Packer::msg_ctype& msg);
size_t do_sync_send_msg(const boost::asio::ip::udp::endpoint& peer_addr, typename Packer::msg_ctype& msg);
直接同步发送消息。不可直接调用,必须从sync_send_msg或者direct_sync_send_msg调用。
virtual bool do_send_msg();
马上开始消息发送,重写自st_socket的do_send_msg,由st_socket调用。
virtual void on_recv_error(const error_code& ec);
接收消息出错时回调。
virtual bool on_heartbeat_error();
心跳包超时时架设。
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(out_msg_type& msg);
重写st_socket的on_msg,功能是打印消息到控制台,使用者重写这个函数以处理消息。
#endif
virtual bool on_msg_handle(out_msg_type& msg, bool link_down);
重写st_socket的on_msg_handle,功能是打印消息到控制台,使用者重写这个函数以处理消息。
void shutdown();
关闭套接字,停止所有定时器,直接派发所有剩余消息,重置所有状态(调用reset_state)。
private:
void recv_handler(const error_code& ec, size_t bytes_transferred);
收到数据后由asio回调。
void send_handler(const error_code& ec, size_t bytes_transferred);
成功发送消息(写入底层套接字)后由asio回调。
bool set_addr(boost::asio::ip::udp::endpoint& endpoint, unsigned short port, const std::string& ip);
设置地址,内部使用。
protected:
typename super::in_msg last_send_msg;
boost::shared_ptr<i_udp_unpacker<typename Packer::msg_type>> unpacker_;
boost::asio::ip::udp::endpoint local_addr, temp_addr, peer_addr;
异步接收udp消息时,asio需要一个endpoint,在整个异步接收过程中,这个endpoint必须有效,所以它是一个成员变量(temp_addr),
它只代表上一次接收udp消息时的对端地址,对于已经接收到的udp消息,对端地址保存在out_msg_type里面。
boost::shared_mutex shutdown_mutex;
让shutdown函数线程安全。
};
} //namespace st_asio_wrapper

#ifdef ASCS_HUGE_MSG
#define ASCS_HEAD_TYPE uint32_t
#define ASCS_HEAD_N2H ntohl
#else
#define ASCS_HEAD_TYPE uint16_t
#define ASCS_HEAD_N2H ntohs
#endif
#define ASCS_HEAD_LEN (sizeof(ASCS_HEAD_TYPE))
namespace st_asio_wrapper { namespace ext {
默认的解包器,如果你需要的消息格式与默认解包器的消息格式不相同,那么显然的,你需要实现自己的解包器;
默认解包器的消息格式是:长度(2字节)+ 消息内容,所以其支持的消息长度为1至(65535-2),如果想突破这个限制,
请参看i_packer。
class unpacker : public i_unpacker<std::string>;
默认的udp解包器,长度限制同packer。
class udp_unpacker : public i_udp_unpacker<std::string>;
作用参看replaceable_packer。
template<typename T = replaceable_buffer>
class replaceable_unpacker : public i_unpacker<T>;
template<typename T = replaceable_buffer>
class replaceable_udp_unpacker : public i_udp_unpacker<T>;
这个解包器与unpacker的不同之处在于,它不需要一个固定大小的缓存,而是先接收包头,再根据包头得到消息的长度信息,
然后分配(new)适当的缓存,这样当缓存写满了之后,一个完整的消息就接收完毕了,所以省掉了消息的拷贝;但也有一个坏处,
就是每次只能接收一个消息,每个消息需要调用parse_msg两次。
如果你的消息很小很频繁,经常粘包,那么用unpacker效率更高,因为它一次可以接收多个包,但non_copy_unpacker还有一个
好处是unpacker无法给予的,那就是,如果你的消息长短非常不齐,比如消息大部分是1K,而很少的哪怕只有一个消息长1M,
那么所有的unpacker不得不分配一个至少1M的缓存用于接收消息,造成了内存的巨大浪费。
class non_copy_unpacker : public i_unpacker<basic_buffer>;
这两个消息不解释,注意没有返回replaceable_buffer的fixed_length_unpacker和prefix_suffix_unpacker,可参考
replaceable_unpacker自行实现。
class fixed_length_unpacker : public i_unpacker<basic_buffer>;
class prefix_suffix_unpacker : public i_unpacker<std::string>;
class stream_unpacker : public tcp::i_unpacker<std::string>;
无协议解包器,收到什么就是什么,类似于调试助手。
}} //namespace
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
<VisualStudioProject <VisualStudioProject
ProjectType="Visual C++" ProjectType="Visual C++"
Version="9.00" Version="9.00"
Name="test_client" Name="echo_client"
ProjectGUID="{3F7C67A4-0DC1-4CEA-B87E-B158146A64D1}" ProjectGUID="{3F7C67A4-0DC1-4CEA-B87E-B158146A64D1}"
RootNamespace="test_client" RootNamespace="echo_client"
Keyword="Win32Proj" Keyword="Win32Proj"
TargetFrameworkVersion="196613" TargetFrameworkVersion="196613"
> >
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<Tool <Tool
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="0" Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true" MinimalRebuild="true"
BasicRuntimeChecks="3" BasicRuntimeChecks="3"
RuntimeLibrary="3" RuntimeLibrary="3"
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
<Tool <Tool
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="0" Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;_DEBUG;_CONSOLE"
MinimalRebuild="true" MinimalRebuild="true"
BasicRuntimeChecks="3" BasicRuntimeChecks="3"
RuntimeLibrary="3" RuntimeLibrary="3"
...@@ -189,7 +189,7 @@ ...@@ -189,7 +189,7 @@
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="2" Optimization="2"
EnableIntrinsicFunctions="true" EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2" RuntimeLibrary="2"
EnableFunctionLevelLinking="true" EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0" UsePrecompiledHeader="0"
...@@ -264,7 +264,7 @@ ...@@ -264,7 +264,7 @@
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="2" Optimization="2"
EnableIntrinsicFunctions="true" EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" PreprocessorDefinitions="WIN32;BOOST_ASIO_NO_DEPRECATED;NDEBUG;_CONSOLE"
RuntimeLibrary="2" RuntimeLibrary="2"
EnableFunctionLevelLinking="true" EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0" UsePrecompiledHeader="0"
...@@ -321,7 +321,7 @@ ...@@ -321,7 +321,7 @@
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
> >
<File <File
RelativePath=".\test_client.cpp" RelativePath=".\echo_client.cpp"
> >
</File> </File>
</Filter> </Filter>
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
//just for compatiblity
#include "tcp.h"
//just for compatiblity
#include "tcp.h"
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册