CAsyncSocket是MFC中對WSAAsyncSelect異步非阻塞通知IO的1個封裝類。我們在《Windows下使用WSAAsyncSelect實現窗口處理socket消息》1文中討論過WSAAsyncSelect的用法,知道它綁定1個窗口到1個socket,并注冊了我們自定義的消息和需要監視的IO事件類型(FD_ACCEPT、FD_READ、FD_WRITE等等)。當綁定的socket產生注冊的IO事件后,操作系統會給上述窗口發送我們自定義的消息,而接下來我們就能夠針對事件類型而做不同的處理。CAsyncSocket就是將WSAAsyncSelect進行了封裝,內部使用了1個不可見的窗口并隱藏了注冊綁定進程,其內部實現原理和WSAAsyncSelect的使用相同。
下面主要討論下CAsyncSocket的用法,我們要實現的效果是,創建1個都帶界面的服務端和客戶端。客戶端允許用戶輸入字符串,然后發送給服務端端,服務端接收客戶真個字符串后原樣返回,在客戶端界面上顯示。
CAsyncSocket主要包括以下成員:
BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );
我們用Create創建1個異步socket對象,在Create的參數中我們可以指定端口號、協議類型、注冊的事件類型、IP地址。如果你不想傳參,Create提供了默許形參;如果你想要修改,可以使用CAsyncSocket提供的Bind、AsyncSelect等接口進行修改。
BOOL Listen( int nConnectionBacklog = 5 );
用來開啟監聽。
virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL );
用來接收客戶端連接
BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );
用來連接服務端。
virtual void OnAccept( int nErrorCode ); virtual void OnClose ( int nErrorCode ); virtual void OnConnect ( int nErrorCode ); virtual void OnReceive ( int nErrorCode ); virtual void OnSend ( int nErrorCode );
上述函數都是系統的回調函數,在socket產生相應的IO事件時進行調用。它們都被定義成了虛函數,需要我們進行繼承,并進行相應的處理。
在使用CAsyncSocket時我們需要定義自己的CAsyncSocket派生類,在派生類中我們通過繼承虛函數的情勢可以自由的處理各類socket的io事件,我們定義的類名稱叫做CMySocket:
class CMySocket : public CAsyncSocket { .... }
在CMySocket中,我們定義1個CWnd*類型的成員變量用來接收伏務端和客戶端窗口的指針:
CWnd* m_pWnd;
我們重載OnAccept等回調函數,在每一個函數中向m_pWnd發送自定義的消息,這樣我們在服務端和客戶真個窗口處理函數中就能夠處理這些消息。
void CMySocket::OnAccept(int nErrorCode) { int param=ACCEPT; if(m_pWnd!=NULL) m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)?m); CAsyncSocket::OnAccept(nErrorCode); } void CMySocket::OnReceive(int nErrorCode) { int param=RECIEVE; if(m_pWnd!=NULL) m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)?m); CAsyncSocket::OnReceive(nErrorCode); } void CMySocket::OnClose(int nErrorCode) { int param=CLOSE; if(m_pWnd!=NULL) m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)?m); CAsyncSocket::OnClose(nErrorCode); } void CMySocket::OnConnect(int nErrorCode) { int param=CONNECT; if(m_pWnd!=NULL) m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)?m); CAsyncSocket::OnConnect(nErrorCode); } void CMySocket::OnSend(int nErrorCode) { int param=SEND; if(m_pWnd!=NULL) m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)?m); CAsyncSocket::OnSend(nErrorCode); }
可以看到,在上述每一個虛函數中,我們都調用m_pWnd的SendMessage函數向系統的消息隊列中發送了自定義的WM_MYSOCKET消息。并通過WPARAM和LPARAM參數把當前的CMySocket對象和代表事件類型的宏附加到消息的參數中。
在服務端我們自定義WM_MYSOCKET的消息處理函數以下:
afx_msg LRESULT CServDlg::OnMysocket(WPARAM wParam, LPARAM lParam) { CMySocket* pservSock=(CMySocket*)wParam; CMySocket* pClntSock=new CMySocket(); SOCKADDR_IN clntAddr; int clntAddrSz=sizeof(clntAddr); int param=*((int*)(lParam)); int recvLen; char buf[BUF_SIZE]; switch(param) { case ACCEPT: { pservSock->Accept(*pClntSock,(SOCKADDR*)&clntAddr,&clntAddrSz); pClntSock->m_pWnd=this; m_pClnts.AddTail(pClntSock); } break; case RECIEVE: { int strLen =pservSock->Receive(buf,BUF_SIZE,0); pservSock->Send(buf,strLen,0); } break; default: break; } return 0; }
在上述消息處理函數中我們new了1個客戶端socket的指針:
CMySocket* pClntSock=new CMySocket();
在這里我們不可以將客戶真個socket聲明為局部變量,由于CAsyncSocket對象離開作用域中會調用析構函數進行析構。如果我們這里在棧中創建1個clntSock而非new1個pClntSock,在OnMysocket調用結束后,已連接的客戶端socket會自動斷開連接,后續將沒法進行send和receive等操作。
在處理ACCEPT消息時,我們調用了Accept函數,這里和普通的accept函數類似,我們獲得到了連接到服務真個pClntSock,接下來將窗口的this指針賦值給了pClntSock的m_pWnd,這點很重要,由于我們在調用pservSock->Send(buf,strLen,0)進行數據的發送時,Send函數內部實際上會調用pClntSock的OnSend回調函數,我們需要在這個回調函數中向pClntSock的m_pWnd發送自定義消息。
在客戶端中,我們也要添加1個自定義消息的處理函數:
afx_msg LRESULT CClntDlg::OnMysocket(WPARAM wParam, LPARAM lParam) { CMySocket* pSock=(CMySocket*)wParam; int param=*((int*)(lParam)); int recvLen; char buf[BUF_SIZE]; switch(param) { case RECIEVE: { int strLen = pSock->Receive(buf,BUF_SIZE⑴,0); buf[strLen]=0; CString str; str.Format("%s",buf); m_recv.SetWindowText(str); } break; default: break; } return 0; }
上述內容,只是對幾處關鍵性的代碼進行了解釋,若需要全部的代碼,請自行從Github下載,服務端和客戶端的運行效果以下:
客戶端:
客戶端:
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本項目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
獲得本文源代碼:
git checkout NL57