异步选择模型WSAAsyncSelect

异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O模型。利用这个模型,应用程序可在一个套接字上,接收以 Windows消息为基础的网络事件通知。具体

的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型的核心即是WSAAsyncSelect函数。

    WSAAsyncSelect函数定义如下:

    int WSAAsyncSelect(

        __in SOCKET s,             //指定的是我们感兴趣的那个套接字。

        __in HWND hWnd,         //指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。

        __in unsigned intwMsg,  //指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。

        __in long lEvent             //指定一个位掩码,对应于一系列网络事件的组合
   );

注意:

      1wMsg参数指定的消息通常是我们自定义的消息,应用程序需要将这个消息设为比WindowsWM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗

口消息发生混淆与冲突。

      2lEvent参数指定的网络类型为:FD_READFD_WRITEFD_ACCEPTFD_CONNECTFD_CLOSE等。当然,到底使用FD_ACCEPT,还是使用

FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算就OK

      FD_READ             应用程序想要接收有关是否可读的通知,以便读入数据

      FD_WRITE           应用程序想要接收有关是否可写的通知,以便写入数据

      FD_ACCEPT         应用程序想接收与进入连接有关的通知

      FD_CONNECT       应用程序想接收与一次连接完成的通知

      FD_CLOSE           应用程序想接收与套接字关闭的通知

      3、多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那

个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有

网络事件通知。

      4、若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从锁定变成非锁定。这样一来,如果调用了像WSARecv这样的Winsock函数,

但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelectuMsg参数指定的

用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

     应用程序在一个套接字上成功调用了WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常

定义如下:

    LRESULT CALLBACK WindowProc(

    HWND hwnd,          //指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。

    UINT uMsg,            //指定需要对哪些消息进行处理。这里我们感兴趣的是WSAAsyncSelect调用中定义的消息。

    WPARAM wParam,   //指定在其上面发生了一个网络事件的套接字。(假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。)

    LPARAM lParam      //包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何

                               //错误代码。

    );

    流程:网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。这里有一个特殊的宏: WSAGETSELECTERROR,可用

它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,具体的做法便是读取lParam低字位的内容。此时可

使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。

    FD_WRITE事件通知:只有在三种条件下,才会发出 FD_WRITE通知:

        1、使用 connect WSAConnect,一个套接字首次建立了连接。

        2、使用 accept WSAAccept,套接字被接受以后。

        3、若 sendWSASendsendto WSASendTo操作失败,返回了 WSAEWOULDBLOCK错误,而且缓冲区的空间变得可用。

#include <winsock.h>

#include <tchar.h>

 

#define PORT      5150

#define MSGSIZE   1024

#define WM_SOCKET WM_USER+0

 

#pragma comment(lib,"ws2_32.lib")

 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

 

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, PSTR szCmdLine,intiCmdShow)

{

static TCHAR szAppName[] = _T("AsyncSelect Model");

HWND         hwnd ;

MSG          msg ;

WNDCLASS     wndclass ;

 

wndclass.style         = CS_HREDRAW| CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

 

if (!RegisterClass(&wndclass))

{

    MessageBox (NULL, TEXT ("This program requires WindowsNT!"), szAppName,MB_ICONERROR) ;

   return 0 ;

}

 

hwnd = CreateWindow (szAppName,                 // window class name

                       TEXT ("AsyncSelect Model"),// window caption

                      WS_OVERLAPPEDWINDOW,       // window style

                      CW_USEDEFAULT,             // initial x position

                       CW_USEDEFAULT,             // initial y position

                      CW_USEDEFAULT,             // initial x size

                      CW_USEDEFAULT,             // initial y size

                       NULL,                      // parent window handle

                       NULL,                      // window menu handle

                      hInstance,                 // program instance handle

                       NULL) ;                    // creation parameters

 

ShowWindow(hwnd, iCmdShow);

UpdateWindow(hwnd);

 

while (GetMessage(&msg, NULL, 0, 0))

{

    TranslateMessage(&msg) ;

    DispatchMessage(&msg) ;

}

 

return msg.wParam;

}

 

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAMlParam)

