Winsock提供了一个很有用的异步I/O模型,利用这个模型,应用程序可以在一个套接字上接
收以Windows消息为基础的网络事件通知。这个模型最开始出现在Winsock 1.1版本中,是为
了帮助开发者面向一些早期的16位Windows平台而设计的。但是现在的应用程序仍然可以从
这种模型中得到好处,就连MFC中的CSocket类也采纳了这种模型。
由于该模型是基于Windows消息机制的,所以要想使用这种模型必须要Create一个窗口,这
个窗口将会被用来接收消息。接下来建立套接字,然后调用WSAAsyncSelect函数,打开窗口
消息通知,函数原型如下:
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int uMsg, long lEvent);
其中s就是我们想要的那个套接字;hWnd是接收消息通知那个窗口句柄;wMsg参数指定在
发生网络事件时要接受的消息,通常设成比WM_USER大的一个值,以避免消息冲突;
lEvent指定了一个位掩码,对应一系列网络事件的组合,见下表:
Event |
含义
|
---|---|
FD_READ | 程序想要接收有关是否可读的通知,以便读入数据 |
FD_WRITE | 程序想要接收有关是否可写的通知,以便写入数据 |
FD_OOB | 程序想要接收是否有OOB数据到达的通知 |
FD_ACCEPT | 程序想要接收与进入连接有关的通知 |
FD_CONNECT | 程序想要接收与一次连接或多点接入有关的通知 |
FD_CLOSE | 程序想要接收与套接字关闭有关的通知 |
FD_QOS | 程序想要接收套接字“服务质量(QoS)”发生变化的通知 |
FD_GROUP_QOS | 暂时没用,属于保留事件 |
FD_ROUTING_INTERFACE_CHANGE | 程序想要接收有关到指定地址的路由接口发生变化的通知 |
FD_ADDRESS_LIST_CHANGE | 程序想要接收本地地址变化的通知 |
当程序在一个套接字上调用WSAAsyncSelect成功后,这个程序就会在与hWnd窗口句柄对
应的窗口例程中以Windows消息的形式接收网络事件通知。窗口例程通常定义成这个样子:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam)
其中wParam参数指定在其上面发生了一个网络事件的套接字,如果定义了多个套接字,这
个参数就显得很重要了。lParam参数则包含了两方面的重要信息,它的低位字指定了已经发
生的网络事件,而高位字包含了可能出现的错误代码。
简单的来说,这个模型的具体使用流程就是:
当网络消息抵达一个窗口例程后,程序要先检测lParam的高位字节,从而判断是否在套接字
上面发生了网络错误。现成的宏已经有在这里了 --> WSAGETSELECTERROR,可以用它返
回高字节包含的错误信息,如果没有发现任何的错误,接下来就是确定究竟是什么类型的网
络事件触发了这条Windows消息,这个操作也有现成的宏 --> WSAGETSELECTEVENT
下面就是源代码,其中部分很基本的代码我就省略掉了,编译平台为
Win2000 Server with SP2 + VC6.0 with SP5
#include <windows.h>
#include <winsock2.h>
#define PORT 5150
#define DATA_BUFSIZE 8192
typedef struct _SOCKET_INFORMATION {
BOOL RecvPosted;
CHAR Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET Socket;
DWORD BytesSEND;
DWORD BytesRECV;
_SOCKET_INFORMATION *Next;
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
#define WM_SOCKET (WM_USER + 1)
void CreateSocketInformation(SOCKET s, HWND);
LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);
void FreeSocketInformation(SOCKET s);
LPSOCKET_INFORMATION SocketInfoList;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
DWORD Ret;
SOCKET Listen;
SOCKADDR_IN InternetAddr;
WSADATA wsaData;
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
// Prepare echo server
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
...
...
RegisterClass (&wndclass);
hwnd = CreateWindow (...) ; // creation parameters
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
{
MessageBox(hwnd, TEXT("Start socket failed"), TEXT("error"), MB_OK);
ExitProcess(1);
}
if ((Listen = socket (PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
MessageBox(hwnd, TEXT("socket() failed"), TEXT("error"), MB_OK);
ExitProcess(1);
}
WSAAsyncSelect(Listen, hwnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr))
== SOCKET_ERROR)
{
MessageBox(hwnd, TEXT("bind() failed"), TEXT("error"), MB_OK);
ExitProcess(1);
}
if (listen(Listen, 5))
{
MessageBox(hwnd, TEXT("listen() failed"), TEXT("error"), MB_OK);
ExitProcess(1);
}
// Translate and dispatch window messages for the application thread
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
SOCKET Accept;
LPSOCKET_INFORMATION SocketInfo;
DWORD RecvBytes, SendBytes;
DWORD Flags;
switch (message)
{
case WM_CREATE:
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Server Started!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
case WM_SOCKET:
if (WSAGETSELECTERROR(lParam))
{
MessageBox(...);
FreeSocketInformation(wParam);
}
else
{
switch(WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)
{
MessageBox(...);
break;
}
// Create a socket information structure to associate with the
// socket for processing I/O.
CreateSocketInformation(Accept, hwnd);
WSAAsyncSelect(Accept, hwnd, WM_SOCKET,
FD_READ|FD_WRITE|FD_CLOSE);
break;
case FD_READ:
SocketInfo = GetSocketInformation(wParam);
// Read data only if the receive buffer is empty.
if (SocketInfo->BytesRECV != 0)
{
SocketInfo->RecvPosted = TRUE;
return 0;
}
else
{
SocketInfo->DataBuf.buf = SocketInfo->Buffer;
SocketInfo->DataBuf.len = DATA_BUFSIZE;
Flags = 0;
if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf),
1, &RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
MessageBox(...);
FreeSocketInformation(wParam);
return 0;
}
}
else // No error so update the byte count
SocketInfo->BytesRECV = RecvBytes;
}
// DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV.
// Go ahead and begin writing data to the client.
case FD_WRITE:
SocketInfo = GetSocketInformation(wParam);
if (SocketInfo->BytesRECV > SocketInfo->BytesSEND)
{
SocketInfo->DataBuf.buf =
SocketInfo->Buffer + SocketInfo->BytesSEND;
SocketInfo->DataBuf.len =
SocketInfo->BytesRECV - SocketInfo->BytesSEND;
if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf),
1, &SendBytes, 0,NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
MessageBox(...);
FreeSocketInformation(wParam);
return 0;
}
}
else // No error so update the byte count
SocketInfo->BytesSEND += SendBytes;
}
if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)
{
SocketInfo->BytesSEND = 0;
SocketInfo->BytesRECV = 0;
// If a RECV occurred during our SENDs then we need to post
// an FD_READ notification on the socket.
if (SocketInfo->RecvPosted == TRUE)
{
SocketInfo->RecvPosted = FALSE;
PostMessage(hwnd, WM_SOCKET, wParam, FD_READ);
}
}
if(SocketInfo->DataBuf.buf != NULL)
MessageBox(hwnd, SocketInfo->DataBuf.buf,
TEXT("Received"), MB_OK);
break;
case FD_CLOSE:
FreeSocketInformation(wParam);
break;
}
}
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
void CreateSocketInformation(SOCKET s, HWND hwnd)
{
LPSOCKET_INFORMATION SI;
if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
sizeof(SOCKET_INFORMATION))) == NULL)
{
MessageBox(...);
return;
}
// Prepare SocketInfo structure for use.
SI->Socket = s;
SI->RecvPosted = FALSE;
SI->BytesSEND = 0;
SI->BytesRECV = 0;
SI->Next = SocketInfoList;
SocketInfoList = SI;
}
LPSOCKET_INFORMATION GetSocketInformation(SOCKET s)
{
SOCKET_INFORMATION *SI = SocketInfoList;
while(SI)
{
if (SI->Socket == s)
return SI;
SI = SI->Next;
}
return NULL;
}
void FreeSocketInformation(SOCKET s)
{
SOCKET_INFORMATION *SI = SocketInfoList;
SOCKET_INFORMATION *PrevSI = NULL;
while(SI)
{
if (SI->Socket == s)
{
if (PrevSI)
PrevSI->Next = SI->Next;
else
SocketInfoList = SI->Next;
closesocket(SI->Socket);
GlobalFree(SI);
return;
}
PrevSI = SI;
SI = SI->Next;
}
}