参考资料:《Boost Asio C++网络编程》第四章
这里将对asio库编写的异步服务端进行解读。异步服务端复杂很多。同步服务端简单来说就是轮询,一般用于业务测试。
主函数启动
配置环境与客户端一致。主函数:
io_context service;
ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(), 8001));
class talk_to_client;
typedef boost::shared_ptr<talk_to_client> client_ptr;
typedef std::vector<client_ptr> array;
array clients;
int main(int argc, char* argv[]) {
talk_to_client::ptr client = talk_to_client::new_();
acceptor.async_accept(client->sock(), boost::bind(handle_accept, client, _1));
service.run();
}
service就是asio::io_context。talk_to_client::new_()是静态成员函数,创建一个智能指针管理的talk_to_client对象。acceptor是服务端独有的类,用于监听ip::tcp::socket sock的连接请求,随后调用回调函数handle_accept。
void handle_accept(talk_to_client::ptr client, const boost::system::error_code & err) {
client->start();
talk_to_client::ptr new_client = talk_to_client::new_();
acceptor.async_accept(new_client->sock(), boost::bind(handle_accept, new_client, _1));
}
handle_accept函数启动服务端类,然后又新建一个服务端对象,继续由acceptor进行异步监听,这样保证有N个服务端对象在工作,至少有一个服务端处于监听状态。async_accept执行完毕后退出handle_accept函数,当有新连接的时候才再次调用handle_accept函数,这里不是递归调用,没有爆栈的危险。
客户端类
属性
class talk_to_client : public boost::enable_shared_from_this<talk_to_client>
, boost::noncopyable {
和上一篇blog讲述的客户端类一样,用shared_ptr<talk_to_client>(this) 代替成员函数第一个参数this;对象不能拷贝。
启动
void start() {
started_ = true;
clients.push_back(shared_from_this());
last_ping = boost::posix_time::microsec_clock::local_time();
// first, we wait for client to login
do_read();
}
这里要把自身的智能指针放入vector容器中进行管理。然后进入读取操作,调用async_read进入异步等待。主要工作与上一篇blog的客户端一样,不断地进行读取—》解码—》处理----》写入结果----》重复或者关闭。
关闭
void stop() {
XXX;//
sock_.close();
ptr self = shared_from_this();
array::iterator it = std::find(clients.begin(), clients.end(), self);
clients.erase(it);
XXX;//
}
在关闭ip::tcp::socket sock之后,没有任何async操作包含shared_ptr<talk_to_client>指针;再将自身从vector<shared_ptr<talk_to_client> >列表中去除。当退出stop函数后,当前服务端对象智能指针再也没有被引用,于是被析构。当然,一个连接就要新建和析构一次很不值得,需要提升并发性能的时候,sock可以被放入一个小对象中,然后嵌入一个大的工作类,反复利用。
断开无用连接
在这个例子中,服务端通过检测客户端是否在5秒内进行过操作,判断连接是否有效而决定是否关闭。
#define MEM_FN2(x,y,z) boost::bind(&self_type::x, shared_from_this(),y,z)
void do_write(const std::string & msg) {
XXX;//工作
sock_.async_write_some(buffer(write_buffer_, msg.size()),
MEM_FN2(on_write, _1, _2));
}
void on_write(const error_code & err, size_t bytes) {
XXX;//工作
do_read();
}
void do_read() {
async_read(sock_, buffer(read_buffer_),
MEM_FN2(read_complete, _1, _2), MEM_FN2(on_read, _1, _2));
post_check_ping();
}
服务端用do_write函数向客户端发送数据,发送完毕之后调用on_write函数进行收尾工作,on_write函数最后又立刻调用do_read函数进入新一轮的异步读取等待。do_read在进入异步读取等待的同时,调用post_check_ping函数,对读取等待时间进行计时。
deadline_timer timer_;
void post_check_ping() {
timer_.expires_from_now(boost::posix_time::millisec(5000));
timer_.async_wait(MEM_FN(on_check_ping));
}
void on_check_ping() {
boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
if ((now - last_ping).total_milliseconds() > 5000) {
std::cout << "stopping " << username_ << " - no ping in time" << std::endl;
stop();
}
last_ping = boost::posix_time::microsec_clock::local_time();
}
deadline_timer是一个非阻塞异步计时器,expires_from_now函数执行时,立即调用回调函数on_check_ping,并重新设置超时触发时间,然后async_wait进入异步等待。如果超时5秒没有重进入do_read函数,调用on_check_ping函数。on_check_ping会检查读取等待时间,然后是否调用stop函数关闭连接。注意这里每次do_read进入读取等待时,都会调用expires_from_now函数立即触发on_check_ping函数,从而刷新last_ping时间。