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