WSAAsyncSelect之win32示例模型

以下转载的是两篇文章:

FD_READ注意: 
1.winsock2发出一个FD_READ后,如果程序没有用recv(),即使还有数据没接收FD_READ也不会再触发另一个FD_READ,要等到recv()调用后FD_READ才会发出。 
2.对一个FD_READ多次recv()的情形:如果程序对一个FD_READ多次recv()将会造成触发多个空的FD_READ,所以程序在第2次recv()前要关掉FD_READ(可以使用WSAAsynSelect关掉FD_READ),然后再多次recv()。 

3.recv()返回WSAECONNABORTED,WSAECONNRESET...等消息,可以不做任何处理,可以等到FD_CLOSE事件触发时再处理



--------------------------------------------------------------------------第一篇-------------------------------------------------------------------------------------------------------

WSAAsynSelect模型也是一个常用的异步I/O模型。应用程序可以在一个套接字上接收以WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函数自动将套接字设置(转变)为非阻塞模式,并向WINDOWS注册一个或多个网络事件,并提供一个通知时使用的窗口句柄。当注册的事件发生时,对应的窗口将收到一个基于消息的通知。

 

  1  #include  < winsock.h >
  2  #include  < tchar.h >
  3 
  4  #define  PORT         5150
  5  #define  MSGSIZE      1024
  6  #define  WM_SOCKET WM_USER+0
  7 
  8  #pragma  comment(lib, "ws2_32.lib")
  9 
 10  LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 11 
 12  int  WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,  int  iCmdShow)
 13  {
 14        static  TCHAR szAppName[]  =  _T( " AsyncSelect Model " );
 15       HWND            hwnd ;
 16       MSG             msg ;
 17       WNDCLASS        wndclass ;
 18 
 19       wndclass.style             =  CS_HREDRAW  |  CS_VREDRAW ;
 20       wndclass.lpfnWndProc       =  WndProc ;
 21       wndclass.cbClsExtra        =   0  ;
 22       wndclass.cbWndExtra        =   0  ;
 23       wndclass.hInstance         =  hInstance ;
 24       wndclass.hIcon             =  LoadIcon (NULL, IDI_APPLICATION) ;
 25       wndclass.hCursor           =  LoadCursor (NULL, IDC_ARROW) ;
 26       wndclass.hbrBackground  =  (HBRUSH) GetStockObject (WHITE_BRUSH) ;
 27       wndclass.lpszMenuName      =  NULL ;
 28       wndclass.lpszClassName  =  szAppName ;
 29 
 30        if  ( ! RegisterClass( & wndclass))
 31       {
 32         MessageBox (NULL, TEXT ( " This program requires Windows NT! " ), szAppName, MB_ICONERROR) ;
 33          return   0  ;
 34       }
 35 
 36       hwnd  =  CreateWindow (szAppName,                      //  window class name
 37                            TEXT ( " AsyncSelect Model " ),  //  window caption
 38                            WS_OVERLAPPEDWINDOW,            //  window style
 39                            CW_USEDEFAULT,                  //  initial x position
 40                            CW_USEDEFAULT,                  //  initial y position
 41                            CW_USEDEFAULT,                  //  initial x size
 42                            CW_USEDEFAULT,                  //  initial y size
 43                            NULL,                           //  parent window handle
 44                            NULL,                           //  window menu handle
 45                            hInstance,                      //  program instance handle
 46                            NULL) ;                         //  creation parameters
 47 
 48       ShowWindow(hwnd, iCmdShow);
 49       UpdateWindow(hwnd);
 50 
 51        while  (GetMessage( & msg, NULL,  0 0 ))
 52       {
 53         TranslateMessage( & msg) ;
 54         DispatchMessage( & msg) ;
 55       }
 56    
 57        return  msg.wParam;
 58  }
 59 
 60  LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 61  {
 62       WSADATA          wsd;
 63        static  SOCKET sListen;
 64       SOCKET           sClient;
 65       SOCKADDR_IN      local, client;
 66        int               ret, iAddrSize  =   sizeof (client);
 67        char              szMessage[MSGSIZE];
 68 
 69        switch  (message)
 70       {
 71  case  WM_CREATE:
 72          //  Initialize Windows Socket library
 73       WSAStartup( 0x0202 & wsd);
 74    
 75        //  Create listening socket
 76         sListen  =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 77      
 78        //  Bind
 79         local.sin_addr.S_un.S_addr  =  htonl(INADDR_ANY);
 80       local.sin_family  =  AF_INET;
 81       local.sin_port  =  htons(PORT);
 82       bind(sListen, ( struct  sockaddr  * ) & local,  sizeof (local));
 83    
 84        //  Listen
 85         listen(sListen,  3 );
 86 
 87          //  Associate listening socket with FD_ACCEPT event
 88       WSAAsyncSelect(sListen, hwnd, WM_SOCKETFD_ACCEPT);
 89        return   0 ;
 90 
 91        case  WM_DESTROY:
 92         closesocket(sListen);
 93         WSACleanup();
 94         PostQuitMessage( 0 );
 95          return   0 ;
 96    
 97        case  WM_SOCKET:
 98          if  (WSAGETSELECTERROR(lParam)) //lParam的高字节包含了可能出现的任何的错误代码
 99         {
100           closesocket(wParam);
101            break ;
102         }
103      
104          switch  (WSAGETSELECTEVENT(lParam))  //lParam的低字节指定已经发生的网络事件
105         {
106          case  FD_ACCEPT:
107            //  Accept a connection from client
108           sClient  =  accept(wParam, ( struct  sockaddr  * ) & client,  & iAddrSize);
109        
110            //  Associate client socket with FD_READ and FD_CLOSE event
111           WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ  |  FD_CLOSE);
112            break ;
113 
114          case  FD_READ:
115           ret  =  recv(wParam, szMessage, MSGSIZE,  0 );
116 
117            if  (ret  ==   0   ||  ret  ==  SOCKET_ERROR  &&  WSAGetLastError()  ==  WSAECONNRESET)
118           {
119             closesocket(wParam);
120           }
121            else
122           {
123             szMessage[ret]  =   ' /0 ' ;
124             send(wParam, szMessage, strlen(szMessage),  0 );
125           }
126            break ;
127        
128          case  FD_CLOSE:
129           closesocket(wParam);      
130            break ;
131         }
132          return   0 ;
133       }
134    
135        return  DefWindowProc(hwnd, message, wParam, lParam);
136  }

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

