重叠I/O详解【转】

时间:2022-03-20 07:54:18

    重叠I/O也是一种异步I/O,同样也支持Win32的其它对象,当然在Winsock中可以发挥很大的作用。使用 Overlapped开发支持一定数量的Socket的应用,效率是相当很高的。

    但就我个人的观点,在Win32下做网络应用的开发,如果要支持100个 以上的Socket的话,还是考虑Completion Port I/O。要求支持Socket最好是100个以下,我是基于这样考虑的:Overlapped是通过多线程支持多Socket的,如果开辟的线程太多的 话,势必影响了系统的性能;Completion Port I/O可以更好支持大量的客户端。这两种在Windows下具有高效率的I/O都不支持Windows CE及其它平台。我在这里说一下在开发网络应用时什么时候用Overlapped I/O 模型:准备在Win98和以上版本或WinNT3.1和以上版本做开发,且要求支持Socket最好在100个以下。另外在串口开发中,考虑效率问题,有 很多地方用到了Overlapped I/O。

         Overlapped I/O主要涉及一个数据结构Overlapped(Winsock中是WSAOverlapped)和一个函数WSAGetOverlappedResult(..)。
         Overlapped I/O执行步骤很清晰,只要下面三步:
         (1) 应用先通过WSASend或WSARecv(不知道有没有其它的请求,我只用过这两个函数),注意要向两者转WSAOverlapped参数,表示,执行的是Overlapped操作;
         (2)在一个循环中,调用GetOverlappedResult(..)等待操作完成,GetOverlappedResult返回时,进行相应的处理,如处理数据;(这个循环应该是在一个单独开辟的线程中实现的,而不会再主线程中阻塞)
         (3) 最后,还在(2)循环中,发送另外一个请求(WSASend或WSARecv),重复处理(2)、(3)两步。
        第一步中执行WSASend或WSARecv时,函数立即返回,得到SOCKET_ERROR信息且此时WSAGetErrorLast返回 WSA_IO_PENDING,说明调用已成功,Winsock正在处理WSASend或WSARecv的请求。 个人认为Winsock在内部开辟了新的 线程处理,应用程序不用管理多线程,达到异步的目的,有利于性能的提高。WSASend或WSARecv也可能返回"0",表示立即成功,这时,应用还是 可以在WSAGetOverlappedResult()处等待,处理过程与上面是一样的;也就是说我们不须要马上在WSASend或WSARecv进行 相关的处理。

        WSAGetOverlappedResult返回FASLE且WSAGetLastError返回WSA_IO_INCOMPLETE,表示处理正在进行中。            下面我给出支持单个Socket及支持多个Socket的Console程序代码。先来看看支持单个Socket的程序,考虑到代码简洁性,只给一个框架,同时不进行出错处理。

 1  int  main()
 2  {
 3            WSAOVERLAPPED     Overlapped;
 4                    
 5             //  启动Winsock及建立监听套接字
 6             listen(hListenSocket,   5 );
 7         
 8             hClientSocket    =    accept(hListenSocket,   NULL,   NULL);
 9             ZeroMemory( & Overlapped,    sizeof (WSAOVERLAPPED));
10             
11             nResult    =    WSARecv(重叠I/O详解【转】);     //  发出请求
12          
13             for  (; ;)
14            {
15                     bResult    =    WSAGetOverlappedResult(重叠I/O详解【转】);
16                      //  函数返回后进行相应的处理
17                     nResult    =    WSARecv(重叠I/O详解【转】);   //  发出另外一个请求
18            }
19  }

 上面的程序只是想说明一下过程,程序没有实现什么功能。这样做的目的是节约字数,用来说明我下面支持多个Socket的Console应用。请继续看。

        先看一个自定义的结构体,单句柄数据结构,通过该结构,主线程与某个特定的子线程中的套接字相互联系。

