在網絡通信中,我個人比較喜歡異步的方式。這樣我程序就不會因為I/O的讀寫而導致線程阻塞。理想的工作方式是通知窗口的事件通知。windows中socket的底層其實是支持窗口事件通知的,但由於boost庫比較強大,我就基於asio的庫來實現這樣的機制。
由於是異步方式,當事件處理完成后,我希望將結果傳遞給回調函數,因此類中有下面3個函數:
virtual void handler_connect(const boost::system::error_code& error); virtual void handler_send(const boost::system::error_code& error); virtual void handler_receive(const boost::system::error_code& error);
參數傳遞過來的只是是否有錯誤。
在主線程中,我只需要調用
void connect(); template<typename ConstBufferSequence> void send(const ConstBufferSequence& buffers); template<typename MutableBufferSequence> void receive(const MutableBufferSequence& buffers)
這三個函數即可,處理完成后會自動調用上面的回調函數,使用回調函數來處理結果。
asio中io_service的run會一直阻塞線程,所以需要將run在輔助線程中運行,但這樣的話,回調函數就會在輔助線程中執行,為了保證線程安全性和消除MFC中不同線程執行后的代碼異常,我需要將回調函數轉入main線程中執行,這就應用了SendMessage函數了通知主線程窗口,下面詳細的代碼:
#define WM_ASIO_MESSAGE (WM_USER + 1) using boost::asio::ip::tcp; using boost::asio::deadline_timer; template <typename CWinWnd> class Win32TcpClient { typedef Win32TcpClient<CWinWnd> MyClassName; typedef void (MyClassName::*handler_ptr)(const boost::system::error_code&); public: Win32TcpClient(boost::asio::io_service& ios,tcp::endpoint endpoint,CWinWnd* pWinWnd) :io_service_(ios),win_wnd_(pWinWnd),socket_(ios), endpoint_(endpoint),deadline_(ios),stop_(false),connect_timeout_(TIME_INF),handler_ptr_(NULL) { set_timeout(connect_timeout_); } enum {TIME_INF = -1}; void connect() { deadline_.async_wait(boost::bind(&MyClassName::check_deadline, this)); socket_.async_connect(endpoint_, boost::bind(&MyClassName::run_main_thread_handler,this,&MyClassName::handler_connect, boost::asio::placeholders::error)); } void set_timeout(int timeout_seconds) { if (timeout_seconds == TIME_INF) deadline_.expires_at(boost::posix_time::pos_infin); else deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds)); } void close() { stop_ = true; socket_.close(); } template<typename ConstBufferSequence> void send(const ConstBufferSequence& buffers) { boost::asio::async_write(socket_,buffers, boost::bind(&MyClassName::run_main_thread_handler,this,&MyClassName::handler_send, boost::asio::placeholders::error)); } template<typename MutableBufferSequence> void receive(const MutableBufferSequence& buffers) { boost::asio::async_read(socket_,buffers, boost::bind(&MyClassName::run_main_thread_handler, this,&MyClassName::handler_receive, boost::asio::placeholders::error)); } void win_proc() { if(handler_ptr_ != NULL) (this->*handler_ptr_)(error_code_); } virtual void handler_connect(const boost::system::error_code& error){} virtual void handler_send(const boost::system::error_code& error){} virtual void handler_receive(const boost::system::error_code& error){} private: void run_main_thread_handler(handler_ptr handler, const boost::system::error_code& error) { handler_ptr_ = handler; error_code_ = error; ::SendMessage(win_wnd_->m_hWnd,WM_ASIO_MESSAGE,NULL,(LPARAM)this); } void check_deadline() { if(stop_)return; if (deadline_.expires_at() <= deadline_timer::traits_type::now()) { close(); deadline_.expires_at(boost::posix_time::pos_infin); } deadline_.async_wait(boost::bind(&MyClassName::check_deadline, this)); } protected: CWinWnd* win_wnd_; boost::asio::io_service& io_service_; tcp::socket socket_; tcp::endpoint endpoint_; deadline_timer deadline_; bool stop_; std::size_t connect_timeout_; handler_ptr handler_ptr_; boost::system::error_code error_code_; };
實際使用時,可以從上面的類中繼承:
class CollectClient : public Win32TcpClient<CTestBoostTimerDlg> { public: CollectClient(boost::asio::io_service& ios,tcp::endpoint endpoint,CTestBoostTimerDlg* pWinWnd):Win32TcpClient<CTestBoostTimerDlg>(ios,endpoint,pWinWnd){ memset(receive_buffer,0,100);} virtual void handler_connect(const boost::system::error_code& error); virtual void handler_send(const boost::system::error_code& error); virtual void handler_receive(const boost::system::error_code& error); private: char receive_buffer[200] ; }; void CollectClient::handler_connect( const boost::system::error_code& error ) { char sendMessage[] = "0020abcdefghijklsdkjaslk"; if (!error) { win_wnd_->MessageBox("success"); receive(boost::asio::buffer(receive_buffer,30)); send(boost::asio::buffer(sendMessage,strlen(sendMessage))); } else { win_wnd_->MessageBox("fail"); if(!stop_) close(); } } void CollectClient::handler_send( const boost::system::error_code& error ) { if(!error) { //win_wnd_->MessageBox("send success"); } //close(); } void CollectClient::handler_receive( const boost::system::error_code& error ) { if(!error) { win_wnd_->MessageBox(receive_buffer); receive(boost::asio::buffer(receive_buffer,30)); } else { win_wnd_->MessageBox(error.message().c_str()); } }
窗口類中需要添加處理代碼是:
BEGIN_MESSAGE_MAP(CTestBoostTimerDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_MESSAGE(WM_ASIO_MESSAGE,&CTestBoostTimerDlg::OnAsioProc) END_MESSAGE_MAP()
紅色為自行添加的消息處理。
OnAsioProc的代碼也是基本固定的。
聲明:
afx_msg LRESULT OnAsioProc(WPARAM wParam,LPARAM lParam);
實現:
LRESULT CTestBoostTimerDlg::OnAsioProc( WPARAM wParam,LPARAM lParam ) { CollectClient* pc = (CollectClient*)lParam; pc->win_proc(); return 0; }
在dialog的初始化OnInitDialog中
boost::asio::ip::tcp::endpoint endpoint( boost::asio::ip::address::from_string("127.0.0.1"), 830); pClient = new CollectClient(ios, endpoint,this); pClient->connect();
這里的pClient和ios可以聲明為全局變量,或者聲明在窗口類中。
boost::asio::io_service ios;
CollectClient* pClient;
為了是異步工作起來,需要啟動另一個線程!
boost::thread th(boost::bind(&io_service::run,&ios));
th.detach();
ok,這樣的client就可以應用了,關於網絡的連接我們其實僅抽象了6個函數connect, handler_connect, send, handler_send, receive, handler_receive。
還有比較有用的是
set_timeout 這個函數設置超時的秒數。到這個時間后,socket會自動關閉,默認為inf,不會超時。
endpoint_ 該變量可查看連接的ip地址和端口號。
win_wnd_ 該變量為窗口類的指針,可以針對窗口做一系列的操作。