提交 301e46b6 编写于 作者: Y youngwolf

ascs first release, version 1.0.0

Strip boost (Based on st_asio_wrapper 1.1.3).
Need c++14, if your compiler detected duplicated 'shared_mutex' definition, please define ASCS_HAS_STD_SHARED_MUTEX macro.
Need to define ASIO_STANDALONE and ASIO_HAS_STD_CHRONO macros.
Collect all macros together.
上级 ad5697a2

包括了公共函数、类、接口、宏等东西。
1. 宏
ASCS_HAS_STD_SHARED_MUTEX, 如果你的编译器报shared_mutex重复定义,请定义该宏;
ASCS_SERVER_IP, 默认IP,可以通过这个宏修改,也可以直接在参数中修改;
ASCS_SERVER_PORT, 默认端口,可以通过这个宏修改,也可以直接在参数中修改;
ASCS_MAX_MSG_NUM, socket中最多能缓存的消息条数,由于采用的是list,所以内存用到多少分配多少,注意发送和接收缓存都可以达到这个大小,而不共享这个大小。
ASCS_SF, 打印size_t时用的格式符,vc和gcc不一样。
ASCS_THIS, 访问父类的函数或者成员时使用,解决vc崩溃问题(部分版本)。
ASCS_UNIFIED_OUT_BUF_NUM, log打印时缓存大小。
ASCS_NO_UNIFIED_OUT, 关闭log打印。
ASCS_CUSTOM_LOG, 定义之后,用户必须提供自己的log打印函数(unified_out::fatal_out/error_out/warning_out/info_out/debug_out)。
2. 接口
i_server:
用于在server_socket_base中调用server_base,server_socket_base使用,server_base实现,
如果想增加更多的功能,则继承自i_server实现自己的接口,继承自server_base并实现这个新接口,最后继承自server_socket_base以使用这个新接口。
这里只解释一下del_client接口:
关闭一个连接时,server_socket_base调用这个方法,i_server的实现者(server就是一个i_server的实现者)一般来说应该把这个连接从自己的
客户端对象池里面删除,调用这个方法的最佳时期是在on_recv_error里面。
注意,client_ptr参数看起来是一个timer,其实它的类型是server_base中的Socket模板参数,用timer完全是为了不让i_server带一个类似于
Socket的模板参数,带上之后会有模板参数的循环依赖的问题,如果哪位同仁可以解决此问题,麻烦告诉我一声。
i_server的实现者在使用client_ptr参数之前,会转换成Socket类型指针,请看server_base的实现。
i_buffer:
如果想要在运行时替换打包解包器,你的缓存必须实现这个接口,然后以auto_buffer或者shared_buffer作为消息类型。
i_packer:
所有打包器必须实现这个接口,tcp和udp共用。每次发送消息的时候,只要不是native的消息,都会调用pack_msg来打包。
tcp::i_unpacker:
tcp解包器必须实现这个接口。每次开始接收消息之后,会调用prepare_next_recv以得到一个缓存,当收到一些数据之后,
asio会调用completion_condition,如果返回0,说明已经成功的接收到了至少一个消息,于是ascs会接着调用parse_msg
来解析消息,解析完了之后再次调用prepare_next_recv进入一个新的循环。注意,解析完消息之后,缓存里面可能还剩余部分数据(消息被分包),
第二次prepare_next_recv返回的缓存,不能覆盖未解析完的数据(如果采用固定缓存的解包器,通常的做法是把未解析完的数据移动到缓存最前面),否则数据会被覆盖。
udp::i_unpacker:
udp解包器必须实现这个接口。
3. 类
auto_buffer:
如果想要运行时替换打包解包器,则打包解包器必须以auto_buffer或者shared_buffer作为消息类型(你写个类似的也可,但没有必要,如果需要更多的功能可以用继承)。
这个类是不允许被复制的,用户只负责分配内存。
shared_buffer:
同auto_buffer,除了允许被复制。
dummy_packer:
这个类只提供消息类型的定义,不做真正的打包,所以用户必须以native方式发送消息,或者用direct_send_msg等发送消息。
udp::udp_msg:
udp消息,其实就是在tcp消息上加了一个对端地址。
log_formater:
log打印函数,只是打印到屏幕,如果需要更详细的功能需要自己实现。
unified_out:
调用log_formater打印log,如果定义ASCS_NO_UNIFIED_OUT,所有log打印函数将变成空函数,什么也不做。
4. 函数
do_something_to_all:
对容器(不得是类map容器,比如map,unordered_map等)中的所有对象执行一个动作,动作由一个函数对象(std::function)决定。
可以选择带锁和不带锁,如果带锁,加的是共享锁,请注意。
do_something_to_one:
同do_something_to_all,如果对某一个对象执行的动作返回true,则跳过所有剩下的对象,如果全部对象都返回false,则等于do_something_to_all,但效率上稍慢。
splice_helper:
拼接两个list,并且将结果(list::size)限制在一个指定的大小之内。

提供了对ASCS_DEFAULT_PACKER和ASCS_DEFAULT_UDP_UNPACKER宏的支持。
提供了不带模板参数的connector,sclient和client。
\ No newline at end of file

包括了扩展公共函数、类、接口、宏等东西,扩展不属于ascs库,可以随意被替换。
1. 宏
ASCS_MSG_BUFFER_SIZE, 对于打包器来说,打包后的消息将不能大于这个值,对于解包器来说,
只能解小于等于(解包前)这个值的消息,否则会出错。对于采用固定缓存的解包器来说(unpacker即可),这个值意味着固定缓存的大小。
2. 类
class string_buffer : public std::string, public i_buffer
{
public:
virtual bool empty() const {return std::string::empty();}
virtual size_t size() const {return std::string::size();}
virtual const char* data() const {return std::string::data();}
};
包装std::string,实现i_buffer接口,这样这个缓存就可被运行时替换。
class basic_buffer : public asio::detail::noncopyable
一个简单的缓存,远远比std::string简单,无法自动增长,效率会高点。
class ascs_cpu_timer;
代替boost::timer::cpu_timer,std中还没有引入任何timer。
3. 函数
inline std::list<std::string> split_string(const std::string& str);
拆分字节串,只能以' '或者'\t'为间隔符,代替boost::tokenizer,std中还没有引入类似的功能库。
#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 ascs { 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的定义。
class replaceable_packer : public i_packer<replaceable_buffer>;
带固定头和固定尾的打包器,头可以为空,尾不能为空。
class prefix_suffix_packer : public i_packer<std::string>;
注意,没有固定长度的打包器(固定长度的解包器有),固定长度的打包器实际意义不大,因为它实际并没有协议相关的东西在包里面,所以每次发送
必须已经是一个完整的包,直接发送即可(ascs::socket::direct_send_msg,ascs::socket::direct_post_msg)。
}} //namespace

提供了对ASCS_DEFAULT_PACKER和ASCS_DEFAULT_UDP_UNPACKER宏的支持。
提供了不带模板参数的server_socket和server。
\ No newline at end of file

提供了对ASCS_DEFAULT_PACKER和ASCS_DEFAULT_UDP_UNPACKER宏的支持。
提供了不带模板参数的udp_socket, udp_sservice和udp_service。
\ No newline at end of file
#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 ascs { 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。
class replaceable_unpacker : public i_unpacker<replaceable_buffer>, public unpacker;
class replaceable_udp_unpacker : public i_udp_unpacker<replaceable_buffer>;
这个解包器与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<std::string>;
class prefix_suffix_unpacker : public i_unpacker<std::string>;
class stream_unpacker : public tcp::i_unpacker<std::string>
无协议解包器,收到什么就是什么,类似于调试助手。
}} //namespace

hook异步调用时的回调函数,以便这个回调函数所在的对象不会被释放,直到所有异步调用结束或者被取消。
只支持对1个待定参数和2个待定参数的函数对象的包装,具体用法举例:
未hook之前,如果你的异步调用是这样的:
asio::async_write(socket, buffer, std::bind(&socket_base::send_handler, this, std::placeholders::_1, std::placeholders::_2));
那么look之后应该是这样的:
asio::async_write(socket, buffer, ASCS_THIS make_handler_error_size(std::bind(&socket_base::send_handler, this, std::placeholders::_1, std::placeholders::_2)));
如果定义了ASCS_ENHANCED_STABILITY宏,则ascs中所有异步调用的函数对象被都将被自动的hook,对于用户自己的异步调用,hook与否由你自己决定。

#ifndef ASCS_MAX_OBJECT_NUM
#define ASCS_MAX_OBJECT_NUM 4096
#endif
#ifndef ASCS_REUSE_OBJECT
#ifndef ASCS_FREE_OBJECT_INTERVAL
#define ASCS_FREE_OBJECT_INTERVAL 10
如果未开启对象池,将自动开启一个定时器,用于定时释放(从内存中删除)失效的对象(失效对象是指obsoleted()返回true,
智能指针只有一个引用并且超时了的对象,超时请参看ASCS_OBSOLETED_OBJECT_LIFE_TIME宏),用这个宏指定定时器间隔,单位为秒。
如果开启了对象池,将不会自动开启这个定时器,但使用者可以自己定义这个宏以强迫object_pool开启这个定时器(建议间隔
就不能再是10秒了,应该更长一点,比如1分钟),然后失效对象将会被关闭(调用对象的close()函数以释放SOCKET句柄)而不是被释放。
其实这个定时器就是以不同的参数调用free_object()函数而已,所以用户也可以自己开定时器来更灵活的控制如果释放或者关闭对象。
#endif
#endif
//#define ASCS_CLEAR_OBJECT_INTERVAL 60
自动清除(从对象池移出到临时链表)失效对象,这个功能有什么用呢?
如果在连接断开时,你没有或者不想去从对象池中删除,那么可以让object_pool周期性的查找失效连接,
然后移出对象池,这样一次遍历可以操作多条连接,对于短连接效率可能会更高,对于长连接,建议每次连接断开时,调用
tcp::server_base::del_client()马上清除这条连接。用这个宏指定定时器间隔,单位为秒。
#ifndef ASCS_OBSOLETED_OBJECT_LIFE_TIME
#define ASCS_OBSOLETED_OBJECT_LIFE_TIME 5
失效对象(并且已经移动到临时链表中的对象),多久时间之后,可以重用或者释放/关闭,单位为秒。
#endif
namespace ascs
{
对象池类,用于管理正常的和失效的连接,对象重用,定时删除已经关闭的连接等;
注意,只能管理由ascs::socket派生的对象
template<typename Object>
class object_pool: public service_pump::i_service, public timer
{
public:
typedef std::shared_ptr<Object> object_type;
typedef const object_type object_ctype;
typedef std::unordered::unordered_map<uint_fast64_t, object_type> container_type;
protected:
已经关闭的连接,从对象池移出到临时链表时,将按这个结构保存,主要目的是为了记录关闭的时间(以判断是否可以释放或者重用)。
struct invalid_object
{
object_type object_ptr;
#ifdef ASCS_ENHANCED_STABILITY
const time_t kick_out_time;
#endif
invalid_object(object_ctype& object_ptr_);
bool is_timeout() const;
bool is_timeout(time_t now) const;
是否超时(超时之后就可以释放或者重用object_ptr对象了)。
};
protected:
object_pool(service_pump& service_pump_);
void start();
开始,根据宏开启一些定时器,比如如果未定义REUSE_OBJECT,则开启一个定时器用于定时查找已经关闭的连接。
void stop();
结束,关闭所有定时器。
bool add_object(object_ctype& object_ptr);
添加一个对象,注意对象总数不能超过ASCS_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。
#ifdef ASCS_REUSE_OBJECT
object_type reuse_object();
查找可重用的对象,如果没有,返回空的智能指针。能被重用的对象必需是:
1. 已经从对象池移到了临时链表里面;
2. 已经超时(参看ASCS_OBSOLETED_OBJECT_LIFE_TIME宏);
3. 对象的引用记数(对象是一个shared_ptr)必须是1;
4. 对象的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);
object_type create_object();
如果定义了ASCS_REUSE_OBJECT宏,则先调用reuse_object尝试重用对象,如果没有对象可被重用,或者未定义该宏,
则创建一个新的,最后都是调用init_object。
public:
container_type& container();
用于配置unordered_set,比如设置负载因子,预分配空间等。注意必须在service_pump启动之前调用,因为没有锁相应的mutex。
size_t max_size() const;
void max_size(size_t _max_size);
对象池最大容量,可运行时修改。占用内存是动态分配的,只有有效的对象(包括等待被重用的对象)会占用内存。
size_t size();
对象池中的对象个数,不包括被移除到临时链表的对象。
size_t invalid_object_size();
获取无效对象总数(临时链表里面的对象),无效对象要么定时被删除,要么等待被重用,由宏控制。
object_type find(uint_fast64_t id);
根据id查找有效对象。
object_type at(size_t index);
获取指定位置的有效对象(连接池中的对象),位置序号从0开始,复杂度O(n)。
object_type invalid_object_at(size_t index);
获取指定位置的无效对象,位置序号从0开始,复杂度O(n)。
object_type invalid_object_find(uint_fast64_t id);
根据id查找无效对象,复杂度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);
与timer的同名函数类似,只操作有效对象。
protected:
std::atomic_uint_fast64_t cur_id;
当前已经分配到哪个id了,用于为每一个通过object_pool::create_object创建的对象分配一个唯一的id。
container_type object_can;
std::shared_mutex object_can_mutex;
存放有效对象(对象池)。
std::list<invalid_object> invalid_object_can;
std::shared_mutex invalid_object_can_mutex;
存放无效对象(临时链表)。
};
} //namespace