4.在窗口销毁消息(WM_DESTROY)的处理函数中,我们关闭监听套接字,清除Windows Socket library

 

下面这张用于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     应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知


--------------------------------------------------------------------------第二篇-------------------------------------------------------------------------------------------------------

WinSock是Windows提供的包含了一系列网络编程接口的套接字程序库。在这篇文章中,我们将介绍如何把它的非阻塞模式引入到应用程序中。

阻塞模式WinSock.下述伪代码给出了阻塞模式下WinSock的使用方式。
//服务器
WSAStartup();
SOCKET server = socket();
bind(server);
listen(server);
SOCKET client = accept(server);
send(client);
recv(client);
closesocket(client);
closesocket(server); 
WSACleanup();

//客户端
WSAStartup();
SOCKET client=socket(); 
bind(client);
ServerAddress server;
connect(client, server);
recv(client);
send(client);
closesocket(client);
WSACleanup();
代码中,服务器端的accept(),客户端的connect(),以及服务器和客户端中共同的recv()、send()函数均会产生阻塞。
服务器在调用accept()后不会返回,直到接收到客户端的连接请求;
客户端在调用connect()后不会返回,直到对服务器连接成功或者失败;
服务器和客户端在调用recv()后不会返回,直到接收到并读取完一条消息;
服务器和客户端在调用send()后不会返回,直到发送完待发送的消息。
如果这两段代码被放在Windows程序的主线程中,你会发现消息循环被阻塞,程序不再响应用户输入及重绘请求。为了解决这个问题,
你可能会想到开辟另外一个线程来运行这些代码。这是可行的,但是考虑到每个SOCKET都不应该被其他SOCKET的操作所阻塞,是不是
需要为每个SOCKET开辟一个线程?再考虑到同一SOCKET的一个读写操作也不应该被另外一个读写操作所阻塞,是不是应该再为每个
SOCKET的读和写分别开辟一个线程?一般来说,这种自实现的多线程解决方案带来的诸多线程管理方面的问题,是你绝对不会想要遇
到的。
 
