DIS工作室

Winsock I/O模型之WSAAsyncSelect

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(0×0202, &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;
 }
}

服务器就这样建好了,只需要一个客户机就可以通信了,具体的代码我就不再贴了,各位可以自己设计客户机,或者去下载区下载源程序。最后向大家推荐《Windows网络编程技术》,真的不错。

本文中部分内容翻译自MSDN,客户机程序使用的是《Windows网络编程技术》中的例子

技术文摘

联系我们

地址: 江苏省常州市

QQ: 79232781

联系人: DIS工作室

电话: 13585315012

邮箱: disstudio@yeah.net