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

参考资料:《Boost Asio C++网络编程》第五章
异步多线程服务端是可实际运用服务端软件的最低要求,其用于解决如下基本问题:并发服务中,有用户连接需要服务端执行耗时操作,同时不影响其他用户连接工作。
一个显然的想法是,为每个耗时用户连接开启一个工作线程进行耗时操作,在线程工作时不阻塞主线程,线程结束后执行异步回调函数。如果我们直接在工作线程末尾调用回调函数,那么回调函数会在工作线程中而不是用户连接所在的主线程中执行,会造成多线程编程问题,需要严谨的使用锁机制。如果我们希望工作线程结束后调用主线程来执行回调函数,那么我们需要使用类似windows编程中的消息队列机制,这就是后话了。
Asio提供的io_context天然提供了便利的多线程机制用于解决第一段描述的基本问题,解决方案类似第二段的第一种情形。io_context是线程安全的,一个io_context对象可以在多线程中执行,其达成的效果是:io_context会在当前线程中执行一次异步操作,当前线程启动回调函数,然后在当前线程往下运行的时候,紧接着又去下一个线程执行一次异步操作,又触发一次回调。但注意每个线程中执行service.run()启动回调函数的时候,其他线程的service是不工作的,被阻塞了;除非我们在每个线程中都给一个单独的service。

启动多线程
boost::thread_group threads;
void listen_thread() {
	service.run();
}
void start_listen(int thread_count) {
	for (int i = 0; i < thread_count; ++i)
		threads.create_thread(listen_thread);
}
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();
	start_listen(10);
	threads.join_all();

我们不需要将特定的回调函数放入线程中,不同线程中service.run()会自然调用绑定的回调函数,相当简单了。

boost::recursive_mutex clients_cs;
mutable talk_to_client::boost::recursive_mutex cs_;

需要构建一个全局锁和服务端类的本地锁才能完成任务。

class talk_to_client;
typedef boost::shared_ptr<talk_to_client> client_ptr;
typedef std::vector<client_ptr> array;
array clients;

void talk_to_client::start() {
		started_ = true;
		{
			boost::recursive_mutex::scoped_lock lk(clients_cs);
			clients.push_back(shared_from_this());
		}
		boost::recursive_mutex::scoped_lock lk(clients_cs);
		last_ping = boost::posix_time::microsec_clock::local_time();
		// first, we wait for client to login
		std::cout << "new connection" << std::endl;
		do_read();
	}
void stop() {
	{
		boost::recursive_mutex::scoped_lock lk(cs_);
		if (!started_) return;
		started_ = false;
		sock_.close();
	}
	{
		boost::recursive_mutex::scoped_lock lk(clients_cs);
		ptr self = shared_from_this();
		array::iterator it = std::find(clients.begin(), clients.end(), self);
		clients.erase(it);
	}
}

类成员函数start和stop调用了两个锁。操作全局变量clients,需要锁定全局锁,所有线程都应该同步这个操作。只操作了自身变量和调用成员函数,需要锁定本地锁,涉及该对象工作的线程都应该同步。其他成员函数,尤其涉及写操作的,都应该注意锁定本地锁。

多线程工作测试

void on_read(const error_code & err, size_t bytes) {
		if (err) stop();
		if (!started()) return;
		// process the msg
		boost::recursive_mutex::scoped_lock lk(cs_);
		std::string msg(read_buffer_, bytes);
		std::cout << msg << std::endl;
		if (msg == "request 1\n") {
			boost::this_thread::sleep(boost::posix_time::millisec(5000));
			do_write("server respond 1\n");
		}
		if (msg == "request 2\n") {
			boost::this_thread::sleep(boost::posix_time::millisec(1000));
			do_write("server respond 2\n");
		}
	}

我启动了两个客户端,第一个客户端的申请,会被服务端识别,并进入5秒的sleep状态,模仿耗时阻塞操作;第二个客户端的申请,则停留1秒钟就回复。结果如图,说明两个客户端提出的工作都被并发处理:
在这里插入图片描述

其他坑

异步读取有两种api:async_read和async_read_some。
前者是一个普通函数,参数列表为async_read(sock对象,buffer对象,读取结束状态判别函数,回调函数)。async_read还有个重载函数,不需要读取结束状态判别函数这个参数,第四个参数会决定如何读取,如果不写入第四个参数,那么默认为0,等价于不读取任何数据。
async_read_some是socket的成员函数,常用参数列表为async_read(buffer对象,回调函数),有数据就全部读满缓冲区。

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