非阻塞模式WinSock
所幸的是,WinSock同时提供了非阻塞模式,并提出了几种I/O模型。最常见的I/O模型有select模型、WSAAsyncSelect模型及
WSAEventSelect模型,下面选择其中的WSAAsyncSelect模型进行介绍。使用WSAAsyncSelect模型将非阻塞模式引入到应用程序中的过
程看起来很简单,事实上你只需要多添加一个函数就够了。
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
该函数会自动将套接字设置为非阻塞模式,并且把发生在该套接字上且是你所感兴趣的事件,以Windows消息的形式发送到指定的窗口,
你需要做的就是在传统的消息处理函数中处理这些事件。参数hWnd表示指定接受消息的窗口句柄;参数wMsg表示消息码值(这意味着你
需要自定义一个Windows消息码);参数IEvent表示你希望接受的网络事件的集合,它可以是如下值的任意组合:FD_READ, FD_WRITE, 
FD_OOB, FD_ACCEPT, FD_CONNECT, FD_CLOSE 之后,就可以在我们熟知的Windows消息处理函数中处理这些事件。如果在某一套接字s上
发生了一个已命名的网络事件,应用程序窗口hWnd会接收到消息wMsg。参数wParam即为该事件相关的套接字s;参数lParam的低字段指
明了发生的网络事件,lParam的高字段则含有一个错误码,事件和错误码可以通过下面的宏从lParam中取出:
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)
下面继续使用伪代码来帮助阐述如何将上一节的阻塞模式WinSock应用升级到非阻塞模式。
首先自定义一个Windows消息码,用于标识我们的网络消息。

#define WM_CUSTOM_NETWORK_MSG (WM_USER + 100) 
//服务器端,在监听之前,将监听套接字置为非阻塞模式,并且标明其感兴趣的事件为FD_ACCEPT。
WSAAsyncSelect(server, wnd, WM_CUSTOM_NETWORK_MSG, FD_ACCEPT); 
listen(server); 

//客户端,在连接之前,将套接字置为非阻塞模式,并标明其感兴趣的事件为FD_CONNECT。
WSAAsyncSelect(client, wnd, WM_CUSTOM_NETWORK_MSG, FD_CONNECT);
ServerAddress?server;
connect(client,?server);

//接着,在Windows消息处理函数中,我们将处理监听事件、连接事件、及读写事件,方便起见,这里将服务器和客户端的处理代码放在
了一起。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)    
{    
    switch (message)    
    {
    case WM_CUSTOM_NETWORK_MSG: // 自定义的网络消息码    
        {    
            SOCKET socket = (SOCKET)wParam; // 发生网络事件的套接字    
            long event = WSAGETSELECTEVENT(lParam); // 事件    
            int error = WSAGETSELECTERROR(lParam); // 错误码    
    
            switch (event)    
            {    
            case FD_ACCEPT: // 服务器收到新客户端的连接请求    
                {    
                    // 接收到客户端连接,分配一个客户端套接字    
                    SOCKET client = accept(socket);     
                    // 将新分配的客户端套接字置为非阻塞模式,并标明其感兴趣的事件为读、写及关闭    
                    WSAAsyncSelect(client, hWnd, WM_CUSTOM_NETWORK_MSG, FD_READ | FD_WRITE | FD_CLOSE);    
                }    
                break;    
            case FD_CONNECT: // 客户端连接到服务器的操作返回结果    
                {    
                    // 成功连接到服务器,将客户端套接字置为非阻塞模式,并标明其感兴趣的事件为读、写及关闭    
                    WSAAsyncSelect(socket, hWnd, message, FD_READ | FD_WRITE | FD_CLOSE);    
                }    
                break;    
            case FD_READ: // 收到网络包,需要读取    
                {    
                    // 使用套接字读取网络包    
                    recv(socket);    
                }    
                break;    
            case FD_WRITE:    
                {    
                    // FD_WRITE的处理后面会具体讨论    
                }    
                break;    
            case FD_CLOSE: // 套接字的连接方(而非本地socket)关闭消息    
                {    
                }    
                break;    
            default:    
                break;    
            }    
        }    
        break;    
    …    
    }    
    …    
}    
以上就是非阻塞模式WinSock的应用框架,WSAAsyncSelect模型将套接字和Windows消息机制很好地粘合在一起,为用户异步SOCKET应用提供
了一种较优雅的解决方案。

扩展讨论
WinSock在系统底层为套接字收发网络数据各提供一个缓冲区,接收到的网络数据会缓存在这里等待应用程序读取,待发送的网络数据也会先
写进这里之后通过网络发送。相关的,针对FD_READ和FD_WRITE事件的读写处理,因涉及的内容稍微复杂而容易使人困惑,这里需要特别进行
讨论。在FD_READ事件中,使用recv()函数读取网络包数据时,由于事先并不知道完整网络包的大小,所以需要多次读取直到读完整个缓冲区
。这就需要类似如下代码的调用:
void* buf = 0;    
int size = 0;    
while (true)    
{    
    char tmp[128];    
    int bytes = recv(socket, tmp, 128, 0);    
    if (bytes <= 0)    
        break;    
    else    
    {    
        int new_size = size + bytes;    
        buf = realloc(buf, new_size);    
        memcpy((void*)(((char*)buf) + size), tmp, bytes);    
        size = new_size;    
    }    
}    
//此时数据已经从缓冲区全部拷贝到buf中,你可以在这里对buf做一些操作      
free(buf);    
这一切看起来都没有什么问题,但是如果程序运行起来,你会收到比预期多出许多的FD_READ事件。如MSDN所述,正常的情况下,应用程序应
当为每一个FD_READ消息仅调用一次recv()函数。如果一个应用程序需要在一个FD_READ事件处理中调用多次recv(),那么它将会收到多个
FD_READ消息,因为每次未读完缓冲区的recv()调用,都会重新触发一个FD_READ消息。针对这种情况,我们需要在读取网络包前关闭掉FD_READ
消息通知,读取完这后再进行恢复,关闭FD_READ消息的方法很简单,只需要调用WSAAsyncSelect时参数lEvent中FD_READ字段不予设置即可。

//关闭FD_READ事件通知    
WSAAsyncSelect(socket, hWnd, message, FD_WRITE | FD_CLOSE);    
// 读取网络包    
…    
// 再次打开FD_READ事件通知    
WSAAsyncSelect(socket, hWnd, message, FD_WRITE | FD_CLOSE | FD_READ);    

第二个需要讨论的是FD_WRITE事件。这个事件指明缓冲区已经准备就绪,有了多出的空位可以让应用程序写入数据以供发送。该事件仅在两种
情况下被触发:
1. 套接字刚建立连接时,表明准备就绪可以立即发送数据。
2. 一次失败的send()调用后缓冲区再次可用时。如果系统缓冲区已经被填满,那么此时调用send()发送数据,将返回SOCKET_ERROR,使用
WSAGetLastError()会得到错误码WSAEWOULDBLOCK表明被阻塞。这种情况下当缓冲区重新整理出可用空间后,会向应用程序发送FD_WRITE消息,
示意其可以继续发送数据了。

