參考資料:《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時間。