{

WSADATA       wsd;

static SOCKET sListen;

SOCKET        sClient;

SOCKADDR_IN   local, client;

int          ret, iAddrSize =sizeof(client);

char         szMessage[MSGSIZE];

 

switch (message)

{

case WM_CREATE:

   // Initialize Windows Socket library

   WSAStartup(0x0202, &wsd);

  // Create listening socket

    sListen = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);

  // Bind

    local.sin_addr.S_un.S_addr =htonl(INADDR_ANY);

   local.sin_family = AF_INET;

   local.sin_port = htons(PORT);

   bind(sListen, (struct sockaddr *)&local,sizeof(local));

  // Listen

    listen(sListen, 3);

 

   // Associate listening socket with FD_ACCEPT event

   WSAAsyncSelect(sListen, hwnd,WM_SOCKET, FD_ACCEPT);

  return 0;

 

case WM_DESTROY:

    closesocket(sListen);

    WSACleanup();

    PostQuitMessage(0);

   return 0;

 

case WM_SOCKET:

   if(WSAGETSELECTERROR(lParam))

    {

      closesocket(wParam);

     break;

    }

   switch(WSAGETSELECTEVENT(lParam))

    {

   caseFD_ACCEPT:

     // Accept a connection from client

      sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);

     // Associate client socket with FD_READ and FD_CLOSEevent

      WSAAsyncSelect(sClient, hwnd,WM_SOCKET, FD_READ | FD_CLOSE);

     break;

 

   caseFD_READ:

      ret = recv(wParam, szMessage,MSGSIZE, 0);

 

     if (ret== 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)

      {

        closesocket(wParam);

      }

     else

      {

        szMessage[ret] ='\0';

        send(wParam, szMessage,strlen(szMessage), 0);

      }

     break;

   caseFD_CLOSE:

      closesocket(wParam);     

     break;

    }

   return 0;

}

 

return DefWindowProc(hwnd, message, wParam,lParam);

}

在我看来,WSAAsyncSelect是最简单的一种Winsock I/O模型(之所以说它简单是因为一个主线程就搞定了)。使用Raw Windows API写过窗口类应用程序的人应该都能看得懂。这里,我们需要做的仅仅是: 
1.在WM_CREATE消息处理函数中,初始化Windows Socketlibrary,创建监听套接字,绑定,监听,并且调用WSAAsyncSelect函数表示我们关心在监听套接字上发生的FD_ACCEPT事件; 
2.自定义一个消息WM_SOCKET,一旦在我们所关心的套接字(监听套接字和客户端套接字)上发生了某个事件,系统就会调用WndProc并且message参数被设置为WM_SOCKET; 
3.在WM_SOCKET的消息处理函数中,分别对FD_ACCEPT、FD_READ和FD_CLOSE事件进行处理; 
4.在窗口销毁消息(WM_DESTROY)的处理函数中,我们关闭监听套接字,清除Windows Socketlibrary

下面这张用于WSAAsyncSelect函数的网络事件类型表可以让你对各个网络事件有更清楚的认识: 
表1 
FD_READ    应用程序想要接收有关是否可读的通知,以便读入数据 
FD_WRITE    应用程序想要接收有关是否可写的通知,以便写入数据 
FD_OOB    应用程序想接收是否有带外(OOB)数据抵达的通知 
FD_ACCEPT    应用程序想接收与进入连接有关的通知 
FD_CONNECT    应用程序想接收与一次连接或者多点join操作完成的通知 
FD_CLOSE    应用程序想接收与套接字关闭有关的通知 
FD_QOS    应用程序想接收套接字“服务质量”(QoS)发生更改的通知 
FD_GROUP_QOS     应用程序想接收套接字组“服务质量”发生更改的通知(现在没什么用处,为未来套接字组的使用保留) 
FD_ROUTING_INTERFACE_CHANGE    应用程序想接收在指定的方向上,与路由接口发生变化的通知 
FD_ADDRESS_LIST_CHANGE     应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知

大体流程

自定义消息
#define WM_SOCKET WM_USER+1
//创建监听socket
socket
//绑定本地地址
bind
//监听
listen


//注册监听socket 上的accept事件
WSAAsyncSelect(sListen, hwnd,WM_SOCKET, FD_ACCEPT);


在消息处理函数中处理WM_SOCKET消息


case WM_SOCKET:
//先检查是否有错误
    if(WSAGETSELECTERROR(lParam))
    {
      closesocket(wParam);
      break;
    }
//处理过来的事件
    switch(WSAGETSELECTEVENT(lParam))
    {
    caseFD_ACCEPT:
//接收一个客户端socket
      sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);
//注册客户端socket的read和close事件
      WSAAsyncSelect(sClient, hwnd,WM_SOCKET, FD_READ | FD_CLOSE);
      break;
    caseFD_READ:
      break;
    caseFD_CLOSE:
    }

总结:

使用windows的消息机制,实现了监听socket的accept和客户端的read之间,以及各个客户端之间的read,可以不用一直阻塞在那,而是在有相应事件的时候再进行阻塞处理,通过消息机制消除了所有的长阻塞(select模型消除了accept和read的长阻塞,增加了一个select长阻塞),给程序腾出了自由的时间。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值