所以说收到FD_WRITE消息并不单纯地等同于这是使用send()的唯一时机。一般来说,如果需要发送消息,直接调用send()发送即可。如果该次
调用返回值为SOCKET_ERROR且WSAGetLastError()得到错误码WSAEWOULDBLOCK,这意味着缓冲区已满暂时无法发送,此刻我们需要将待发数据
保存起来,等到系统发出FD_WRITE消息后尝试重新发送。也就是说,你需要针对FD_WRITE构建一套数据重发的机制,文末的工程源码里包含有
这套机制以供大家参考,这里不再赘述。

结语
至此,如何在非阻塞模式下使用WinSock进行编程介绍完毕,这个框架可以满足大多数网络游戏客户端及部分服务器的通信需求。更多应用层面
上的问题(如TCP粘包等)这里没有讨论,或许会在以后的文章中给出。


WSAAsyncSelect模型(同步I/O模型)

这里为什么说他是同步的,就是因为实际的数据的Copy是同步进行///的,而不是异步的,只是相应的通知机制(通知数据已经准备好了),是异步的

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


WSAAsyncSelect模型是Windows socket的一个异步IO模型。利用该模型可以接收以Windows消息为基础的网络事件。Windows sockets应用程序在创建套接字后,调用WSAAsyncSelect函数注册感兴趣的网络事件,当该事件发生时Windows窗口收到消息,应用程序就可以对接收到的网络时间进行处理。

 

WSAAsyncSelect是select模型的异步版本。在应用程序使用select函数时会发生阻塞现象。可以通过selecttimeout参数设置阻塞的时间。在设置的时间内,select函数等待,直到一个或多个套接字满足可读或可写的条件。


WSAAsyncSelect是非阻塞的。Windows sockets程序在调用recvsend之前,调用WSAAsyncSelect注册网络事件。WSAAsyncSelect函数立即返回。当系统中数据准备好时,会向应用程序发送消息。此此消息的处理函数中可以调用recvsend进行接收或发送数据。

 

WSAAsyncSelect模型与select模型的相同点是它们都可以对多个套接字进行管理。但它们也有不小的区别。首先WSAAsyncSelect模型是异步的,且通知方式不同。更重要的一点是:WSAAsyncSelect模型应用在基于消息的Windows环境下,使用该模型时必须创建窗口,而select模型可以广泛应用在Unix系统,使用该模型不需要创建窗口。最后一点区别:应用程序在调用WSAAsyncSelect函数后,套接字就被设置为非阻塞状态。而使用select函数不改变套接字的工作方式。

 

WSAAsyncSelect函数。

该函数告诉系统当网络事件发生时为套接字发送消息。声明如下: 

[html]  view plain copy
  1. int WSAAsyncSelect(  
  2.   
  3.     SOCKET s,  
  4.   
  5.     HWND hWnd,  
  6.   
  7.     u_int wMsg,  
  8.   
  9.     long lEvent);  

     s为需要通知的套接字。

     hWnd为当网络事件发生时接收消息的窗口句柄。

     wMsg为当网络事件发生时窗口收到的消息。在此消息的响应函数内对网络事件进行处理。

     lEvent为应用程序感兴趣的网络事件集合。

     应用程序调用该函数后自动将套接字设置为非阻塞模式。通常用户自定义消息应该在WM_USER的基础之上定义。如WM_USER+1,以避免与Windows预定义的消息发生混淆。

     网络事件可以有以下几种:

 

     FD_READ:套接字可读通知。

     FD_WRITE:可写通知。

     FD_ACCEPT:服务器接收连接的通知。

     FD_CONNECT:有客户连接通知。

     FD_OOB:外带数据到达通知。

     FD_CLOSE:套接字关闭通知。

     FD_QOS:服务质量发生变化通知。

     FD_GROUP_QOS:组服务质量发生变化通知。

     FD_ROUTING_INTERFACE_CHANGE:与路由器接口发生变化的通知。

     FD_ADDRESS_LIST_CHANGE:本地地址列表发生变化的通知。

 

     开发人员应向应用程序注册感兴趣的网络事件。可以将它们按位或并传给lEvent函数。如:

[cpp]  view plain copy
  1. WSAAsyncSelect(s,hWnd,WM_SOCKET,FD_CONNECT|FD_READ|FD_CLOSE);  

     上述代码表示:当套接字连接到来、有数据可读或这套接字关闭的网络事件发生时,WM_SOCKET消息就会发送给hWnd为句柄的窗口。

     消息处理函数。

     消息处理函数是对网络事件发生时窗口消息的处理。它的声明如下:

[cpp]  view plain cop
  1. LRESULT CALLBACK WindowProc(  
  2.   
  3.        HWND hWnd,  
  4.   
  5.        UINT uMsg,  
  6.   
  7.        WPARAM wParam,  
  8.   
  9.        LPARAM lParam)  

     hWnd为窗口句柄。

     uMsg为当网络事件发生时的消息。

     wParam为消息参数。该参数表明发生网络事件的套接字。

     lParam也为消息参数。低字节表明已发生的网络事件。高字节包含错误代码。

 

     在Windows sockets应用程序中,当WindowProc接收到网络消息时,在该函数内执行下面的步骤:

     1:读取lParam的高字节,判断是否有错误发生。可以使用WSAGETSElECTERROR宏。

     2:如果没有错误,读取lParam的低字节,检查发生了什么网络事件,可以使用WSAGETSELECTEVENT宏。

 

     WSAGETSElECTERROR和WSAGETSELECTEVENT宏定义如下:

 

[cpp]  view plain copy
  1. #define WSAGETSElECTERROR(lParam)  LOWORD(lParam)  
  2.   
  3. #define WSAGETSELECTEVENT(lParam)  HIWORD(lParam)  


 

接下来就需要创建窗口和将网络消息与消息处理函数关联起来。如果使用MFC可以使用MFC提供的宏来进行处理。


注意:多次调用WSAAsyncSelect时,最后一次调用会取消前面注册的网络事件。

 

因为调用accept接受的套接字和监听套接字具有同样的属性。所以,任何为监听套接字设置的网络事件对接受套接字同样起作用。如果一个监听套接字请求FD_ACCEPTFD_READFD_WRITE网络事件。则在该监听套接字上接受的任何套接字也会请求FD_ACCEPTFD_READFD_WRITE网络事件。

 

FD_CLOSE网络事件用来判断套接字是否已经关闭。错误代码会指出套接字是从容关闭还是硬关闭。如果为0,为从容关闭。若错误代码为WSAECONNRESET,则套接字是硬关闭。调用closesocket不会投递FD_CLOSE事件。


发生网络事件的条件。

 

下列条件下会发生FD_READ事件:

1:当调用WSAAsyncSelect函数时,如果当前有数据可读。

2:当数据到达并且没有发送FD_READ网络事件时。

3:调用recv()或这recvfrom,如果仍有数据可读里。

 

下列情况下会发生FD_WRITE事件:

1:调用WSAAsyncSelect函数时,如果能够发送数据时。

2:connect或者accept函数后,连接已经建立时。

3:调用send或者sendto函数,返回WSAWOULDBLOCK错误后,再次调用send()或者sendto函数可能成功时。因为此时可能是套接字还处于不可写状态,多次调用直到调用成功为止。

 

WSAAsyncSelect的优势与不足。

该模型是在基于消息的Windows环境下开发应用程序。开发人员可以像处理其他消息一样,对网络事件进行处理。而且为确保接受所有数据提供了很好的机制。

不足:由于该模型基于Windows消息机制,必须在应用程序中创建窗口。虽然可以在开发中,确定是否显示该窗口。 由于调用WSAAsyncSelect函数后自动将套接字设置为非阻塞状态,当应用程序接收到网络事件时,未必能够成功返回。这无疑增加了使用该模型的难度。