typedef    struct     _PER_HANDLE_DATA
         {
                   SOCKET   hSocket;     
//  主键:通信套接字
                   char     szClientIP[ 16 ]; //  自定义字段:客户端地址
                   int  nOperateType;    //  自定义字段:操作类型
          }PER_HANDLE_DATA,   FAR *   LPPER_HANDLE_DATA;

  在上面的结构中还可以加入自己需要的字段。在我下面的例子程序中,szClientIP是用来保存连接上来的客户端的IP的,这样在主线程将这个结构体传 给子线程后,在子线程中根据客户端IP就知道目前处理的是哪个客户端了。下面是程序的大部分,同样除去一些简单的出错输出。

  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       char           Buffer[DATA_BUFSIZE];
 48      BOOL          bResult;
 49       int            nResult;
 50 
 51      ZeroMemory( & Overlapped,  sizeof (WSAOVERLAPPED));
 52      wsaBuf.buf  =  Buffer;
 53      wsaBuf.len  =   sizeof (Buffer);
 54       lpPerHandleData->nOperateType = POST_RECV ;      //  记录本次操作是Recv(..)
 55 
 56      dwFlags  =   0 ;
 57      nResult  =  WSARecv(
 58          lpPerHandleData -> hSocket,    //  Receive socket
 59           & wsaBuf,                                   //  指向WSABUF结构的指针
 60           1 ,                                                  //  WSABUF数组的个数
 61           & dwNumOfBytesRecved,       //  存放当WSARecv完成后所接收到的字节数 ,实际接收到的字节数
 62           & dwFlags,                                  //  A pointer to flags
 63           & Overlapped,                            //  A pointer to a WSAOVERLAPPED structure
 64          NULL                                          //  A pointer to the completion routine,this is NULL
 65          );
 66       if    ( nResult    ==    SOCKET_ERROR      &&    GetLastError()  !=        WSA_IO_PENDING)
 67      {
 68          printf( " WSARecv(..) failed.\n " );
 69          free(lpPerHandleData);
 70           return   - 1 ;
 71      }
 72 
 73       while  (TRUE)
 74      {
 75          bResult   =   WSAGetOverlappedResult(
 76              lpPerHandleData -> hSocket,  
 77               & Overlapped,           
 78               & dwBytesTransferred,        //  当一个同步I/O完成后,接收到的字节数
 79              TRUE,                       //  等待I/O操作的完成
 80               & dwFlags                   
 81              );
 82           if    (bResult   ==   FALSE   &&   WSAGetLastError()   !=   WSA_IO_INCOMPLETE)
 83          {
 84              printf( " WSAGetOverlappdResult(..) failed.\n " );
 85              free(lpPerHandleData);
 86               return   0 ;    //  错误退出
 87          }
 88 
 89           if   (dwBytesTransferred  ==   0 )
 90          {
 91              printf( " 客户端已退出,将断开与之的连接!\n " );
 92               closesocket(lpPerHandleData->hSocket);
 93              free(lpPerHandleData);
 94               break ;
 95          }
 96 
 97           //  在这里将接收到的数据进行处理
 98           printf("Received from IP: %s.\ndata: %s\n", lpPerHandleData->szClientIP, wsaBuf.buf);     
 99 
100           //  发送另外一个请求操作
101          ZeroMemory( & Overlapped,  sizeof (WSAOVERLAPPED));
102           lp PerHandleData->nOperateType = POST_RECV;
103 
104          dwFlags  =   0 ;
105          nResult  =  WSARecv(重叠I/O详解【转】);
106           if  (重叠I/O详解【转】){}
107 
108      }
109 
110       return   1 ;
111  }

 程序结构比较清晰,lpPerHandleData是主线程与子线程联系的纽带,子线程是通过这个结构获得所处理客户端的 情况的。在不同的应用中可以将这个结构定义成不同的形式,以符合所实现应用的需要。注意结构体的nOperateType在 GetOverlappedResult返回时用到,可以根据这个字段确定我们下一步的操作