複製鏈接
請複製以下鏈接發送給好友

CAsyncSocket

鎖定
它是一個異步非阻塞Socket封裝類,CAsyncSocket::Create()有一個參數指明瞭你想要處理哪些Socket事件,你關心的事件被指定以後,這個Socket默認就被用作了異步方式。CAsyncSocket的Create()函數,除了創建了一個SOCKET以外,還創建了個CSocketWnd窗口對象,並使用WSAAsyncSelect()將這個SOCKET與該窗口對象關聯,以讓該窗口對象處理來自Socket的事件(消息),然而CSocketWnd收到Socket事件之後,只是簡單地回調CAsyncSocket::OnReceive()等虛函數。所以CAsyncSocket的派生類,只需要在這些虛函數里添加發送和接收的代碼。
中文名
CAsyncSocket
好    處
極大方便
實    質
異步非阻塞Socket封裝類
發送和接收
虛函數

CAsyncSocket大致的代碼為

bool CAsyncSocket::Create( long lEvent ) //參數lEvent是指定你所關心的Socket事件
{
m_hSocket = socket( PF_INET, SOCK_STREAM, 0 ); //創建Socket對象
CSocketWnd* pSockWnd = new CSocketWnd; //創建響應事件的窗口,實際的這個窗口在AfxSockInit()調用時就被創建了。
pSockWnd->Create(...);
WSAAsyncSelect( m_hSocket, pSockWnd->m_hWnd, WM_SOCKET_NOTIFY, lEvent ); //Socket 事件和窗口關聯
}
static void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)
{
CAsyncSocket*pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, FALSE); //注意這裏獲取的是已經與m_socketwnd關聯的
int nErrorCode = WSAGETSELECTERROR(lParam); //lParam 是錯誤碼與事件碼的合成
switch (WSAGETSELECTEVENT(lParam))
{
case FD_READ:
pSocket->OnReceive(nErrorCode);
break;
case FD_WRITE:
pSocket->OnSend(nErrorCode);
break;
case FD_OOB:
pSocket->OnOutOfBandData(nErrorCode);
break;
case FD_ACCEPT:
pSocket->OnAccept(nErrorCode);
break;
case FD_CONNECT:
pSocket->OnConnect(nErrorCode);
break;
case FD_CLOSE:
pSocket->OnClose(nErrorCode);
break;
}
}
CSocketWnd類大致為:
BEGIN_MESSAGE_MAP(CSocketWnd, CWnd)
ON_MESSAGE(WM_SOCKET_NOTIFY, OnSocketNotify)
END_MESSAGE_MAP()
LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)
{
CAsyncSocket::DoCallBack( wParam, lParam ); //收到Socket事件消息,回調CAsyncSocket的DoCallBack()函數
return 0L;
}
然而,最不容易被初學Socket編程的人理解的,也是本文最要提醒的一點是,客户方在使用CAsyncSocket::Connect()時,往往返回一個WSAEWOULDBLOCK的錯誤(其它的某些函數調用也如此),實際上這不應該算作一個錯誤,它是Socket提醒我們,由於你使用了非阻塞Socket方式,所以(連接)操作需要時間,不能瞬間建立。既然如此,我們可以等待呀,等它連接成功為止,於是許多程序員就在調用Connect()之後,Sleep(0),然後不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的錯誤,直到返回成功為止。這是一種錯誤的做法,斷言,你不能達到預期目的。事實上,我們可以在Connect()調用之後等待CAsyncSocket::OnConnect()事件被觸發,CAsyncSocket::OnConnect()是要表明Socket要麼連接成功了,要麼連接徹底失敗了。至此,我們在CAsyncSocket::OnConnect()被調用之後就知道是否Socket連接成功了,還是失敗了。
類似的,Send()如果返回WSAEWOULDBLOCK錯誤,我們在OnSend()處等待,Receive()如果返回WSAEWOULDBLOCK錯誤,我們在OnReceive()處等待,以此類推。

CAsyncSocket特別注意

還有一點,也許是個難點,那就是在客户方調用Connect()連接服務方,那麼服務方如何Accept(),以建立連接的問題。簡單的做法就是在監聽的Socket收到OnAccept()時,用一個新的CAsyncSocket對象去建立連接,例如:
void CMySocket::OnAccept( int ErrCode )
{
CMySocket* pSocket = new CMySocket;
Accept( *pSocket );
}
於是,上面的pSocket和客户方建立了連接,以後的通信就是這個pSocket對象去和客户方進行,而監聽的Socket仍然繼續在監聽,一旦又有一個客户方要連接服務方,則上面的OnAccept()又會被調用一次。當然pSocket是和客户方通信的服務方,它不會觸發OnAccept()事件,因為它不是監聽Socket。

CAsyncSocketCAsyncsocket擴展

Tim kosse 改進了MFC的CAsyncsocket,封裝為CAsyncsocketEx。