接下来展示一个使用如何WSAAsyncSelect模型的例子。该程序使用WSAAsyncSelect模型管理接受的客户端套接字。编码步骤如下:

     1:声明自定义消息。如WM_SOCKET

     2:声明窗口例程。

     3:将自定义消息与消息处理函数相关联。

     4:初始化套接字动态库,创建套接字。

     5:调用WSAAsyncSelect注册感兴趣的网络事件。本例服务器感兴趣的网络事件有FD_ACCEPTFD_CLOSE

     6:绑定套接字开始监听。

     一:声明自定义消息:

 

[cpp]  view plain copy
  1. #define WM_SOCKET WM_USER+1 //套接字消息。  

     除了声明自定义消息外还需要声明最大字符串长度、服务器监听端口、数据缓冲区。

 

[cpp]  view plain copy
  1. #define MAX_STRING 100     //最大字符串长度。  
  2.   
  3. #define SERVERPORT 5000    //服务器端口。  
  4.   
  5. #define MAX_SIZE_BUF 1024  //数据缓冲区长度。  

     二:声明消息处理函数并与消息关联:

 

     1:在窗口类头文件中声明消息处理函数。如:

 

[cpp]  view plain copy
  1. afx_msg LRESULT onWmSocket(WPARAM wParam, LPARAM lParam);  

 

     2:在消息映射宏中将自定义消息如声明的消息处理函数关联:

 

[cpp]  view plain copy
  1. ON_MESSAGE(WM_SOCKET,&onWmSocket)  

 

     3:实现消息处理函数:

[cpp]  view plain copy
  1. LRESULT CuserdefinedMessageTestDlg::onWmSocket( WPARAM wParam, LPARAM lParam )  
  2. {  
  3.     if(WSAGETSELECTERROR(lParam))  
  4.     {  
  5.         m_list.deleteNode(wParam);//wParam为发生消息的套接字。出现错误,则从链表中将该套接字对应的CClient类对象删除。  
  6.         return false;  
  7.     }  
  8.     else  
  9.     {  
  10.         switch(WSAGETSELECTEVENT(lParam))  
  11.         {  
  12.         case FD_ACCEPT://接受客户端连接请求。  
  13.             {  
  14.                 SOCKET sAccept;  
  15.                 if((sAccept==accept(wParam,NULL,NULL)==INVALID_SOCKET))  
  16.                     break;  
  17.                 m_list.add(sAccept);  
  18.                 //在新接受的套接字发生FD_READ,FD_WRITE,FD_CLOSE网络事件发生,发送WM_SOCKET消息;  
  19.                 WSAAsyncSelect(sAccept,this->m_hWnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOSE);  
  20.             }  
  21.             break;  
  22.         case FD_READ://可读,接收数据。  
  23.             {  
  24.                 CClient *pClinet=GetClient(wParam);//根据套接字,获取客户端节点。  
  25.                 pClient->RecvData();  
  26.             }  
  27.             break;  
  28.         case FD_WRITE://可写,发送数据。  
  29.             {  
  30.                 CClient*pClient=GetClient(wParam);  
  31.                 pClient->SendData();  
  32.                   
  33.             }  
  34.             break;  
  35.         case FD_CLOSE://对方关闭套接字连接。  
  36.             {  
  37.                 if(WSAGETSELECTERROR(lParam)==0)  
  38.                 {  
  39.                     //从容关闭。  
  40.                 }  
  41.                 else if(WSAGETSELECTERROR(lParam)==WSAECONNREFUSED)  
  42.                 {  
  43.                     //硬关闭。  
  44.                 }  
  45.                 m_list.deleteNode(wParam);  
  46.             }  
  47.             break;  
  48.         default:  
  49.             break;  
  50.         }  
  51.     }  
  52.     return 0;  
  53. }  




分别来自:http://blog.csdn.net/clzdl/article/details/4327972
和http://blog.csdn.net/liujiayu2/article/details/46375563
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值