提交 a573ef27 编写于 作者: W wolf 提交者: youngowlf

Sync doc.

Make shutdown operation thread safe.
Decouple object and container (for example, st_server_socket and st_server).
Drop ssize_t definition for Visual C++.
Version number keeps unchanged.
上级 4790f279
......@@ -15,8 +15,7 @@
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_client.h"
#include "../st_asio_wrapper_connector.h"
#include "../st_asio_wrapper_tcp_client.h"
#ifndef ST_ASIO_DEFAULT_PACKER
......
......@@ -15,7 +15,7 @@
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_server_socket.h"
#include "../st_asio_wrapper_server.h"
#ifndef ST_ASIO_DEFAULT_PACKER
......
......@@ -13,10 +13,9 @@
#ifndef ST_ASIO_WRAPPER_EXT_SSL_H_
#define ST_ASIO_WRAPPER_EXT_SSL_H_
#include "../st_asio_wrapper_ssl.h"
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_ssl.h"
#ifndef ST_ASIO_DEFAULT_PACKER
#define ST_ASIO_DEFAULT_PACKER packer
......
......@@ -15,8 +15,7 @@
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_client.h"
#include "../st_asio_wrapper_udp_socket.h"
#include "../st_asio_wrapper_udp_client.h"
#ifndef ST_ASIO_DEFAULT_PACKER
......
......@@ -48,7 +48,6 @@
#if defined _MSC_VER
#define ST_ASIO_SF "%Iu"
#define ST_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 ST_ASIO_SF "%zu"
#define ST_THIS this->
......@@ -56,6 +55,16 @@
namespace st_asio_wrapper
{
class st_service_pump;
class st_timer;
class i_server
{
public:
virtual st_service_pump& get_service_pump() = 0;
virtual const st_service_pump& get_service_pump() const = 0;
virtual bool del_client(const boost::shared_ptr<st_timer>& client_ptr) = 0;
};
class i_buffer
{
public:
......@@ -245,7 +254,7 @@ namespace st_asio_wrapper
if (left_num > size) //find the minimum movement
std::advance(end_iter = begin_iter, size);
else
std::advance(end_iter, -(ssize_t) left_num);
std::advance(end_iter, -(int) left_num);
}
else
size = src_can.size();
......
......@@ -84,6 +84,9 @@ public:
}
//sync must be false if you call graceful_shutdown in on_msg
//furthermore, you're recommended to call this function with sync equal to false in all service threads,
//all callbacks will be called in service threads.
//this function is not thread safe, please note.
void graceful_shutdown(bool reconnect = false, bool sync = true)
{
if (ST_THIS is_shutting_down())
......@@ -180,7 +183,7 @@ private:
return false;
}
bool async_shutdown_handler(unsigned char id, ssize_t loop_num)
bool async_shutdown_handler(unsigned char id, size_t loop_num)
{
assert(TIMER_ASYNC_SHUTDOWN == id);
......
......@@ -13,7 +13,6 @@
#ifndef ST_ASIO_WRAPPER_SERVER_H_
#define ST_ASIO_WRAPPER_SERVER_H_
#include "st_asio_wrapper_server_socket.h"
#include "st_asio_wrapper_object_pool.h"
#ifndef ST_ASIO_SERVER_PORT
......
......@@ -13,20 +13,11 @@
#ifndef ST_ASIO_WRAPPER_SERVER_SOCKET_H_
#define ST_ASIO_WRAPPER_SERVER_SOCKET_H_
#include "st_asio_wrapper_service_pump.h"
#include "st_asio_wrapper_tcp_socket.h"
namespace st_asio_wrapper
{
class i_server
{
public:
virtual st_service_pump& get_service_pump() = 0;
virtual const st_service_pump& get_service_pump() const = 0;
virtual bool del_client(const boost::shared_ptr<st_timer>& client_ptr) = 0;
};
template<typename Packer, typename Unpacker, typename Server = i_server, typename Socket = boost::asio::ip::tcp::socket>
class st_server_socket_base : public st_tcp_socket_base<Socket, Packer, Unpacker>, public boost::enable_shared_from_this<st_server_socket_base<Packer, Unpacker, Server, Socket> >
{
......@@ -56,7 +47,11 @@ public:
super::force_shutdown();
}
void graceful_shutdown(bool sync = true)
//sync must be false if you call graceful_shutdown in on_msg
//furthermore, you're recommended to call this function with sync equal to false in all service threads,
//all callbacks will be called in service threads.
//this function is not thread safe, please note.
void graceful_shutdown(bool sync = false)
{
if (!ST_THIS is_shutting_down())
show_info("server link:", "being shut down gracefully.");
......@@ -104,12 +99,11 @@ protected:
#else
server.del_client(boost::dynamic_pointer_cast<st_timer>(ST_THIS shared_from_this()));
#endif
ST_THIS shutdown_state = 0;
}
private:
bool async_shutdown_handler(unsigned char id, ssize_t loop_num)
bool async_shutdown_handler(unsigned char id, size_t loop_num)
{
assert(TIMER_ASYNC_SHUTDOWN == id);
......
......@@ -16,7 +16,9 @@
#include <boost/asio/ssl.hpp>
#include "st_asio_wrapper_object_pool.h"
#include "st_asio_wrapper_connector.h"
#include "st_asio_wrapper_tcp_client.h"
#include "st_asio_wrapper_server_socket.h"
#include "st_asio_wrapper_server.h"
#ifdef ST_ASIO_REUSE_OBJECT
......
......@@ -13,7 +13,6 @@
#ifndef ST_ASIO_WRAPPER_TCP_CLIENT_H_
#define ST_ASIO_WRAPPER_TCP_CLIENT_H_
#include "st_asio_wrapper_connector.h"
#include "st_asio_wrapper_client.h"
namespace st_asio_wrapper
......
......@@ -170,6 +170,8 @@ protected:
void shutdown()
{
boost::unique_lock<boost::shared_mutex> lock(shutdown_mutex);
shutdown_state = 1;
ST_THIS stop_all_timer();
ST_THIS close(); //must after stop_all_timer(), it's very important
......@@ -252,6 +254,8 @@ protected:
typename super::in_container_type last_send_msg;
boost::shared_ptr<i_unpacker<out_msg_type> > unpacker_;
int shutdown_state; //2-the first step of graceful shutdown, 1-force shutdown, 0-normal state
boost::shared_mutex shutdown_mutex;
};
} //namespace
......
......@@ -13,7 +13,6 @@
#ifndef ST_ASIO_WRAPPER_UDP_CLIENT_H_
#define ST_ASIO_WRAPPER_UDP_CLIENT_H_
#include "st_asio_wrapper_udp_socket.h"
#include "st_asio_wrapper_client.h"
namespace st_asio_wrapper
......
......@@ -138,8 +138,8 @@ protected:
ST_THIS send_msg_buffer.front().swap(last_send_msg);
ST_THIS send_msg_buffer.pop_front();
boost::shared_lock<boost::shared_mutex> lock(shutdown_mutex);
last_send_msg.restart();
boost::shared_lock<boost::shared_mutex> lock(shutdown_mutex);
ST_THIS next_layer().async_send_to(boost::asio::buffer(last_send_msg.data(), last_send_msg.size()), last_send_msg.peer_addr,
ST_THIS make_handler_error_size(boost::bind(&st_udp_socket_base::send_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
}
......@@ -174,6 +174,8 @@ protected:
void shutdown()
{
boost::unique_lock<boost::shared_mutex> lock(shutdown_mutex);
ST_THIS stop_all_timer();
ST_THIS close(); //must after stop_all_timer(), it's very important
ST_THIS started_ = false;
......@@ -183,8 +185,6 @@ protected:
{
boost::system::error_code ec;
ST_THIS lowest_layer().shutdown(boost::asio::ip::udp::socket::shutdown_both, ec);
boost::unique_lock<boost::shared_mutex> lock(shutdown_mutex);
ST_THIS lowest_layer().close(ec);
}
}
......
......@@ -299,22 +299,26 @@ int main(int argc, const char* argv[])
n = 1;
if ('+' == str[0])
for (; n > 0 && client.add_client(port, ip); ++link_num, --n);
for (; n > 0 && client.add_client(port, ip); --n);
else
{
if (n > client.size())
n = client.size();
client.shutdown_some_client(n);
link_num = client.size();
}
link_num = client.size();
continue;
}
#ifdef ST_ASIO_CLEAR_OBJECT_INTERVAL
link_num = client.size();
printf("link number: " ST_ASIO_SF "\n", link_num);
if (link_num != client.valid_size())
{
puts("please wait for a while, because st_object_pool has not cleaned up invalid links.");
continue;
}
#endif
size_t msg_num = 1024;
size_t msg_len = 1024; //must greater than or equal to sizeof(size_t)
......
......@@ -12,9 +12,8 @@ public:
protected:
virtual void init();
virtual void uninit();
实现i_service的纯虚接口,供st_service_pump使用(start_service时调用init,stop_service时调用uninit),这两个接口其实
就是实现了开始和结束逻辑,开始意味着什么由Socket决定,这个我们在前面讲st_socket、st_tcp_socket、st_udp_socket、
st_connector和st_server_socket的时候,已经说过多次了。
实现i_service的纯虚接口,供st_service_pump在start_service/stop_service时调用,这两个接口其实就是实现了开始和结束逻辑,开始意味着什么由Socket决定,
这个我们在前面讲st_socket、st_tcp_socket、st_udp_socket、st_connector和st_server_socket的时候,已经说过多次了。
};
支持多条连接的客户端(或者说多个套接字),tcp和udp通用,其实它更像一个容器,只是在st_object_pool上扩展了一些helper函数
......@@ -29,12 +28,11 @@ protected:
后者由ssl使用。
virtual void init();
实现i_service的纯虚接口,供st_service_pump使用(start_service时调用),跟st_sclient::init功能一样,只是对所有客户端都
做一个“开始”操作。
实现i_service的纯虚接口,供st_service_pump在start_service时调用,跟st_sclient::init功能一样,只是对所有客户端都做一个“开始”操作。
public:
bool add_client(typename st_client::object_ctype& client_ptr, bool reset = true);
添加一个连接到对象池,并调用client_ptr->start(),如果reset为true,那么在这之前还会先调用client_ptr->reset。
添加一个连接到对象池,并调用client_ptr->start(),如果reset为true,那么在这之前还会先调用client_ptr->reset()
using Pool::create_object;
对客户端开放对象创建功能。
......
......@@ -14,8 +14,7 @@ namespace st_asio_wrapper
{
带连接功能的st_tcp_socket,算是一个真正的客户端了
template <typename Packer = ST_ASIO_DEFAULT_PACKER, typename Unpacker = ST_ASIO_DEFAULT_UNPACKER, typename Socket = boost::asio::ip::tcp::socket>
template <typename Packer, typename Unpacker, typename Socket = boost::asio::ip::tcp::socket>
class st_connector_base : public st_tcp_socket_base<Socket, Packer, Unpacker>
{
public:
......@@ -39,13 +38,14 @@ public:
是否已经连接成功。
void disconnect(bool reconnect = false);
直接调用force_close
直接调用force_shutdown
void force_close(bool reconnect = false);
强制关闭————调用父类的do_close,如果reconnect为true,则关闭之后,马上重新连接服务器。
void force_shutdown(bool reconnect = false);
强制关闭————调用父类的shutdown,如果reconnect为true,则关闭之后,马上重新连接服务器。
void graceful_close(bool reconnect = false, bool sync = true);
优雅关闭,调用父类的graceful_close函数,reconnect参数的意义同上,sync参数直接传递给父类。
void graceful_shutdown(bool reconnect = false, bool sync = true);
优雅关闭,调用父类的graceful_shutdown函数,reconnect参数的意义同上,sync参数直接传递给父类。
在on_msg中,请以sync为false调用该函数,在其它所有service线程中,推荐也用sync为false调用该函数。
void show_info(const char* head, const char* tail) const;
在head和tail中间,显示自己的地址(IP加端口)。
......@@ -63,6 +63,9 @@ virtual int prepare_reconnect(const boost::system::error_code& ec);
virtual void on_connect();
连接成功时回调,用户重写它以得到连接成功事件。
virtual bool is_closable();
如果正在重连接,则不允许close这个st_connector。
virtual bool is_send_allowed() const;
是否可发送数据,is_connected加上父类的is_send_allowed为最终的判定结果。
......@@ -76,8 +79,8 @@ virtual int prepare_reconnect(const boost::system::error_code& ec);
如果允许(io_service仍然在运行且prepare_reconnect返回大于等于0),启动定时器以延时一小段时间之后重新连接服务器。
private:
bool async_close_handler(unsigned char id, ssize_t loop_num);
异步优雅关闭超时定时器。
bool async_shutdown_handler(unsigned char id, ssize_t loop_num);
异步优雅关闭(shutdown)超时定时器。
void connect_handler(const error_code& ec);
连接成功或者失败后由asio回调。
......@@ -90,6 +93,5 @@ protected:
bool reconnecting;
是否正在重新连接。
};
typedef st_connector_base<> st_connector;
} //namespace
......@@ -18,7 +18,7 @@ namespace st_asio_wrapper
{
服务端服务器类,主要功能是监听和接受连接,连接管理继承于Pool
template<typename Socket = st_server_socket, typename Pool = st_object_pool<Socket>, typename Server = i_server>
template<typename Socket, typename Pool = st_object_pool<Socket>, typename Server = i_server>
class st_server_base : public Server, public Pool
{
public:
......@@ -64,6 +64,11 @@ ssl使用。
void safe_broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
对每一个连接调用st_tcp_socket中的同名函数。
void disconnect(typename Pool::object_ctype& client_ptr);
void force_shutdown(typename Pool::object_ctype& client_ptr);
void graceful_shutdown(typename Pool::object_ctype& client_ptr, bool sync = true);
先从对象池中删除client_ptr,然后调用client_ptr的同名函数。
protected:
virtual void init();
virtual void uninit();
......@@ -74,6 +79,11 @@ protected:
(你应该在返回false之前关闭这个st_socket)或者你想自己管理这个连接(你应该在返回false之前调用client_ptr->start以开始数据接收),
总之st_ojbect_pool不再维护这个连接。
virtual bool on_accept_error(const boost::system::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的话(参看ST_ASIO_ASYNC_ACCEPT_NUM宏)。
......@@ -83,7 +93,8 @@ protected:
添加一条连接到对象池(调用st_object_pool::add_object),如果成功,打印一些连接建议相关的信息。
void accept_handler(const error_code& ec, typename st_server_base::object_ctype& client_ptr);
异步接受到连接时asio回调,如果出错(ec为真),将调用stop_listen,否则继承异步接受连接(start_next_accept)。
异步接受到连接时asio回调,如果出错(ec为真),将调用on_accept_error(),否则继续异步接受连接(start_next_accept),
如果on_accept_error()返回true,也将继续异步接受连接(start_next_accept)。
protected:
boost::asio::ip::tcp::endpoint server_addr;
......@@ -91,7 +102,6 @@ protected:
boost::asio::ip::tcp::acceptor acceptor;
连接异步接受器。
};
typedef st_server_base<> st_server;
} //namespace
......@@ -18,8 +18,7 @@ i_server的实现者在使用client_ptr参数之前,会转换成Socket类型
};
服务端套接字类
template<typename Packer = ST_ASIO_DEFAULT_PACKER, typename Unpacker = ST_ASIO_DEFAULT_UNPACKER, typename Server = i_server, typename Socket = boost::asio::ip::tcp::socket>
template<typename Packer, typename Unpacker, typename Server = i_server, typename Socket = boost::asio::ip::tcp::socket>
class st_server_socket_base : public st_tcp_socket_base<Socket, Packer, Unpacker>, public boost::enable_shared_from_this<st_server_socket_base<Packer, Unpacker, Server, Socket>>
{
public:
......@@ -33,13 +32,14 @@ ssl使用。
重置所有,st_object_pool在重用时会调用。st_server_socket的子类可重写它以重置自己的状态,记得最后需要调用本类的reset。
void disconnect();
直接调用force_close
直接调用force_shutdown
void force_close();
void force_shutdown();
强制退出————调用父类的同名函数。
void graceful_close(bool sync = true);
void graceful_shutdown(bool sync = true);
优雅关闭————调用父类的同名函数。
在on_msg中,请以sync为false调用该函数,在其它所有service线程中,推荐也用sync为false调用该函数。
void show_info(const char* head, const char* tail) const;
在head和tail中间,显示对方的地址(IP加端口)。
......@@ -61,7 +61,6 @@ protected:
Server& server;
用于操控st_server,st_server在创建(其实是st_object_pool创建,st_server是其子类)st_server_socket的时候,会把自己的引用通过构造函数传入。
};
typedef st_server_socket_base<> st_server_socket;
} //namespace
#ifndef ST_ASIO_DEFAULT_PACKER
#define ST_ASIO_DEFAULT_PACKER packer
#ifdef ST_ASIO_ENHANCED_STABILITY
#if defined(ST_ASIO_DELAY_CLOSE) && ST_ASIO_DELAY_CLOSE != 0
#warning ST_ASIO_DELAY_CLOSE will always be zero if ST_ASIO_ENHANCED_STABILITY macro been defined.
#endif
#undef ST_ASIO_DELAY_CLOSE
#define ST_ASIO_DELAY_CLOSE 0
#else
#ifndef ST_ASIO_DELAY_CLOSE
#define ST_ASIO_DELAY_CLOSE 5 //seconds
#endif
static_assert(ST_ASIO_DELAY_CLOSE > 0, "ST_ASIO_DELAY_CLOSE must be bigger than zero.");
#endif
默认的打包器,也可以通过模板参数控制,提供这个宏可以让使用者省略相应的模板参数。
namespace st_asio_wrapper
{
......@@ -14,6 +23,21 @@ public:
所有时间统计项将无效(stat_duration)。在打开情况下,时间统计的数据类型其实是boost::posix_time::time_duration。
struct statistic
{
#ifdef ST_ASIO_FULL_STATISTIC
static bool enabled();
typedef boost::posix_time::ptime stat_time;
static stat_time local_time();
typedef boost::posix_time::time_duration stat_duration;
#else
struct dummy_duration;
struct dummy_time;
static bool enabled();
typedef dummy_time stat_time;
static stat_time local_time();
typedef dummy_duration stat_duration;
#endif
statistic();
void reset();
由于统计涉及多个方面,并且是多线程修改不同的部分,这个函数只是在某些特殊情况下才可以调用,比如在构造函数里面,或者只有一个service线程,
......@@ -42,8 +66,15 @@ public:
};
protected:
typedef boost::container::list<InMsgType> in_container_type;
typedef typename Unpacker::container_type out_container_type;
struct in_msg : public InMsgType;
struct out_msg : public OutMsgType;
在InMsgType或OutMsgType的基础上增加了一个stat_time,用于统计时间消耗
typedef boost::container::list<in_msg> in_container_type;
typedef boost::container::list<out_msg> out_container_type;
static const unsigned char TIMER_BEGIN = st_timer::TIMER_END;
static const unsigned char TIMER_END = TIMER_BEGIN+ 10;
st_socket(boost::asio::io_service& io_service_);
......@@ -58,9 +89,9 @@ ssl使用。
清空所有buffer。
public:
void id(uint_fast64_t id) {_id = id;}
void id(uint_fast64_t id);
uint_fast64_t id() const {return _id;}
uint_fast64_t id() const;
id的设置与获取,注意使用者不可设置id,只有st_socket的创建者(st_object_pool或者其继承者)才可设置id,
除非这个st_socket没有被任何对象池管理。
......@@ -154,6 +185,9 @@ protected:
virtual bool do_send_msg() = 0;
真正的消息发送(调用asio函数),具体怎么发请看st_tcp_socket和st_udp_socket的实现。
virtual bool is_closable();
当连接主动或者被被关闭时(shutdown),是否可以close它,只有close之后的对象才可以被释放或者被重用,默认返回true。
virtual bool is_send_allowed() const;
是否允许发送消息,对于st_socket来说,只要未暂停消息发送,就是允许消息发送,子类重写这个函数实现自己的判断逻辑,然后加上
st_socket的判断结果,最终确认是否可发送数据。请看st_tcp_socket、st_connector和st_udp_socket的实现。
......@@ -162,6 +196,9 @@ st_socket的判断结果,最终确认是否可发送数据。请看st_tcp_sock
virtual void on_recv_error(const boost::system::error_code& ec) = 0;
发送接收失败时回调,对于st_tcp_socket,如果需要连接断开事件,建议重写on_recv_error。
virtual void on_close();
当对象真正被close之前,会调用这个回调,用户可以在这里面释放资源,在这之后,对象可能会被重用或者被释放。
#ifndef ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER
virtual bool on_msg(OutMsgType& msg) = 0;
收到一条消息时回调,返回true表示消息被成功处理,返回false表示消息无法立即处理,于是进入接收缓存,通过on_msg_handle再次派发。
......@@ -182,6 +219,12 @@ st_socket的判断结果,最终确认是否可发送数据。请看st_tcp_sock
当发送缓存由非空变为空的时候回调,消息是打包过后的。
#endif
void close();
开启close流程,由继承者调用。st_socket会定时检测自己是否可以安全的被重用或被释放(即所有异步调用都已结束,包括正常结束和非正常结束),
如果是,调用上面的on_close(), 然后st_object_pool将完全接管这个st_socket,以便在适当的时候重用或者释放它。
如果定义了ST_ASIO_ENHANCED_STABILITY宏,则st_socket将保证以上说的行为,如果没有定义,则简单地在ST_ASIO_DELAY_CLOSE秒后,调用on_close(),
然后两样的道理,st_object_pool将完全接管这个st_socket,以便在适当的时候重用或者释放它。
void dispatch_msg();
派发消息,它要么直接调用on_msg,要么把消息放入消息接收缓存,最后调用do_dispatch_msg,如果消息处理完毕(调用on_msg)
或者都放入了消息接收缓存,则调用do_start以继续接收数据。
......@@ -206,8 +249,7 @@ protected:
Socket next_layer_;
前面在next_layer里面解释过了。
InMsgType last_send_msg;
OutMsgType last_dispatch_msg;
out_msg last_dispatch_msg;
由于是异步发送和派发消息,这两个成员变量保证其在异步处理过程中的有效性。
boost::shared_ptr<i_packer<MsgDataType>> packer_;
打包器。
......@@ -216,8 +258,8 @@ protected:
out_container_type recv_msg_buffer, temp_msg_buffer;
boost::shared_mutex post_msg_buffer_mutex, send_msg_buffer_mutex;
boost::shared_mutex recv_msg_buffer_mutex;
缓存及操作它们时需要的互斥体,访问temp_msg_buffer无需互斥,它只能在内部访问,作用是当收到消息之后,发现需要存入接收缓存
on_msg返回false),但接收缓存已满,那么多余的消息将被存放于temp_msg_buffer,并且不再继续接收消息,直到temp_msg_buffer
缓存及操作它们时需要的互斥体,访问temp_msg_buffer无需互斥,它只能在内部访问,作用是当收到消息之后,消息无法存入接收缓存
消息派发被暂停,或者正在post消息,即post_msg_buffer非空),那么消息将被存放于temp_msg_buffer,并且不再继续接收消息,直到temp_msg_buffer
里面的消息全部被处理掉,或者移到了recv_msg_buffer,st_socket会周期性的做以上尝试。
post_msg_buffer和send_msg_buffer里面存放的都是待发送的消息,通过send_msg发送的消息,只会进入send_msg_buffer,通过post_msg
发送的消息,如果发送缓存足够的话,会进入send_msg_buffer,如果不够的话,则会进入post_msg_buffer,当post_msg_buffer非空时,
......@@ -236,10 +278,19 @@ post_msg_buffer中的消息。
bool sending, suspend_send_msg_;
bool dispatching, suspend_dispatch_msg_;
内部使用的一些状态,看名字应该能猜到其意思。
#ifndef ST_ASIO_ENHANCED_STABILITY
bool closing;
#endif
正在closing,意思是正在周期性的检测当前st_socket是否可以被安全的重用或释放。如果未定义ST_ASIO_ENHANCED_STABILITY宏,则无法做任何检测,
只是简单的等待ST_ASIO_DELAY_CLOSE秒(异步的)。
bool started_;
boost::shared_mutex start_mutex;
是否已经开始,开始的概念由子类具体实现,st_socket只是记录是否已经调用过start函数而已。
struct statistic stat;
typename statistic::stat_time recv_idle_begin_time;
时间消耗统计。
};
} //namespace
......
......@@ -2,11 +2,8 @@
namespace st_asio_wrapper
{
typedef st_sclient<st_connector> st_tcp_sclient;
一个只支持一条连接的tcp客户端
支持多条连接的tcp客户端
template<typename Socket = st_connector, typename Pool = st_object_pool<Socket>>
template<typename Socket, typename Pool = st_object_pool<Socket>>
class st_tcp_client_base : public st_client<Socket, Pool>
{
public:
......@@ -23,7 +20,7 @@ ssl使用。
typename Pool::object_type add_client();
创建或者重用一个对象,然后以reset为true调用父类的add_client。注意未设地址,将使用SERVER_IP和SERVER_PORT
作为服务端地址,如果你要用别的地,请用下面那个add_client。
作为服务端地址,如果你要用别的地,请用下面那个add_client。
typename Pool::object_type add_client(unsigned short port, const std::string& ip = std::string());
创建或者重用一个对象,设置服务端地址,然后以reset为true调用父类的add_client。
......@@ -45,16 +42,19 @@ ssl使用。
void safe_broadcast_native_msg(const char* const pstr[], const size_t len[], size_t num, bool can_overflow = false);
对每一个连接调用st_tcp_socket中的同名函数。
void disconnect(typename Pool::object_ctype& client_ptr, bool reconnect = false);
void force_close(typename Pool::object_ctype& client_ptr, bool reconnect = false);
void graceful_close(typename Pool::object_ctype& client_ptr, bool reconnect = false, bool sync = true);
如果reconnect为假,则从对象池中删除,最后均调用client_ptr的同名函数。
void disconnect(typename Pool::object_ctype& client_ptr);
void disconnect(bool reconnect = false);
void force_shutdown(typename Pool::object_ctype& client_ptr);
void force_shutdown(bool reconnect = false);
void graceful_shutdown(typename Pool::object_ctype& client_ptr, bool sync = true);
void graceful_shutdown(bool reconnect = false, bool sync = true);
先从对象池中删除client_ptr,然后以默认参数调用client_ptr的同名函数。对于没有client_ptr参数的重载,对所有对象调用同名函数,参数直接传递。
那么如果我想以非默认参数调用client_ptr的同名函数怎么办呢,你可以直接通过client_ptr调用,这就没st_tcp_client什么事了。
protected:
virtual void uninit();
实现i_service的纯虚接口,由st_service_pump调用(stop_service时调用),跟st_sclient::uninit功能一样,只是对所有
客户端都做一个“结束”操作。
};
typedef st_tcp_client_base<> st_tcp_client;
} //namespace
#ifndef ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION
#define ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION 5 //seconds, maximum duration while graceful shutdown
#endif
namespace st_asio_wrapper
{
namespace st_tcp
{
tcp套接字类,实现tcp数据的收发
template <typename Socket, typename Packer, typename Unpacker>
......@@ -78,15 +80,18 @@ post_msg和send_msg的区别请看st_socket里面的post_msg_buffer和send_msg_b
同上,只是以native为true调用i_packer::pack_msg接口。
protected:
void force_close();
void graceful_close(bool sync = true);
前两个函数功能完全一样(为了完整性而提供了它们),都是直接调用clean_up
三个函数优雅关闭套接字,所谓优雅关闭,就是先关闭自己的数据发送,然后接收完遗留数据之后,才完全关闭套接字。当sync为假时,graceful_close马上返回,
优雅关闭将在后台继承进行,当回调到on_recv_error的时候,关闭结束(有可能优雅关闭成功,有可能超时被强制关闭)。
void force_shutdown();
void graceful_shutdown(bool sync = true);
第一个直接直接调用shutdown()
二个函数优雅关闭套接字,所谓优雅关闭,就是先关闭自己的数据发送,然后接收完遗留数据之后,才完全关闭套接字。当sync为假时,graceful_shutdown马上返回,
优雅关闭将在后台继承进行,当回调到on_recv_error的时候,关闭结束(有可能优雅关闭成功,有可能超时被强制关闭,超时由ST_ASIO_GRACEFUL_SHUTDOWN_MAX_DURATION宏决定)。
virtual bool do_send_msg();
马上开始消息发送,重写自st_socket的do_send_msg,由st_socket调用。
void do_recv_msg();
马上开始接收数据,由子类调用,因为st_tcp_socket不知道什么时候可以接收数据(比如是否连接成功等)。
virtual bool is_send_allowed() const;
重写st_socket的is_send_allowed,记住,自己的判断(is_closing)加上st_socket的判断,才是最终结果。
......@@ -101,11 +106,9 @@ protected:
virtual bool on_msg_handle(msg_type& msg, bool link_down);
重写st_socket的on_msg_handle,功能是打印消息到控制台,使用者重写这个函数以处理消息。
void do_recv_msg();
马上开始接收数据,由子类调用,因为st_tcp_socket不知道什么时候可以接收数据(比如是否连接成功等)。
void clean_up();
关闭套接字,停止所有定时器,直接派发所有剩余消息,重置所有状态(调用reset_state)。
void shutdown();
关闭套接字,停止所有定时器,直接派发所有剩余消息,最后启动一个定时器,如果定义了ST_ASIO_ENHANCED_STABILITY宏,则这个将周期性的检测
当前套接字是否可以安全地被重用或释放,如果未定义,则简单的在ST_ASIO_DELAY_CLOSE秒后认为当前大量接字可被安全地重用或释放。
void recv_handler(const error_code& ec, size_t bytes_transferred);
收到数据时后asio回调。
......@@ -114,12 +117,12 @@ protected:
成功发送消息(写入底层套接字)后由asio回调。
protected:
typename super::in_container_type last_send_msg;
boost::shared_ptr<i_unpacker<out_msg_type>> unpacker_;
int close_state; //2-the first step of graceful close, 1-force close, 0-normal state
int shutdown_state; //2-the first step of graceful close, 1-force close, 0-normal state
boost::shared_mutex shutdown_mutex;
让shutdown函数线程安全。
};
} //namespace st_tcp
} //namespace st_asio_wrapper
using namespace st_asio_wrapper::st_tcp;
兼容老版本。
......@@ -2,11 +2,8 @@
namespace st_asio_wrapper
{
typedef st_sclient<st_udp_socket> st_udp_sclient;
一个只支持一个套接字的udp客户端(也可以说是服务端,udp不区分服务端还是客户端)
支持多个套接字的udp客户端
template<typename Socket = st_udp_socket, typename Pool = st_object_pool<Socket>>
template<typename Socket, typename Pool = st_object_pool<Socket>>
class st_udp_client_base : public st_client<Socke, Pool>
{
public:
......@@ -18,15 +15,16 @@ public:
创建或者重用一个对象,然后以reset为true调用父类的add_client。
void disconnect(typename Pool::object_ctype& client_ptr);
void force_close(typename Pool::object_ctype& client_ptr);
void graceful_close(typename Pool::object_ctype& client_ptr);
从对象池中删除,然后调用client_ptr的同名函数。
void disconnect();
void force_shutdown(typename Pool::object_ctype& client_ptr);
void force_shutdown();
void graceful_shutdown(typename Pool::object_ctype& client_ptr);
void graceful_shutdown();
先从对象池中删除client_ptr,然后调用client_ptr的同名函数。对于没有client_ptr参数的重载,对所有对象调用同名函数。
protected:
virtual void uninit();
实现i_service的纯虚接口,由st_service_pump调用(stop_service时调用),跟st_sclient::uninit功能一样,只是对所有
客户端都做一个“结束”操作。
实现i_service的纯虚接口,由st_service_pump在stop_service时调用,跟st_sclient::uninit功能一样,只是对所有客户端都做一个“结束”操作。
};
typedef st_udp_client_base<> st_udp_client;
} //namespace
......@@ -4,18 +4,11 @@
#endif
绑定地址时,在不指定ip的情况下,指定ip地址的版本(v4还是v6),如果指定了ip,则ip地址的版本可以从ip中推导出来。
#ifndef ST_ASIO_DEFAULT_UDP_UNPACKER
#define ST_ASIO_DEFAULT_UDP_UNPACKER udp_unpacker
#endif
默认的解包器,也可以通过模板参数控制,提供这个宏可以让使用者省略相应的模板参数。
namespace st_asio_wrapper
{
namespace st_udp
{
udp套接字类,实现udp数据的收发
template <typename Packer = ST_ASIO_DEFAULT_PACKER, typename Unpacker = ST_ASIO_DEFAULT_UDP_UNPACKER, typename Socket = boost::asio::ip::udp::socket>
template <typename Packer, typename Unpacker, typename Socket = boost::asio::ip::udp::socket>
class st_udp_socket_base : public st_socket<Socket, Packer, Unpacker, udp_msg<typename Packer::msg_type>, udp_msg<typename Unpacker::msg_type>>
{
public:
......@@ -108,7 +101,7 @@ protected:
virtual bool on_msg_handle(out_msg_type& msg, bool link_down);
重写st_socket的on_msg_handle,功能是打印消息到控制台,使用者重写这个函数以处理消息。
void clean_up();
void shutdown();
关闭套接字,停止所有定时器,直接派发所有剩余消息,重置所有状态(调用reset_state)。
void recv_handler(const error_code& ec, size_t bytes_transferred);
......@@ -118,15 +111,14 @@ protected:
成功发送消息(写入底层套接字)后由asio回调。
protected:
typename super::in_msg last_send_msg;
boost::shared_ptr<i_udp_unpacker<typename Packer::msg_type>> unpacker_;
boost::asio::ip::udp::endpoint peer_addr, local_addr;
异步接收udp消息时,asio需要一个endpoint,在整个异步接收过程中,这个endpoint必须有效,所以它是一个成员变量,
它只代表上一次接收udp消息时的对端地址,对于已经接收到的udp消息,对端地方保存在out_msg_type里面。
它只代表上一次接收udp消息时的对端地址,对于已经接收到的udp消息,对端地址保存在out_msg_type里面。
boost::shared_mutex shutdown_mutex;
让shutdown函数线程安全。
};
typedef st_udp_socket_base<> st_udp_socket;
} //namespace st_udp
} //namespace st_asio_wrapper
using namespace st_asio_wrapper::st_udp;
兼容老版本。
......@@ -15,8 +15,7 @@
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_client.h"
#include "../st_asio_wrapper_connector.h"
#include "../st_asio_wrapper_tcp_client.h"
#ifndef ST_ASIO_DEFAULT_PACKER
......
......@@ -15,7 +15,7 @@
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_server_socket.h"
#include "../st_asio_wrapper_server.h"
#ifndef ST_ASIO_DEFAULT_PACKER
......
......@@ -13,10 +13,9 @@
#ifndef ST_ASIO_WRAPPER_EXT_SSL_H_
#define ST_ASIO_WRAPPER_EXT_SSL_H_
#include "../st_asio_wrapper_ssl.h"
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_ssl.h"
#ifndef ST_ASIO_DEFAULT_PACKER
#define ST_ASIO_DEFAULT_PACKER packer
......
......@@ -15,8 +15,7 @@
#include "st_asio_wrapper_packer.h"
#include "st_asio_wrapper_unpacker.h"
#include "../st_asio_wrapper_client.h"
#include "../st_asio_wrapper_udp_socket.h"
#include "../st_asio_wrapper_udp_client.h"
#ifndef ST_ASIO_DEFAULT_PACKER
......
......@@ -66,11 +66,11 @@
* Strip boost::bind and boost::ref for standard edition.
*
* known issues:
* 1. On boost-1.49, st_object::is_last_async_call() cannot work properly, it is because before asio call any callbacks, it copied the callback(not a
* good behaviour), this cause st_object::is_last_async_call() never return true, so objects in st_object_pool can never be reused or freed.
* 1. On boost-1.49, compatible edition's st_object::is_last_async_call() cannot work properly, it is because before asio calling any callbacks, it copied
* the callback(not a good behaviour), this cause st_object::is_last_async_call() never return true, so objects in st_object_pool can never be reused or freed.
* To fix this issue, we must not define ST_ASIO_ENHANCED_STABILITY macro.
* BTW, boost-1.61 doesn't have such issue, I'm not sure in which edition, asio fixed this defect, if you have other versions, please help me to
* find out the minimum version via the following steps:
* BTW, boost-1.61 and standard editon even with boost-1.49 don't have such issue, I'm not sure in which edition, asio fixed this defect,
* if you have other versions, please help me to find out the minimum version via the following steps:
* 1. Compile demo asio_server and test_client;
* 2. Run asio_server and test_client without any parameters;
* 3. Stop test_client (input 'quit');
......
......@@ -45,7 +45,6 @@ static_assert(ST_ASIO_MAX_MSG_NUM > 0, "message capacity must be bigger than zer
#if defined _MSC_VER
#define ST_ASIO_SF "%Iu"
#define ST_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 ST_ASIO_SF "%zu"
#define ST_THIS this->
......@@ -53,6 +52,16 @@ static_assert(ST_ASIO_MAX_MSG_NUM > 0, "message capacity must be bigger than zer
namespace st_asio_wrapper
{
class st_service_pump;
class st_timer;
class i_server
{
public:
virtual st_service_pump& get_service_pump() = 0;
virtual const st_service_pump& get_service_pump() const = 0;
virtual bool del_client(const boost::shared_ptr<st_timer>& client_ptr) = 0;
};
class i_buffer
{
public:
......
......@@ -83,6 +83,9 @@ public:
}
//sync must be false if you call graceful_shutdown in on_msg
//furthermore, you're recommended to call this function with sync equal to false in all service threads,
//all callbacks will be called in service threads.
//this function is not thread safe, please note.
void graceful_shutdown(bool reconnect = false, bool sync = true)
{
if (ST_THIS is_shutting_down())
......@@ -171,7 +174,7 @@ protected:
}
private:
bool async_shutdown_handler(unsigned char id, ssize_t loop_num)
bool async_shutdown_handler(unsigned char id, size_t loop_num)
{
assert(TIMER_ASYNC_SHUTDOWN == id);
......
......@@ -29,25 +29,25 @@ public:
#ifdef ST_ASIO_ENHANCED_STABILITY
template<typename CallbackHandler>
void post(const CallbackHandler& handler) {auto unused(ST_THIS async_call_indicator); io_service_.post([=]() {handler();});}
void post(const CallbackHandler& handler) {auto unused(async_call_indicator); io_service_.post([=]() {handler();});}
template<typename CallbackHandler>
void post(CallbackHandler&& handler) {auto unused(ST_THIS async_call_indicator); io_service_.post([=]() {handler();});}
void post(CallbackHandler&& handler) {auto unused(async_call_indicator); io_service_.post([=]() {handler();});}
bool is_async_calling() const {return !async_call_indicator.unique();}
bool is_last_async_call() const {return async_call_indicator.use_count() <= 2;} //can only be called in callbacks
template<typename CallbackHandler>
std::function<void(const boost::system::error_code&)> make_handler_error(CallbackHandler&& handler) const
{boost::shared_ptr<char>unused(async_call_indicator); return [=](const boost::system::error_code& ec) {handler(ec);};}
{auto unused(async_call_indicator); return [=](const boost::system::error_code& ec) {handler(ec);};}
template<typename CallbackHandler>
std::function<void(const boost::system::error_code&)> make_handler_error(const CallbackHandler& handler) const
{boost::shared_ptr<char>unused(async_call_indicator); return [=](const boost::system::error_code& ec) {handler(ec);};}
{auto unused(async_call_indicator); return [=](const boost::system::error_code& ec) {handler(ec);};}
template<typename CallbackHandler>
std::function<void(const boost::system::error_code&, size_t)> make_handler_error_size(CallbackHandler&& handler) const
{boost::shared_ptr<char>unused(async_call_indicator); return [=](const boost::system::error_code& ec, size_t bytes_transferred) {handler(ec, bytes_transferred);};}
{auto unused(async_call_indicator); return [=](const boost::system::error_code& ec, size_t bytes_transferred) {handler(ec, bytes_transferred);};}
template<typename CallbackHandler>
std::function<void(const boost::system::error_code&, size_t)> make_handler_error_size(CallbackHandler& handler) const
{boost::shared_ptr<char>unused(async_call_indicator); return [=](const boost::system::error_code& ec, size_t bytes_transferred) {handler(ec, bytes_transferred);};}
{auto unused(async_call_indicator); return [=](const boost::system::error_code& ec, size_t bytes_transferred) {handler(ec, bytes_transferred);};}
protected:
void reset() {async_call_indicator = boost::make_shared<char>('\0');}
......
......@@ -13,7 +13,6 @@
#ifndef ST_ASIO_WRAPPER_SERVER_H_
#define ST_ASIO_WRAPPER_SERVER_H_
#include "st_asio_wrapper_server_socket.h"
#include "st_asio_wrapper_object_pool.h"
#ifndef ST_ASIO_SERVER_PORT
......@@ -92,7 +91,7 @@ public:
void disconnect(typename Pool::object_ctype& client_ptr) {ST_THIS del_object(client_ptr); client_ptr->disconnect();}
void force_shutdown(typename Pool::object_ctype& client_ptr) {ST_THIS del_object(client_ptr); client_ptr->force_shutdown();}
void graceful_shutdown(typename Pool::object_ctype& client_ptr, bool sync = true) {ST_THIS del_object(client_ptr); client_ptr->graceful_shutdown(sync);}
void graceful_shutdown(typename Pool::object_ctype& client_ptr, bool sync = false) {ST_THIS del_object(client_ptr); client_ptr->graceful_shutdown(sync);}
protected:
virtual bool init()
......
......@@ -13,20 +13,11 @@
#ifndef ST_ASIO_WRAPPER_SERVER_SOCKET_H_
#define ST_ASIO_WRAPPER_SERVER_SOCKET_H_
#include "st_asio_wrapper_service_pump.h"
#include "st_asio_wrapper_tcp_socket.h"
namespace st_asio_wrapper
{
class i_server
{
public:
virtual st_service_pump& get_service_pump() = 0;
virtual const st_service_pump& get_service_pump() const = 0;
virtual bool del_client(const boost::shared_ptr<st_timer>& client_ptr) = 0;
};
template<typename Packer, typename Unpacker, typename Server = i_server, typename Socket = boost::asio::ip::tcp::socket>
class st_server_socket_base : public st_tcp_socket_base<Socket, Packer, Unpacker>, public boost::enable_shared_from_this<st_server_socket_base<Packer, Unpacker, Server, Socket>>
{
......@@ -56,7 +47,11 @@ public:
super::force_shutdown();
}
void graceful_shutdown(bool sync = true)
//sync must be false if you call graceful_shutdown in on_msg
//furthermore, you're recommended to call this function with sync equal to false in all service threads,
//all callbacks will be called in service threads.
//this function is not thread safe, please note.
void graceful_shutdown(bool sync = false)
{
if (!ST_THIS is_shutting_down())
show_info("server link:", "being shut down gracefully.");
......@@ -104,12 +99,11 @@ protected:
#else
server.del_client(boost::dynamic_pointer_cast<st_timer>(ST_THIS shared_from_this()));
#endif
ST_THIS shutdown_state = 0;
}
private:
bool async_shutdown_handler(unsigned char id, ssize_t loop_num)
bool async_shutdown_handler(unsigned char id, size_t loop_num)
{
assert(TIMER_ASYNC_SHUTDOWN == id);
......
......@@ -16,7 +16,9 @@
#include <boost/asio/ssl.hpp>
#include "st_asio_wrapper_object_pool.h"
#include "st_asio_wrapper_connector.h"
#include "st_asio_wrapper_tcp_client.h"
#include "st_asio_wrapper_server_socket.h"
#include "st_asio_wrapper_server.h"
#ifdef ST_ASIO_REUSE_OBJECT
......
......@@ -13,7 +13,6 @@
#ifndef ST_ASIO_WRAPPER_TCP_CLIENT_H_
#define ST_ASIO_WRAPPER_TCP_CLIENT_H_
#include "st_asio_wrapper_connector.h"
#include "st_asio_wrapper_client.h"
namespace st_asio_wrapper
......
......@@ -169,6 +169,8 @@ protected:
void shutdown()
{
boost::unique_lock<boost::shared_mutex> lock(shutdown_mutex);
shutdown_state = 1;
ST_THIS stop_all_timer();
ST_THIS close(); //must after stop_all_timer(), it's very important
......@@ -251,6 +253,8 @@ protected:
typename super::in_container_type last_send_msg;
boost::shared_ptr<i_unpacker<out_msg_type>> unpacker_;
int shutdown_state; //2-the first step of graceful shutdown, 1-force shutdown, 0-normal state
boost::shared_mutex shutdown_mutex;
};
} //namespace
......
......@@ -13,7 +13,6 @@
#ifndef ST_ASIO_WRAPPER_UDP_CLIENT_H_
#define ST_ASIO_WRAPPER_UDP_CLIENT_H_
#include "st_asio_wrapper_udp_socket.h"
#include "st_asio_wrapper_client.h"
namespace st_asio_wrapper
......@@ -53,4 +52,4 @@ protected:
} //namespace
#endif /* ST_ASIO_WRAPPER_TEST_CLIENT_H_ */
#endif /* ST_ASIO_WRAPPER_UDP_CLIENT_H_ */
......@@ -138,8 +138,8 @@ protected:
ST_THIS send_msg_buffer.front().swap(last_send_msg);
ST_THIS send_msg_buffer.pop_front();
boost::shared_lock<boost::shared_mutex> lock(shutdown_mutex);
last_send_msg.restart();
boost::shared_lock<boost::shared_mutex> lock(shutdown_mutex);
ST_THIS next_layer().async_send_to(boost::asio::buffer(last_send_msg.data(), last_send_msg.size()), last_send_msg.peer_addr,
ST_THIS make_handler_error_size([this](const boost::system::error_code& ec, size_t bytes_transferred) {ST_THIS send_handler(ec, bytes_transferred);}));
}
......@@ -174,6 +174,8 @@ protected:
void shutdown()
{
boost::unique_lock<boost::shared_mutex> lock(shutdown_mutex);
ST_THIS stop_all_timer();
ST_THIS close(); //must after stop_all_timer(), it's very important
ST_THIS started_ = false;
......@@ -183,8 +185,6 @@ protected:
{
boost::system::error_code ec;
ST_THIS lowest_layer().shutdown(boost::asio::ip::udp::socket::shutdown_both, ec);
boost::unique_lock<boost::shared_mutex> lock(shutdown_mutex);
ST_THIS lowest_layer().close(ec);
}
}
......
......@@ -291,22 +291,26 @@ int main(int argc, const char* argv[])
n = 1;
if ('+' == str[0])
for (; n > 0 && client.add_client(port, ip); ++link_num, --n);
for (; n > 0 && client.add_client(port, ip); --n);
else
{
if (n > client.size())
n = client.size();
client.shutdown_some_client(n);
link_num = client.size();
}
link_num = client.size();
continue;
}
#ifdef ST_ASIO_CLEAR_OBJECT_INTERVAL
link_num = client.size();
printf("link number: " ST_ASIO_SF "\n", link_num);
if (link_num != client.valid_size())
{
puts("please wait for a while, because st_object_pool has not cleaned up invalid links.");
continue;
}
#endif
size_t msg_num = 1024;
size_t msg_len = 1024; //must greater than or equal to sizeof(size_t)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册