先說下上一篇文章中提到的保持io_service::run不退出的簡單辦法。因為只要異步事件隊列中有事件,io_service::run就會一直阻塞不退出,所以只要保證異步事件隊列中一直有事件就行了,如何讓異步事件隊列中一直有事件呢?一個簡單的辦法就是循環發起異步讀操作,如果對方一直都不發數據過來,則這個異步讀事件就會一直在異步事件隊列中,這樣io_service::run就不會退出了。但是這樣有一個缺點就是io_service::run處於阻塞會阻塞當前線程,如果不希望阻塞當前線程,就還是通過work來保持io_service::run不退出。
現在言歸正傳,看看如何用asio寫一個簡單的客戶端。我希望這個客戶端具備讀寫能力,還能自動重連。我希望用一個連接器類去實現連接以及IO事件的處理,連接器具體的職責有三個:1.連接到服務器;2.重連。3.通過事件處理器實現讀寫。其中,實現重連可以用一個專門的線程去檢測,為了簡單,不設置重連次數,保持一直重連。實現讀寫可以直接用上篇中的RWHandler。看看連接器Connctor是如何寫的:
class Connector { public: Connector(io_service& ios, const string& strIP, short port) :m_ios(ios), m_socket(ios), m_serverAddr(tcp::endpoint(address::from_string(strIP), port)), m_isConnected(false), m_chkThread(nullptr) { CreateEventHandler(ios); } ~Connector() { } bool Start() { m_eventHandler->GetSocket().async_connect(m_serverAddr, [this](const boost::system::error_code& error) { if (error) { HandleConnectError(error); return; } cout << "connect ok" << endl; m_isConnected = true; m_eventHandler->HandleRead(); //連接成功后發起一個異步讀的操作 }); boost::this_thread::sleep(boost::posix_time::seconds(1)); return m_isConnected; } bool IsConnected() const { return m_isConnected; } void Send(char* data, int len) { if (!m_isConnected) return; m_eventHandler->HandleWrite(data, len); } void AsyncSend(char* data, int len) { if (!m_isConnected) return; m_eventHandler->HandleAsyncWrite(data, len); } private: void CreateEventHandler(io_service& ios) { m_eventHandler = std::make_shared<RWHandler>(ios); m_eventHandler->SetCallBackError([this](int connid){HandleRWError(connid); }); } void CheckConnect() { if (m_chkThread != nullptr) return; m_chkThread = std::make_shared<std::thread>([this] { while (true) { if (!IsConnected()) Start(); boost::this_thread::sleep(boost::posix_time::seconds(1)); } }); } void HandleConnectError(const boost::system::error_code& error) { m_isConnected = false; cout << error.message() << endl; m_eventHandler->CloseSocket(); CheckConnect(); } void HandleRWError(int connid) { m_isConnected = false; CheckConnect(); } private: io_service& m_ios; tcp::socket m_socket; tcp::endpoint m_serverAddr; //服務器地址 std::shared_ptr<RWHandler> m_eventHandler; bool m_isConnected; std::shared_ptr<std::thread> m_chkThread; //專門檢測重連的線程 };
注意看連接成功之后,我發起了一個異步讀操作,它的作用除了接收數據之外,還可以用來判斷連接是否斷開,因為當連接斷開時,異步接收事件會觸發,據此可以做重連操作。可以看到,在連接失敗后,或者讀寫發生錯誤之后我會關閉連接然后開始自動重連。
再看看測試代碼:
int main() { io_service ios; boost::asio::io_service::work work(ios); boost::thread thd([&ios]{ios.run(); }); Connector conn(ios, "127.0.0.1", 9900); conn.Start(); if (!conn.IsConnected()) { Pause(); return -1; } const int len = 512; char buf[len] = ""; while (std::cin.getline(line, sizeof(line))) { conn.Send(line, sizeof(line)); } return 0; }
注意看我是通過work和一個專門的線程的線程去run保持io_service不退出的。 至此,一個簡單的客戶端完成了。 不過,我並沒有提到如何異步發送,異步發送稍微麻煩一點,它涉及發送隊列以及如何異步循環發送的問題,為了簡單起見就沒有專門去講它,也許后面會用專門的篇幅去講異步發送。一般情況下同步發送足夠了,如果希望更高的發送效率,可以考慮半同步半異步的線程池去發送,以提高效率。
如果你覺得這篇文章對你有用,可以點一下推薦,謝謝。
c++11 boost技術交流群:296561497,歡迎大家來交流技術。