【C++】BOOST ASIO 异步服务端代码分析

参考资料:《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时间。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章