Overlapped I/O模型--事件通知【摘录自《Windows网络编程》】

    重叠I / O的事件通知方法要求将Wi n 3 2事件对象与W S A O V E R L A P P E D结构关联在一起。若使用一个W S A O V E R L A P P E D结构,发出像W S A S e n d和W S A R e c v这样的I / O调用,它们会立即返回。

    一个重叠I / O请求最终完成后,我们的应用程序要负责取回重叠I / O操作的结果。一个重叠请求操作最终完成之后,在事件通知方法中, Wi n s o c k会更改与一个W S A O V E R L A P P E D结构对应的一个事件对象的事件传信状态,将其从“未传信”变成“已传信”。 由于一个事件对象

已分配给W S A O V E R L A P P E D结构,所以只需简单地调用W S AWa i t F o r M u l t i p l e E v e n t s函数,从而判断出一个重叠I / O调用在什么时候完成。

   注意: W S AWa i t F o r M u l t i p l e E v e n t s返回只是说明重叠IO操作完成,但是是成功的完成还是失败的完成还要调用W S A G e t O v e r l a p p e dR e s u l t(取得重叠结构)函数

    如W S A G e t O v e r l a p p e d R e s u l t函数调用成功,返回值就是T R U E。这意味着我们的重叠I / O操作已成功完成,而且由l p c b Tr a n s f e r参数指向的值已进行了更新。

 

   我们向大家阐述了如何编制一个简单的服务器应用,令其在一个套接字上对重叠I / O操作进行管理,程序完全利用了前述的事件通知机制。对该程序采用的编程步骤总结如下:
1) 创建一个套接字,开始在指定的端口上监听连接请求。
2) 接受一个进入的连接请求。
3) 为接受的套接字新建一个W S A O V E R L A P P E D结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由W S AWa i t F o r M u l t i p l e E v e n t s函数使用。
4) 在套接字上投递一个异步W S A R e c v请求,指定参数为W S A O V E R L A P P E D结构。
注意函数通常会以失败告终,返回S O C K E T _ E R R O R错误状态W S A _ I O _ P E N D I N G
(I/O操作尚未完成)。
5) 使用步骤3 )的事件数组,调用W S AWa i t F o r M u l t i p l e E v e n t s函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6) WSAWa i t F o r M u l t i p l e E v e n t s函数完成后,针对事件数组,调用W S A R e s e t E v e n t(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7) 使用W S A G e t O v e r l a p p e d R e s u l t函数,判断重叠调用的返回状态是什么。
8) 在套接字上投递另一个重叠W S A R e c v请求。

