標籤:非同步通訊端
Windows通訊端在兩種模式下執行I/O操作,阻塞模式和非阻塞模式。在阻塞模式下,執行操作的函數會一直等待,不會立即返回,知道發送完資料或者接受完資料為止。這在一定條件下是對效能的浪費,例如recvfrom函數沒有收到資料的時候吧就會一直等待下去。
為了提高系統的效能,Winsock提供了基於訊息的非同步socket。下面介紹主要的Socket非同步通訊函數。
<1>int WSASyncSelect(SOCKET s,HWND hwnd,unsigned int uMsg,longlEvent);
這個函數的作用是註冊一個lEvent事件,那麼當lEvent事件被觸發的時候(比如註冊FD_READ,那麼一旦有資料發送過來,那麼就會觸發lEvent事件),系統就會發送一個uMsg訊息,發送的視窗就是hwnd控制代碼所指向的視窗。
<2>int WSAEnumProtocols(LPINT lpiProtocols, LPWSAPROTOCOL_INFOlpProtocolBuffer,ILPDWORD lpdwBufferLength);
lpiProtocols:以NULL結尾的協議標識號數組,此參數可選,如果lpiProtocols為NULL,則函數返回所有可用協議的資訊,否則只返回數組中列出的協議資訊。
lpProtocolBuffer:out類型資料,作為返回值使用,一個用WSAPROTOCOL_INFO結構體填充的緩衝區。這個結構體用存放一個指定協議的完整資訊。
lpdwBufferLength :In/out型別參數,在輸入的時候,指定傳遞給lpProtocolBuffer緩衝區的長度,在輸出的時候,擷取存取所有請求資訊傳遞給WSAEnumProtocols函數的最小緩衝區的長度。
<3>int WSASocket(int af,int type,intprotocol,LPWSAPROTOCOL_INFO,lpProtocolInfo,GROUP g,DWORD dwFlags);
lpProtocolInfo:指向 WSAPROTOCOL_INFO結構體的指標,該結構體指定了所建立通訊端的特性,如果lpProtocolInfo為NULL,則win32的Dll會使用前三個參數來決定使用哪一個服務提供者。
g:保留
dwFlags:指定通訊端屬性的描述。如果該參數為WSA_FLAG_OVERLAPPED,那麼將建立一個重疊通訊端。
<4> int WSARecvFrom(SOCKET s,LPWSABUFlpBuffers,DWORD dwBufferCount,LPDWORD lpNumerOfBytesRecvd,LPDWORDlpFlags,sockaddr *lpFrom,LPINT lpFromlen,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine);
lpBuffers:in/out類型的參數,一個指向WSABUF結構體數組的指標,該結構體定義如下所示:
typedef struct _WSABUF{u_long len;char *buf;}WSABUF,*LPWSABUF;
可見這個結構體就是指向的接受資料的緩衝區以及長度。
dwBufferCount:lpBuffers所指向的數組中WSABUF結構體的數目。
lpNumberOfBytesRecvd:out類型的參數,如果接受操作立即完成,那麼該參數是指向本次調用所接受位元組數的指標。
lpFlags:in/out類型的參數,一個指向標誌位的指標。
lpFrom:out類型的指標,是一個可選的指標,指向重疊操作完成後存放源地址的緩衝區。
lpFromlen:in/out類型的參數,一個指向lpFrom指定緩衝區大小的指標,僅當指定了lpFrom參數的時候才需要使用這個參數。
lpOverlapped:指向WSAOVERLAPPED結構體的指標,對於非重疊socket則忽略。
lpCompletionRoutine:一個指向接受操作完成時調用的完成常式的指標,對於非重疊socket則忽略。
<5>int WSASendTo(SOCKET s,LPWSABUF lpBuffers,DWORDdwBufferCount,LPDWORD lpNumberOfByteSent,DWORD dwFlags,sockaddr* lpTo,intlen,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine)
這個函數的參數是和WSARecvFrom的對應著,所以這個不用多說。
樣本程式:
下面是一個簡單的網路聊天程式,先說下它的流程:
1 載入通訊端庫
2 初始化並且綁定通訊端,中間包括自訂訊息的定義。
3 自訂訊息響應函數完成
4 發送訊息函數的完成。
核心代碼如下所示:
//socket lib loadBOOLCmfcAChatApp::InitInstance(){ //載入通訊端庫 WORD wVersionRequested; WSADATA wsaData; interr; wVersionRequested = MAKEWORD(2,2); err =WSAStartup(wVersionRequested,&wsaData); if (err!= 0) { returnFALSE; } if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); returnFALSE; }//……}
// 建立並且初始化通訊端BOOLCmfcAChatDlg::InitSocket(void){ m_socket =WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED); if(INVALID_SOCKET == m_socket) { MessageBox(TEXT("Create socket failed")); returnFALSE; } sockaddr_in addrSock; addrSock.sin_family = AF_INET; addrSock.sin_port = htons(6000); addrSock.sin_addr.S_un.S_addr =htonl(INADDR_ANY); interror = bind(m_socket,(sockaddr*)&addrSock,sizeof(sockaddr)); if(SOCKET_ERROR == error) { MessageBox(TEXT("bind socket failed")); returnFALSE; } error =WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ); if(SOCKET_ERROR == error) { MessageBox(TEXT("register network event failed")); returnFALSE; } returnTRUE;}
//接受訊息響應函數fx_msg LRESULTCmfcAChatDlg::OnSock(WPARAM wParam, LPARAM lParam){ switch(LOWORD(lParam)) { caseFD_READ: WSABUF wsaBuf; wsaBuf.buf = new char[200]; wsaBuf.len = 200; DWORD dwRead; DWORD dwFlag = 0; sockaddr_in addrFrom; intlen = sizeof(sockaddr); CString str; CString strTemp; if(SOCKET_ERROR ==WSARecvFrom(m_socket,&wsaBuf,1,&dwRead,&dwFlag,(sockaddr*)&addrFrom,&len,NULL,NULL)) { MessageBox( TEXT("receive datafailed") ); delete[] wsaBuf.buf; wsaBuf.buf = NULL; return 0; } str.Format(TEXT("%s say :%s"),inet_ntoa(addrFrom.sin_addr),wsaBuf.buf); str += TEXT("\r\n"); GetDlgItemText(IDC_EDIT_RECV,strTemp); strTemp += str; SetDlgItemText(IDC_EDIT_RECV,strTemp); delete[]wsaBuf.buf; break; } return0;}
//發送訊息void CmfcAChatDlg::OnBnClickedBtnSend(){ // TODO: 在此添加控制項通知處理常式代碼 DWORD dwIP; CString strSend; WSABUF wsaBuf; DWORD dwSend; intlen; sockaddr_in addrTo; ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1) )->GetAddress(dwIP); addrTo.sin_addr.S_un.S_addr =htonl(dwIP); addrTo.sin_family = AF_INET; addrTo.sin_port = htons(6000); GetDlgItemText(IDC_EDIT_SEND,strSend); len = strSend.GetLength(); wsaBuf.buf = strSend.GetBuffer(len); wsaBuf.len = len + 1; if(SOCKET_ERROR == WSASendTo(m_socket,&wsaBuf,1,&dwSend,0,(sockaddr*)&addrTo,sizeof(sockaddr),NULL,NULL) ) { MessageBox(TEXT("Send message failed") ); return; } SetDlgItemText(IDC_EDIT_SEND,TEXT(""));}
程式的運行結果
基於訊息的非同步通訊端