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 load BOOLCmfcAChatApp::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("")); }
程序的运行结果