9) 重复步骤5 ) ~ 8 )。

 

  1  #include  " stdafx.h "
  2  #include  < winsock2.h >
  3 
  4  #define  DATA_BUFSIZE 4096
  5 
  6  #pragma  comment(lib, "ws2_32.lib")
  7 
  8  int  _tmain( int  argc, _TCHAR *  argv[])
  9  {
 10      DWORD EventTotal  =   0 ,RecvBytes  =   0 , Flags  =   0 ;
 11       char  buffer[DATA_BUFSIZE];
 12 
 13      WSABUF  DataBuf;    
 14      WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
 15      WSAOVERLAPPED  AcceptOverlapped;
 16      SOCKET  Listen,Accept;
 17 
 18       // step1:
 19       // start Winsock and set up a listening socket
 20      WSADATA wsaData;
 21      WSAStartup(MAKEWORD( 2 , 2 ),  & wsaData);
 22 
 23      ListenSocket  =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 24      u_short port  =   27015 ;
 25       char *  ip;
 26      sockaddr_in service;
 27      service.sin_family  =  AF_INET;
 28      service.sin_port  =  htons(port);
 29      hostent *  thisHost;
 30      thisHost  =  gethostbyname( "" );
 31      ip  =  inet_ntoa ( * ( struct  in_addr  * ) * thisHost -> h_addr_list);
 32 
 33      service.sin_addr.s_addr  =  inet_addr(ip);
 34 
 35       // -----------------------------------------
 36       //  Bind the listening socket to the local IP address
 37       //  and port number
 38      bind(ListenSocket, (SOCKADDR  * & service,  sizeof (SOCKADDR));
 39 
 40       // -----------------------------------------
 41       //  Set the socket to listen for incoming
 42       //  connection requests
 43      listen(ListenSocket,  1 );
 44      printf( " Listening\n " );
 45 
 46       // step2:
 47      Accept = accept(Listen,NULL,NULL);
 48 
 49       // step3:
 50       // set up  an overlapped structure
 51      EventArray[EventTotal] = WSACreateEvent(); // 先存到事件数组中
 52      ZeroMemory( & AcceptOverlapped, sizeof (WSAOVERLAPPED));
 53      AcceptOverlapped.hEvent  =  EventArray[EventTotal];
 54 
 55      DataBuf.len  =  DATA_BUFSIZE;
 56      DataBuf.buf  = buffer;
 57 
 58      EventTotal ++ ;
 59 
 60       // step4:
 61       // 投递WSARecv准备在Accept套接字上接收数据
 62      WSARecv(Accept, & DataBuf, 1 , & RecvBytes, & Flag, & AcceptOverlapped,NULL);
 63 
 64       while  (TRUE){
 65           // step5:
 66           // 等待overlapped IO调用的完成
 67          DWORD Index;
 68          Index  =  WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
 69 
 70           // step6:
 71           //  Reset the signaled event
 72          WSAResetEvent(EventArray[Index  -  WSA_WAIT_EVENT_0]);
 73 
 74           // step7:
 75           //  Determine the status of the overlapped event
 76          WSAGetOverlappedResult(AcceptSocket,  & AcceptOverlapped,  & BytesTransferred, FALSE,  & Flags);
 77 
 78 
 79           //  If the connection has been closed, close the accepted socket
 80           if  (BytesTransferred  ==   0 ) {
 81              printf( " Closing Socket %d\n " , AcceptSocket);
 82              closesocket(AcceptSocket);
 83              WSACloseEvent(EventArray[Index  -  WSA_WAIT_EVENT_0]);
 84               return ;
 85          }
 86 
 87           //  If data has been received,do something with received data
 88           // DataBuf  contains the received data
 89 
 90           // step8:
 91           // post another WSARecv () request on the socket
 92          Flag = 0 ;
 93          ZeroMemory( & AcceptOverlapped, sizeof (WSAOVERLAPPED));
 94          AcceptOverlapped.hEvent = EventArray[Index  -  WSA_WAIT_EVENT_0]; // 该事件对象已经被复位
 95 
 96          DataBuf.len  =  DATA_BUFSIZE;
 97          DataBuf.buf  = buffer;
 98 
 99          WSARecv(Accept, & DataBuf, 1 , & RecvBytes, & Flag, & AcceptOverlapped,NULL);
100      }
101       return   0 ;
102  }

 

改进后的程序(并非实际可以运行的程序,只是为了理清思路):

 

  1  #define      LISTEN_PORT 5000
  2  #define      DATA_BUFSIZE 8192
  3  #define      POST_RECV 0X01   
  4  #define      POST_SEND 0X02
  5 
  6  int    main(  )
  7  {
  8      LPPER_HANDLE_DATA    lpPerHandleData;
  9      SOCKET               hListenSocket;
 10      SOCKET               hClientSocket;
 11      SOCKADDR_IN          ClientAddr;
 12       int                   nAddrLen;
 13      HANDLE               hThread; 
 14 
 15       //  Start WinSock and create a listen socket.
 16 
 17      listen(hListenSocket,   5 ); 
 18       for  (; ;)
 19      {
 20          nAddrLen   =    sizeof (SOCKADDR);
 21          hClientSocket   =   accept(hListenSocket,  (LPSOCKADDR) & ClientAddr,   & nAddrLen);
 22 
 23          lpPerHandleData  =  (LPPER_HANDLE_DATA)malloc( sizeof (PER_HANDLE_DATA));
 24          lpPerHandleData -> hSocket   =   hClientSocket;
 25           //  注意这里将连接的客户端的IP地址,保存到了lpPerHandleData字段中了
 26          strcpy(lpPerHandleData -> szClientIP,   inet_ntoa(ClientAddr.sin_addr));
 27 
 28           //  为本次客户请求产生子线程
 29          hThread  =  CreateThread(
 30              NULL,
 31               0 ,
 32              OverlappedThread,
 33              lpPerHandleData,    //  将lpPerHandleData传给子线程
 34               0 ,
 35              NULL
 36              );
 37          CloseHandle(hThread);
 38      }   
 39       return   0 ;
 40  }
 41 
 42  DWORD   WINAPI   OverlappedThread(LPVOID    lpParam)
 43  {
 44      LPPER_HANDLE_DATA     lpPerHandleData    =    (LPPER_HANDLE_DATA)lpParam;
 45      WSAOVERLAPPED Overlapped;
 46      WSABUF        wsaBuf;
 47      WSAEVENT    EventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件对象数组
 48 
 49      DWORD     dwEventTotal  =   0 ,             //  程序中事件的总数
 50       char           Buffer[DATA_BUFSIZE];
 51      BOOL          bResult;
 52       int            nResult;
 53 
 54      EventArray[dwEventTotal]  =  WSACreateEvent();      
 55 
 56      ZeroMemory( & Overlapped,  sizeof (WSAOVERLAPPED));
 57 
 58      Overlapped.hEvent  =  EventArray[dwEventTotal];             //  关联事件
 59 
 60      ZeroMemory(Buffer, DATA_BUFSIZE);
 61      wsaBuf.buf  =  Buffer;
 62      wsaBuf.len  =   sizeof (Buffer);
 63      
 64      lpPerHandleData -> nOperateType  =  POST_RECV;      //  记录本次操作是Recv(..)
 65 
 66      dwEventTotal  ++ ;                               //  总数加一
 67      dwFlags  =   0 ;
 68 
 69      nResult  =  WSARecv(
 70          lpPerHandleData -> hSocket,    //  Receive socket
 71           & wsaBuf,                                   //  指向WSABUF结构的指针
 72           1 ,                                                  //  WSABUF数组的个数
 73           & dwNumOfBytesRecved,       //  存放当WSARecv完成后所接收到的字节数,实际接收到的字节数
 74           & dwFlags,                                  //  A pointer to flags
 75           & Overlapped,                            //  A pointer to a WSAOVERLAPPED structure
 76          NULL                                          //  A pointer to the completion routine,this is NULL
 77          );
 78 
 79       if    ( nResult    ==    SOCKET_ERROR      &&    GetLastError()  !=        WSA_IO_PENDING)
 80      {
 81          printf( " WSARecv(..) failed.\n " );
 82          free(lpPerHandleData);
 83 
 84          closesocket(lpPerHandleData -> hSocket;
 85           WSACloseEvent(EventArray[dwEventTotal]);
 86           return   - 1 ;
 87      }
 88 
 89       while  (TRUE)
 90      {
 91          DWORD dwIndex;
 92 
 93          dwIndex  =  WSAWaitForMultipleEvents(dwEventTotal, EventArray ,
 94              FALSE ,WSA_INFINITE,FALSE);
 95 
 96         WSAResetEvent(EventArray[dwIndex– WSA_WAIT_EVENT_0]);
 97 
 98 
 99          bResult   =   WSAGetOverlappedResult(
100              lpPerHandleData -> hSocket,  
101               & Overlapped,           
102               & dwBytesTransferred,        //  当一个同步I/O完成后,接收到的字节数
103              TRUE,                       //  等待I/O操作的完成
104               & dwFlags                   
105              );
106           if    (bResult   ==   FALSE   &&   WSAGetLastError()   !=   WSA_IO_INCOMPLETE)
107          {
108              printf( " WSAGetOverlappdResult(..) failed.\n " );
109              free(lpPerHandleData);
110               return   0 ;    //  错误退出
111          }
112 
113           if   (dwBytesTransferred  ==   0 )
114          {
115              printf( " 客户端已退出,将断开与之的连接!\n " );
116              closesocket(lpPerHandleData -> hSocket);
117              free(lpPerHandleData);
118               break ;
119          }
120 
121           //  在这里将接收到的数据进行处理
122          printf( " Received from IP: %s.\ndata: %s\n " , lpPerHandleData -> szClientIP, wsaBuf.buf);    
123 
124           //  发送另外一个请求操作
125          ZeroMemory( & Overlapped,  sizeof (WSAOVERLAPPED));
126          lpPerHandleData -> nOperateType  =  POST_RECV;
127 
128          dwFlags  =   0 ;
129          nResult  =  WSARecv();
130           if  (){}
131 
132      }
133 
134       return   1 ;
135  }

 

最后的一个改进版本,看上去是个不错的版本,相对来说算是比较实用的。但是使用了过多的全局变量,代码是C风格的,不可取。

  1  #include  < winsock2.h >
  2  #include  < stdio.h >
  3 
  4  #define  PORT    5150
  5  #define  MSGSIZE 1024
  6 
  7  #pragma  comment(lib, "ws2_32.lib")
  8 
  9  typedef  struct
 10  {
 11  WSAOVERLAPPED overlap;
 12  WSABUF        Buffer;
 13  char           szMessage[MSGSIZE];
 14  DWORD         NumberOfBytesRecvd;
 15  DWORD         Flags;
 16  }PER_IO_OPERATION_DATA,  * LPPER_IO_OPERATION_DATA;
 17 
 18  int                      g_iTotalConn  =   0 ;
 19  SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
 20  WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
 21  LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];
 22 
 23  DWORD WINAPI WorkerThread(LPVOID);
 24 
 25  void  Cleanup( int );
 26 
 27  int  main()
 28  {
 29  WSADATA     wsaData;
 30  SOCKET      sListen, sClient;
 31  SOCKADDR_IN local, client;
 32  DWORD       dwThreadId;
 33  int          iaddrSize  =   sizeof (SOCKADDR_IN);
 34  //  Initialize Windows Socket library
 35  WSAStartup( 0x0202 & wsaData);
 36  //  Create listening socket
 37  sListen  =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 38  //  Bind
 39  local.sin_addr.S_un.S_addr  =  htonl(INADDR_ANY);
 40  local.sin_family  =  AF_INET;
 41  local.sin_port  =  htons(PORT);
 42  bind(sListen, ( struct  sockaddr  * ) & local,  sizeof (SOCKADDR_IN));
 43  //  Listen

 44 listen(sListen, 3);

 

 

//网上的代码如此,好奇怪,为什么是在连接还没开始的时候就创建一个线程,而不是像上面的程序一样,accept一个

//connection创建一个线程,并将从connection 的socket中获取的信息当作形参传递给workThread?

//这里是只创建一个线程为重叠socket I/O操作服务

 45  //  Create worker thread
 46  CreateThread(NULL,  0 WorkerThread, NULL,  0 & dwThreadId);
 47  while  (TRUE)
 48  {
 49       //  Accept a connection

 50     sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);

 

         //这很好,在accept函数中的后两个形参中获取client的相关信息,并直接在server端的控制台中显示出来

 51      printf( " Accepted client:%s:%d\n " , inet_ntoa(client.sin_addr), ntohs(client.sin_port));
 52      g_CliSocketArr[g_iTotalConn]  =  sClient;//添加到socket数组中
 53     
 54       //  Allocate a PER_IO_OPERATION_DATA structure
 55      g_pPerIODataArr[g_iTotalConn]  = ER (LPPER_IO_OP ATION_DATA)HeapAlloc(

 56              GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(PER_IO_OPERATION_DATA));

 

 

 59      g_pPerIODataArr[g_iTotalConn] ->Buffer.len =  MSGSIZE;
 60      g_pPerIODataArr[g_iTotalConn] ->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->eszM ssage;
 61      g_CliEventArr[g_iTotalConn]  = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent =  WSACreateEvent();
 62       //  Launch an asynchronous operation
 63      WSARecv(
 64        g_CliSocketArr[g_iTotalConn],
 65         & g_pPerIODataArr[g_iTotalConn] -> Buffer,
 66         1 ,
 67         & g_pPerIODataArr[g_iTotalConn] -> NumberOfBytesRecvd,
 68         & g_pPerIODataArr[g_iTotalConn] -> Flags,
 69         & g_pPerIODataArr[g_iTotalConn] -> overlap,
 70        NULL);
 71     
 72      g_iTotalConn ++ ;
 73  }
 74 
 75  closesocket(sListen);
 76  WSACleanup();
 77  return   0 ;
 78  }
 79  DWORD WINAPI WorkerThread(LPVOID lpParam)
 80  {
 81  int    ret, index;
 82  DWORD cbTransferred;
 83  while  (TRUE)
 84  {
 85      ret  =  WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE,  1000 , FALSE);
 86       if  (ret  ==  WSA_WAIT_FAILED  ||  ret  ==  WSA_WAIT_TIMEOUT)
 87      {
 88         continue ;
 89      }
 90      index  = ret -  WSA_WAIT_EVENT_0;
 91      WSAResetEvent(g_CliEventArr[index]);
 92      WSAGetOverlappedResult(
 93        g_CliSocketArr[index],
 94         & g_pPerIODataArr[index] -> overlap,
 95         & cbTransferred,
 96        TRUE,
 97         & g_pPerIODataArr[g_iTotalConn] -> Flags);
 98       if  (cbTransferred  ==   0 )
 99      {
100         //  The connection was closed by client
101        Cleanup(index);
102      }
103       else
104      {
105         //  g_pPerIODataArr[index]->szMessage contains the received data
106        g_pPerIODataArr[index] -> szMessage[cbTransferred]  =   ' \0 ' ;
107        send(g_CliSocketArr[index], g_pPerIODataArr[index] -> szMessage,\
108          cbTransferred,  0 );
109         //  Launch another asynchronous operation
110        WSARecv(
111          g_CliSocketArr[index],
112           & g_pPerIODataArr[index] -> Buffer,
113           1 ,
114           & g_pPerIODataArr[index] -> NumberOfBytesRecvd,
115           & g_pPerIODataArr[index] -> Flags,
116           & g_pPerIODataArr[index] -> overlap,
117          NULL);
118      }
119  }
120  return   0 ;
121  }
122  void  Cleanup( int  index)
123  {
124  closesocket(g_CliSocketArr[index]);
125  WSACloseEvent(g_CliEventArr[index]);
126  HeapFree(GetProcessHeap(),  0 , g_pPerIODataArr[index]);
127  if  (index  <  g_iTotalConn  -   1 )
128  {
129      g_CliSocketArr[index]  =  g_CliSocketArr[g_iTotalConn  -   1 ];
130      g_CliEventArr[index]  =  g_CliEventArr[g_iTotalConn  -   1 ];
131      g_pPerIODataArr[index]  =  g_pPerIODataArr[g_iTotalConn  -   1 ];
132  }
133  g_pPerIODataArr[ -- g_iTotalConn]  =  NULL;
134  }

  

    这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。在调用WSARecv时,指定一个WSAOVERLAPPED结构,这个调用不是阻塞的,也就是说,它会立刻返回。一旦有数据到达的时候,被指定的WSAOVERLAPPED结构中的hEvent被Signaled。由于下面这个语句g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;

    使得与该套接字相关联的WSAEVENT对象也被Signaled,所以WSAWaitForMultipleEvents的调用操作成功返回。我们现在应该做的就是与调用WSARecv相同的WSAOVERLAPPED结构为参数调用WSAGetOverlappedResult,从而得到本次I/O传送的字节数等相关信息。在取得接收的数据后,把数据原封不动的发送到客户端,然后重新激活一个WSARecv异步操作。

 

你可能感兴趣的:(windows)