Winsock IO模型之Overlapped模型

这个模型的基本思想是使用重叠数据结构一次投递一个或多个异步I/O请求。当提交的I/O请求完成之后,与之关联的重叠数据结构中的事件对象受信,应用程序便可使用WSAGetOverlappedResult函数获取重叠操作结果。这个和使用重叠结构调用ReadFile和WriteFile函数操作文件类似。

    使用这个模型,必须要搞清楚以下这几个异步I/O函数:WSASocket,AcceptEx,WSASend(UDP的:WSASendTo),WSARecv(UDP的:WSARecvFrom),WSAGetOverlappedResult,最关键的还需要知道WSAOVERLAPPED在这些异步IO调用中所起的作用(其实是通过事件),以及WSABUF(WSASend/WSARecv类似函数中需要填入的一个重要参数,在投递完后未受信之前这块buffer将被system锁定)结构体和OVERLAPPED(比WSAOVERLAPPED看起来更通用,但其本质是一样的,WSAOVERLAPPED名称看起来多了WSA)结构体,最后需要与那些通用的I/O函数(如recv,send等)进行对比才能理解得更深。

    以下是从《Windows网络与通信程序设计》中摘出来的几条注意点:

Winsock IO模型之Overlapped模型_第1张图片

Winsock IO模型之Overlapped模型_第2张图片

Winsock IO模型之Overlapped模型_第3张图片

Winsock IO模型之Overlapped模型_第4张图片

    以下是这本书上给出的一个tcp回显的实例:

[cpp]  view plain copy
  1. ///////////////////////////////////////////////////////  
  2. // OverlappedServer.cpp文件  
  3.   
  4. #include "../common/initsock.h"  
  5.   
  6. #include <Mswsock.h>  
  7. #include <stdio.h>  
  8. #include <windows.h>  
  9.   
  10. CInitSock theSock;  
  11.   
  12. #define BUFFER_SIZE 1024  
  13.   
  14. typedef struct _SOCKET_OBJ  
  15. {  
  16.     SOCKET s;                       // 套节字句柄  
  17.     int nOutstandingOps;            // 记录此套节字上的重叠I/O数量  
  18.       
  19.     LPFN_ACCEPTEX lpfnAcceptEx;     // 扩展函数AcceptEx的指针(仅对监听套节字而言)  
  20. } SOCKET_OBJ, *PSOCKET_OBJ;  
  21.   
  22. typedef struct _BUFFER_OBJ  
  23. {     
  24.     OVERLAPPED ol;          // 重叠结构  
  25.     char *buff;             // send/recv/AcceptEx所使用的缓冲区  
  26.     int nLen;               // buff的长度  
  27.     PSOCKET_OBJ pSocket;    // 此I/O所属的套节字对象  
  28.   
  29.     int nOperation;         // 提交的操作类型  
  30. #define OP_ACCEPT   1  
  31. #define OP_READ     2  
  32. #define OP_WRITE    3  
  33.   
  34.     SOCKET sAccept;         // 用来保存AcceptEx接受的客户套节字(仅对监听套节字而言)  
  35.     _BUFFER_OBJ *pNext;  
  36. } BUFFER_OBJ, *PBUFFER_OBJ;  
  37.   
  38. HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS];   // I/O事件句柄数组  
  39. int g_nBufferCount;                         // 上数组中有效句柄数量  
  40. PBUFFER_OBJ g_pBufferHead, g_pBufferTail;   // 记录缓冲区对象组成的表的地址  
  41.   
  42. // 申请套节字对象和释放套节字对象的函数  
  43. PSOCKET_OBJ GetSocketObj(SOCKET s)  
  44. {  
  45.     PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GlobalAlloc(GPTR, sizeof(SOCKET_OBJ));  
  46.     if(pSocket != NULL)  
  47.     {  
  48.         pSocket->s = s;  
  49.     }  
  50.     return pSocket;  
  51. }  
  52. void FreeSocketObj(PSOCKET_OBJ pSocket)  
  53. {  
  54.     if(pSocket->s != INVALID_SOCKET)  
  55.         ::closesocket(pSocket->s);  
  56.     ::GlobalFree(pSocket);  
  57. }  
  58.   
  59. PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket, ULONG nLen)  
  60. {  
  61.     if(g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS - 1)  
  62.         return NULL;  
  63.   
  64.     PBUFFER_OBJ pBuffer = (PBUFFER_OBJ)::GlobalAlloc(GPTR, sizeof(BUFFER_OBJ));  
  65.     if(pBuffer != NULL)  
  66.     {  
  67.         pBuffer->buff = (char*)::GlobalAlloc(GPTR, nLen);  
  68.         pBuffer->ol.hEvent = ::WSACreateEvent();  
  69.         pBuffer->pSocket = pSocket;  
  70.         pBuffer->sAccept = INVALID_SOCKET;  
  71.   
  72.         // 将新的BUFFER_OBJ添加到列表中  
  73.         if(g_pBufferHead == NULL)  
  74.         {  
  75.             g_pBufferHead = g_pBufferTail = pBuffer;  
  76.         }  
  77.         else  
  78.         {  
  79.             g_pBufferTail->pNext = pBuffer;  
  80.             g_pBufferTail = pBuffer;  
  81.         }  
  82.         g_events[++ g_nBufferCount] = pBuffer->ol.hEvent;  
  83.     }  
  84.     return pBuffer;  
  85. }  
  86.   
  87. void FreeBufferObj(PBUFFER_OBJ pBuffer)  
  88. {  
  89.     // 从列表中移除BUFFER_OBJ对象  
  90.     PBUFFER_OBJ pTest = g_pBufferHead;  
  91.     BOOL bFind = FALSE;  
  92.     if(pTest == pBuffer)  
  93.     {  
  94.         g_pBufferHead = g_pBufferTail = NULL;  
  95.         bFind = TRUE;  
  96.     }  
  97.     else  
  98.     {  
  99.         while(pTest != NULL && pTest->pNext != pBuffer)  
  100.             pTest = pTest->pNext;  
  101.         if(pTest != NULL)  
  102.         {  
  103.             pTest->pNext = pBuffer->pNext;  
  104.             if(pTest->pNext == NULL)  
  105.                 g_pBufferTail = pTest;  
  106.             bFind = TRUE;  
  107.         }  
  108.     }  
  109.     // 释放它占用的内存空间  
  110.     if(bFind)  
  111.     {  
  112.         g_nBufferCount --;  
  113.         ::CloseHandle(pBuffer->ol.hEvent);  
  114.         ::GlobalFree(pBuffer->buff);  
  115.         ::GlobalFree(pBuffer);    
  116.     }  
  117. }  
  118.   
  119. PBUFFER_OBJ FindBufferObj(HANDLE hEvent)  
  120. {  
  121.     PBUFFER_OBJ pBuffer = g_pBufferHead;  
  122.     while(pBuffer != NULL)  
  123.     {  
  124.         if(pBuffer->ol.hEvent == hEvent)  
  125.             break;  
  126.         pBuffer = pBuffer->pNext;  
  127.     }  
  128.     return pBuffer;  
  129. }  
  130.   
  131. void RebuildArray()  
  132. {  
  133.     PBUFFER_OBJ pBuffer = g_pBufferHead;  
  134.     int i =  1;  
  135.     while(pBuffer != NULL)  
  136.     {  
  137.         g_events[i++] = pBuffer->ol.hEvent;  
  138.         pBuffer = pBuffer->pNext;  
  139.     }  
  140. }  
  141.   
  142. BOOL PostAccept(PBUFFER_OBJ pBuffer)  
  143. {  
  144.     PSOCKET_OBJ pSocket = pBuffer->pSocket;  
  145.     if(pSocket->lpfnAcceptEx != NULL)  
  146.     {     
  147.         // 设置I/O类型,增加套节字上的重叠I/O计数  
  148.         pBuffer->nOperation = OP_ACCEPT;  
  149.         pSocket->nOutstandingOps ++;  
  150.   
  151.         // 投递此重叠I/O    
  152.         DWORD dwBytes;  
  153.         pBuffer->sAccept =   
  154.             ::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);  
  155.         BOOL b = pSocket->lpfnAcceptEx(pSocket->s,   
  156.             pBuffer->sAccept,  
  157.             pBuffer->buff,   
  158.             BUFFER_SIZE - ((sizeof(sockaddr_in) + 16) * 2),  
  159.             sizeof(sockaddr_in) + 16,   
  160.             sizeof(sockaddr_in) + 16,   
  161.             &dwBytes,   
  162.             &pBuffer->ol);  
  163.         if(!b)  
  164.         {  
  165.             if(::WSAGetLastError() != WSA_IO_PENDING)  
  166.                 return FALSE;  
  167.         }  
  168.         return TRUE;  
  169.     }  
  170.     return FALSE;  
  171. };  
  172.   
  173. BOOL PostRecv(PBUFFER_OBJ pBuffer)  
  174. {     
  175.     // 设置I/O类型,增加套节字上的重叠I/O计数  
  176.     pBuffer->nOperation = OP_READ;  
  177.     pBuffer->pSocket->nOutstandingOps ++;  
  178.   
  179.     // 投递此重叠I/O  
  180.     DWORD dwBytes;  
  181.     DWORD dwFlags = 0;  
  182.     WSABUF buf;  
  183.     buf.buf = pBuffer->buff;  
  184.     buf.len = pBuffer->nLen;  
  185.     if(::WSARecv(pBuffer->pSocket->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL) != NO_ERROR)  
  186.     {  
  187.         if(::WSAGetLastError() != WSA_IO_PENDING)  
  188.             return FALSE;  
  189.     }  
  190.     return TRUE;  
  191. }  
  192.   
  193. BOOL PostSend(PBUFFER_OBJ pBuffer)  
  194. {  
  195.     // 设置I/O类型,增加套节字上的重叠I/O计数  
  196.     pBuffer->nOperation = OP_WRITE;  
  197.     pBuffer->pSocket->nOutstandingOps ++;  
  198.   
  199.     // 投递此重叠I/O  
  200.     DWORD dwBytes;  
  201.     DWORD dwFlags = 0;  
  202.     WSABUF buf;  
  203.     buf.buf = pBuffer->buff;  
  204.     buf.len = pBuffer->nLen;  
  205.     if(::WSASend(pBuffer->pSocket->s,   
  206.             &buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR)  
  207.     {  
  208.         if(::WSAGetLastError() != WSA_IO_PENDING)  
  209.             return FALSE;  
  210.     }  
  211.     return TRUE;  
  212. }  
  213.   
  214. BOOL HandleIO(PBUFFER_OBJ pBuffer)  
  215. {  
  216.     PSOCKET_OBJ pSocket = pBuffer->pSocket; // 从BUFFER_OBJ对象中提取SOCKET_OBJ对象指针,为的是方便引用  
  217.     pSocket->nOutstandingOps --;  
  218.   
  219.     // 获取重叠操作结果  
  220.     DWORD dwTrans;  
  221.     DWORD dwFlags;  
  222.     BOOL bRet = ::WSAGetOverlappedResult(pSocket->s, &pBuffer->ol, &dwTrans, FALSE, &dwFlags);  
  223.     if(!bRet)  
  224.     {  
  225.         // 在此套节字上有错误发生,因此,关闭套节字,移除此缓冲区对象。  
  226.         // 如果没有其它抛出的I/O请求了,释放此缓冲区对象,否则,等待此套节字上的其它I/O也完成  
  227.         if(pSocket->s != INVALID_SOCKET)  
  228.         {  
  229.             ::closesocket(pSocket->s);  
  230.             pSocket->s = INVALID_SOCKET;  
  231.         }  
  232.   
  233.         if(pSocket->nOutstandingOps == 0)  
  234.             FreeSocketObj(pSocket);   
  235.           
  236.         FreeBufferObj(pBuffer);  
  237.         return FALSE;  
  238.     }  
  239.   
  240.     // 没有错误发生,处理已完成的I/O  
  241.     switch(pBuffer->nOperation)  
  242.     {  
  243.     case OP_ACCEPT: // 接收到一个新的连接,并接收到了对方发来的第一个封包  
  244.         {  
  245.             // 为新客户创建一个SOCKET_OBJ对象  
  246.             PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept);  
  247.   
  248.             // 为发送数据创建一个BUFFER_OBJ对象,这个对象会在套节字出错或者关闭时释放  
  249.             PBUFFER_OBJ pSend = GetBufferObj(pClient, BUFFER_SIZE);   
  250.             if(pSend == NULL)  
  251.             {  
  252.                 printf(" Too much connections! \n");  
  253.                 FreeSocketObj(pClient);  
  254.                 return FALSE;  
  255.             }  
  256.             RebuildArray();  
  257.               
  258.             // 将数据复制到发送缓冲区  
  259.             pSend->nLen = dwTrans;  
  260.             memcpy(pSend->buff, pBuffer->buff, dwTrans);  
  261.   
  262.             // 投递此发送I/O(将数据回显给客户)  
  263.             if(!PostSend(pSend))  
  264.             {  
  265.                 // 万一出错的话,释放上面刚申请的两个对象  
  266.                 FreeSocketObj(pSocket);   
  267.                 FreeBufferObj(pSend);  
  268.                 return FALSE;  
  269.             }  
  270.             // 继续投递接受I/O  
  271.             PostAccept(pBuffer);  
  272.         }  
  273.         break;  
  274.     case OP_READ:   // 接收数据完成  
  275.         {  
  276.             if(dwTrans > 0)  
  277.             {  
  278.                 // 创建一个缓冲区,以发送数据。这里就使用原来的缓冲区  
  279.                 PBUFFER_OBJ pSend = pBuffer;  
  280.                 pSend->nLen = dwTrans;  
  281.                   
  282.                 // 投递发送I/O(将数据回显给客户)  
  283.                 PostSend(pSend);  
  284.             }  
  285.             else    // 套节字关闭  
  286.             {  
  287.       
  288.                 // 必须先关闭套节字,以便在此套节字上投递的其它I/O也返回  
  289.                 if(pSocket->s != INVALID_SOCKET)  
  290.                 {  
  291.                     ::closesocket(pSocket->s);  
  292.                     pSocket->s = INVALID_SOCKET;  
  293.                 }  
  294.   
  295.                 if(pSocket->nOutstandingOps == 0)  
  296.                     FreeSocketObj(pSocket);       
  297.                   
  298.                 FreeBufferObj(pBuffer);  
  299.                 return FALSE;  
  300.             }  
  301.         }  
  302.         break;  
  303.     case OP_WRITE:      // 发送数据完成  
  304.         {  
  305.             if(dwTrans > 0)  
  306.             {  
  307.                 // 继续使用这个缓冲区投递接收数据的请求  
  308.                 pBuffer->nLen = BUFFER_SIZE;  
  309.                 PostRecv(pBuffer);  
  310.             }  
  311.             else    // 套节字关闭  
  312.             {  
  313.                 // 同样,要先关闭套节字  
  314.                 if(pSocket->s != INVALID_SOCKET)  
  315.                 {  
  316.                     ::closesocket(pSocket->s);  
  317.                     pSocket->s = INVALID_SOCKET;  
  318.                 }  
  319.   
  320.                 if(pSocket->nOutstandingOps == 0)  
  321.                     FreeSocketObj(pSocket);   
  322.   
  323.                 FreeBufferObj(pBuffer);  
  324.                 return FALSE;  
  325.             }  
  326.         }  
  327.         break;  
  328.     }  
  329.     return TRUE;  
  330. }  
  331.   
  332.   
  333. void main()  
  334. {  
  335.     // 创建监听套节字,绑定到本地端口,进入监听模式  
  336.     int nPort = 4567;  
  337.     SOCKET sListen =   
  338.         ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);  
  339.     SOCKADDR_IN si;  
  340.     si.sin_family = AF_INET;  
  341.     si.sin_port = ::ntohs(nPort);  
  342.     si.sin_addr.S_un.S_addr = INADDR_ANY;  
  343.     ::bind(sListen, (sockaddr*)&si, sizeof(si));  
  344.     ::listen(sListen, 200);  
  345.   
  346.     // 为监听套节字创建一个SOCKET_OBJ对象  
  347.     PSOCKET_OBJ pListen = GetSocketObj(sListen);  
  348.   
  349.     // 加载扩展函数AcceptEx  
  350.     GUID GuidAcceptEx = WSAID_ACCEPTEX;  
  351.     DWORD dwBytes;  
  352.     WSAIoctl(pListen->s,   
  353.         SIO_GET_EXTENSION_FUNCTION_POINTER,   
  354.         &GuidAcceptEx,   
  355.         sizeof(GuidAcceptEx),  
  356.         &pListen->lpfnAcceptEx,   
  357.         sizeof(pListen->lpfnAcceptEx),   
  358.         &dwBytes,   
  359.         NULL,   
  360.         NULL);  
  361.   
  362.     // 创建用来重新建立g_events数组的事件对象  
  363.     g_events[0] = ::WSACreateEvent();  
  364.   
  365.     // 在此可以投递多个接受I/O请求  
  366.     for(int i=0; i<5; i++)  
  367.     {  
  368.         PostAccept(GetBufferObj(pListen, BUFFER_SIZE));  
  369.     }  
  370.     ::WSASetEvent(g_events[0]);  
  371.       
  372.     while(TRUE)  
  373.     {  
  374.         int nIndex =   
  375.             ::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);  
  376.         if(nIndex == WSA_WAIT_FAILED)  
  377.         {  
  378.             printf("WSAWaitForMultipleEvents() failed \n");  
  379.             break;  
  380.         }  
  381.         nIndex = nIndex - WSA_WAIT_EVENT_0;  
  382.         for(int i=0; i<=nIndex; i++) // 这里我认为应该改为:for(int i = nIndex; i <= g_nBufferCount; i++)  
  383.         {  
  384.             int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);  
  385.             if(nRet == WSA_WAIT_TIMEOUT)  
  386.                 continue;  
  387.             else  
  388.             {  
  389.                 ::WSAResetEvent(g_events[i]);  
  390.                 // 重新建立g_events数组  
  391.                 if(i == 0)  
  392.                 {  
  393.                     RebuildArray();  
  394.                     continue;  
  395.                 }  
  396.   
  397.                 // 处理这个I/O  
  398.                 PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);  
  399.                 if(pBuffer != NULL)  
  400.                 {  
  401.                     if(!HandleIO(pBuffer))  
  402.                         RebuildArray();  
  403.                 }  
  404.             }  
  405.         }  
  406.     }  
  407. }  


参考:王艳平 张越 《Windows网络与通信程序设计》

你可能感兴趣的:(Winsock IO模型之Overlapped模型)