【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時間。

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