#ifndef ASCS_SERVICE_THREAD_NUM
#define ASCS_SERVICE_THREAD_NUM 8
同时开启多少个线程执行asio::io_service::run函数。
#endif
namespace ascs
{
包装io_service,用于启动ascs里面的service(实现了i_service的所有对象)。
class service_pump : public asio::io_service
{
public:
class i_service
{
protected:
i_service(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,由使用者决定,ascs目前未使用service id,
如果你需要查找service,则最好启用它。
void user_data(void* data_);
void* user_data() const;
用户数据,用不用或者怎么使用请发挥想象,ascs库本身并不使用这个值,也不管理其生命周期。
service_pump& get_service_pump();
const service_pump& get_service_pump() const;
获取service_pump对象。
protected:
virtual void init() = 0;
virtual void uninit() = 0;
继承者实现,在启动/停止service的时候,service_pump会调用。
protected:
service_pump& sp
private:
bool started;
int id_;
void* data;
};
public:
typedef i_service* object_type;
typedef const object_type object_ctype;
typedef std::list<object_type> container_type;
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 = ASCS_SERVICE_THREAD_NUM);
void stop_service();
启动/停止service(调用end_service),thread_num是线程数量(用于调用io_service::run)。
void start_service(object_type i_service_, int thread_num = ASCS_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 = ASCS_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 ASCS_ENHANCED_STABILITY
virtual bool on_exception(const std::exception& e);
如果io_service::run抛出异常,则会调用这个函数,如果返回真,则忽略异常再次调用io_service::run。
size_t run(asio::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);
与timer的同名函数类似。
private:
void add(object_type i_service_);
添加一个service,由i_service使用。
protected:
container_type service_can;
std::shared_mutex service_can_mutex;
std::thread_group service_threads;
bool started;
};
} //namespace

namespace ascs
{
template<typename Socket, typename Packer, typename Unpacker, typename InMsgType = typename Packer::msg_type, typename OutMsgType = typename Unpacker::msg_type>
class socket: public timer
{
public:
效率统计,注意这个功能会轻微的影响性能,默认关闭,可以通过ASCS_FULL_STATISTIC宏来开启。在关闭情况下,所有整数项统计(uint_fast64_t)仍然有效,
所有时间统计项将无效(stat_duration)。在打开情况下,时间统计的数据类型其实是std::chrono::system_clock::time_point。
struct statistic
{
statistic();
void reset();
由于统计涉及多个方面,并且是多线程修改不同的部分,这个函数只是在某些特殊情况下才可以调用,比如在构造函数里面,或者只有一个service线程,
所以这个函数基本上还是用在对象重用时。
statistic& operator +=(const struct statistic& other);
std::string to_string() const;
消息发送相关的统计
uint_fast64_t send_msg_sum; 成功发送到asio的消息条数
uint_fast64_t send_byte_sum; 成功发送到asio的消息总字节数
stat_duration send_delay_sum; 从消息发送(send_(native_)msg, post_(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; 暂停消息接收的总时间,如果接收缓存满,或者在发送缓存满的情况下调用了post_(native_)msg,将会暂停消息接收,
注意,在回调on_msg期间,也算是暂停了消息接收。
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
stat_duration handle_time_1_sum; 调用on_msg花费的总时间
#endif
stat_duration handle_time_2_sum; 调用on_msg_handle花费的总时间
};
protected:
typedef std::list<InMsgType> in_container_type;
typedef typename Unpacker::container_type out_container_type;
socket(asio::io_service& io_service_);
template<typename Arg>
socket(asio::io_service& io_service_, Arg& arg);
ssl使用。
void reset_state();
重置所有状态。
void clear_buffer();
清空所有buffer。
public:
void id(uint_fast64_t id);
uint_fast64_t id() const;
id的设置与获取,注意使用者不可设置id,只有socket的创建者(object_pool或者其继承者)才可设置id,
除非这个socket没有被任何对象池管理。
Socket& next_layer();
const Socket& next_layer() const;
typename Socket::lowest_layer_type& lowest_layer();
const typename Socket::lowest_layer_type& lowest_layer() const;
底层对象,它应该是一个asio::ip::tcp::socket,asio::ip::udp::socket或者
asio::ssl::stream<asio::ip::tcp::socket>对象及其从它们继承的对象。
最底层对象其实就是调用底层对象的lowest_layer(),我们真正要读写的其实就是最底层对象。
virtual bool obsoleted();
判断本对象是否可以被重用或者被释放。
bool started() const;
是否已经开始,已经开始意思是已经调用过start()了,多次调用start()会有严重的包乱顺问题,好在我在某个版本
增加了防止多次调用start()的功能,之前靠用户保证,现在ascs库可以保证,即使用户多次调用也没问题。
boos started() const;
是否已经调用过下面的start函数,多次调用start是没有意义的,只有第一次调用有效。
void start();
开始,开始的动作由子类实现,本函数只是简单的判断start是否已经被调用过了,如果没有,则调用do_start函数(纯虚)。
bool send_msg();
发送缓存里面的消息,如果当前已经在发送消息了,则调用本函数无任何作用。
socket内部有消息发送缓存,当连接未建立的时候,用户仍然可以发送消息(注意缓存满问题),这些消息会缓存起来,
当连接建立之后,会自动开始真正的发送消息,这个功能就是调用这个函数实现的。
void suspend_send_msg(bool suspend);
bool suspend_send_msg() const;
暂停/恢复消息发送,这里指暂停/恢复真正的消息发送,所以就算暂停了消息发送,在发送缓存可用的情况下,send_msg和send_safe_msg
仍然可以成功调用。
注意,如果消息发送处于暂停状态,则safe_send_msg在发送缓存溢出的情况下,马上返回失败,而不是等待发送缓存直到可用为止。
post_msg不受这个属性的影响,所以post_msg一定只能在on_msg和on_msg_handle里面调用,再次强调。
void suspend_dispatch_msg(bool suspend);
bool suspend_dispatch_msg() const;
暂停/恢复消息派发,这里的消息派发,是指当socket收到消息的时候,调用on_msg或者on_msg_handle,这个功能有什么用呢?
当你在处理一个耗时消息时,不管你是在on_msg还是on_msg_handle里面处理,处理过程中,都将消耗掉一个service线程,那么必将对自己和
其它socket的数据收发造成一定的影响(当所有service线程耗尽时,就不是一定的影响了,而是所有的socket在这期间都将无法发送和
接收数据),为了解决这个问题,你可以开启一个线程来做这个耗时业务,但记得在启动线程之前,先暂停消息派发,线程结束之前,再恢复
消息派发,这样消息就不会乱序,否则由于你开启线程之后,马上退出了on_msg或者on_msg_handle,那么下一条消息(如果有的话)将马上被
派发,这样就出现了乱序问题(前一条消息还未处理完,后一条消息就被派发了)。
std::shared_ptr<i_packer<typename Packer::msg_type>> inner_packer();
std::shared_ptr<const i_packer<typename Packer::msg_type>> inner_packer() const;
void inner_packer(const std::shared_ptr<i_packer<typename Packer::msg_type>>& _packer_);
获取/修改打包器。
注意,运行时修改打包器是非线程安全的,它会与消息发送冲突,由于消息发送和打包器修改都是使用者触发的,所以如果有资源竞争,使用者
有义务解决冲突问题。不支持多线程一是为了效率,二是这个功能用得很少。
bool is_send_buffer_available();
判断消息发送缓存是否可用,即里面的消息数量是否小于ASCS_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函数,其实socket内部在发送消息时也是调用这个函数,只是在调用
之前先调用了i_packer::pack_msg而已。
bool direct_post_msg(const InMsgType& msg, bool can_overflow = false);
bool direct_post_msg(InMsgType&& msg, bool can_overflow = false);
同上,它们之前的区别就是send_msg和post_msg之间的区别。
size_t get_pending_post_msg_num();
size_t get_pending_send_msg_num();
size_t get_pending_recv_msg_num();
获取缓存里面的消息数量,其中post和send缓存里面的消息是打包过的;recv缓存里面的消息是解包过后的,下同。
void peek_first_pending_post_msg(InMsgType& msg);
void peek_first_pending_send_msg(InMsgType& msg);
void peek_first_pending_recv_msg(OutMsgType& msg);
偷看一下缓存中第一个包,如果得到一个空包(msg.empty()等于true),则说明缓存里面没有消息。
void pop_first_pending_post_msg(InMsgType& msg);
void pop_first_pending_send_msg(InMsgType& msg);
void pop_first_pending_recv_msg(OutMsgType& msg);
弹出缓存中第一个包,如果得到一个空包(msg.empty()等于true),则说明缓存里面没有消息。
void pop_all_pending_post_msg(in_container_type& msg_list);
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;
子类重写,请看tcp:server_socket、tcp::connector、udp::socket和ssl::connector的实现。
virtual bool do_send_msg() = 0;
真正的消息发送(调用asio函数),具体怎么发请看tcp::socket和udp::socket的实现。
virtual bool is_send_allowed() const;
是否允许发送消息,对于socket来说,只要未暂停消息发送,就是允许消息发送,子类重写这个函数实现自己的判断逻辑,然后加上
socket的判断结果,最终确认是否可发送数据。请看tcp::socket、tcp::connector和udp::socket的实现。
virtual void on_send_error(const error_code& ec);
virtual void on_recv_error(const asio::error_code& ec) = 0;
发送接收失败时回调,对于tcp::socket,如果需要连接断开事件,建议重写on_recv_error。
#ifndef ASCS_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 ASCS_WANT_MSG_SEND_NOTIFY
virtual void on_msg_send(InMsgType& msg);
成功发送(消息写入底层socket缓存)一个消息之后回调,消息是打包过后的。
#endif
#ifdef ASCS_WANT_ALL_MSG_SEND_NOTIFY
virtual void on_all_msg_send(InMsgType& msg);
当发送缓存由非空变为空的时候回调,消息是打包过后的。
#endif
void dispatch_msg();
派发消息,它要么直接调用on_msg,要么把消息放入消息接收缓存,最后调用do_dispatch_msg,如果消息处理完毕(调用on_msg)
或者都放入了消息接收缓存,则调用do_start以继续接收数据。
void do_dispatch_msg(bool need_lock);
调用io_service::post发出一个异步调用,调度到时回调msg_handler。
bool do_direct_send_msg(InMsgType&& msg);
bool do_direct_post_msg(InMsgType&& msg);
把消息直接放入消息发送缓存,在direct_send_msg和direct_post_msg中有调用。
private:
bool timer_handler(unsigned char id);
处理所有定时器
void msg_handler();
异步派发接收缓存里面的消息时,asio调用本函数,在这个函数里面将调用on_msg_handle,然后调用do_dispatch_msg继承派发消息。
protected:
uint_fast64_t _id;
保存在对象池中时,作为key,所以必须唯一,对象池用hash作为容器以快速查找。
Socket next_layer_;
前面在next_layer里面解释过了。
InMsgType last_send_msg;
OutMsgType last_dispatch_msg;
由于是异步发送和派发消息,这两个成员变量保证其在异步处理过程中的有效性。
std::shared_ptr<i_packer<MsgDataType>> packer_;
打包器。
in_container_type post_msg_buffer, send_msg_buffer;
out_container_type recv_msg_buffer, temp_msg_buffer;
std::shared_mutex post_msg_buffer_mutex, send_msg_buffer_mutex;
std::shared_mutex recv_msg_buffer_mutex;
缓存及操作它们时需要的互斥体,访问temp_msg_buffer无需互斥,它只能在内部访问,作用是当收到消息之后,发现需要存入接收缓存
(on_msg返回false),但接收缓存已满,那么多余的消息将被存放于temp_msg_buffer,并且不再继续接收消息,直到temp_msg_buffer
里面的消息全部被处理掉,或者移到了recv_msg_buffer,socket会周期性的做以上尝试。
post_msg_buffer和send_msg_buffer里面存放的都是待发送的消息,通过send_msg发送的消息,只会进入send_msg_buffer,通过post_msg
发送的消息,如果发送缓存足够的话,会进入send_msg_buffer,如果不够的话,则会进入post_msg_buffer,当post_msg_buffer非空时,
tcp::socket或者udp::socket将暂停接收消息,直到post_msg_buffer里面的消息全部被移入send_msg_buffer,这样设计有什么用呢?
考虑如下情况,你需要发送一个消息,但不想发送缓存溢出,也不想等待直到发送缓存可用,你会怎么做?你不得不把这个消息暂存起来,
然后立即退出on_msg或者on_msg_handle,然后开个定时器周期性的尝试发送暂存的消息。由于你在第一个消息产生的结果还没有送入发送
缓存就退出了on_msg或者on_msg_handle,那么第二个消息会接着被派发,第二个消息产生的结果你也必须暂存起来,那么你是不是需要
一个链表来暂存这些还未送入发送缓存的消息呢?这个链表就是post_msg_buffer。socket通过post_msg_buffer和post_msg为你做了上面
的工作,你只需要把send_msg改为调用post_msg即可,这个调用既不会失败,也不会阻塞线程,还不会让发送缓存失控。
post_msg_buffer不做大小限制,那为什么不会让发送缓存失控呢?这个我在教程里面强调过,post_msg只能在on_msg或者on_msg_handle
里面调用,如果消息进入了post_msg_buffer,消息派发将被暂停(最终将导致接受缓存满,进而暂停消息接收),那么on_msg或者
on_msg_handle将不再被调用,所以最终post_msg_buffer将会被消化(移至send_msg_buffer),socket将会周期性的尝试消化
post_msg_buffer中的消息。
bool posting;
bool sending, suspend_send_msg_;
bool dispatching, suspend_dispatch_msg_;
内部使用的一些状态,看名字应该能猜到其意思。
bool started_;
std::shared_mutex start_mutex;
是否已经开始,开始的概念由子类具体实现,socket只是记录是否已经调用过start函数而已。
};
} //namespace

socket_service主要是实现service_pump::i_service接口,以便service_pump能启动它,用在tcp客户端和udp两端。
single_socket_service只包括一个套接字,如果你需要一系列套接字,则使用multi_socket_service。
namespace ascs
{
只支持一条连接的客户端(或者说一个套接字),tcp和udp通用
template<typename Socket>
class single_socket_service : public service_pump::i_service, public Socket
{
public:
single_socket_service(service_pump& service_pump_);
protected:
virtual void init();
virtual void uninit();
实现i_service的纯虚接口,供service_pump在start_service时调用,这两个接口其实就是实现了开始和结束逻辑,开始意味着什么由Socket决定,
这个我们在前面讲socket、tcp::socket、udp::socket、tcp::connector和tcp::server_socket的时候,已经说过多次了。
};
支持多条连接的客户端(或者说多个套接字),tcp和udp通用,其实它更像一个容器,只是在object_pool上扩展了一些helper函数
template<typename Socket, typename Pool>
class multi_socket_service : public Pool
{
protected:
multi_socket_service(service_pump& service_pump_);
template<typename Arg>
multi_socket_service(service_pump& service_pump_, Arg arg);
后者由ssl使用。
virtual void init();
实现i_service的纯虚接口,供service_pump在start_service时调用,跟single_socket_service::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

ssl中所有类与tcp一一对应,对于上层命名空间ascs包括打包解包器,则是完全共用的。
ssl只做了一个握手环节,其它ssl相关的环节,比如证书加载,完全在asio::ssl::context中实现。
namespace ascs { namespace tcp {
支持多条连接的tcp客户端
template<typename Socket = connector, typename Pool = object_pool<Socket>>
class client_base : public multi_socket_service<Socket, Pool>
{
public:
client_base(service_pump& service_pump_);
template<typename Arg>
client_base(service_pump& service_pump_, Arg arg);
ssl使用。
size_t valid_size();
有效(已连接)的连接数量。
typename Pool::object_type add_client();
创建或者重用一个对象,然后以reset为true调用父类的add_socket。注意未设地址,将使用SERVER_IP和SERVER_PORT
作为服务端地址,如果你要用别的地址,请用下面那个add_client。
typename Pool::object_type add_client(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);
对每一个连接调用ascs::socket中的同名函数。
void disconnect(typename Pool::object_ctype& client_ptr);
void disconnect(bool reconnect = false);
void force_close(typename Pool::object_ctype& client_ptr);
void force_close(bool reconnect = false);
void graceful_close(typename Pool::object_ctype& client_ptr, bool sync = true);
void graceful_close(bool reconnect = false, bool sync = true);
带client_ptr参数者会把client_ptr从容器中删除,然后调用client_ptr的同名函数且不做重连接。
不带client_ptr参数者不会从容器中删除任何对象,只对容器中所有对象调用同名函数,是否重连接由reconnect决定。
protected:
virtual void uninit();
实现i_service的纯虚接口,由service_pump在stop_service时调用,跟single_socket_service::uninit功能一样,只是对所有客户端都做一个“结束”操作。
};
}} //namespace
#ifndef ASCS_RECONNECT_INTERVAL
#define ASCS_RECONNECT_INTERVAL 500 //millisecond(s)
异步连接,asio返回失败之后,暂停一定时间继续重试(即再次调用asnc_connect)。
#endif
namespace ascs { namespace tcp {
带连接功能的tcp::socket_base,算是一个真正的客户端了
template <typename Packer, typename Unpacker, typename Socket = asio::ip::tcp::socket>
class connector_base : public socket_base<Socket, Packer, Unpacker>
{
public:
connector_base(asio::io_service& io_service_);
template<typename Arg>
connector_base(asio::io_service& io_service_, Arg& arg);
ssl使用。
public:
virtual void reset();
重置所有,object_pool在重用时会调用。connector_base的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
virtual bool obsoleted();
在调用父类同名函数的基础上,增加了对是否重连接的判断,如果需要重连接则返回假。
void set_server_addr(unsigned short port, const std::string& ip);
设置服务端地址用于连接之用,需要在do_start之前被调用。
bool is_connected() const;
是否已经连接成功。
void disconnect(bool reconnect = false);
直接调用force_close。
void force_close(bool reconnect = false);
强制关闭————调用父类的do_close,如果reconnect为true,则关闭之后,马上重新连接服务器。
void graceful_close(bool reconnect = false, bool sync = true);
优雅关闭,调用父类的graceful_close函数,reconnect参数的意义同上,sync参数直接传递给父类。
void show_info(const char* head, const char* tail) const;
在head和tail中间,显示自己的地址(IP加端口)。
void show_info(const char* head, const char* tail, const asio::error_code& ec) const;
同上,但加上了显示ec.message.data()的内容。
protected:
virtual bool do_start();
开始,这里开始的意义是,如果连接未成功,则开始连接服务器,如果连接已经成功,则开始接收数据。
virtual int prepare_reconnect(const asio::error_code& ec);
连接失败时回调,返回大于等于零则继续在相应毫秒之后重试,返回负数则放弃。
virtual void on_connect();
连接成功时回调,用户重写它以得到连接成功事件。
virtual bool is_send_allowed() const;
是否可发送数据,is_connected加上父类的is_send_allowed为最终的判定结果。
virtual void on_unpack_error();
解包错误,默认的行为是关闭连接,可以重写以自定义行为。
virtual void on_recv_error(const error_code& ec);
接收错误,默认的行为是关闭连接,可以重写以自定义行为。
bool prepare_next_reconnect(const asio::error_code& ec);
如果允许(io_service仍然在运行且prepare_reconnect返回大于等于0),启动定时器以延时一小段时间之后重新连接服务器。
private:
bool async_close_handler(unsigned char id, ssize_t loop_num);
异步优雅关闭超时定时器。
void connect_handler(const error_code& ec);
连接成功或者失败后由asio回调。
protected:
asio::ip::tcp::endpoint server_addr;
服务器地址。
bool connected;
是否已经连接成功。
bool reconnecting;
是否正在重新连接。
};
}} //namespace
#ifndef ASCS_ASYNC_ACCEPT_NUM
#define ASCS_ASYNC_ACCEPT_NUM 1
同时投递多少个async_accept。
#endif
#ifndef ASCS_TCP_DEFAULT_IP_VERSION
#define ASCS_TCP_DEFAULT_IP_VERSION asio::ip::tcp::v4()
为监听套接字绑定地址时,在不指定ip的情况下,指定ip地址的版本(v4还是v6),如果指定了ip,则ip地址的版本可以从ip中推导出来。
#endif
namespace ascs { namespace tcp {
服务端服务器类,主要功能是监听和接受连接,连接管理继承于Pool
template<typename Socket, typename Pool = object_pool<Socket>, typename Server = i_server>
class server_base : public Server, public Pool
{
public:
server_base(service_pump& service_pump_);
template<typename Arg>
server_base(service_pump& service_pump_, Arg arg);
ssl使用。
void set_server_addr(unsigned short port, const std::string& ip = std::string());
const asio::ip::tcp::endpoint& get_server_addr() const;
设置获取监听地址。
void stop_listen();
停止监听。
bool is_listening() const;
是否正在监听。
virtual service_pump& get_service_pump();
virtual const service_pump& get_service_pump() const;
virtual bool del_client(const std::shared_ptr<timer>& client_ptr);
实现i_server中的三个纯虚接口。del_client的功能主要是调用object_pool的del_object函数,并输出一些信息,主要用于连接断开时调用,
也可用于主动断开连接,但是用tcp::socket的force_close或者graceful_close更理合理一些。
void close_all_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);
对每一个连接调用tcp::socket中的同名函数。
protected:
virtual void init();
virtual void uninit();
实现i_service纯虚接口,在启动和关闭service的时候,由service_pump调用。
virtual bool on_accept(typename server_base::object_ctype& client_ptr);
一个连接成功被接受后调用这个函数,如果返回true,连接将被object_pool管理;如果返回false,那么可能意味着你不想要这个连接
(你应该在返回false之前关闭这个socket)或者你想自己管理这个连接(你应该在返回false之前调用client_ptr->start以开始数据接收),
总之ojbect_pool不再维护这个连接。
virtual bool on_accept_error(const asio::error_code& ec, typename Pool::object_ctype& client_ptr);
在接受连接失败时回调这个函数,如果你想忽略这个错误且马上继续接受新连接,则返回true;如果你想忽略这个错误且等一段时间之后再继续接受新连接,
则启动一个定时器并返回fale,当定时器到达时,调用start_next_accept()函数;如果你想永远终止再接受新连接,则不要重写这个虚函数,如果你
非要重写,那么在你的代码后面调用stop_listen()且返回false。
void start_next_accept();
开始下一次异步接受连接,抽象成一个方法是因为两个地方需要调用,一是init内,一accept_handler内。
使用者也可以随时调用这个方法,如果你想增加一些异步accept的话(参看ASCS_ASYNC_ACCEPT_NUM宏)。
protected:
bool add_client(typename server_base::object_ctype& client_ptr);
添加一条连接到对象池(调用object_pool::add_object),如果成功,打印一些连接建议相关的信息。
void accept_handler(const error_code& ec, typename server_base::object_ctype& client_ptr);
异步接受到连接时asio回调,如果出错(ec为真),将调用stop_listen,否则继续异步接受连接(start_next_accept)。
protected:
asio::ip::tcp::endpoint server_addr;
监听地址。
asio::ip::tcp::acceptor acceptor;
连接异步接受器。
};
}} //namespace
namespace ascs { namespace tcp {
服务端套接字类
template<typename Packer, typename Unpacker, typename Server = i_server, typename Socket = asio::ip::tcp::socket>
class server_socket_base : public socket_base<Socket, Packer, Unpacker>, public std::enable_shared_from_this<server_socket_base<Packer, Unpacker, Server, Socket>>
{
public:
server_socket_base(Server& server_);
template<typename Arg>
server_socket_base(Server& server_, Arg& arg);
ssl使用。
virtual void reset();
重置所有,object_pool在重用时会调用。server_socket_base的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
void disconnect();
直接调用force_close。
void force_close();
强制退出————调用父类的同名函数。
void graceful_close(bool sync = true);
优雅关闭————调用父类的同名函数。
void show_info(const char* head, const char* tail) const;
在head和tail中间,显示对方的地址(IP加端口)。
void show_info(const char* head, const char* tail, const asio::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,可以重写以自定义行为。
protected:
Server& server;
用于操控server_base,server_base在创建(其实是object_pool,server_base是其子类)server_socket_base的时候,会把自己的引用通过构造函数传入。
};
}} //namespace
namespace ascs { namespace tcp {
tcp套接字类,实现tcp数据的收发
template <typename Socket, typename Packer, typename Unpacker>
class socket_base : public socket<Socket, Packer, Unpacker>
{
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:
socket_base(asio::io_service& io_service_);
template<typename Arg>
socket_base(asio::io_service& io_service_, Arg& arg);
ssl使用。
public:
virtual bool obsoleted();
在调用父类同名函数的基础上,增加了对是否正在关闭连接的判断,如果是,则返回假。
void reset();
重置所有(reset_state加clear_buffer,后者由父类实现)。
void reset_state();
重载父类同名函数,重置自己的状态之后,调用父类同名函数。
bool is_closing() const;
是否正在优雅关闭套接字,此时不再发送消息(如果发送会出错,因为已经关闭了自己的数据发送),但继续接收消息。
std::shared_ptr<i_unpacker<out_msg_type>> inner_unpacker();
std::shared_ptr<const i_unpacker<out_msg_type>> inner_unpacker() const;
void inner_unpacker(const std::shared_ptr<i_unpacker<out_msg_type>>& _unpacker_);
获取/修改解包器。
注意,运行时修改解包器是非线程安全的,而且只能在构造函数、子类的reset函数(虚的那个)和on_msg里面修改。不支持多线程一是为了
效率,二是支持了也必须要在前面说的那三个地方修改,而这三个地方不会有多线程问题,三是这个功能用得很少。
using 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 char* pstr, size_t len, bool can_overflow = false);
bool send_native_msg(const std::string& str, 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 char* pstr, size_t len, bool can_overflow = false);
bool safe_send_msg(const std::string& str, 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 char* pstr, size_t len, bool can_overflow = false);
bool safe_send_native_msg(const std::string& str, 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 post_msg(const char* pstr, size_t len, bool can_overflow = false);
bool post_msg(const std::string& str, bool can_overflow = false);
bool post_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同send_msg,只是它永远能马上成功。
注意:如果消息发送缓存溢出了(当然can_overflow得为false,否则不检测缓存是否溢出),则暂停消息派发(进而也会停止消息接收,
因为不派发只接收消息,迟早会让消息接收缓存满而暂停消息接收,如果不使用消息接收缓存,则马上暂停消息接收)。
post_msg和send_msg的区别请看st_socket里面的post_msg_buffer和send_msg_buffer。
bool post_native_msg(const char* pstr, size_t len, bool can_overflow = false);
bool post_native_msg(const std::string& str, bool can_overflow = false);
bool post_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
protected:
void force_close();
void graceful_close(bool sync = true);
前两个函数功能完全一样(为了完整性而提供了它们),都是直接调用clean_up。
第三个函数优雅关闭套接字,所谓优雅关闭,就是先关闭自己的数据发送,然后接收完遗留数据之后,才完全关闭套接字。当sync为假时,graceful_close马上返回,
优雅关闭将在后台继承进行,当回调到on_recv_error的时候,关闭结束(有可能优雅关闭成功,有可能超时被强制关闭)。
virtual bool do_send_msg();
马上开始消息发送,重写自ascs::socket的do_send_msg,由ascs::socket调用。
virtual bool is_send_allowed() const;
重写ascs::socket的is_send_allowed,记住,自己的判断(is_closing)加上ascs::socket的判断,才是最终结果。
virtual void on_unpack_error() = 0;
解包出错时回调。
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(out_msg_type& msg);
重写ascs::socket的on_msg,功能是打印消息到控制台,使用者重写这个函数以处理消息。
#endif
virtual bool on_msg_handle(msg_type& msg, bool link_down);
重写ascs::socket的on_msg_handle,功能是打印消息到控制台,使用者重写这个函数以处理消息。
void do_recv_msg();
马上开始接收数据,由子类调用,因为socket_base不知道什么时候可以接收数据(比如是否连接成功等)。
void clean_up();
关闭套接字,停止所有定时器,直接派发所有剩余消息,重置所有状态(调用reset_state)。
void recv_handler(const error_code& ec, size_t bytes_transferred);
收到数据时后asio回调。
void send_handler(const error_code& ec, size_t bytes_transferred);
成功发送消息(写入底层套接字)后由asio回调。
protected:
std::shared_ptr<i_unpacker<out_msg_type>> unpacker_;
int close_state; //2-the first step of graceful close, 1-force close, 0-normal state
};
}} //namespace

namespace ascs
{
定时器类
class timer
{
protected:
typedef std::chrono::milliseconds milliseconds;
#ifdef ASCS_USE_STEADY_TIMER
typedef asio::steady_timer timer_type;
#else
typedef asio::system_timer timer_type;
#endif
protected:
struct timer_info
{
enum timer_status {TIMER_OK, TIMER_CANCELED};
unsigned char id;
timer_status status;
size_t milliseconds;
std::function<bool (unsigned char)> call_back;
在定时器到达后,call_back被回调,并根据返回值决定是否继续这个定时器(true即继续),同一个定时器,call_back的调用是顺序的。
std::shared_ptr<timer_type> timer;
bool operator <(const timer_info& other) const {return id < other.id;}
};
定时器数据结构。
static const unsigned char TIMER_END = 0;
继承者的定义器ID必须从父类的TIMER_END开始,然后最好也定义一个自己的TIMER_END,如果你这个类可能会被继承的话。
timer(io_service& _io_service_);
public:
typedef const timer_info timer_cinfo;
typedef std::set<timer_info> container_type;
void update_timer_info(unsigned char id, size_t milliseconds, std::function<bool(unsigned char)>&& call_back, bool start = false);
void update_timer_info(unsigned char id, size_t milliseconds, const std::function<bool(unsigned char)>& call_back, bool start = false)
更新timer,如果start为true,更新完了之后马上会开启或者重新开启这个timer。
注意,对同一个timer里面的同一个定时器操作并不是线程安全的。
void set_timer(unsigned char id, size_t milliseconds, std::function<bool(unsigned char)>&& call_back);
void set_timer(unsigned char id, size_t milliseconds, const std::function<bool(unsigned char)>& call_back);
开启定时器,定时器以id分区,如果定时器已经存在,则重新开始记时。这个函数其实就是以start为true调用update_timer_info而已。
timer_info find_timer(unsigned char id);
查找定义器。
bool start_timer(unsigned char id);
开启一个已经存在的定义器。注意stop_timer之后,定义器还是存在的,只是未启动。
void stop_timer(unsigned char id);
停止定时器,如果定时时间已经到达,且已经被post到io_server的队列中,则on_timer仍然会在未来某个时候被回调,这是asio的设计。
asio::io_service& get_io_service();
const asio::io_service& get_io_service() const;
template<typename _Predicate> void do_something_to_all(const _Predicate& __pred);
对所有定时器做一个操作,操作由__pred来定,ascs库只是调用__pred()。
template<typename _Predicate> void do_something_to_one(const _Predicate& __pred);
与do_something_to_all类似,只是当__pred()返回真时就不再继续循环处理后面的定时器了(如果你永远返回false,那就等于
so_something_to_all),跟查找功能类似。
void stop_all_timer();
停止所有定时器。
protected:
void start_timer(timer_cinfo& ti);
内部使用的helper函数,真正的开启定时器(timer_type::async_wait)。
void stop_timer(timer_info& ti);
内部使用的helper函数,真正的结束定时器(调用timer_type::cancel)。
protected:
asio::io_service& io_service_;
container_type timer_can;
shared_mutex timer_can_mutex;
};
} //namespace
#ifndef ASCS_UDP_DEFAULT_IP_VERSION
#define ASCS_UDP_DEFAULT_IP_VERSION asio::ip::udp::v4()
#endif
绑定地址时,在不指定ip的情况下,指定ip地址的版本(v4还是v6),如果指定了ip,则ip地址的版本可以从ip中推导出来。
namespace ascs { namespace udp {
udp套接字类,实现udp数据的收发
template <typename Packer, typename Unpacker, typename Socket = asio::ip::udp::socket>
class socket_base : public socket<Socket, Packer, Unpacker, udp_msg<typename Packer::msg_type>, udp_msg<typename Unpacker::msg_type>>
{
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:
socket_base(asio::io_service& io_service_);
public:
virtual void reset();
重置所有,object_pool在重用时会调用。socket_base的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
void set_local_addr(unsigned short port, const std::string& ip = std::string());
const asio::ip::udp::endpoint& get_local_addr() const;
设置获取本端地址。
void disconnect();
void force_close();
void graceful_close();
调用clean_up,这三个函数功能完全一样,为了与tcp::socket_base在接口上保持一致才设计成三个。
std::shared_ptr<i_unpacker<typename Packer::msg_type>> inner_unpacker();
std::shared_ptr<const i_unpacker<typename Packer::msg_type>> inner_unpacker() const;
void inner_unpacker(const std::shared_ptr<i_unpacker<typename Packer::msg_type>>& _unpacker_);
获取/修改解包器。
注意,运行时修改解包器是非线程安全的,而且只能在构造函数、子类的reset函数(虚的那个)和on_msg里面修改。不支持多线程一是为了
效率,二是支持了也必须要在前面说的那三个地方修改,而这三个地方不会有多线程问题,三是这个功能用得很少。
using socket<Socket, Packer, Unpacker, in_msg_type, out_msg_type>::send_msg;
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 std::string& str, 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 char* pstr, size_t len, bool can_overflow = false);
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* 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 char* pstr, size_t len, bool can_overflow = false);
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* 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 char* pstr, size_t len, bool can_overflow = false);
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* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同上,只是以native为true调用i_packer::pack_msg接口。
bool post_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool post_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool post_msg(const udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
同send_msg,只是它永远能马上成功。
注意:如果消息发送缓存溢出了(当然can_overflow得为false,否则不检测缓存是否溢出),则暂停消息派发(进而也会停止消息接收,
因为不派发只接收消息,迟早会让消息接收缓存满而暂停消息接收,如果不使用消息接收缓存,则马上暂停消息接收)。
post_msg和send_msg的区别请看st_socket里面的post_msg_buffer和send_msg_buffer。
bool post_native_msg(const udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false);
bool post_native_msg(const udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false);
bool post_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接口。
void show_info(const char* head, const char* tail);
打印日志,在head和tail中间加上本端ip和端口。
protected:
virtual bool do_start();
马上开始消息接收,重写自socket的do_start,由socket调用。
virtual bool do_send_msg();
马上开始消息发送,重写自socket的do_send_msg,由socket调用。
virtual bool is_send_allowed() const;
重写socket的is_send_allowed,记住,自己的判断(is_open)加上socket的判断,才是最终结果。
virtual void on_recv_error(const error_code& ec);
接收消息出错时回调。
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(out_msg_type& msg);
重写socket的on_msg,功能是打印消息到控制台,使用者重写这个函数以处理消息。
#endif
virtual bool on_msg_handle(out_msg_type& msg, bool link_down);
重写socket的on_msg_handle,功能是打印消息到控制台,使用者重写这个函数以处理消息。
void clean_up();
关闭套接字,停止所有定时器,直接派发所有剩余消息,重置所有状态(调用reset_state)。
void recv_handler(const error_code& ec, size_t bytes_transferred);
收到数据后由asio回调。
void send_handler(const error_code& ec, size_t bytes_transferred);
成功发送消息(写入底层套接字)后由asio回调。
protected:
std::shared_ptr<i_unpacker<typename Packer::msg_type>> unpacker_;
asio::ip::udp::endpoint peer_addr, local_addr;
异步接收udp消息时,asio需要一个endpoint,在整个异步接收过程中,这个endpoint必须有效,所以它是一个成员变量,
它只代表上一次接收udp消息时的对端地址,对于已经接收到的udp消息,对端地址保存在out_msg_type里面。
};
}} //namespace
namespace ascs { namespace udp {
支持多个套接字的udp服务
template<typename Socket, typename Pool = object_pool<Socket>>
class service_base : public multi_socket_service<Socke, Pool>
{
public:
service_base(service_pump& service_pump_);
using super::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_close(typename Pool::object_ctype& socket_ptr);
void force_close();
void graceful_close(typename Pool::object_ctype& socket_ptr);
void graceful_close();
带socket_ptr参数者会把socket_ptr从容器中删除,然后调用socket_ptr的同名函数。
不带socket_ptr参数者不会从容器中删除任何对象,只对容器中所有对象调用同名函数。
protected:
virtual void uninit();
实现i_service的纯虚接口,由service_pump在stop_service时调用,跟single_socket_service::uninit功能一样,只是对所有套接字都做一个“结束”操作。
};
}} //namespace
#include <iostream>
//configuration
#define ASCS_SERVER_PORT 9527
#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
#define ASCS_CUSTOM_LOG
#define ASCS_DEFAULT_UNPACKER non_copy_unpacker
//#define ASCS_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 tcp::socket has a
//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 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 ASCS_HUGE_MSG and ASCS_MSG_BUFFER_SIZE macros too.
//#define ASCS_HUGE_MSG
//#define ASCS_MSG_BUFFER_SIZE (1024 * 1024)
//#define ASCS_MAX_MSG_NUM 8 //reduce msg buffer size to reduce memory occupation
//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 ascs header files except base.h
//notice: please don't forget to define the ASCS_CUSTOM_LOG macro.
#include <ascs/base.h>
using namespace ascs;
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 <ascs/ext/client.h>
using namespace ascs::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define RECONNECT_COMMAND "reconnect"
#define SUSPEND_COMMAND "suspend"
#define RESUME_COMMAND "resume"
int main(int argc, const char* argv[])
{
printf("usage: asio_client [<port=%d> [ip=%s]]\n", ASCS_SERVER_PORT + 100, ASCS_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
service_pump sp;
sclient client(sp);
//there is no corresponding echo client, because echo server with echo client will cause dead loop, and occupy almost all the network resource
// 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]), ASCS_SERVER_IP);
else
client.set_server_addr(ASCS_SERVER_PORT + 100, ASCS_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_close(true);
//the following two commands demonstrate how to suspend msg sending, no matter recv buffer been used or not
else if (SUSPEND_COMMAND == str)
client.suspend_send_msg(true);
else if (RESUME_COMMAND == str)
client.suspend_send_msg(false);
else
client.safe_send_msg(str);
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
#undef ASCS_CUSTOM_LOG
#undef ASCS_DEFAULT_UNPACKER
//#undef ASCS_HUGE_MSG
//#undef ASCS_MAX_MSG_LEN
//#undef ASCS_MAX_MSG_NUM
//restore configuration
module = client
include ../config.mk
# If your compiler cannot find asio, please specify it explicitly like this:
#asio_location = -I/usr/local/include/
# asio.hpp and asio directory should be available in this place.
cflag = -Wall -fexceptions -std=c++1y
ifeq (${MAKECMDGOALS}, debug)
cflag += -g -DDEBUG
dir = debug
else
cflag += -O2 -DNDEBUG
lflag = -s
dir = release
endif
cflag += -DASIO_STANDALONE -DASIO_HAS_STD_CHRONO
# If your compiler detected duplicated 'shared_mutex' definition, please define ASCS_HAS_STD_SHARED_MUTEX macro:
#cflag += -DASCS_HAS_STD_SHARED_MUTEX
cflag += -pthread ${ext_cflag} ${asio_location} -I../../include/
lflag += -pthread ${ext_libs}
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>
//configuration
#define ASCS_SERVER_PORT 9527
//#define ASCS_REUSE_OBJECT //use objects pool
//#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
//#define ASCS_CLEAR_OBJECT_INTERVAL 1
//#define ASCS_WANT_MSG_SEND_NOTIFY
#define ASCS_FULL_STATISTIC //full statistic will slightly impact efficiency.
//configuration
//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-default replaceable_packer and replaceable_unpacker, head(length) + body
//2-fixed length unpacker
//3-prefix and suffix packer and unpacker
#if 1 == PACKER_UNPACKER_TYPE
#define ASCS_DEFAULT_PACKER replaceable_packer
#define ASCS_DEFAULT_UNPACKER replaceable_unpacker
#elif 2 == PACKER_UNPACKER_TYPE
#define ASCS_DEFAULT_UNPACKER fixed_length_unpacker
#elif 3 == PACKER_UNPACKER_TYPE
#define ASCS_DEFAULT_PACKER prefix_suffix_packer
#define ASCS_DEFAULT_UNPACKER prefix_suffix_unpacker
#endif
#include <ascs/ext/client.h>
using namespace ascs;
using namespace ascs::ext;
#ifdef _MSC_VER
#define atoll _atoi64
#endif
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define LIST_ALL_CLIENT "list_all_client"
#define LIST_STATUS "status"
#define SUSPEND_COMMAND "suspend"
#define RESUME_COMMAND "resume"
static bool check_msg;
///////////////////////////////////////////////////
//msg sending interface
#define TCP_RANDOM_SEND_MSG(FUNNAME, SEND_FUNNAME) \
void FUNNAME(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) \
{ \
auto index = (size_t) ((uint64_t) rand() * (size() - 1) / RAND_MAX); \
at(index)->SEND_FUNNAME(pstr, len, num, can_overflow); \
} \
TCP_SEND_MSG_CALL_SWITCH(FUNNAME, void)
//msg sending interface
///////////////////////////////////////////////////
class echo_socket : public connector
{
public:
echo_socket(asio::io_service& io_service_) : connector(io_service_), recv_bytes(0), recv_index(0)
{
#if 2 == PACKER_UNPACKER_TYPE
std::dynamic_pointer_cast<ASCS_DEFAULT_UNPACKER>(inner_unpacker())->fixed_length(1024);
#elif 3 == PACKER_UNPACKER_TYPE
std::dynamic_pointer_cast<ASCS_DEFAULT_PACKER>(inner_packer())->prefix_suffix("begin", "end");
std::dynamic_pointer_cast<ASCS_DEFAULT_UNPACKER>(inner_unpacker())->prefix_suffix("begin", "end");
#endif
}
uint64_t get_recv_bytes() const {return recv_bytes;}
operator uint64_t() const {return recv_bytes;}
void clear_status() {recv_bytes = recv_index = 0;}
void begin(size_t msg_num_, size_t msg_len, char msg_fill)
{
clear_status();
msg_num = msg_num_;
auto buff = new char[msg_len];
memset(buff, msg_fill, msg_len);
memcpy(buff, &recv_index, sizeof(size_t)); //seq
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
send_native_msg(buff, msg_len);
#else
send_msg(buff, msg_len);
#endif
delete[] buff;
}
protected:
//msg handling
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER //not force to use msg recv buffer(so on_msg will make the decision)
//we can handle msg very fast, so we don't use recv buffer(return true)
virtual bool on_msg(out_msg_type& msg) {handle_msg(msg); return true;}
#endif
//we should handle msg in on_msg_handle for time-consuming task like this:
virtual bool on_msg_handle(out_msg_type& msg, bool link_down) {handle_msg(msg); return true;}
//msg handling end
#ifdef ASCS_WANT_MSG_SEND_NOTIFY
virtual void on_msg_send(in_msg_type& msg)
{
if (0 == --msg_num)
return;
auto pstr = inner_packer()->raw_data(msg);
auto msg_len = inner_packer()->raw_data_len(msg);
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
std::advance(pstr, -ASCS_HEAD_LEN);
msg_len += ASCS_HEAD_LEN;
#endif
size_t send_index;
memcpy(&send_index, pstr, sizeof(size_t));
++send_index;
memcpy(pstr, &send_index, sizeof(size_t)); //seq
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
send_native_msg(pstr, msg_len);
#else
send_msg(pstr, msg_len);
#endif
}
#endif
private:
void handle_msg(out_msg_ctype& msg)
{
recv_bytes += msg.size();
if (check_msg && (msg.size() < sizeof(size_t) || 0 != memcmp(&recv_index, msg.data(), sizeof(size_t))))
printf("check msg error: " ASCS_SF ".\n", recv_index);
++recv_index;
}
private:
uint64_t recv_bytes;
size_t recv_index, msg_num;
};
class test_client : public tcp::client_base<echo_socket>
{
public:
test_client(service_pump& service_pump_) : tcp::client_base<echo_socket>(service_pump_) {}
uint64_t get_recv_bytes()
{
uint64_t total_recv_bytes = 0;
do_something_to_all([&total_recv_bytes](object_ctype& item) {total_recv_bytes += *item;});
// do_something_to_all([&total_recv_bytes](object_ctype& item) {total_recv_bytes += item->get_recv_bytes();});
return total_recv_bytes;
}
echo_socket::statistic get_statistic()
{
echo_socket::statistic stat;
do_something_to_all([&stat](object_ctype& item) {stat += item->get_statistic(); });
return stat;
}
void clear_status() {do_something_to_all([](object_ctype& item) {item->clear_status();});}
void begin(size_t msg_num, size_t msg_len, char msg_fill) {do_something_to_all([=](object_ctype& item) {item->begin(msg_num, msg_len, msg_fill);});}
void close_some_client(size_t n)
{
static auto test_index = -1;
++test_index;
switch (test_index % 6)
{
#ifdef ASCS_CLEAR_OBJECT_INTERVAL
//method #1
//notice: these methods need to define ASCS_CLEAR_OBJECT_INTERVAL macro, because it just close the socket,
//not really remove them from object pool, this will cause test_client still send data via them, and wait responses from them.
//for this scenario, the smaller ASCS_CLEAR_OBJECT_INTERVAL macro is, the better experience you will get, so set it to 1 second.
case 0: do_something_to_one([&n](object_ctype& item) {return n-- > 0 ? item->graceful_close(), false : true;}); break;
case 1: do_something_to_one([&n](object_ctype& item) {return n-- > 0 ? item->graceful_close(false, false), false : true;}); break;
case 2: do_something_to_one([&n](object_ctype& item) {return n-- > 0 ? item->force_close(), false : true;}); break;
#else
//method #2
//this is a equivalence of calling i_server::del_client in server_socket_base::on_recv_error(see server_socket_base for more details).
case 0: while (n-- > 0) graceful_close(at(0)); break;
case 1: while (n-- > 0) graceful_close(at(0), false); break;
case 2: while (n-- > 0) force_close(at(0)); break;
#endif
//if you just want to reconnect to the server, you should do it like this:
case 3: do_something_to_one([&n](object_ctype& item) {return n-- > 0 ? item->graceful_close(true), false : true;}); break;
case 4: do_something_to_one([&n](object_ctype& item) {return n-- > 0 ? item->graceful_close(true, false), false : true;}); break;
case 5: do_something_to_one([&n](object_ctype& item) {return n-- > 0 ? item->force_close(true), false : true;}); break;
}
}
///////////////////////////////////////////////////
//msg sending interface
//guarantee send msg successfully even if can_overflow equal to false, success at here just means putting the msg into tcp::socket's send buffer successfully
TCP_RANDOM_SEND_MSG(safe_random_send_msg, safe_send_msg)
TCP_RANDOM_SEND_MSG(safe_random_send_native_msg, safe_send_native_msg)
//msg sending interface
///////////////////////////////////////////////////
};
int main(int argc, const char* argv[])
{
printf("usage: test_client [<service thread number=1> [<port=%d> [<ip=%s> [link num=16]]]]\n", ASCS_SERVER_PORT, ASCS_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
///////////////////////////////////////////////////////////
size_t link_num = 16;
if (argc > 4)
link_num = std::min(ASCS_MAX_OBJECT_NUM, std::max(atoi(argv[4]), 1));
printf("exec: test_client with " ASCS_SF " links\n", link_num);
///////////////////////////////////////////////////////////
service_pump sp;
test_client client(sp);
// argv[2] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4
unsigned short port = argc > 2 ? atoi(argv[2]) : ASCS_SERVER_PORT;
std::string ip = argc > 3 ? argv[3] : ASCS_SERVER_IP;
//method #1, create and add clients manually.
auto client_ptr = client.create_object();
//client_ptr->set_server_addr(port, ip); //we don't have to set server address at here, the following do_something_to_all will do it for me
//some other initializations according to your business
client.add_socket(client_ptr, false);
//method #2, add clients first without any arguments, then set the server address.
for (size_t i = 1; i < link_num / 2; ++i)
client.add_client();
client.do_something_to_all([argv, port, &ip](test_client::object_ctype& item) {item->set_server_addr(port, ip);});
//method #3, add clients and set server address in one invocation.
for (auto i = std::max((size_t) 1, link_num / 2); i < link_num; ++i)
client.add_client(port, ip);
auto thread_num = 1;
if (argc > 1)
thread_num = std::min(16, std::max(thread_num, atoi(argv[1])));
#ifdef ASCS_CLEAR_OBJECT_INTERVAL
if (1 == thread_num)
++thread_num;
#endif
sp.start_service(thread_num);
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(thread_num);
}
else if (LIST_STATUS == str)
{
printf("link #: " ASCS_SF ", valid links: " ASCS_SF ", invalid links: " ASCS_SF "\n", client.size(), client.valid_size(), client.invalid_object_size());
puts("");
puts(client.get_statistic().to_string().data());
}
//the following two commands demonstrate how to suspend msg dispatching, no matter recv buffer been used or not
else if (SUSPEND_COMMAND == str)
client.do_something_to_all([](test_client::object_ctype& item) {item->suspend_dispatch_msg(true);});
else if (RESUME_COMMAND == str)
client.do_something_to_all([](test_client::object_ctype& item) {item->suspend_dispatch_msg(false);});
else if (LIST_ALL_CLIENT == str)
client.list_all_object();
else if (!str.empty())
{
if ('+' == str[0] || '-' == str[0])
{
auto n = (size_t) atoi(std::next(str.data()));
if (0 == n)
n = 1;
if ('+' == str[0])
for (; n > 0 && client.add_client(port, ip); ++link_num, --n);
else
{
if (n > client.size())
n = client.size();
client.close_some_client(n);
link_num = client.size();
}
continue;
}
#ifdef ASCS_CLEAR_OBJECT_INTERVAL
link_num = client.size();
printf("link number: " ASCS_SF "\n", link_num);
#endif
size_t msg_num = 1024;
size_t msg_len = 1024; //must greater than or equal to sizeof(size_t)
auto msg_fill = '0';
char model = 0; //0 broadcast, 1 randomly pick one link per msg
auto parameters = split_string(str);
auto iter = std::begin(parameters);
if (iter != std::end(parameters)) msg_num = std::max((size_t) atoll(iter++->data()), (size_t) 1);
#if 1 == PACKER_UNPACKER_TYPE
if (iter != std::end(parameters)) msg_len = std::min(packer::get_max_msg_size(),
std::max((size_t) atoi(iter++->data()), sizeof(size_t))); //include seq
#elif 2 == PACKER_UNPACKER_TYPE
if (iter != std::end(parameters)) ++iter;
msg_len = 1024; //we hard code this because we fixedly initialized the length of fixed_length_unpacker to 1024
#elif 3 == PACKER_UNPACKER_TYPE
if (iter != std::end(parameters)) msg_len = std::min((size_t) ASCS_MSG_BUFFER_SIZE,
std::max((size_t) atoi(iter++->data()), sizeof(size_t)));
#endif
if (iter != std::end(parameters)) msg_fill = *iter++->data();
if (iter != std::end(parameters)) model = *iter++->data() - '0';
unsigned percent = 0;
uint64_t total_msg_bytes;
switch (model)
{
case 0:
check_msg = true;
total_msg_bytes = msg_num * link_num; break;
case 1:
check_msg = false;
srand(time(nullptr));
total_msg_bytes = msg_num; break;
default:
total_msg_bytes = 0; break;
}
if (total_msg_bytes > 0)
{
printf("test parameters after adjustment: " ASCS_SF " " ASCS_SF " %c %d\n", msg_num, msg_len, msg_fill, model);
puts("performance test begin, this application will have no response during the test!");
client.clear_status();
total_msg_bytes *= msg_len;
ascs_cpu_timer begin_time;
#ifdef ASCS_WANT_MSG_SEND_NOTIFY
if (0 == model)
{
client.begin(msg_num, msg_len, msg_fill);
unsigned new_percent = 0;
do
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
new_percent = (unsigned) (100 * client.get_recv_bytes() / total_msg_bytes);
if (percent != new_percent)
{
percent = new_percent;
printf("\r%u%%", percent);
fflush(stdout);
}
} while (100 != new_percent);
printf("\r100%%\ntime spent statistics: %.1f seconds.\n", begin_time.elapsed());
printf("speed: %.0f(*2)kB/s.\n", total_msg_bytes / begin_time.elapsed() / 1024);
}
else
puts("if ASCS_WANT_MSG_SEND_NOTIFY defined, only support model 0!");
#else
auto buff = new char[msg_len];
memset(buff, msg_fill, msg_len);
uint64_t send_bytes = 0;
for (size_t i = 0; i < msg_num; ++i)
{
memcpy(buff, &i, sizeof(size_t)); //seq
switch (model)
{
case 0:
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
client.safe_broadcast_native_msg(buff, msg_len);
#else
client.safe_broadcast_msg(buff, msg_len);
#endif
send_bytes += link_num * msg_len;
break;
case 1:
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
client.safe_random_send_native_msg(buff, msg_len);
#else
client.safe_random_send_msg(buff, msg_len);
#endif
send_bytes += msg_len;
break;
default:
break;
}
auto new_percent = (unsigned) (100 * send_bytes / total_msg_bytes);
if (percent != new_percent)
{
percent = new_percent;
printf("\r%u%%", percent);
fflush(stdout);
}
}
delete[] buff;
while(client.get_recv_bytes() != total_msg_bytes)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
printf("\r100%%\ntime spent statistics: %.1f seconds.\n", begin_time.elapsed());
printf("speed: %.0f(*2)kB/s.\n", total_msg_bytes / begin_time.elapsed() / 1024);
#endif
} // if (total_data_num > 0)
}
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_REUSE_OBJECT
#undef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
#undef ASCS_CLEAR_OBJECT_INTERVAL
#undef ASCS_WANT_MSG_SEND_NOTIFY
#undef ASCS_FULL_STATISTIC
#undef ASCS_DEFAULT_PACKER
#undef ASCS_DEFAULT_UNPACKER
//restore configuration
module = echo_client
include ../config.mk
#include <iostream>
//configuration
#define ASCS_SERVER_PORT 9527
#define ASCS_ASYNC_ACCEPT_NUM 5
#define ASCS_REUSE_OBJECT //use objects pool
#define ASCS_FREE_OBJECT_INTERVAL 60
//#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
#define ASCS_ENHANCED_STABILITY
//#define ASCS_FULL_STATISTIC //full statistic will slightly impact efficiency.
//#define ASCS_USE_STEADY_TIMER
//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-default replaceable_packer and replaceable_unpacker, head(length) + body
//2-fixed length unpacker
//3-prefix and suffix packer and unpacker
#if 1 == PACKER_UNPACKER_TYPE
#define ASCS_DEFAULT_PACKER replaceable_packer
#define ASCS_DEFAULT_UNPACKER replaceable_unpacker
#elif 2 == PACKER_UNPACKER_TYPE
#define ASCS_DEFAULT_UNPACKER fixed_length_unpacker
#elif 3 == PACKER_UNPACKER_TYPE
#define ASCS_DEFAULT_PACKER prefix_suffix_packer
#define ASCS_DEFAULT_UNPACKER prefix_suffix_unpacker
#endif
//configuration
#include <ascs/ext/server.h>
using namespace ascs;
using namespace ascs::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define LIST_ALL_CLIENT "list_all_client"
#define LIST_STATUS "status"
#define SUSPEND_COMMAND "suspend"
#define RESUME_COMMAND "resume"
//demonstrate how to use custom packer
//under the default behavior, each 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(std::make_shared<ASCS_DEFAULT_PACKER>());
//demonstrate how to control the type of tcp::server_socket_base::server from template parameter
class i_echo_server : public i_server
{
public:
virtual void test() = 0;
};
class echo_socket : public tcp::server_socket_base<ASCS_DEFAULT_PACKER, ASCS_DEFAULT_UNPACKER, i_echo_server>
{
public:
echo_socket(i_echo_server& server_) : server_socket_base(server_)
{
inner_packer(global_packer);
#if 2 == PACKER_UNPACKER_TYPE
std::dynamic_pointer_cast<ASCS_DEFAULT_UNPACKER>(inner_unpacker())->fixed_length(1024);
#elif 3 == PACKER_UNPACKER_TYPE
std::dynamic_pointer_cast<ASCS_DEFAULT_UNPACKER>(inner_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() {server_socket_base::reset();}
protected:
virtual void on_recv_error(const asio::error_code& ec)
{
//the type of tcp::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();
server_socket_base::on_recv_error(ec);
}
//msg handling: send the original msg back(echo server)
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
//this virtual function doesn't exists if ASCS_FORCE_TO_USE_MSG_RECV_BUFFER been defined
virtual bool on_msg(out_msg_type& msg)
{
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
return post_native_msg(msg.data(), msg.size());
#else
return post_msg(msg.data(), msg.size());
#endif
}
#endif
//we should handle msg in on_msg_handle for time-consuming task like this:
virtual bool on_msg_handle(out_msg_type& msg, bool link_down)
{
#if 2 == PACKER_UNPACKER_TYPE
//we don't have fixed_length_packer, so use packer instead, but need to pack msgs with native manner.
return send_native_msg(msg.data(), msg.size());
#else
return send_msg(msg.data(), msg.size());
#endif
}
//please remember that we have defined ASCS_FORCE_TO_USE_MSG_RECV_BUFFER, so, tcp::socket will directly
//use msg recv buffer, and we need not rewrite on_msg(), which doesn't exists any more
//msg handling end
};
class echo_server : public tcp::server_base<echo_socket, object_pool<echo_socket>, i_echo_server>
{
public:
echo_server(service_pump& service_pump_) : server_base(service_pump_) {}
echo_socket::statistic get_statistic()
{
echo_socket::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()");*/}
};
int main(int argc, const char* argv[])
{
printf("usage: asio_server [<service thread number=1> [<port=%d> [ip=0.0.0.0]]]\n", ASCS_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.");
service_pump sp;
//only need a simple server? you can directly use server or tcp::server_base.
//because we use tcp::server_socket_base directly, so this server cannot support fixed_length_unpacker and prefix_suffix_packer/prefix_suffix_unpacker,
//the reason is these packer and unpacker need additional initializations that tcp::server_socket_base not implemented, see echo_socket's constructor for more details.
typedef tcp::server_socket_base<packer, unpacker> normal_server_socket;
tcp::server_base<normal_server_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(ASCS_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 #: " ASCS_SF ", invalid links: " ASCS_SF "\n", server_.size(), server_.invalid_object_size());
printf("echo server, link #: " ASCS_SF ", invalid links: " ASCS_SF "\n", echo_server_.size(), echo_server_.invalid_object_size());
puts("");
puts(echo_server_.get_statistic().to_string().data());
}
//the following two commands demonstrate how to suspend msg dispatching, no matter recv buffer been used or not
else if (SUSPEND_COMMAND == str)
echo_server_.do_something_to_all([](echo_server::object_ctype& item) {item->suspend_dispatch_msg(true);});
else if (RESUME_COMMAND == str)
echo_server_.do_something_to_all([](echo_server::object_ctype& item) {item->suspend_dispatch_msg(false);});
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_.broadcast_msg(str.data(), str.size() + 1);
//send \0 character too, because 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 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](tcp::server_base<normal_server_socket>::object_ctype& item) {item->direct_send_msg(msg);});
//if asio_client is using stream_unpacker
// if (!str.empty())
// server_.do_something_to_all([&str](tcp::server_base<normal_server_socket>::object_ctype& item) {item->direct_send_msg(str);});
}
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_ASYNC_ACCEPT_NUM
#undef ASCS_REUSE_OBJECT
#undef ASCS_FREE_OBJECT_INTERVAL
#undef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
#undef ASCS_ENHANCED_STABILITY
#undef ASCS_FULL_STATISTIC
#undef ASCS_DEFAULT_PACKER
#undef ASCS_DEFAULT_UNPACKER
#undef ASCS_USE_STEADY_TIMER
//restore configuration
module = echo_server
include ../config.mk
#include <iostream>
//configuration
#define ASCS_SERVER_PORT 5050
#define ASCS_DEFAULT_UNPACKER replaceable_unpacker
//configuration
#include "file_client.h"
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define REQUEST_FILE "get"
std::atomic_ushort completed_client_num;
int link_num = 1;
fl_type file_size;
int main(int argc, const char* argv[])
{
puts("this is a file transfer client.");
printf("usage: file_client [<port=%d> [<ip=%s> [link num=1]]]\n", ASCS_SERVER_PORT, ASCS_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
service_pump sp;
file_client client(sp);
if (argc > 3)
link_num = std::min(256, std::max(atoi(argv[3]), 1));
for (auto i = 0; i < link_num; ++i)
{
// argv[2] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4
if (argc > 2)
client.add_client(atoi(argv[1]), argv[2])->set_index(i);
else if (argc > 1)
client.add_client(atoi(argv[1]))->set_index(i);
else
client.add_client()->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));
auto files = split_string(str);
do_something_to_all(files, [&](const std::string& item) {
completed_client_num = 0;
file_size = 0;
printf("transfer %s begin.\n", item.data());
if (client.find(0)->get_file(item))
{
//if you always return false, do_something_to_one will be equal to do_something_to_all.
client.do_something_to_one([&item](file_client::object_ctype& item2)->bool {if (0 != item2->id()) item2->get_file(item); return false;});
client.start();
while (completed_client_num != (unsigned short) link_num)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
client.stop(item);
}
else
printf("transfer %s failed!\n", item.data());
});
}
else
client.at(0)->talk(str);
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_DEFAULT_UNPACKER
//restore configuration
#ifndef FILE_CLIENT_H_
#define FILE_CLIENT_H_
#include "../file_server/packer_unpacker.h"
#include <ascs/ext/client.h>
using namespace ascs;
using namespace ascs::ext;
extern std::atomic_ushort completed_client_num;
extern int link_num;
extern fl_type file_size;
class file_socket : public base_socket, public connector
{
public:
file_socket(asio::io_service& io_service_) : connector(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(); connector::reset();}
void set_index(int index_) {index = index_;}
fl_type get_rest_size() const
{
auto unpacker = std::dynamic_pointer_cast<const data_unpacker>(inner_unpacker());
return nullptr == unpacker ? 0 : unpacker->get_rest_size();
}
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 (nullptr == 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 ASCS_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) {handle_msg(msg); return true;}
#endif
//we will change unpacker at runtime, this operation can only be done in on_msg(), reset() or constructor
//virtual bool on_msg_handle(out_msg_type& msg, bool link_down) {handle_msg(msg); return true;}
//msg handling end
private:
void clear()
{
state = TRANS_IDLE;
if (nullptr != file)
{
fclose(file);
file = nullptr;
}
inner_unpacker(std::make_shared<ASCS_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: " ASCS_SF ".\n", msg.size());
return;
}
switch (*msg.data())
{
case 0:
if (ORDER_LEN + DATA_LEN == msg.size() && nullptr != file && TRANS_PREPARE == state)
{
fl_type length;
memcpy(&length, std::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;
auto my_length = length / link_num;
auto 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(std::next(buffer, ORDER_LEN), &offset, OFFSET_LEN);
memcpy(std::next(buffer, ORDER_LEN + OFFSET_LEN), &my_length, DATA_LEN);
state = TRANS_BUSY;
send_msg(buffer, sizeof(buffer), true);
fseeko(file, offset, SEEK_SET);
inner_unpacker(std::make_shared<data_unpacker>(file, my_length));
}
else
trans_end();
}
}
break;
case 2:
if (0 == index)
printf("server says: %s\n", std::next(msg.data(), ORDER_LEN));
break;
default:
break;
}
}
private:
int index;
};
class file_client : public tcp::client_base<file_socket>
{
public:
static const unsigned char TIMER_BEGIN = tcp::client_base<file_socket>::TIMER_END;
static const unsigned char UPDATE_PROGRESS = TIMER_BEGIN;
static const unsigned char TIMER_END = TIMER_BEGIN + 10;
file_client(service_pump& service_pump_) : client_base<file_socket>(service_pump_) {}
void start()
{
begin_time.restart();
set_timer(UPDATE_PROGRESS, 50, std::bind(&file_client::update_progress_handler, this, std::placeholders::_1, -1));
}
void stop(const std::string& file_name)
{
stop_timer(UPDATE_PROGRESS);
printf("\r100%%\ntransfer %s end, speed: %.0f kB/s.\n", file_name.data(), file_size / begin_time.elapsed() / 1024);
}
fl_type get_total_rest_size()
{
fl_type total_rest_size = 0;
do_something_to_all([&total_rest_size](object_ctype& item) {total_rest_size += *item;});
// do_something_to_all([&total_rest_size](object_ctype& item) {total_rest_size += item->get_rest_size();});
return total_rest_size;
}
private:
bool update_progress_handler(unsigned char id, unsigned last_percent)
{
assert(UPDATE_PROGRESS == id);
auto total_rest_size = get_total_rest_size();
if (total_rest_size > 0)
{
auto new_percent = (unsigned) ((file_size - total_rest_size) * 100 / file_size);
if (last_percent != new_percent)
{
printf("\r%u%%", new_percent);
fflush(stdout);
ASCS_THIS update_timer_info(id, 50, std::bind(&file_client::update_progress_handler, this, std::placeholders::_1, new_percent));
}
}
return true;
}
protected:
ascs_cpu_timer begin_time;
};
#endif //#ifndef FILE_CLIENT_H_
module = file_client
ext_cflag = -D_FILE_OFFSET_BITS=64
include ../config.mk
#include <iostream>
//configuration
#define ASCS_SERVER_PORT 5050
#define ASCS_ASYNC_ACCEPT_NUM 5
#define ASCS_CLEAR_OBJECT_INTERVAL 60
#define ASCS_ENHANCED_STABILITY
#define ASCS_WANT_MSG_SEND_NOTIFY
#define ASCS_DEFAULT_PACKER replaceable_packer
//configuration
#include <ascs/tcp/server.h>
using namespace ascs;
#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: file_server [<port=%d> [ip=0.0.0.0]]\n", ASCS_SERVER_PORT);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
service_pump sp;
tcp::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;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_ASYNC_ACCEPT_NUM
#undef ASCS_CLEAR_OBJECT_INTERVAL
#undef ASCS_ENHANCED_STABILITY
#undef ASCS_WANT_MSG_SEND_NOTIFY
#undef ASCS_DEFAULT_PACKER
//restore configuration
//configuration
#define ASCS_SERVER_PORT 5050
#define ASCS_CLEAR_OBJECT_INTERVAL 60
#define ASCS_ENHANCED_STABILITY
#define ASCS_WANT_MSG_SEND_NOTIFY
#define ASCS_DEFAULT_PACKER replaceable_packer
//configuration
#include "file_socket.h"
file_socket::file_socket(i_server& server_) : server_socket(server_) {}
file_socket::~file_socket() {trans_end();}
void file_socket::reset() {trans_end(); server_socket::reset();}
//msg handling
#ifndef ASCS_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, bool link_down) {handle_msg(msg); return true;}
//msg handling end
#ifdef ASCS_WANT_MSG_SEND_NOTIFY
void file_socket::on_msg_send(in_msg_type& msg)
{
auto buffer = dynamic_cast<file_buffer*>(msg.raw_buffer());
if (nullptr != buffer)
{
buffer->read();
if (buffer->empty())
trans_end();
else
direct_send_msg(std::move(msg), true);
}
}
#endif
void file_socket::trans_end()
{
state = TRANS_IDLE;
if (nullptr != file)
{
fclose(file);
file = nullptr;
}
}
void file_socket::handle_msg(out_msg_ctype& msg)
{
if (msg.size() <= ORDER_LEN)
{
printf("wrong order length: " ASCS_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(std::next(msg.data(), ORDER_LEN), "rb");
if (nullptr != file)
{
fseeko(file, 0, SEEK_END);
auto length = ftello(file);
memcpy(std::next(buffer, ORDER_LEN), &length, DATA_LEN);
state = TRANS_PREPARE;
}
else
{
memset(std::next(buffer, ORDER_LEN), -1, DATA_LEN);
printf("can't not open file %s!\n", std::next(msg.data(), ORDER_LEN));
}
send_msg(buffer, sizeof(buffer), true);
}
break;
case 1:
if (TRANS_PREPARE == state && nullptr != file && ORDER_LEN + OFFSET_LEN + DATA_LEN == msg.size())
{
fl_type offset;
memcpy(&offset, std::next(msg.data(), ORDER_LEN), OFFSET_LEN);
fl_type length;
memcpy(&length, std::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);
direct_send_msg(in_msg_type(new file_buffer(file, length)), true);
}
}
break;
case 2:
printf("client says: %s\n", std::next(msg.data(), ORDER_LEN));
break;
default:
break;
}
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_CLEAR_OBJECT_INTERVAL
#undef ASCS_ENHANCED_STABILITY
#undef ASCS_WANT_MSG_SEND_NOTIFY
#undef ASCS_DEFAULT_PACKER
//restore configuration
#ifndef FILE_SOCKET_H_
#define FILE_SOCKET_H_
#include "packer_unpacker.h"
#include <ascs/ext/server.h>
using namespace ascs::ext;
class file_socket : public base_socket, public server_socket
{
public:
file_socket(i_server& server_);
virtual ~file_socket();
public:
//because we don't use objects pool(we don't defined ASCS_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();
protected:
//msg handling
#ifndef ASCS_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, bool link_down);
//msg handling end
#ifdef ASCS_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
#ifndef PACKER_UNPACKER_H_
#define PACKER_UNPACKER_H_
#include <ascs/base.h>
using namespace ascs;
#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 transmitting a file or not.
return: n/a
*/
class file_buffer : public i_buffer
{
public:
file_buffer(FILE* file, fl_type data_len) : _file(file), _data_len(data_len)
{
assert(nullptr != _file);
buffer = new char[asio::detail::default_max_transfer_size];
assert(nullptr != 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 > asio::detail::default_max_transfer_size ? 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(" ASCS_SF ") error!\n", buffer_len);
buffer_len = 0;
}
}
}
protected:
FILE* _file;
char* buffer;
size_t buffer_len;
fl_type _data_len;
};
class data_unpacker : public tcp::i_unpacker<replaceable_buffer>
{
public:
data_unpacker(FILE* file, fl_type data_len) : _file(file), _data_len(data_len)
{
assert(nullptr != _file);
buffer = new char[asio::detail::default_max_transfer_size];
assert(nullptr != buffer);
}
~data_unpacker() {delete[] buffer;}
fl_type get_rest_size() const {return _data_len;}
virtual void reset_state() {_file = nullptr; delete[] buffer; buffer = nullptr; _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(" ASCS_SF ") error!\n", bytes_transferred);
return false;
}
if (0 == _data_len)
msg_can.resize(msg_can.size() + 1);
return true;
}
virtual size_t completion_condition(const asio::error_code& ec, size_t bytes_transferred) {return ec ? 0 : asio::detail::default_max_transfer_size;}
virtual asio::mutable_buffers_1 prepare_next_recv()
{
auto buffer_len = _data_len > asio::detail::default_max_transfer_size ? asio::detail::default_max_transfer_size : (size_t) _data_len;
return asio::buffer(buffer, buffer_len);
}
protected:
FILE* _file;
char* buffer;
fl_type _data_len;
};
class base_socket
{
public:
base_socket() : state(TRANS_IDLE), file(nullptr) {}
protected:
enum TRANS_STATE {TRANS_IDLE, TRANS_PREPARE, TRANS_BUSY};
TRANS_STATE state;
FILE* file;
};
#endif // PACKER_UNPACKER_H_
ST_MAKE = ${MAKE_COMMAND}
ifeq (${MAKECMDGOALS}, debug)
ST_MAKE += debug
else
ifeq (${MAKECMDGOALS}, clean)
ST_MAKE += clean
endif
endif
release debug clean :
cd echo_server && ${ST_MAKE}
cd echo_client && ${ST_MAKE}
cd client && ${ST_MAKE}
cd file_server && ${ST_MAKE}
cd file_client && ${ST_MAKE}
cd pingpong_server && ${ST_MAKE}
cd pingpong_client && ${ST_MAKE}
cd udp_test && ${ST_MAKE}
cd ssl_test && ${ST_MAKE}
module = pingpong_client
include ../config.mk
#include <iostream>
//configuration
#define ASCS_SERVER_PORT 9527
#define ASCS_REUSE_OBJECT //use objects pool
//#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
//#define ASCS_WANT_MSG_SEND_NOTIFY
#define ASCS_MSG_BUFFER_SIZE 65536
#define ASCS_DEFAULT_UNPACKER stream_unpacker //non-protocol
//configuration
#include <ascs/ext/client.h>
using namespace ascs;
using namespace ascs::ext;
#ifdef _MSC_VER
#define atoll _atoi64
#endif
#define QUIT_COMMAND "quit"
#define LIST_STATUS "status"
ascs_cpu_timer begin_time;
std::atomic_ushort completed_session_num;
class echo_socket : public connector
{
public:
echo_socket(asio::io_service& io_service_) : connector(io_service_) {}
void begin(size_t msg_num, const char* msg, size_t msg_len)
{
total_bytes = msg_len;
total_bytes *= msg_num;
send_bytes = recv_bytes = 0;
send_native_msg(msg, msg_len);
}
protected:
virtual void on_connect() {asio::ip::tcp::no_delay option(true); lowest_layer().set_option(option); connector::on_connect();}
//msg handling
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(out_msg_type& msg) {handle_msg(msg); return true;}
#endif
virtual bool on_msg_handle(out_msg_type& msg, bool link_down) {handle_msg(msg); return true;}
#ifdef ASCS_WANT_MSG_SEND_NOTIFY
virtual void on_msg_send(in_msg_type& msg)
{
send_bytes += msg.size();
if (send_bytes < total_bytes)
direct_send_msg(std::move(msg));
}
#endif
private:
#ifdef ASCS_WANT_MSG_SEND_NOTIFY
void handle_msg(out_msg_ctype& msg)
{
recv_bytes += msg.size();
if (recv_bytes >= total_bytes && 0 == --completed_session_num)
begin_time.stop();
}
#else
void handle_msg(out_msg_type& msg)
{
if (0 == total_bytes)
return;
recv_bytes += msg.size();
if (recv_bytes >= total_bytes)
{
total_bytes = 0;
if (0 == --completed_session_num)
begin_time.stop();
}
else
direct_send_msg(std::move(msg));
}
#endif
private:
uint64_t total_bytes, send_bytes, recv_bytes;
};
class echo_client : public tcp::client_base<echo_socket>
{
public:
echo_client(service_pump& service_pump_) : client_base<echo_socket>(service_pump_) {}
echo_socket::statistic get_statistic()
{
echo_socket::statistic stat;
do_something_to_all([&stat](object_ctype& item) {stat += item->get_statistic(); });
return stat;
}
void begin(size_t msg_num, const char* msg, size_t msg_len) {do_something_to_all([=](object_ctype& item) {item->begin(msg_num, msg, msg_len);});}
};
int main(int argc, const char* argv[])
{
printf("usage: pingpong_client [<service thread number=1> [<port=%d> [<ip=%s> [link num=16]]]]\n", ASCS_SERVER_PORT, ASCS_SERVER_IP);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
///////////////////////////////////////////////////////////
size_t link_num = 16;
if (argc > 4)
link_num = std::min(ASCS_MAX_OBJECT_NUM, std::max(atoi(argv[4]), 1));
printf("exec: echo_client with " ASCS_SF " links\n", link_num);
///////////////////////////////////////////////////////////
service_pump sp;
echo_client client(sp);
// argv[2] = "::1" //ipv6
// argv[2] = "127.0.0.1" //ipv4
std::string ip = argc > 3 ? argv[3] : ASCS_SERVER_IP;
unsigned short port = argc > 2 ? atoi(argv[2]) : ASCS_SERVER_PORT;
auto thread_num = 1;
if (argc > 1)
thread_num = std::min(16, std::max(thread_num, atoi(argv[1])));
#ifdef ASCS_CLEAR_OBJECT_INTERVAL
if (1 == thread_num)
++thread_num;
#endif
for (size_t i = 0; i < link_num; ++i)
client.add_client(port, ip);
sp.start_service(thread_num);
while(sp.is_running())
{
std::string str;
std::getline(std::cin, str);
if (QUIT_COMMAND == str)
sp.stop_service();
else if (LIST_STATUS == str)
{
printf("link #: " ASCS_SF ", valid links: " ASCS_SF ", invalid links: " ASCS_SF "\n", client.size(), client.valid_size(), client.invalid_object_size());
puts("");
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)
auto msg_fill = '0';
auto parameters = split_string(str);
auto iter = std::begin(parameters);
if (iter != std::end(parameters)) msg_num = std::max((size_t) atoll(iter++->data()), (size_t) 1);
if (iter != std::end(parameters)) msg_len = std::min((size_t) ASCS_MSG_BUFFER_SIZE, std::max((size_t) atoi(iter++->data()), (size_t) 1));
if (iter != std::end(parameters)) msg_fill = *iter++->data();
printf("test parameters after adjustment: " ASCS_SF " " ASCS_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;
auto init_msg = new char[msg_len];
memset(init_msg, msg_fill, msg_len);
client.begin(msg_num, init_msg, msg_len);
begin_time.restart();
while (0 != completed_session_num)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
uint64_t total_msg_bytes = link_num; total_msg_bytes *= msg_len; total_msg_bytes *= msg_num;
printf("\r100%%\ntime spent statistics: %f seconds.\n", begin_time.elapsed());
printf("speed: %f(*2) MBps.\n", total_msg_bytes / begin_time.elapsed() / 1024 / 1024);
delete[] init_msg;
}
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_REUSE_OBJECT
#undef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
#undef ASCS_WANT_MSG_SEND_NOTIFY
#undef ASCS_DEFAULT_PACKER
#undef ASCS_DEFAULT_UNPACKER
#undef ASCS_MSG_BUFFER_SIZE
//restore configuration
module = pingpong_server
include ../config.mk
#include <iostream>
//configuration
#define ASCS_SERVER_PORT 9527
#define ASCS_ASYNC_ACCEPT_NUM 5
#define ASCS_REUSE_OBJECT //use objects pool
//#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
#define ASCS_MSG_BUFFER_SIZE 65536
#define ASCS_DEFAULT_UNPACKER stream_unpacker //non-protocol
//configuration
#include <ascs/ext/server.h>
using namespace ascs;
using namespace ascs::ext;
#ifdef _MSC_VER
#define atoll _atoi64
#endif
#define QUIT_COMMAND "quit"
#define LIST_STATUS "status"
class echo_socket : public server_socket
{
public:
echo_socket(i_server& server_) : server_socket(server_) {}
protected:
#ifndef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(out_msg_type& msg) {return direct_post_msg(std::move(msg));}
#endif
virtual bool on_msg_handle(out_msg_type& msg, bool link_down) {return direct_post_msg(std::move(msg));}
};
class echo_server : public tcp::server_base<echo_socket>
{
public:
echo_server(service_pump& service_pump_) : server_base(service_pump_) {}
echo_socket::statistic get_statistic()
{
echo_socket::statistic stat;
do_something_to_all([&stat](object_ctype& item) {stat += item->get_statistic();});
return stat;
}
protected:
virtual bool on_accept(object_ctype& client_ptr) {asio::ip::tcp::no_delay option(true); client_ptr->lowest_layer().set_option(option); return true;}
};
int main(int argc, const char* argv[])
{
printf("usage: pingpong_server [<service thread number=1> [<port=%d> [ip=0.0.0.0]]]\n", ASCS_SERVER_PORT);
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
service_pump sp;
echo_server echo_server_(sp);
if (argc > 3)
echo_server_.set_server_addr(atoi(argv[2]), argv[3]);
else if (argc > 2)
echo_server_.set_server_addr(atoi(argv[2]));
auto thread_num = 1;
if (argc > 1)
thread_num = std::min(16, std::max(thread_num, atoi(argv[1])));
sp.start_service(thread_num);
while(sp.is_running())
{
std::string str;
std::cin >> str;
if (QUIT_COMMAND == str)
sp.stop_service();
else if (LIST_STATUS == str)
{
printf("link #: " ASCS_SF ", invalid links: " ASCS_SF "\n", echo_server_.size(), echo_server_.invalid_object_size());
puts("");
puts(echo_server_.get_statistic().to_string().data());
}
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_ASYNC_ACCEPT_NUM
#undef ASCS_REUSE_OBJECT
#undef ASCS_FREE_OBJECT_INTERVAL
#undef ASCS_DEFAULT_PACKER
#undef ASCS_DEFAULT_UNPACKER
#undef ASCS_MSG_BUFFER_SIZE
//restore configuration
-----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 ASCS_SERVER_PORT 9527
#define ASCS_ASYNC_ACCEPT_NUM 5
//#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer
#define ASCS_ENHANCED_STABILITY
//#define ASCS_DEFAULT_PACKER replaceable_packer
//#define ASCS_DEFAULT_UNPACKER replaceable_unpacker
//configuration
#include <ascs/ext/ssl.h>
using namespace ascs;
using namespace ascs::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
#define RECONNECT_COMMAND "reconnect"
int main(int argc, const char* argv[])
{
puts("Directories certs and client_certs must be in the current directory.");
if (argc >= 2 && (0 == strcmp(argv[1], "--help") || 0 == strcmp(argv[1], "-h")))
return 0;
else
puts("type " QUIT_COMMAND " to end.");
service_pump sp;
ssl_server server_(sp, asio::ssl::context::sslv23_server);
server_.ssl_context().set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use);
server_.ssl_context().set_verify_mode(asio::ssl::context::verify_peer | asio::ssl::context::verify_fail_if_no_peer_cert);
server_.ssl_context().load_verify_file("client_certs/server.crt");
server_.ssl_context().use_certificate_chain_file("certs/server.crt");
server_.ssl_context().use_private_key_file("certs/server.key", asio::ssl::context::pem);
server_.ssl_context().use_tmp_dh_file("certs/dh1024.pem");
///*
//method #1
ssl_client client_(sp, asio::ssl::context::sslv23_client);
client_.ssl_context().set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use);
client_.ssl_context().set_verify_mode(asio::ssl::context::verify_peer | asio::ssl::context::verify_fail_if_no_peer_cert);
client_.ssl_context().load_verify_file("certs/server.crt");
client_.ssl_context().use_certificate_chain_file("client_certs/server.crt");
client_.ssl_context().use_private_key_file("client_certs/server.key", asio::ssl::context::pem);
client_.ssl_context().use_tmp_dh_file("client_certs/dh1024.pem");
//please config the ssl context before creating any clients.
client_.add_client();
client_.add_client();
//*/
/*
//method #2
//to use ssl_sclient, we must construct ssl context first.
asio::ssl::context ctx(asio::ssl::context::sslv23_client);
ctx.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use);
ctx.set_verify_mode(asio::ssl::context::verify_peer | 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", asio::ssl::context::pem);
ctx.use_tmp_dh_file("client_certs/dh1024.pem");
ssl_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_);
sleep(1);
sp.stop_service();
}
else if (RESTART_COMMAND == str || RECONNECT_COMMAND == str)
puts("I still not find a way to reuse a asio::ssl::stream,\n"
"it can reconnect to the server, but can not re-handshake with the server,\n"
"if somebody knows how to fix this defect, please tell me, thanks in advance.");
/*
else if (RESTART_COMMAND == str)
{
sp.stop_service(&client_);
sleep(1);
sp.stop_service();
sp.start_service();
}
else if (RECONNECT_COMMAND == str)
client_.graceful_close(true);
*/
else
server_.broadcast_msg(str);
}
return 0;
}
//restore configuration
#undef ASCS_SERVER_PORT
#undef ASCS_ASYNC_ACCEPT_NUM
#undef ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
#undef ASCS_ENHANCED_STABILITY
#undef ASCS_DEFAULT_PACKER
#undef ASCS_DEFAULT_UNPACKER
//restore configuration
module = udp_test
include ../config.mk
#include <iostream>
//configuration
//#define ASCS_DEFAULT_PACKER replaceable_packer
//#define ASCS_DEFAULT_UDP_UNPACKER replaceable_udp_unpacker
//configuration
#include <ascs/ext/udp.h>
using namespace ascs;
using namespace ascs::ext;
#define QUIT_COMMAND "quit"
#define RESTART_COMMAND "restart"
int main(int argc, const char* argv[])
{
puts("usage: udp_test <my port> <peer port> [peer ip=127.0.0.1]");
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.");
auto local_port = (unsigned short) atoi(argv[1]);
asio::error_code ec;
auto peer_addr = asio::ip::udp::endpoint(asio::ip::address::from_string(argc >= 4 ? argv[3] : "127.0.0.1", ec), (unsigned short) atoi(argv[2]));
assert(!ec);
std::string str;
service_pump sp;
udp_sservice service(sp);
service.set_local_addr(local_port);
sp.start_service();
while(sp.is_running())
{
std::cin >> str;
if (QUIT_COMMAND == str)
sp.stop_service();
else if (RESTART_COMMAND == str)
{
sp.stop_service();
sp.start_service();
}
else
service.safe_send_native_msg(peer_addr, str);
}
return 0;
}
//restore configuration
#undef ASCS_DEFAULT_PACKER
#undef ASCS_DEFAULT_UNPACKER
//restore configuration
/*
* base.h
*
* Created on: 2012-3-2
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* this is a global head file
*/
#ifndef _ASCS_BASE_H_
#define _ASCS_BASE_H_
#include <stdio.h>
#include <stdarg.h>
#include <list>
#include <memory>
#include <string>
#include <thread>
#include <sstream>
#include <shared_mutex>
#include <asio.hpp>
#include <asio/detail/noncopyable.hpp>
#include "config.h"
#if defined _MSC_VER
#define ASCS_SF "%Iu"
#define ASCS_THIS //workaround to make up the BOOST_AUTO's defect under vc2008 and compiler bugs before vc2012
#define ssize_t SSIZE_T
#else // defined __GNUC__
#define ASCS_SF "%zu"
#define ASCS_THIS this->
#endif
namespace ascs
{
class service_pump;
class timer;
class i_server
{
public:
virtual service_pump& get_service_pump() = 0;
virtual const service_pump& get_service_pump() const = 0;
virtual bool del_client(const std::shared_ptr<timer>& client_ptr) = 0;
};
class i_buffer
{
public:
virtual ~i_buffer() {}
virtual bool empty() const = 0;
virtual size_t size() const = 0;
virtual const char* data() const = 0;
};
//convert '->' operation to '.' operation
//user need to allocate object, and auto_buffer will free it
template<typename T>
class auto_buffer : public asio::detail::noncopyable
{
public:
typedef T* buffer_type;
typedef const buffer_type buffer_ctype;
auto_buffer() : buffer(nullptr) {}
auto_buffer(buffer_type _buffer) : buffer(_buffer) {}
auto_buffer(auto_buffer&& other) : buffer(other.buffer) {other.buffer = nullptr;}
~auto_buffer() {clear();}
buffer_type raw_buffer() const {return buffer;}
void raw_buffer(buffer_type _buffer) {buffer = _buffer;}
//the following five functions are needed by ascs
bool empty() const {return nullptr == buffer || buffer->empty();}
size_t size() const {return nullptr == buffer ? 0 : buffer->size();}
const char* data() const {return nullptr == buffer ? nullptr : buffer->data();}
void swap(auto_buffer& other) {std::swap(buffer, other.buffer);}
void clear() {delete buffer; buffer = nullptr;}
protected:
buffer_type buffer;
};
typedef auto_buffer<i_buffer> replaceable_buffer;
//replaceable_packer and replaceable_unpacker used replaceable_buffer as their msg type, so they're replaceable,
//shared_buffer<i_buffer> is available too.
//convert '->' operation to '.' operation
//user need to allocate object, and shared_buffer will free it
template<typename T>
class shared_buffer
{
public:
typedef std::shared_ptr<T> buffer_type;
typedef const buffer_type buffer_ctype;
shared_buffer() {}
shared_buffer(buffer_type _buffer) : buffer(_buffer) {}
shared_buffer(const shared_buffer& other) : buffer(other.buffer) {}
shared_buffer(shared_buffer&& other) : buffer(std::move(other.buffer)) {}
const shared_buffer& operator=(const shared_buffer& other) {buffer = other.buffer; return *this;}
~shared_buffer() {clear();}
buffer_type raw_buffer() const {return buffer;}
void raw_buffer(buffer_ctype _buffer) {buffer = _buffer;}
//the following five functions are needed by ascs
bool empty() const {return !buffer || buffer->empty();}
size_t size() const {return !buffer ? 0 : buffer->size();}
const char* data() const {return !buffer ? nullptr : buffer->data();}
void swap(shared_buffer& other) {buffer.swap(other.buffer);}
void clear() {buffer.reset();}
protected:
buffer_type buffer;
};
//not like auto_buffer, shared_buffer is copyable, but auto_buffer is a bit more efficient.
//packer concept
template<typename MsgType>
class i_packer
{
public:
typedef MsgType msg_type;
typedef const msg_type msg_ctype;
protected:
virtual ~i_packer() {}
public:
virtual void reset_state() {}
virtual msg_type pack_msg(const char* const pstr[], const size_t len[], size_t num, bool native = false) = 0;
virtual char* raw_data(msg_type& msg) const {return nullptr;}
virtual const char* raw_data(msg_ctype& msg) const {return nullptr;}
virtual size_t raw_data_len(msg_ctype& msg) const {return 0;}
msg_type pack_msg(const char* pstr, size_t len, bool native = false) {return pack_msg(&pstr, &len, 1, native);}
msg_type pack_msg(const std::string& str, bool native = false) {return pack_msg(str.data(), str.size(), native);}
};
//packer concept
//just provide msg_type definition, you should not call any functions of it, but send msgs directly
template<typename MsgType>
class dummy_packer : public i_packer<MsgType>
{
public:
using typename i_packer<MsgType>::msg_type;
using typename i_packer<MsgType>::msg_ctype;
virtual msg_type pack_msg(const char* const pstr[], const size_t len[], size_t num, bool native = false) {assert(false); return msg_type();}
};
//unpacker concept
namespace tcp
{
template<typename MsgType>
class i_unpacker
{
public:
typedef MsgType msg_type;
typedef const msg_type msg_ctype;
typedef std::list<msg_type> container_type;
protected:
virtual ~i_unpacker() {}
public:
virtual void reset_state() = 0;
virtual bool parse_msg(size_t bytes_transferred, container_type& msg_can) = 0;
virtual size_t completion_condition(const asio::error_code& ec, size_t bytes_transferred) = 0;
virtual asio::mutable_buffers_1 prepare_next_recv() = 0;
};
} //namespace
namespace udp
{
template<typename MsgType>
class udp_msg : public MsgType
{
public:
asio::ip::udp::endpoint peer_addr;
udp_msg() {}
udp_msg(const asio::ip::udp::endpoint& _peer_addr, MsgType&& msg) : MsgType(std::move(msg)), peer_addr(_peer_addr) {}
void swap(udp_msg& other) {std::swap(peer_addr, other.peer_addr); MsgType::swap(other);}
void swap(asio::ip::udp::endpoint& addr, MsgType&& tmp_msg) {std::swap(peer_addr, addr); MsgType::swap(tmp_msg);}
};
template<typename MsgType>
class i_unpacker
{
public:
typedef MsgType msg_type;
typedef const msg_type msg_ctype;
typedef std::list<udp_msg<msg_type>> container_type;
protected:
virtual ~i_unpacker() {}
public:
virtual void reset_state() {}
virtual msg_type parse_msg(size_t bytes_transferred) = 0;
virtual asio::mutable_buffers_1 prepare_next_recv() = 0;
};
} //namespace
//unpacker concept
//free functions, used to do something to any container(except map and multimap) optionally with any mutex
template<typename _Can, typename _Mutex, typename _Predicate>
void do_something_to_all(_Can& __can, _Mutex& __mutex, const _Predicate& __pred) {std::shared_lock<std::shared_mutex> lock(__mutex); for (auto& item : __can) __pred(item);}
template<typename _Can, typename _Predicate>
void do_something_to_all(_Can& __can, const _Predicate& __pred) {for (auto& item : __can) __pred(item);}
template<typename _Can, typename _Mutex, typename _Predicate>
void do_something_to_one(_Can& __can, _Mutex& __mutex, const _Predicate& __pred)
{
std::shared_lock<std::shared_mutex> lock(__mutex);
for (auto iter = std::begin(__can); iter != std::end(__can); ++iter) if (__pred(*iter)) break;
}
template<typename _Can, typename _Predicate>
void do_something_to_one(_Can& __can, const _Predicate& __pred) {for (auto iter = std::begin(__can); iter != std::end(__can); ++iter) if (__pred(*iter)) break;}
template<typename _Can>
bool splice_helper(_Can& dest_can, _Can& src_can, size_t max_size = ASCS_MAX_MSG_NUM)
{
if (src_can.empty())
return false;
auto size = dest_can.size();
if (size >= max_size) //dest_can can hold more items.
return false;
size = max_size - size; //maximum items this time can handle
if (src_can.size() > size) //some items left behind
{
auto begin_iter = std::begin(src_can), end_iter = std::end(src_can);
auto left_num = src_can.size() - size;
end_iter = left_num > size ? std::next(begin_iter, size) : std::prev(end_iter, left_num); //minimize iterator movement
dest_can.splice(std::end(dest_can), src_can, begin_iter, end_iter);
}
else
dest_can.splice(std::end(dest_can), src_can);
return true;
}
//member functions, used to do something to any member container(except map and multimap) optionally with any member mutex
#define DO_SOMETHING_TO_ALL_MUTEX(CAN, MUTEX) DO_SOMETHING_TO_ALL_MUTEX_NAME(do_something_to_all, CAN, MUTEX)
#define DO_SOMETHING_TO_ALL(CAN) DO_SOMETHING_TO_ALL_NAME(do_something_to_all, CAN)
#define DO_SOMETHING_TO_ALL_MUTEX_NAME(NAME, CAN, MUTEX) \
template<typename _Predicate> void NAME(const _Predicate& __pred) {std::shared_lock<std::shared_mutex> lock(MUTEX); for (auto& item : CAN) __pred(item);}
#define DO_SOMETHING_TO_ALL_NAME(NAME, CAN) \
template<typename _Predicate> void NAME(const _Predicate& __pred) {for (auto& item : CAN) __pred(item);} \
template<typename _Predicate> void NAME(const _Predicate& __pred) const {for (auto& item : CAN) __pred(item);}
#define DO_SOMETHING_TO_ONE_MUTEX(CAN, MUTEX) DO_SOMETHING_TO_ONE_MUTEX_NAME(do_something_to_one, CAN, MUTEX)
#define DO_SOMETHING_TO_ONE(CAN) DO_SOMETHING_TO_ONE_NAME(do_something_to_one, CAN)
#define DO_SOMETHING_TO_ONE_MUTEX_NAME(NAME, CAN, MUTEX) \
template<typename _Predicate> void NAME(const _Predicate& __pred) \
{std::shared_lock<std::shared_mutex> lock(MUTEX); for (auto iter = std::begin(CAN); iter != std::end(CAN); ++iter) if (__pred(*iter)) break;}
#define DO_SOMETHING_TO_ONE_NAME(NAME, CAN) \
template<typename _Predicate> void NAME(const _Predicate& __pred) {for (auto iter = std::begin(CAN); iter != std::end(CAN); ++iter) if (__pred(*iter)) break;} \
template<typename _Predicate> void NAME(const _Predicate& __pred) const {for (auto iter = std::begin(CAN); iter != std::end(CAN); ++iter) if (__pred(*iter)) break;}
//used by both TCP and UDP
#define SAFE_SEND_MSG_CHECK \
{ \
if (!ASCS_THIS is_send_allowed() || ASCS_THIS stopped()) return false; \
std::this_thread::sleep_for(std::chrono::milliseconds(50)); \
}
#define GET_PENDING_MSG_NUM(FUNNAME, CAN, MUTEX) size_t FUNNAME() {std::shared_lock<std::shared_mutex> lock(MUTEX); return CAN.size();}
#define PEEK_FIRST_PENDING_MSG(FUNNAME, CAN, MUTEX, MSGTYPE) \
void FUNNAME(MSGTYPE& msg) \
{ \
msg.clear(); \
std::shared_lock<std::shared_mutex> lock(MUTEX); \
if (!CAN.empty()) \
msg = CAN.front(); \
}
#define POP_FIRST_PENDING_MSG(FUNNAME, CAN, MUTEX, MSGTYPE) \
void FUNNAME(MSGTYPE& msg) \
{ \
msg.clear(); \
std::unique_lock<std::shared_mutex> lock(MUTEX); \
if (!CAN.empty()) \
{ \
msg.swap(CAN.front()); \
CAN.pop_front(); \
} \
}
#define POP_ALL_PENDING_MSG(FUNNAME, CAN, MUTEX, CANTYPE) \
void FUNNAME(CANTYPE& msg_list) \
{ \
std::unique_lock<std::shared_mutex> lock(MUTEX); \
msg_list.splice(msg_list.end(), CAN); \
}
///////////////////////////////////////////////////
//TCP msg sending interface
#define TCP_SEND_MSG_CALL_SWITCH(FUNNAME, TYPE) \
TYPE FUNNAME(const char* pstr, size_t len, bool can_overflow = false) {return FUNNAME(&pstr, &len, 1, can_overflow);} \
TYPE FUNNAME(const std::string& str, bool can_overflow = false) {return FUNNAME(str.data(), str.size(), can_overflow);}
#define TCP_SEND_MSG(FUNNAME, NATIVE) \
bool FUNNAME(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) \
{ \
std::unique_lock<std::shared_mutex> lock(ASCS_THIS send_msg_buffer_mutex); \
return (can_overflow || ASCS_THIS send_msg_buffer.size() < ASCS_MAX_MSG_NUM) ? ASCS_THIS do_direct_send_msg(ASCS_THIS packer_->pack_msg(pstr, len, num, NATIVE)) : false; \
} \
TCP_SEND_MSG_CALL_SWITCH(FUNNAME, bool)
#define TCP_POST_MSG(FUNNAME, NATIVE) \
bool FUNNAME(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) {return ASCS_THIS direct_post_msg(ASCS_THIS packer_->pack_msg(pstr, len, num, NATIVE), can_overflow);} \
TCP_SEND_MSG_CALL_SWITCH(FUNNAME, bool)
//guarantee send msg successfully even if can_overflow equal to false, success at here just means putting the msg into tcp::socket's send buffer successfully
//if can_overflow equal to false and the buffer is not available, will wait until it becomes available
#define TCP_SAFE_SEND_MSG(FUNNAME, SEND_FUNNAME) \
bool FUNNAME(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) {while (!SEND_FUNNAME(pstr, len, num, can_overflow)) SAFE_SEND_MSG_CHECK return true;} \
TCP_SEND_MSG_CALL_SWITCH(FUNNAME, bool)
#define TCP_BROADCAST_MSG(FUNNAME, SEND_FUNNAME) \
void FUNNAME(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) \
{ASCS_THIS do_something_to_all([=](typename Pool::object_ctype& item) {item->SEND_FUNNAME(pstr, len, num, can_overflow);});} \
TCP_SEND_MSG_CALL_SWITCH(FUNNAME, void)
//TCP msg sending interface
///////////////////////////////////////////////////
///////////////////////////////////////////////////
//UDP msg sending interface
#define UDP_SEND_MSG_CALL_SWITCH(FUNNAME, TYPE) \
TYPE FUNNAME(const asio::ip::udp::endpoint& peer_addr, const char* pstr, size_t len, bool can_overflow = false) {return FUNNAME(peer_addr, &pstr, &len, 1, can_overflow);} \
TYPE FUNNAME(const asio::ip::udp::endpoint& peer_addr, const std::string& str, bool can_overflow = false) {return FUNNAME(peer_addr, str.data(), str.size(), can_overflow);}
#define UDP_SEND_MSG(FUNNAME, NATIVE) \
bool FUNNAME(const asio::ip::udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) \
{ \
std::unique_lock<std::shared_mutex> lock(ASCS_THIS send_msg_buffer_mutex); \
if (can_overflow || ASCS_THIS send_msg_buffer.size() < ASCS_MAX_MSG_NUM) \
{ \
in_msg_type msg(peer_addr, ASCS_THIS packer_->pack_msg(pstr, len, num, NATIVE)); \
return ASCS_THIS do_direct_send_msg(std::move(msg)); \
} \
return false; \
} \
UDP_SEND_MSG_CALL_SWITCH(FUNNAME, bool)
#define UDP_POST_MSG(FUNNAME, NATIVE) \
bool FUNNAME(const asio::ip::udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) \
{ \
in_msg_type msg(peer_addr, ASCS_THIS packer_->pack_msg(pstr, len, num, NATIVE)); \
return ASCS_THIS direct_post_msg(std::move(msg), can_overflow); \
} \
UDP_SEND_MSG_CALL_SWITCH(FUNNAME, bool)
//guarantee send msg successfully even if can_overflow equal to false, success at here just means putting the msg into udp::socket's send buffer successfully
//if can_overflow equal to false and the buffer is not available, will wait until it becomes available
#define UDP_SAFE_SEND_MSG(FUNNAME, SEND_FUNNAME) \
bool FUNNAME(const asio::ip::udp::endpoint& peer_addr, const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false) \
{while (!SEND_FUNNAME(peer_addr, pstr, len, num, can_overflow)) SAFE_SEND_MSG_CHECK return true;} \
UDP_SEND_MSG_CALL_SWITCH(FUNNAME, bool)
//UDP msg sending interface
///////////////////////////////////////////////////
class log_formater
{
public:
static void all_out(const char* head, char* buff, size_t buff_len, const char* fmt, va_list& ap)
{
assert(nullptr != buff && buff_len > 0);
std::stringstream os;
os.rdbuf()->pubsetbuf(buff, buff_len);
if (nullptr != head)
os << '[' << head << "] ";
char time_buff[64];
auto now = time(nullptr);
#ifdef _MSC_VER
ctime_s(time_buff, sizeof(time_buff), &now);
#else
ctime_r(&now, time_buff);
#endif
auto len = strlen(time_buff);
assert(len > 0);
if ('\n' == *std::next(time_buff, --len))
*std::next(time_buff, len) = '\0';
os << time_buff << " -> ";
#if defined _MSC_VER || (defined __unix__ && !defined __linux__)
os.rdbuf()->sgetn(buff, buff_len);
#endif
len = (size_t) os.tellp();
if (len >= buff_len)
*std::next(buff, buff_len - 1) = '\0';
else
#if defined(_MSC_VER) && _MSC_VER >= 1400
vsnprintf_s(std::next(buff, len), buff_len - len, _TRUNCATE, fmt, ap);
#else
vsnprintf(std::next(buff, len), buff_len - len, fmt, ap);
#endif
}
};
#define all_out_helper(head, buff, buff_len) va_list ap; va_start(ap, fmt); log_formater::all_out(head, buff, buff_len, fmt, ap); va_end(ap)
#define all_out_helper2(head) char output_buff[ASCS_UNIFIED_OUT_BUF_NUM]; all_out_helper(head, output_buff, sizeof(output_buff)); puts(output_buff)
#ifndef ASCS_CUSTOM_LOG
class unified_out
{
public:
#ifdef ASCS_NO_UNIFIED_OUT
static void fatal_out(const char* fmt, ...) {}
static void error_out(const char* fmt, ...) {}
static void warning_out(const char* fmt, ...) {}
static void info_out(const char* fmt, ...) {}
static void debug_out(const char* fmt, ...) {}
#else
static void fatal_out(const char* fmt, ...) {all_out_helper2(nullptr);}
static void error_out(const char* fmt, ...) {all_out_helper2(nullptr);}
static void warning_out(const char* fmt, ...) {all_out_helper2(nullptr);}
static void info_out(const char* fmt, ...) {all_out_helper2(nullptr);}
static void debug_out(const char* fmt, ...) {all_out_helper2(nullptr);}
#endif
};
#endif
} //namespace
#endif /* _ASCS_BASE_H_ */
/*
* config.h
*
* Created on: 2016-9-14
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* ascs top header file.
*
* license: www.boost.org/LICENSE_1_0.txt
*
* 2016.9.13 version 1.0.0
* Based on st_asio_wrapper 1.1.3.
* Directory structure refactoring.
* Classes renaming, remove 'st_', 'tcp_' and 'udp_' prefix.
* File renaming, remove 'st_asio_wrapper_' prefix.
* Distinguish TCP and UDP related classes and files by tcp/udp namespace and tcp/udp directory.
* Need c++14, if your compiler detected duplicated 'shared_mutex' definition, please define ASCS_HAS_STD_SHARED_MUTEX macro.
* Need to define ASIO_STANDALONE and ASIO_HAS_STD_CHRONO macros.
*
*/
#ifndef _ASCS_CONFIG_H_
#define _ASCS_CONFIG_H_
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#define ASCS_VER 10000 //[x]xyyzz -> [x]x.[y]y.[z]z
#define ASCS_VERSION "1.0.0"
//asio and compiler check
#ifdef _MSC_VER
static_assert(_MSC_VER >= 1900, "ascs need Visual C++ 14.0 or higher.");
#elif defined(__GNUC__)
#ifdef __clang__
static_assert(__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 4), "ascs need Clang 3.4 or higher.");
#else
static_assert(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9), "ascs need GCC 4.9 or higher.");
#endif
#if !defined(__cplusplus) || __cplusplus <= 201103L
#error ascs at least need c++14.
#endif
#else
#error ascs only support Visual C++, GCC and Clang.
#endif
static_assert(ASIO_VERSION >= 101001, "ascs need asio 1.10.1 or higher.");
//asio and compiler check
//configurations
#ifndef ASCS_SERVER_IP
#define ASCS_SERVER_IP "127.0.0.1"
#endif
#ifndef ASCS_SERVER_PORT
#define ASCS_SERVER_PORT 5050
#endif
static_assert(ASCS_SERVER_PORT > 0, "server port must be bigger than zero.");
//msg send and recv buffer's maximum size (list::size()), corresponding buffers are expanded dynamically, which means only allocate memory when needed.
#ifndef ASCS_MAX_MSG_NUM
#define ASCS_MAX_MSG_NUM 1024
#endif
static_assert(ASCS_MAX_MSG_NUM > 0, "message capacity must be bigger than zero.");
//buffer (on stack) size used when writing logs.
#ifndef ASCS_UNIFIED_OUT_BUF_NUM
#define ASCS_UNIFIED_OUT_BUF_NUM 2048
#endif
//use customized log system (you must provide unified_out::fatal_out/error_out/warning_out/info_out/debug_out)
//#define ASCS_CUSTOM_LOG
//don't write any logs.
//#define ASCS_NO_UNIFIED_OUT
//if defined, service_pump will catch exceptions for asio::io_service::run(), and all function objects in asynchronous calls
//will be hooked by object, this can avoid the object been freed during asynchronous call.
//#define ASCS_ENHANCED_STABILITY
//if defined, asio::steady_timer will be used in ascs::timer, otherwise, asio::system_timer will be used.
//#define ASCS_USE_STEADY_TIMER
//full statistic include time consumption, or only numerable informations will be gathered
//#define ASCS_FULL_STATISTIC
//when got some msgs, not call on_msg(), but asynchronously dispatch them, on_msg_handle() will be called later.
//#define ASCS_FORCE_TO_USE_MSG_RECV_BUFFER
//after every msg sent, call ascs::socket::on_msg_send()
//#define ASCS_WANT_MSG_SEND_NOTIFY
//after sending buffer became empty, call ascs::socket::on_all_msg_send()
//#define ASCS_WANT_ALL_MSG_SEND_NOTIFY
//when link down, msgs in receiving buffer (already unpacked) will be discarded.
//#define ASCS_DISCARD_MSG_WHEN_LINK_DOWN
//max number of objects object_pool can hold.
#ifndef ASCS_MAX_OBJECT_NUM
#define ASCS_MAX_OBJECT_NUM 4096
#endif
static_assert(ASCS_MAX_OBJECT_NUM > 0, "object capacity must be bigger than zero.");
//if defined, objects will never be freed, but remain in object_pool waiting for reuse.
//#define ASCS_REUSE_OBJECT
//define ASCS_REUSE_OBJECT macro will enable object pool, all objects in invalid_object_can will never be freed, but kept for reuse,
//otherwise, object_pool will free objects in invalid_object_can automatically and periodically, ASCS_FREE_OBJECT_INTERVAL means the interval, unit is second,
//see invalid_object_can at the end of object_pool class for more details.
//please note that even if you defined ASCS_REUSE_OBJECT macro, ASCS_FREE_OBJECT_INTERVAL macro is still useful, it will make object_pool
//to close (just close, not free, Object must has close function which takes no parameter) objects automatically and periodically for saving SOCKET handles.
#ifndef ASCS_REUSE_OBJECT
#ifndef ASCS_FREE_OBJECT_INTERVAL
#define ASCS_FREE_OBJECT_INTERVAL 10 //seconds
#endif
#endif
#if defined(ASCS_FREE_OBJECT_INTERVAL) && ASCS_FREE_OBJECT_INTERVAL <= 0
#error free/close object interval must be bigger than zero.
#endif
//define ASCS_CLEAR_OBJECT_INTERVAL macro to let object_pool to invoke clear_obsoleted_object() automatically and periodically
//this feature may affect performance with huge number of objects, so re-write tcp::server_socket_base::on_recv_error and invoke object_pool::del_object()
//is recommended for long-term connection system, but for short-term connection system, you are recommended to open this feature.
//you must define this macro as a value, not just define it, the value means the interval, unit is second
//#define ASCS_CLEAR_OBJECT_INTERVAL 60 //seconds
#if defined(ASCS_CLEAR_OBJECT_INTERVAL) && ASCS_CLEAR_OBJECT_INTERVAL <= 0
#error clear object interval must be bigger than zero.
#endif
//after this duration, corresponding objects in invalid_object_can can be freed from the heap or reused,
//you must define this macro as a value, not just define it, the value means the duration, unit is second.
//if macro ASCS_ENHANCED_STABILITY been defined, this macro is useless, object's life time is always zero.
#ifdef ASCS_ENHANCED_STABILITY
#if defined(ASCS_OBSOLETED_OBJECT_LIFE_TIME) && ASCS_OBSOLETED_OBJECT_LIFE_TIME != 0
#warning ASCS_OBSOLETED_OBJECT_LIFE_TIME will always be zero if ASCS_ENHANCED_STABILITY macro been defined.
#endif
#else
#ifndef ASCS_OBSOLETED_OBJECT_LIFE_TIME
#define ASCS_OBSOLETED_OBJECT_LIFE_TIME 5 //seconds
#endif
static_assert(ASCS_OBSOLETED_OBJECT_LIFE_TIME > 0, "obsoleted object's life time must be bigger than zero.");
#endif
//IO thread number
//listen, msg send and receive, msg handle(on_msg_handle() and on_msg()) will use these threads
//keep big enough, no empirical value I can suggest, you must try to find it in your own environment
#ifndef ASCS_SERVICE_THREAD_NUM
#define ASCS_SERVICE_THREAD_NUM 8
#endif
static_assert(ASCS_SERVICE_THREAD_NUM > 0, "service thread number be bigger than zero.");
//graceful closing must finish within this duration, otherwise, socket will be forcedly closed.
#ifndef ASCS_GRACEFUL_CLOSE_MAX_DURATION
#define ASCS_GRACEFUL_CLOSE_MAX_DURATION 5 //seconds
#endif
static_assert(ASCS_GRACEFUL_CLOSE_MAX_DURATION > 0, "graceful close duration must be bigger than zero.");
//if connecting (or reconnecting) failed, delay how much milliseconds before reconnecting, negative value means stop reconnecting,
//you can also rewrite ascs::tcp::connector_base::prepare_reconnect(), and return a negative value.
#ifndef ASCS_RECONNECT_INTERVAL
#define ASCS_RECONNECT_INTERVAL 500 //millisecond(s)
#endif
//how many async_accept delivery concurrently
#ifndef ASCS_ASYNC_ACCEPT_NUM
#define ASCS_ASYNC_ACCEPT_NUM 1
#endif
static_assert(ASCS_ASYNC_ACCEPT_NUM > 0, "async accept number must be bigger than zero.");
//in set_server_addr, if the IP is empty, ASCS_TCP_DEFAULT_IP_VERSION will define the IP version, or the IP version will be deduced by the IP address.
//asio::ip::tcp::v4() means ipv4 and asio::ip::tcp::v6() means ipv6.
#ifndef ASCS_TCP_DEFAULT_IP_VERSION
#define ASCS_TCP_DEFAULT_IP_VERSION asio::ip::tcp::v4()
#endif
#ifndef ASCS_UDP_DEFAULT_IP_VERSION
#define ASCS_UDP_DEFAULT_IP_VERSION asio::ip::udp::v4()
#endif
//close port reuse
//#define ASCS_NOT_REUSE_ADDRESS
//If your compiler detected duplicated 'shared_mutex' definition, please define this macro.
#ifndef ASCS_HAS_STD_SHARED_MUTEX
namespace std {typedef shared_timed_mutex shared_mutex;}
#endif
//configurations
#endif /* _ASCS_CONFIG_H_ */
/*
* client.h
*
* Created on: 2016-7-30
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* client related conveniences.
*/
#ifndef _ASCS_EXT_CLIENT_H_
#define _ASCS_EXT_CLIENT_H_
#include "packer.h"
#include "unpacker.h"
#include "../tcp/connector.h"
#include "../tcp/client.h"
#ifndef ASCS_DEFAULT_PACKER
#define ASCS_DEFAULT_PACKER packer
#endif
#ifndef ASCS_DEFAULT_UNPACKER
#define ASCS_DEFAULT_UNPACKER unpacker
#endif
namespace ascs { namespace ext {
typedef tcp::connector_base<ASCS_DEFAULT_PACKER, ASCS_DEFAULT_UNPACKER> connector;
typedef single_socket_service<connector> sclient;
typedef tcp::client_base<connector> client;
}} //namespace
#endif /* _ASCS_EXT_CLIENT_H_ */
/*
* ext.h
*
* Created on: 2016-7-30
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* extensional common.
*/
#ifndef _ASCS_EXT_H_
#define _ASCS_EXT_H_
#include <chrono>
#include "../base.h"
//the size of the buffer used when receiving msg, must equal to or larger than the biggest msg size,
//the bigger this buffer is, the more msgs can be received in one time if there are enough msgs buffered in the SOCKET.
//for unpackers use fixed buffer, every unpacker has a fixed buffer with this size, every tcp::socket has an unpacker,
//so this size is not the bigger the better, bigger buffers may waste more memory.
//if you customized the packer and unpacker, the above principle maybe not right anymore, it should depends on your implementations.
#ifndef ASCS_MSG_BUFFER_SIZE
#define ASCS_MSG_BUFFER_SIZE 4000
#endif
static_assert(ASCS_MSG_BUFFER_SIZE > 0, "message buffer size must be bigger than zero.");
namespace ascs { namespace ext {
//buffers who implemented i_buffer interface can be wrapped by replaceable_buffer
class string_buffer : public std::string, public i_buffer
{
public:
virtual bool empty() const {return std::string::empty();}
virtual size_t size() const {return std::string::size();}
virtual const char* data() const {return std::string::data();}
};
class basic_buffer : public asio::detail::noncopyable
{
public:
basic_buffer() {do_detach();}
basic_buffer(size_t len) {do_detach(); assign(len);}
basic_buffer(basic_buffer&& other) {do_attach(other.buff, other.len, other.buff_len); other.do_detach();}
~basic_buffer() {free();}
void assign(size_t len) {free(); do_attach(new char[len], len, len);}
//the following five functions are needed by ascs
bool empty() const {return 0 == len || nullptr == buff;}
size_t size() const {return nullptr == buff ? 0 : len;}
const char* data() const {return buff;}
void swap(basic_buffer& other) {swap(std::move(other));}
void clear() {free();}
//functions needed by packer and unpacker
char* data() {return buff;}
void swap(basic_buffer&& other) {std::swap(buff, other.buff); std::swap(len, other.len); std::swap(buff_len, other.buff_len);}
bool size(size_t _len) {assert(_len <= buff_len); return (_len <= buff_len) ? (len = _len, true) : false;}
size_t buffer_size() const {return nullptr == buff ? 0 : buff_len;}
protected:
void do_attach(char* _buff, size_t _len, size_t _buff_len) {buff = _buff; len = _len; buff_len = _buff_len;}
void do_detach() {buff = nullptr; len = buff_len = 0;}
void free() {delete[] buff; do_detach();}
protected:
char* buff;
size_t len, buff_len;
};
class ascs_cpu_timer //a substitute for boost::timer::cpu_timer
{
public:
ascs_cpu_timer() {restart();}
void restart() {started = false; elapsed_seconds = .0f; start();}
void start() {if (started) return; started = true; start_time = std::chrono::system_clock::now();}
void resume() {start();}
void stop() {if (!started) return; started = false; elapsed_seconds += std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::system_clock::now() - start_time).count();}
bool stopped() const { return !started; }
float elapsed() const {if (!started) return elapsed_seconds; else return std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::system_clock::now() - start_time).count();}
protected:
bool started;
float elapsed_seconds;
std::chrono::system_clock::time_point start_time;
};
inline std::list<std::string> split_string(const std::string& str) //delimiters can only be ' ' or '\t'
{
std::list<std::string> re;
auto start_pos = std::string::npos;
for (std::string::size_type pos = 0; pos < str.size(); ++pos)
{
if (' ' == str[pos] || '\t' == str[pos])
{
if (std::string::npos != start_pos)
{
re.push_back(str.substr(start_pos, pos - start_pos));
start_pos = std::string::npos;
}
}
else if (std::string::npos == start_pos)
start_pos = pos;
}
if (std::string::npos != start_pos)
re.push_back(str.substr(start_pos));
return re;
}
}} //namespace
#endif /* _ASCS_EXT_H_ */
/*
* packer.h
*
* Created on: 2012-3-2
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* packers
*/
#ifndef _ASCS_EXT_PACKER_H_
#define _ASCS_EXT_PACKER_H_
#include "ext.h"
#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 ascs { namespace ext {
class packer_helper
{
public:
//return (size_t) -1 means length exceeded the ASCS_MSG_BUFFER_SIZE
static size_t msg_size_check(size_t pre_len, const char* const pstr[], const size_t len[], size_t num)
{
if (nullptr == pstr || nullptr == len)
return -1;
auto total_len = pre_len;
auto last_total_len = total_len;
for (size_t i = 0; i < num; ++i)
if (nullptr != pstr[i])
{
total_len += len[i];
if (last_total_len > total_len || total_len > ASCS_MSG_BUFFER_SIZE) //overflow
{
unified_out::error_out("pack msg error: length exceeded the ASCS_MSG_BUFFER_SIZE!");
return -1;
}
last_total_len = total_len;
}
return total_len;
}
};
//protocol: length + body
class packer : public i_packer<std::string>
{
public:
static size_t get_max_msg_size() {return ASCS_MSG_BUFFER_SIZE - ASCS_HEAD_LEN;}
using i_packer<msg_type>::pack_msg;
virtual msg_type pack_msg(const char* const pstr[], const size_t len[], size_t num, bool native = false)
{
msg_type msg;
auto pre_len = native ? 0 : ASCS_HEAD_LEN;
auto total_len = packer_helper::msg_size_check(pre_len, pstr, len, num);
if ((size_t) -1 == total_len)
return msg;
else if (total_len > pre_len)
{
if (!native)
{
auto head_len = (ASCS_HEAD_TYPE) total_len;
if (total_len != head_len)
{
unified_out::error_out("pack msg error: length exceeded the header's range!");
return msg;
}
head_len = ASCS_HEAD_H2N(head_len);
msg.reserve(total_len);
msg.append((const char*) &head_len, ASCS_HEAD_LEN);
}
else
msg.reserve(total_len);
for (size_t i = 0; i < num; ++i)
if (nullptr != pstr[i])
msg.append(pstr[i], len[i]);
} //if (total_len > pre_len)
return msg;
}
virtual char* raw_data(msg_type& msg) const {return const_cast<char*>(std::next(msg.data(), ASCS_HEAD_LEN));}
virtual const char* raw_data(msg_ctype& msg) const {return std::next(msg.data(), ASCS_HEAD_LEN);}
virtual size_t raw_data_len(msg_ctype& msg) const {return msg.size() - ASCS_HEAD_LEN;}
};
//protocol: length + body
class replaceable_packer : public i_packer<replaceable_buffer>
{
public:
using i_packer<msg_type>::pack_msg;
virtual msg_type pack_msg(const char* const pstr[], const size_t len[], size_t num, bool native = false)
{
auto raw_msg = new string_buffer();
auto str = packer().pack_msg(pstr, len, num, native);
raw_msg->swap(str);
return msg_type(raw_msg);
}
virtual char* raw_data(msg_type& msg) const {return const_cast<char*>(std::next(msg.data(), ASCS_HEAD_LEN));}
virtual const char* raw_data(msg_ctype& msg) const {return std::next(msg.data(), ASCS_HEAD_LEN);}
virtual size_t raw_data_len(msg_ctype& msg) const {return msg.size() - ASCS_HEAD_LEN;}
};
//protocol: [prefix] + body + suffix
class prefix_suffix_packer : public i_packer<std::string>
{
public:
void prefix_suffix(const std::string& prefix, const std::string& suffix) {assert(!suffix.empty() && prefix.size() + suffix.size() < ASCS_MSG_BUFFER_SIZE); _prefix = prefix; _suffix = suffix;}
const std::string& prefix() const {return _prefix;}
const std::string& suffix() const {return _suffix;}
public:
using i_packer<msg_type>::pack_msg;
virtual msg_type pack_msg(const char* const pstr[], const size_t len[], size_t num, bool native = false)
{
msg_type msg;
auto pre_len = native ? 0 : _prefix.size() + _suffix.size();
auto total_len = packer_helper::msg_size_check(pre_len, pstr, len, num);
if ((size_t) -1 == total_len)
return msg;
else if (total_len > pre_len)
{
msg.reserve(total_len);
if (!native)
msg.append(_prefix);
for (size_t i = 0; i < num; ++i)
if (nullptr != pstr[i])
msg.append(pstr[i], len[i]);
if (!native)
msg.append(_suffix);
} //if (total_len > pre_len)
return msg;
}
virtual char* raw_data(msg_type& msg) const {return const_cast<char*>(std::next(msg.data(), _prefix.size()));}
virtual const char* raw_data(msg_ctype& msg) const {return std::next(msg.data(), _prefix.size());}
virtual size_t raw_data_len(msg_ctype& msg) const {return msg.size() - _prefix.size() - _suffix.size();}
private:
std::string _prefix, _suffix;
};
}} //namespace
#endif /* _ASCS_EXT_PACKER_H_ */
/*
* server.h
*
* Created on: 2016-9-7
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* server related conveniences.
*/
#ifndef _ASCS_EXT_SERVER_H_
#define _ASCS_EXT_SERVER_H_
#include "packer.h"
#include "unpacker.h"
#include "../tcp/server_socket.h"
#include "../tcp/server.h"
#ifndef ASCS_DEFAULT_PACKER
#define ASCS_DEFAULT_PACKER packer
#endif
#ifndef ASCS_DEFAULT_UNPACKER
#define ASCS_DEFAULT_UNPACKER unpacker
#endif
namespace ascs { namespace ext {
typedef tcp::server_socket_base<ASCS_DEFAULT_PACKER, ASCS_DEFAULT_UNPACKER> server_socket;
typedef tcp::server_base<server_socket> server;
}} //namespace
#endif /* _ASCS_EXT_SERVER_H_ */
/*
* ssl.h
*
* Created on: 2016-7-30
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* ssl related conveniences.
*/
#ifndef _ASCS_EXT_SSL_H_
#define _ASCS_EXT_SSL_H_
#include "packer.h"
#include "unpacker.h"
#include "../ssl/ssl.h"
#ifndef ASCS_DEFAULT_PACKER
#define ASCS_DEFAULT_PACKER packer
#endif
#ifndef ASCS_DEFAULT_UNPACKER
#define ASCS_DEFAULT_UNPACKER unpacker
#endif
namespace ascs { namespace ext {
typedef ssl::server_socket_base<ASCS_DEFAULT_PACKER, ASCS_DEFAULT_UNPACKER> ssl_server_socket;
typedef ssl::server_base<ssl_server_socket> ssl_server;
typedef ssl::connector_base<ASCS_DEFAULT_PACKER, ASCS_DEFAULT_UNPACKER> ssl_connector;
typedef single_socket_service<ssl_connector> ssl_sclient;
typedef tcp::client_base<ssl_connector, ssl::object_pool<ssl_connector> > ssl_client;
}} //namespace
#endif /* _ASCS_EXT_SSL_H_ */
/*
* udp.h
*
* Created on: 2016-9-7
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* udp related conveniences.
*/
#ifndef _ASCS_EXT_UDP_H_
#define _ASCS_EXT_UDP_H_
#include "packer.h"
#include "unpacker.h"
#include "../udp/socket.h"
#include "../udp/socket_service.h"
#ifndef ASCS_DEFAULT_PACKER
#define ASCS_DEFAULT_PACKER packer
#endif
#ifndef ASCS_DEFAULT_UDP_UNPACKER
#define ASCS_DEFAULT_UDP_UNPACKER udp_unpacker
#endif
namespace ascs { namespace ext {
typedef udp::socket_base<ASCS_DEFAULT_PACKER, ASCS_DEFAULT_UDP_UNPACKER> udp_socket;
typedef single_socket_service<udp_socket> udp_sservice;
typedef udp::service_base<udp_socket> udp_service;
}} //namespace
#endif /* _ASCS_EXT_UDP_H_ */
此差异已折叠。
/*
* object.h
*
* Created on: 2016-6-11
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* the top class
*/
#ifndef _ASCS_OBJECT_H_
#define _ASCS_OBJECT_H_
#include "base.h"
namespace ascs
{
class object
{
protected:
object(asio::io_service& _io_service_) : io_service_(_io_service_) {reset();}
virtual ~object() {}
public:
bool stopped() const {return io_service_.stopped();}
#ifdef ASCS_ENHANCED_STABILITY
template<typename CallbackHandler>
void post(const CallbackHandler& handler) {auto unused(ASCS_THIS async_call_indicator); io_service_.post([=]() {handler();});}
template<typename CallbackHandler>
void post(CallbackHandler&& handler) {auto unused(ASCS_THIS async_call_indicator); io_service_.post([=]() {handler();});}
bool is_async_calling() const {return !async_call_indicator.unique();}
template<typename CallbackHandler>
std::function<void(const asio::error_code&)> make_handler_error(CallbackHandler&& handler) const
{std::shared_ptr<char>unused(async_call_indicator); return [=](const asio::error_code& ec) {handler(ec);};}
template<typename CallbackHandler>
std::function<void(const asio::error_code&)> make_handler_error(const CallbackHandler& handler) const
{std::shared_ptr<char>unused(async_call_indicator); return [=](const asio::error_code& ec) {handler(ec);};}
template<typename CallbackHandler>
std::function<void(const asio::error_code&, size_t)> make_handler_error_size(CallbackHandler&& handler) const
{std::shared_ptr<char>unused(async_call_indicator); return [=](const asio::error_code& ec, size_t bytes_transferred) {handler(ec, bytes_transferred);};}
template<typename CallbackHandler>
std::function<void(const asio::error_code&, size_t)> make_handler_error_size(CallbackHandler& handler) const
{std::shared_ptr<char>unused(async_call_indicator); return [=](const asio::error_code& ec, size_t bytes_transferred) {handler(ec, bytes_transferred);};}
protected:
void reset() {async_call_indicator = std::make_shared<char>('\0');}
protected:
std::shared_ptr<char> async_call_indicator;
#else
template<typename CallbackHandler>
void post(const CallbackHandler& handler) {io_service_.post(handler);}
template<typename CallbackHandler>
void post(CallbackHandler&& handler) {io_service_.post(std::move(handler));}
bool is_async_calling() const {return false;}
template<typename F>
inline F&& make_handler_error(F&& f) const {return std::move(f);}
template<typename F>
inline const F& make_handler_error(const F& f) const {return f;}
template<typename F>
inline F&& make_handler_error_size(F&& f) const {return std::move(f);}
template<typename F>
inline const F& make_handler_error_size(const F& f) const {return f;}
protected:
void reset() {}
#endif
protected:
asio::io_service& io_service_;
};
} //namespace
#endif /* _ASCS_OBJECT_H_ */
/*
* object_pool.h
*
* Created on: 2013-8-7
* Author: youngwolf
* email: mail2tao@163.com
* QQ: 676218192
* Community on QQ: 198941541
*
* this class used at both client and server endpoint, and in both TCP and UDP socket
* this class can only manage objects that inherit from socket
*/
#ifndef _ASCS_OBJECT_POOL_H_
#define _ASCS_OBJECT_POOL_H_
#include <atomic>
#include <unordered_map>
#include "timer.h"
#include "service_pump.h"
namespace ascs
{
template<typename Object>
class object_pool : public service_pump::i_service, public timer
{
public:
typedef std::shared_ptr<Object> object_type;
typedef const object_type object_ctype;
typedef std::unordered_map<uint_fast64_t, object_type> container_type;
protected:
struct invalid_object
{
object_ctype object_ptr;
#ifdef ASCS_ENHANCED_STABILITY
invalid_object(object_ctype& object_ptr_) : object_ptr(object_ptr_) {assert(object_ptr);}
bool is_timeout() const {return true;}
bool is_timeout(time_t now) const {return true;}
#else
const time_t kick_out_time;
invalid_object(object_ctype& object_ptr_) : object_ptr(object_ptr_), kick_out_time(time(nullptr)) {assert(object_ptr);}
bool is_timeout() const {return is_timeout(time(nullptr));}
bool is_timeout(time_t now) const {return kick_out_time <= now - ASCS_OBSOLETED_OBJECT_LIFE_TIME;}
#endif
};
protected:
static const unsigned char TIMER_BEGIN = timer::TIMER_END;
static const unsigned char TIMER_FREE_SOCKET = TIMER_BEGIN;
static const unsigned char TIMER_CLEAR_SOCKET = TIMER_BEGIN + 1;
static const unsigned char TIMER_END = TIMER_BEGIN + 10;
object_pool(service_pump& service_pump_) : i_service(service_pump_), timer(service_pump_), cur_id(-1), max_size_(ASCS_MAX_OBJECT_NUM) {}
void start()
{
#ifdef ASCS_FREE_OBJECT_INTERVAL
set_timer(TIMER_FREE_SOCKET, 1000 * ASCS_FREE_OBJECT_INTERVAL, [this](unsigned char id)->bool {ASCS_THIS free_object(); return true;});
#endif
#ifdef ASCS_CLEAR_OBJECT_INTERVAL
set_timer(TIMER_CLEAR_SOCKET, 1000 * ASCS_CLEAR_OBJECT_INTERVAL, [this](unsigned char id)->bool {ASCS_THIS clear_obsoleted_object(); return true;});
#endif
}
void stop() {stop_all_timer();}
bool add_object(object_ctype& object_ptr)
{
assert(object_ptr);
std::unique_lock<std::shared_mutex> lock(object_can_mutex);
return object_can.size() < max_size_ ? object_can.insert(std::make_pair(object_ptr->id(), object_ptr)).second : false;
}
//only add object_ptr to invalid_object_can when it's in object_can, this can avoid duplicated items in invalid_object_can, because invalid_object_can is a list,
//there's no way to check the existence of an item in a list efficiently.
bool del_object(object_ctype& object_ptr)
{
assert(object_ptr);
std::unique_lock<std::shared_mutex> lock(object_can_mutex);
auto exist = object_can.erase(object_ptr->id()) > 0;
lock.unlock();
if (exist)
{
std::unique_lock<std::shared_mutex> lock(invalid_object_can_mutex);
invalid_object_can.push_back(object_ptr);
}
return exist;
}
virtual void on_create(object_ctype& object_ptr) {}
void init_object(object_ctype& object_ptr)
{
if (object_ptr)
{
object_ptr->id(++cur_id);
on_create(object_ptr);
}
else
unified_out::error_out("create object failed!");
}
#ifdef ASCS_REUSE_OBJECT
object_type reuse_object()
{
std::unique_lock<std::shared_mutex> lock(invalid_object_can_mutex);
//objects are order by time, so we don't have to go through all items in invalid_object_can
for (auto iter = std::begin(invalid_object_can); iter != std::end(invalid_object_can) && iter->is_timeout(); ++iter)
if (iter->object_ptr.unique() && iter->object_ptr->obsoleted())
{
auto object_ptr(std::move(iter->object_ptr));
invalid_object_can.erase(iter);
lock.unlock();
object_ptr->reset();
return object_ptr;
}
return object_type();
}
template<typename Arg>
object_type create_object(Arg& arg)
{
auto object_ptr = reuse_object();
if (!object_ptr)
object_ptr = std::make_shared<Object>(arg);
init_object(object_ptr);
return object_ptr;
}
template<typename Arg1, typename Arg2>
object_type create_object(Arg1& arg1, Arg2& arg2)
{
auto object_ptr = reuse_object();
if (!object_ptr)
object_ptr = std::make_shared<Object>(arg1, arg2);
init_object(object_ptr);
return object_ptr;
}
#else
template<typename Arg>
object_type create_object(Arg& arg)
{
auto object_ptr = std::make_shared<Object>(arg);
init_object(object_ptr);
return object_ptr;
}
template<typename Arg1, typename Arg2>
object_type create_object(Arg1& arg1, Arg2& arg2)
{
auto object_ptr = std::make_shared<Object>(arg1, arg2);
init_object(object_ptr);
return object_ptr;
}
#endif
object_type create_object() {return create_object(sp);}
public:
//to configure unordered_set(for example, set factor or reserved size), not locked the mutex, so must be called before service_pump starting up.
container_type& container() {return object_can;}
size_t max_size() const {return max_size_;}
void max_size(size_t _max_size) {max_size_ = _max_size;}
size_t size()
{
std::shared_lock<std::shared_mutex> lock(object_can_mutex);
return object_can.size();
}
size_t invalid_object_size()
{
std::shared_lock<std::shared_mutex> lock(invalid_object_can_mutex);
return invalid_object_can.size();
}
object_type find(uint_fast64_t id)
{
std::shared_lock<std::shared_mutex> lock(object_can_mutex);
auto iter = object_can.find(id);
return iter != std::end(object_can) ? iter->second : object_type();
}
//this method has linear complexity, please note.
object_type at(size_t index)
{
std::shared_lock<std::shared_mutex> lock(object_can_mutex);
assert(index < object_can.size());
return index < object_can.size() ? std::next(std::begin(object_can), index)->second : object_type();
}
//this method has linear complexity, please note.
object_type invalid_object_at(size_t index)
{
std::shared_lock<std::shared_mutex> lock(invalid_object_can_mutex);
assert(index < invalid_object_can.size());
return index < invalid_object_can.size() ? std::next(std::begin(invalid_object_can), index)->object_ptr : object_type();
}
//this method has linear complexity, please note.
object_type invalid_object_find(uint_fast64_t id)
{
std::shared_lock<std::shared_mutex> lock(invalid_object_can_mutex);
auto iter = std::find_if(std::begin(invalid_object_can), std::end(invalid_object_can), [id](const invalid_object& item) {return id == item.object_ptr->id();});
return iter == std::end(invalid_object_can) ? object_type() : iter->object_ptr;
}
//this method has linear complexity, please note.
object_type invalid_object_pop(uint_fast64_t id)
{
std::shared_lock<std::shared_mutex> lock(invalid_object_can_mutex);
auto iter = std::find_if(std::begin(invalid_object_can), std::end(invalid_object_can), [id](const invalid_object& item) {return id == item.object_ptr->id();});
if (iter != std::end(invalid_object_can))
{
auto object_ptr = iter->object_ptr;
invalid_object_can.erase(iter);
return object_ptr;
}
return object_type();
}
void list_all_object() {do_something_to_all([](object_ctype& item) {item->show_info("", ""); });}
//Kick out obsoleted objects
//Consider the following assumptions:
//1.You didn't invoke del_object in on_recv_error or other places.
//2.For some reason(I haven't met yet), on_recv_error not been invoked
//object_pool will automatically invoke this function if ASCS_CLEAR_OBJECT_INTERVAL been defined
size_t clear_obsoleted_object()
{
std::list<object_type> objects;
std::unique_lock<std::shared_mutex> lock(object_can_mutex);
for (auto iter = std::begin(object_can); iter != std::end(object_can);)
if (iter->second.unique() && iter->second->obsoleted())
{
#ifdef ASCS_REUSE_OBJECT
iter->second->show_info("object:", "is obsoleted, kick it out, it will be reused in the future.");
#else
iter->second->show_info("object:", "is obsoleted, kick it out, it will be freed in the future.");
#endif
#ifdef ASCS_ENHANCED_STABILITY
iter->second->close();
#endif
objects.push_back(iter->second);
iter = object_can.erase(iter);
}
else
++iter;
lock.unlock();
auto size = objects.size();
if (0 != size)
{
unified_out::warning_out(ASCS_SF " object(s) been kicked out!", size);
std::unique_lock<std::shared_mutex> lock(invalid_object_can_mutex);
invalid_object_can.insert(std::end(invalid_object_can), std::begin(objects), std::end(objects));
}
return size;
}
//free or close a specific number of objects
//if you used object pool(define ASCS_REUSE_OBJECT), you can manually call this function to free some objects after the object pool(invalid_object_size())
// goes big enough for memory saving(because the objects in invalid_object_can are waiting for reusing and will never be freed),
// you can also define ASCS_FREE_OBJECT_INTERVAL to let object_pool to call this function automatically and periodically, but objects will only be closed.
//if you don't used object pool, object_pool will invoke this function automatically and periodically, so you don't need to invoke this function exactly
//return affected object number, if just_close equal to true, then closed objects will be treated as unaffected.
#ifdef ASCS_REUSE_OBJECT
size_t free_object(size_t num = -1, bool just_close = true)
#else
size_t free_object(size_t num = -1, bool just_close = false)
#endif
{
size_t num_affected = 0;
std::unique_lock<std::shared_mutex> lock(invalid_object_can_mutex);
//objects are order by time, so we don't have to go through all items in invalid_object_can
for (auto iter = std::begin(invalid_object_can); num > 0 && iter != std::end(invalid_object_can) && iter->is_timeout();)
if (iter->object_ptr.unique() && iter->object_ptr->obsoleted())
{
--num;
if (just_close)
{
if (iter->object_ptr->close())
++num_affected;
++iter;
}
else
{
++num_affected;
iter = invalid_object_can.erase(iter);
}
}
else
++iter;
if (num_affected > 0)
unified_out::warning_out(ASCS_SF " object(s) been %s!", num_affected, just_close ? "closed" : "freed");
return num_affected;
}
template<typename _Predicate>
void do_something_to_all(const _Predicate& __pred) {std::shared_lock<std::shared_mutex> lock(object_can_mutex); for (auto& item : object_can) __pred(item.second);}
template<typename _Predicate> void do_something_to_one(const _Predicate& __pred)
{std::shared_lock<std::shared_mutex> lock(object_can_mutex); for (auto iter = std::begin(object_can); iter != std::end(object_can); ++iter) if (__pred(iter->second)) break;}
protected:
std::atomic_uint_fast64_t cur_id;
container_type object_can;
std::shared_mutex object_can_mutex;
size_t max_size_;
//because all objects are dynamic created and stored in object_can, maybe when receiving error occur
//(you are recommended to delete the object from object_can, for example via tcp::server_base::del_client), some other asynchronous calls are still queued in asio::io_service,
//and will be dequeued in the future, we must guarantee these objects not be freed from the heap or reused, so we move these objects from object_can to invalid_object_can,
//and free them from the heap or reuse them in the near future, see ASCS_OBSOLETED_OBJECT_LIFE_TIME macro for more details.
//if ASCS_CLEAR_OBJECT_INTERVAL been defined, clear_obsoleted_object() will be invoked automatically and periodically to move all invalid objects into invalid_object_can.
std::list<invalid_object> invalid_object_can;
std::shared_mutex invalid_object_can_mutex;
};
} //namespace
#endif /* _ASCS_OBJECT_POOL_H_ */
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册