您的位置:首页 > 其它

Winsock提供了一个很有用的异步I/O模型之WSAAsyncSelect

2005-08-25 14:43 555 查看
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[align=center]含义[/align]
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;
 }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: