【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對象,回調函數),有數據就全部讀滿緩衝區。

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