网络编程之 Socket的模式(四) --- “Window网络I/O模型” 续

1. Windows下的异步I/O

            在接下来讨论"Overlapped I/O 事件通知模型"、"Overlapped I/O 完成例程模型"、"IOCP模型"之前,先来看一下Windows的Overlapped I/O,它实际上对应于"网络编程之 Socket的模式(二) --- Linux网络I/O模型"里的异步I/O(asynchronous I/O或者nonblocking I/O)。通过使用overlapped I/O,开发者可以要求操作系统主动传送数据,并且在传送完毕时通知开发者。这使得开发者的程序在I/O进行过程中,仍然可以处理其他事务。事实上,操作系统内部正是以线程来完成overlapped I/O的。

            关于这一点,插上两句。我们自己在写程序的时候,一般很少会把需要循环读写的I/O操作放在主线程中,通常情况下会对此类I/O操作单独开线程处理,并通过消息队列来进行设计上的解耦,当然这需要一点点设计的技巧。如果需要操作的I/O数目很多,程序结构上设计不好的话,这通常会成为开发者的一个负担。而这正是Window的Overlapped I/O想解决的问题,在Windows的Overlapped I/O机制中,I/O的操作线程被封装在了操作系统内核中,API只暴露出有限的接口,Overlapped I/O的API并不需要用户对线程进行管理,用户对内部实现完全不可见。当然这对于想要盘根问底的程序员来说,并不是什么好事。何况在Window的异步I/O模型中,系统内部对I/O的处理在性能上有很大的优化。


            让我们先来看一下,异步I/O中关键数据结构OVERLAPPED。其定义如下:
[cpp]  view plain copy
  1. typedef struct _OVERLAPPED {  
  2.     DWORD   Internal;      // 通常被保留,当GetOverlappedResult()传回False并且GatLastError()并非传回ERROR_IO_PENDINO时,该状态置为系统定的状态。  
  3.     DWORD   InternalHigh;  // 通常被保留,当GetOverlappedResult()传回False时,为被传输数据的长度。  
  4.     DWORD   Offset;        // 指定文件的位置,从该位置传送数据,文件位置是相对文件开始处的字节偏移量。调用 ReadFile或WriteFile函数之前调用进  
  5.                            // 程设置这个成员,读写命名管道及通信设备时调用进程忽略这个成员;  
  6.     DWORD   OffsetHigh;    // 64位的文件偏移位置中,较高的32位,读写命名管道及通信设备时调用进程忽略这个成员(因为流式的I/O不支持文件位置);  
  7.     HANDLE hEvent;         // 一个手动重置的event事件,当overlapped I/O完成时被激发。ReadFileEx()和WriteFileEx()会忽略这个栏位,此时这个地方  
  8.                            // 可能被传递一个用户自定义的指针  
  9. } OVERLAPPED, *LPOVERLAPPED;  

            Windows的Overlapped I/O只是一种模型,它可以由内核对象(handle),事件内核对象(hEvent), 异步过程调用(apcs) 和完成端口(I/O completion)来实现。所有的I/O设备都可以使用这套机制,包括文件、管道、Socket、串口等。
            OVERLAPPED定义了windows上异步I/O所需要的数据结构。在 网络编程之 Socket的模式(一) --- “阻塞/非阻塞” 与 “同步/异步” 中,我们也讨论了模块之间获取异步结果的方式,有两种:
            第一,调用方在异步操作后,不断向被调用方轮询调用结果。
            第二,调用方在异步操作后,被调用方主动通知调用方结果。

            Windows也异步操作也为上述两种方式提供了相应的接口。
             1.  主动查询异步调用结果
[cpp]  view plain copy
  1. BOOL GetOverlappedResult(  
  2.        HANDLE hFile,                           // 串口的句柄  
  3.        LPOVERLAPPED lpOverlapped,              // 指向重叠操作开始时指定的OVERLAPPED结构  
  4.        LPDWORD lpNumberOfBytesTransferred,     // 指向一个32位变量,该变量的值返回实际读写操作传输的字节数。  
  5.        BOOL bWait                              // 该参数用于指定函数是否一直等到重叠操作结束。  
  6.                                                // 如果该参数为TRUE,函数直到操作结束才返回。  
  7.                                                // 如果该参数为FALSE,函数直接返回。  
  8. );  
            返回值:
            如果overlapped操作成功,此函数返回TRUE,失败则返回FALSE.GetLastError()函数可以获得更详细的失败信息。如果bWait为FALSE而overlapped还是没有完成,GetLastError()函数会返回ERROR_IO_INCOMPLETE。

             2.  被调用方给出通知
              下面两个函数都很熟悉了,不再介绍。
[cpp]  view plain copy
  1. DWORD WINAPI WaitForSingleObject(  
  2.        HANDLE hHandle,  
  3.        DWORD dwMilliseconds  
  4. );  
  5. DWORD WINAPI WaitForMultipleObject(  
  6.        DWORD dwCount,  
  7.        CONST HANDLE* phObject,  
  8.        BOOL fWaitAll,  
  9.        DWORD dwMillisecinds);  

              如果被调用方向调用方给出通知,调用方应该向被调方设置回调函数,或者至少留下一个关联信息,在这里就是核心对象的句柄。而OVERLAPPED结构体把需要操作I/O同核心对象相绑定,如此一来通过上述函数就可以对I/O实现异步操作了。

2. Overlapped I/O 事件通知模型

2.1 函数

              回到Socket模式上,Socket同样可以被看成为一个I/O设备。为了Socket编程方便,Windows定义了一个同OVERLAPPED相似的数据结构WSAOVERLAPPED,基本上换汤不换药。定义如下: 
[cpp]  view plain copy
  1. typedef struct _WSAOVERLAPPED {  
  2.     DWORD Internal;    
  3.     DWORD InternalHigh;    
  4.     DWORD Offset;    
  5.     DWORD OffsetHigh;    
  6.     WSAEVENT hEvent;      // 唯一需要关注的参数,用来关联WSAEvent对象  
  7. } WSAOVERLAPPED, *LPWSAOVERLAPPED;  

              相似的两个函数还包括:
[cpp]  view plain copy
  1. BOOL WSAGetOverlappedResult(  
  2.        SOCKET s,                    // SOCKET  
  3.        LPWSAOVERLAPPED lpOverlapped,    // 这里是想要查询结果的那个重叠结构的指针  
  4.        LPDWORD lpcbTransfer,            // 本次重叠操作的实际接收(或发送)的字节数  
  5.        BOOL fWait,                      // 设置为TRUE,除非重叠操作完成,否则函数不会返回  
  6.                                         // 设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE,错误为WSA_IO_INCOMPLETE  
  7.        LPDWORD lpdwFlags                // 指向DWORD的指针,负责接收结果标志  
  8. );  

[cpp]  view plain copy
  1. DWORD WSAWaitForMultipleEvents(  
  2.        DWORD cEvents,                   // 等候事件的总数量  
  3.        const WSAEVENT* lphEvents,       // 事件数组的指针  
  4.        BOOL fWaitAll,                   // 这个要多说两句: 如果设置为 TRUE,则事件数组中所有事件被传信的时候函数才会返回,  
  5.                                         // FALSE则任何一个事件被传信函数都要返回,我们这里肯定是要设置为FALSE的  
  6.        DWORD dwTimeout,                 // 超时时间,如果超时,函数会返回WSA_WAIT_TIMEOUT,如果设置为0,函数会立即返回,  
  7.                                         // 如果设置为 WSA_INFINITE只有在某一个事件被传信后才会返回,在这里不建议设置为WSA_INFINITE  
  8.        BOOL fAlertable                  // 在完成例程中会用到这个参数,这里我们先设置为FALSE  
  9. );  
            返回值:
            WSA_WAIT_TIMEOUT: 最常见的返回值,需要做的就是继续Wait
            WSA_WAIT_FAILED:  出现了错误,请检查cEvents和lphEvents两个参数是否有效
            如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置。

            同Socket相关,特有的函数包括:
[cpp]  view plain copy
  1. int WSARecv(    
  2.        SOCKET s,                        // 当然是投递这个操作的套接字    
  3.        LPWSABUF lpBuffers,              // 接收缓冲区,与Recv函数不同    
  4.                                         // 这里需要一个由WSABUF结构构成的数组    
  5.        DWORD dwBufferCount,             // 数组中WSABUF结构的数量    
  6.        LPDWORD lpNumberOfBytesRecvd,    // 如果接收操作立即完成,这里会返回函数调用所接收到的字节数    
  7.        LPDWORD lpFlags,                 //一个指向标志位的指针。   
  8.        LPWSAOVERLAPPED lpOverlapped,    // “绑定”的重叠结构    
  9.        LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine   // 完成例程中将会用到的参数,我们这里设置为 NULL  
  10. );  
            返回值:
            若无错误发生且接收操作立即完成,则WSARecv()函数返回0。否则的话,将返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()来获取相应的错误代码。错误代码WSA_IO_PENDING表示重叠操作成功启动,但是I/O操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成。任何其他的错误表示重叠操作未能成功地启动,以后也不会有完成指示。

[cpp]  view plain copy
  1. int WSASend (  
  2.        SOCKET s,                                                   // s:标识一个已连接套接口的描述字。  
  3.        LPWSABUF lpBuffers,                                         // 一个指向WSABUF结构数组的指针。每个WSABUF结构包含缓冲区的指针和缓冲区的大小。  
  4.        DWORD dwBufferCount,                                        // lpBuffers数组中WSABUF结构的数目。  
  5.        LPDWORD lpNumberOfBytesSent,                                // 如果发送操作立即完成,则为一个指向所发送数据字节数的指针。  
  6.        DWORD dwFlags,                                              // 标志位。  
  7.        LPWSAOVERLAPPED lpOverlapped,                               // 指向WSAOVERLAPPED结构的指针(对于非重叠套接口则忽略)。  
  8.        LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine      // 一个指向发送操作完成后调用的完成例程的指针。(对于非重叠套接口则忽略)。  
  9. );  

            返回值:
            若无错误发生且发送操作立即完成,则WSASend()函数返回0。这时,完成例程(Completion Routine)应该已经被调度,一旦调用线程处于alertable状态时就会调用它。否则,返回SOCKET_ERROR 。通过WSAGetLastError获得详细的错误代码。WSA_IO_PENDING 这个错误码(其实表示没有错误)表示重叠操作已经提交成功(就是异步IO的意思了),稍后会提示完成(这个完成可不一定是发送成功,没准出问题也不一定)。其他的错误代码都代表重叠操作没有正确开始,也不会有完成标志出现。


2.2 概述

            Overlapped I/O 事件通知模型是个异步模型,和" 网络编程之 Socket的模式(二) --- Linux网络I/O模型 "中的异步模型类似,操作系统负责数据缓冲区数据的拷贝。而WSAAsyncSelect以及WSAEventSelect模型中,数据的拷贝则是在应用层。

            下面是一个例子:

[cpp]  view plain copy
  1. #include <winsock2.h>   
  2. #include <stdio.h>   
  3.   
  4. #pragma comment(lib,"ws2_32.lib")   
  5.   
  6. #define DATA_BUF_LEN 1024       // 接收缓冲区大小   
  7.   
  8. SOCKET  SLisent;  
  9. SOCKET  SWorker[DATA_BUF_LEN] = {0};   
  10. WSABUF  RecvBuffer[DATA_BUF_LEN];   
  11. WSAOVERLAPPED Overlapped[DATA_BUF_LEN];              // 重叠结构   
  12. WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];        // 用来通知重叠操作完成的事件句柄数组   
  13. DWORD   dwRecvBytes = 0,                             // 接收到的字符长度   
  14. DWORD   Flags = 0;                                   // WSARecv的参数   
  15. DWORD volatile dwEventTotal = 0;                     // 程序中事件的总数   
  16.   
  17. //由于EVENT数量限制,目前最多只能支持64个连接   
  18. DWORD WINAPI AcceptThread(LPVOID lpParameter)   
  19. {   
  20.         WSADATA wsaData;   
  21.         WSAStartup(MAKEWORD(2,2),&wsaData);   
  22.   
  23.         // 使用Overlapped I/O模型必须设置WSA_FLAG_OVERLAPPED参数  
  24.         SLisent = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,NULL,WSA_FLAG_OVERLAPPED);  
  25.   
  26.         // 创建Socket       
  27.         SOCKADDR_IN ServerAddr;   
  28.         ServerAddr.sin_family = AF_INET;   
  29.         ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
  30.         ServerAddr.sin_port = htons(1234);   
  31.   
  32.         // 监听  
  33.         bind(SLisent,(LPSOCKADDR)&ServerAddr,sizeof(ServerAddr));   
  34.         listen(SLisent,100);   
  35.           
  36.         int i = 0;   
  37.         SOCKADDR_IN ClientAddr;   
  38.         int addr_length=sizeof(ClientAddr);   
  39.         while (TRUE)   
  40.         {   
  41.                 while((SWorker[i] == 0) && (SWorker[i] = accept(SLisent,(SOCKADDR*)&ClientAddr, &addr_length)) != INVALID_SOCKET)   
  42.                 {   
  43.                         printf("accept %d ip:%s port:%dn",i+1,inet_ntoa(ClientAddr.sin_addr),ClientAddr.sin_port);   
  44.   
  45.   
  46.                         // 创建触发事件  
  47.                         EventArray[i] = WSACreateEvent();   
  48.                         dwEventTotal++;   
  49.                         memset(&Overlapped[i],0,sizeof(WSAOVERLAPPED));   
  50.   
  51.   
  52.                         // 绑定overlapped i/o和事件  
  53.                         Overlapped[i].hEvent = EventArray[i];   
  54.                         char * buffer = new char[DATA_BUF_LEN];   
  55.                         memset(buffer,0,DATA_BUF_LEN);   
  56.                         RecvBuffer[i].buf = buffer;   
  57.                         RecvBuffer[i].len = DATA_BUF_LEN;   
  58.                         if(WSARecv(SWorker[i], &RecvBuffer[i], dwEventTotal, &dwRecvBytes, &Flags, &Overlapped[i], NULL) == SOCKET_ERROR)   
  59.                         {   
  60.                                 int err = WSAGetLastError();   
  61.                                 if(WSAGetLastError() != WSA_IO_PENDING)   
  62.                                 {   
  63.                                         printf("disconnect: %dn",i+1);   
  64.                                         closesocket(SWorker[i]);   
  65.                                         SWorker[i] = 0;   
  66.                                         WSACloseEvent(EventArray[i]);    // 关闭事件   
  67.                                         RecvBuffer[i].buf = NULL;   
  68.                                         RecvBuffer[i].len = NULL;   
  69.                                         continue;   
  70.                                 }   
  71.                         }   
  72.                         i = (i+1)%WSA_MAXIMUM_WAIT_EVENTS;   
  73.                 }   
  74.                    
  75.         }   
  76.         return FALSE;   
  77. }   
  78.   
  79. DWORD WINAPI ReceiveThread(LPVOID lpParameter)   
  80. {   
  81.         DWORD dwIndex = 0;   
  82.         while (true)   
  83.         {   
  84.                 dwIndex = WSAWaitForMultipleEvents(dwEventTotal, EventArray, FALSE, 1000, FALSE);   
  85.                 if (dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT)   
  86.                         continue;   
  87.                 dwIndex = dwIndex - WSA_WAIT_EVENT_0;   
  88.                 WSAResetEvent(EventArray[dwIndex]);   
  89.                    
  90.                 DWORD dwBytesTransferred;   
  91.                 WSAGetOverlappedResult( SWorker[dwIndex], &Overlapped[dwIndex], &dwBytesTransferred, FALSE, &Flags);   
  92.                 if(dwBytesTransferred == 0)   
  93.                 {   
  94.                         printf("disconnect: %dn",dwIndex+1);   
  95.                         closesocket(SWorker[dwIndex]);   
  96.                         SWorker[dwIndex] = 0;   
  97.                         WSACloseEvent(EventArray[dwIndex]);    // 关闭事件   
  98.                         RecvBuffer[dwIndex].buf = NULL;   
  99.                         RecvBuffer[dwIndex].len = NULL;   
  100.                         continue;   
  101.                 }   
  102.   
  103.                 //使用数据   
  104.                 printf("%sn",RecvBuffer[dwIndex].buf);   
  105.                 memset(RecvBuffer[dwIndex].buf,0,DATA_BUF_LEN);   
  106.                 if(WSARecv(SWorker[dwInde x], &RecvBuffer[dwIndex], dwEventTotal, &dwRecvBytes, &Flags, &Overlapped[dwIndex], NULL) == SOCKET_ERROR)   
  107.                 {   
  108.                         if(WSAGetLastError() != WSA_IO_PENDING)   
  109.                         {   
  110.                                 printf("disconnect: %dn",dwIndex+1);   
  111.                                 closesocket(SWorker[dwIndex]);   
  112.                                 SWorker[dwIndex] = 0;   
  113.                                 WSACloseEvent(EventArray[dwIndex]);    // 关闭事件   
  114.                                 RecvBuffer[dwIndex].buf = NULL;   
  115.                                 RecvBuffer[dwIndex].len = NULL;   
  116.                                 continue;   
  117.                         }   
  118.                 }   
  119.         }   
  120.            
  121.         return FALSE;   
  122. }   
  123.   
  124.   
  125. void main()     
  126. {   
  127.         HANDLE hThreads[2];   
  128.         hThreads[0] = CreateThread(NULL, 0, AcceptThread, NULL, NULL, NULL);   
  129.         hThreads[1] = CreateThread(NULL, 0, ReceiveThread, NULL, NULL, NULL);   
  130.              
  131.         WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);   
  132.         printf("exitn");   
  133.         CloseHandle(hThreads[0]);   
  134.         CloseHandle(hThreads[1]);   
  135. }   

            Overlapped I/O 事件通知模型的缺点是,使用了WSAWaitForMultipleEvents函数,而该函数最多只能同时等待64个消息。所以如果想要支持更多的连接,就必须自己开辟线程去管理.该模型的所接入连接数同线程数成线性关系。在线程过多情况下,线程上下文之间的切换,将会影响程序的性能,


3. Overlapped I/O 完成例程模型

            完成例程模型相比与事件通知模型有个很大的优点就是不再受64个消息的限制,一个线程可以同时管理成百上千个socket连接,且保持较高的性能,使用上则类似。而效率上大幅提升的原因在于在完成例程模型中使用了回调函数。在Overlapped I/O 事件通知模型中WSAWaitForMultipleEvents,用来捕获缓冲区满时的信号,当收到信号后,开发者使用接受到的数据进行相关的业务操作。而在完成例程模型中,用户处理相关业务的函数被当作回调函数,直接设置进入WSARecv中,在接受数据后操作系统会自动调用用户的回调函数完成工作。

            在这种模式下,本来接受数据,并对数据进行分析的工作,在Overlapped I/O 事件通知模型中需要用户线程参与的事情(操作系统系统内部,接受完数据,通知用户线程,用户线程接管数据并处理),只需在操作系统内部处理即可(回调函数已经告知操作系统该如何处理数据)。这实际上是一个异步过程调用(Asynchronous Procedure Calls, APCs)的用例。

            下面是一个例子:
[cpp]  view plain copy
  1. #include <winsock2.h>   
  2. #include <stdio.h>   
  3.   
  4.   
  5. #pragma comment(lib,"ws2_32.lib")   
  6.   
  7.   
  8. #define DATA_BUF_LEN 1024                // 接收缓冲区大小   
  9. #define MAXSESSION 10000                 // 最大连接数   
  10.   
  11.   
  12. typedef struct _SOCKET_INFORMATION {   
  13.    OVERLAPPED Overlapped;   
  14.    SOCKET Socket;   
  15.    WSABUF DataBuf;   
  16.    DWORD BytesSEND;   
  17.    DWORD BytesRECV;   
  18. } SOCKET_INFORMATION, * LPSOCKET_INFORMATION;   
  19.   
  20.   
  21. SOCKET ListenSocket = INVALID_SOCKET;   
  22. DWORD  Flags = 0;             
  23.                                                      
  24. void   CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred,LPWSAOVERLAPPED Overlapped, DWORD InFlags);   
  25.   
  26.   
  27. DWORD WINAPI AcceptThread(LPVOID lpParameter)   
  28. {   
  29.         WSADATA wsaData;   
  30.         WSAStartup(MAKEWORD(2,2),&wsaData);   
  31.   
  32.   
  33.         // 创建Socket,设置异步I/O标志WSA_FLAG_OVERLAPPED  
  34.         ListenSocket = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,NULL,WSA_FLAG_OVERLAPPED);   
  35.         SOCKADDR_IN ServerAddr;   
  36.         ServerAddr.sin_family = AF_INET;   
  37.         ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
  38.         ServerAddr.sin_port = htons(1234);   
  39.   
  40.   
  41.         // 绑定  
  42.         bind(ListenSocket,(LPSOCKADDR)&ServerAddr,sizeof(ServerAddr));   
  43.   
  44.   
  45.         // 监听  
  46.         listen(ListenSocket,MAXSESSION);   
  47.         printf("listenning.../n");   
  48.         SOCKADDR_IN ClientAddr;   
  49.         int addr_length=sizeof(ClientAddr);   
  50.         while (TRUE)   
  51.         {   
  52.                 LPSOCKET_INFORMATION  SI = new SOCKET_INFORMATION;   
  53.   
  54.   
  55.                 // 接受新连接  
  56.                 if ((SI->Socket = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length)) != INVALID_SOCKET)   
  57.                 {   
  58.                         printf("accept ip:%s port:%d/n",inet_ntoa(ClientAddr.sin_addr),ClientAddr.sin_port);   
  59.                         memset(&SI->Overlapped,0,sizeof(WSAOVERLAPPED));   
  60.                         SI->DataBuf.buf = new char[DATA_BUF_LEN];   
  61.                         SI->DataBuf.len = DATA_BUF_LEN;   
  62.                         memset(SI->DataBuf.buf,0,DATA_BUF_LEN);   
  63.   
  64.   
  65.   
  66.   
  67.                         // 设置回调函数  
  68.                         if(WSARecv(SI->Socket, &SI->DataBuf, 1, &SI->BytesRECV, &Flags, &SI->Overlapped, WorkerRoutine) == SOCKET_ERROR)   
  69.                         {   
  70.                                 int err = WSAGetLastError();   
  71.                                 if(WSAGetLastError() != WSA_IO_PENDING)   
  72.                                 {   
  73.                                         printf("disconnect/n");   
  74.                                         closesocket(SI->Socket);   
  75.                                         delete [] SI->DataBuf.buf;   
  76.                                         delete SI;   
  77.                                         continue;   
  78.                                 }   
  79.                         }   
  80.                 }   
  81.                    
  82.         }   
  83.         return FALSE;   
  84. }   
  85.   
  86.   
  87.   
  88.   
  89. void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD InFlags)   
  90. {   
  91.         LPSOCKET_INFORMATION SI = (LPSOCKET_INFORMATION)Overlapped;   
  92.         if (Error != 0 || BytesTransferred == 0)   
  93.         {   
  94.                 printf("disconnect/n");   
  95.                 closesocket(SI->Socket);   
  96.                 delete [] SI->DataBuf.buf;   
  97.                 delete SI;   
  98.                 return;   
  99.         }   
  100.   
  101.   
  102.   
  103.   
  104.         // 使用数据   
  105.         printf("call back:%s/n",SI->DataBuf.buf);   
  106.         memset(SI->DataBuf.buf,0,DATA_BUF_LEN);   
  107.           
  108.         // 设置回调函数  
  109.         if(WSARecv(SI->Socket, &SI->DataBuf, 1, &SI->BytesRECV, &Flags, &SI->Overlapped, WorkerRoutine) == SOCKET_ERROR)   
  110.         {   
  111.                 int err = WSAGetLastError();   
  112.                 if(WSAGetLastError() != WSA_IO_PENDING)   
  113.                 {   
  114.                         printf("disconnect/n");   
  115.                         closesocket(SI->Socket);   
  116.                         delete [] SI->DataBuf.buf;   
  117.                         delete SI;   
  118.                         return;   
  119.                 }   
  120.         }   
  121. }   
  122.   
  123.   
  124. void main()     
  125. {   
  126.         HANDLE hThreads = CreateThread(NULL, 0, AcceptThread, NULL, NULL, NULL);   
  127.              
  128.         WaitForSingleObject(hThreads,INFINITE);   
  129.         printf("exit/n");   
  130.         CloseHandle(hThreads);   
  131. }   


4. IOCP模型

            IOCP(I/O Completion Port,I/O完成端口)是Window下性能最好的一种I/O模型。同Overlapped I/O 完成例程模型相比,IOCP模型具有更好的伸缩性,也就是说当CPU或内存增加时,性能能够成线性的同步增加。
            让我们来看一下"Overlapped I/O 完成例程模型"(APCs调用方式)的特点,可以发现只有发出"overlapped请求"的线程才能够提供callback函数,而IOCP模型则不需要。使用IOCP模型具有以下优点:
             1.  I/O Completion Port允许一个线程将一个请求暂时保存下来,而由另外一个线程为它做实际服务。
             2.  I/O Completion Port支持scalable架构。
            Overlapped I/O 完成例程模型可以被这样描述:它是用来管理一堆线程如何为Completion overlapped I/O request服务的机制,在这种机制下,一个或多个CPU将尽可能的保持忙碌,但也不会被太多的线程淹没。

            IOCP模型内部实现复杂,但使用却很简单。步骤如下:
            1. 产生一个I/O completion port。
            2. 让它和一个I/O句柄产生关联
            3. 产生一堆线程
            4. 让每一个线程都在completion port上等待。
            5. 开始对着I/O句柄发出一些overlapped I/O请求。


            涉及到的函数如下:
             创建一个Completion Port核心对象 或 关联I/O与Completion Port核心对象
[cpp]  view plain copy
  1. HANDLE CreateIoCompletionPort(  
  2.     HANDLE FileHandle,                  // 文件或设备(device)的handle。在Windows NT3.51之后,此栏位可设定为INVALID_HANDLE_VALUE,于是产生一个没有和  
  3.                                         // 任何文件handle有关系的port  
  4.     HANDLE ExistingCompletionPort,      // 如果此栏位被指定,那么上一栏位FileHandle就会被加到此port之上,而不会产生新的port。指定NULL可以产生一个新  
  5.                                         // 的port  
  6.     ULONG_PTR CompletionKey,            // 用户自定义的一个数值,将被交给提供服务的线程。此值和FileHandle有关联。  
  7.     DWORD NumberOfConcurrentThreads     // 与此I/O completion port有关联的线程个数。  
  8. );  
            返回值:
            如果函数成功,则传回一个I/O completion port的handle。如果函数失败,则传回FALSE。GetLastError()可以获得更详细的失败原因。

            CreateIoCompletionPort()通常要被调用两次。第一次先指定FileHandle为INVALID_HANDLE_VALUE,并设定ExistingCompletionPort为NULL,用以产生一个核心对象port。然后再为每一个欲附着上去的I/O调用一次CreateIoCompletionPort().后续的调用应该将ExistingCompletionPort设定为第一次调用所传回的handle。

             在I/O Completion Port上等待
[cpp]  view plain copy
  1. BOOL GetQueuedCompletionStatus(  
  2.     HANDLE CompletionPort,              // CompletionPort参数对应于要在上面等待的完成端口  
  3.     LPDWORD lpNumberOfBytes,            // 参数负责在完成了一次I/O操作后(如WSASend或WSARecv),接收实际传输的字节数  
  4.     PULONG_PTR lpCompletionKey,         // 为原先传递进入CreateIoCompletionPort函数的套接字返回“单句柄数据”,最好将套接字句柄保存在这个“键”(Key)中。  
  5.     LPOVERLAPPED* lpOverlapped,         // 用于接收完成的I/O操作的重叠结果  
  6.     DWORD dwMilliseconds                // 用于指定调用者希望等待一个完成数据包在完成端口上出现的时间  
  7. );  
            返回值:
            如果函数成功的将一个completion packet从队列中取出,并完成一个成功的操作,函数将传回TRUE,并填写由lpNumberOfBytes、lpCompletionKey、lpOverlapped所指向的变量内容。
            如果操作失败,但completion packet已经从队列中取出,则函数传回FALSE,并将lpOverlapped设置为NULL,调用GetLastError()可以获得更详细的失败原因。同其他核心对象不同,在completion port上等待的线程是以先进后出的次序提供服务,这是因为所有的线程提供的服务都一样。

             强行释放完成端口上的所有工作者线程
[cpp]  view plain copy
  1. BOOL PostQueuedCompletionStatus(  
  2.     HANDLE CompletionPort,              // 指定想向其发送一个完成数据包的完成端口对象  
  3.     DWORD dwNumberOfBytesTransferred,   // 参数负责在完成了一次I/O操作后(如WSASend或WSARecv),接收实际传输的字节数。设置为0,完成端口上的工作线程被强制释放  
  4.     ULONG_PTR dwCompletionKey,          // 为原先传递进入CreateIoCompletionPort函数的套接字返回“单句柄数据”,最好将套接字句柄保存在这个“键”(Key)中。  
  5.     LPOVERLAPPED lpOverlapped           // 用于接收完成的I/O操作的重叠结果  
  6. );  

            下面是Win32 程序设计上的一个例子:
            客户端:
[cpp]  view plain copy
  1. /* 
  2.  * EchoCli.c 
  3.  * 
  4.  * Sample code for "Multithreading Applications in Win32" 
  5.  * This is from Chapter 6. 
  6.  * 
  7.  * This is a command line client to drive the ECHO server. 
  8.  * Run the server in one Commmand Prompt Window, 
  9.  * then run this program in one or more other windows. 
  10.  * EchoCli will wait for you to type in some text when 
  11.  * it starts up. Each line of text will be sent to the 
  12.  * echo server on TCP port 5554. 
  13.  */  
  14.   
  15. #include <windows.h>  
  16. #include <tchar.h>  
  17. #include <stdio.h>  
  18. #include <stdlib.h>  
  19. #include <string.h>  
  20. #include <winsock.h>  
  21.   
  22. /* Function Prototypes */  
  23. void FatalError(char *s);  
  24. int writen(SOCKET sock, char *ptr, int nBytes);  
  25. int readline(SOCKET sock, char *ptr, int maxlen);  
  26. void DoClientLoop(FILE *fp, SOCKET sock);  
  27.   
  28. /* Constants */  
  29. #define MAXLINE 512  
  30. #define SERVER_TCP_PORT     5554  
  31. #define SERVER_ADDRESS      "127.0.0.1"  
  32.   
  33. /* 
  34.  * Error handler 
  35.  */  
  36. void FatalError(char *s)  
  37. {  
  38.     fprintf(stdout, "%s\n", s);  
  39.     exit(EXIT_FAILURE);  
  40. }  
  41.   
  42. /* 
  43.  * Write bytes to the port with proper block size handling. 
  44.  */  
  45. int writen(SOCKET sock, char *ptr, int nBytes)  
  46. {  
  47.     int nleft;  
  48.     int nwritten;  
  49.   
  50.     nleft = nBytes;  
  51.     while (nleft > 0)  
  52.     {  
  53.        nwritten = send(sock,  
  54.                    ptr,  
  55.                    nBytes,  
  56.                    0  
  57.           );  
  58.   
  59.        if (nwritten == SOCKET_ERROR)  
  60.        {  
  61.           fprintf(stdout, "Send Failed\n");  
  62.           exit(1);  
  63.        }  
  64.   
  65.         nleft -= nwritten;  
  66.         ptr += nwritten;  
  67.     }  
  68.   
  69.     return nBytes - nleft;  
  70. }  
  71.   
  72.   
  73. /* 
  74.  * Read a line of text of the port. This version 
  75.  * is very inefficient, but it's simple. 
  76.  */  
  77. int readline(SOCKET sock, char *ptr, int maxlen)  
  78. {  
  79.     int n;  
  80.     int rc;  
  81.     char c;  
  82.   
  83.     for (n=1; n<maxlen; n++)  
  84.     {  
  85.         if ( ( rc= recv(sock, &c, 1, 0)) == 1)  
  86.         {  
  87.             *ptr++ = c;  
  88.             if (c=='\n')  
  89.                 break;  
  90.         }  
  91.         else if (rc == 0)  
  92.         {  
  93.             if (n == 1)  
  94.                 return 0;  
  95.             else  
  96.                 break;  
  97.         }  
  98.         else  
  99.             return -1;  /* Error */  
  100.     }  
  101.   
  102.     *ptr = '\0';  
  103.     return n;  
  104. }  
  105.   
  106.   
  107. int main(int argc, char *argv[])  
  108. {  
  109.     WSADATA WsaData;  
  110.     SOCKET sock;  
  111.     struct sockaddr_in  serv_addr;  
  112.     int err;  
  113.   
  114.     puts("EchoCli - client for echo server sample program\n");  
  115.     puts("Type a line of text followed by Return.");  
  116.     puts("Exit this program by typing Ctrl+Z followed by Return.");  
  117.   
  118.     err = WSAStartup(0x0101, &WsaData);  
  119.     if (err == SOCKET_ERROR)  
  120.         FatalError("WSAStartup Failed");  
  121.   
  122.     /* 
  123.      * Bind our local address 
  124.      */  
  125.     memset(&serv_addr, 0, sizeof(serv_addr));  
  126.     serv_addr.sin_family    = AF_INET;  
  127.     // Use the local host  
  128.     serv_addr.sin_addr.s_addr   = inet_addr(SERVER_ADDRESS);  
  129.     serv_addr.sin_port          = htons(SERVER_TCP_PORT);  
  130.   
  131.     /* 
  132.      * Open a TCP socket (an Internet stream socket) 
  133.      */  
  134.   
  135.     sock = socket(AF_INET, SOCK_STREAM, 0);  
  136.     if (sock < 0)  
  137.         FatalError("socket() failed -- do you have TCP/IP networking installed?");  
  138.   
  139.     if (connect(sock, (struct sockaddr *) &serv_addr,  
  140.                 sizeof(serv_addr)) < 0)  
  141.         FatalError("connect() failed -- is the server running?");  
  142.   
  143.     DoClientLoop(stdin, sock);  
  144.   
  145.     closesocket(sock);  
  146.   
  147.     return EXIT_SUCCESS;  
  148. }  
  149.   
  150.   
  151. /* 
  152.  * As long as there is input from "fp", copy it to 
  153.  * the server, read what the server gives back, 
  154.  * and print it. 
  155.  */  
  156. void DoClientLoop(FILE *fp, SOCKET sock)  
  157. {  
  158.     int n;  
  159.     char sendline[MAXLINE];  
  160.     char recvline[MAXLINE+1];  
  161.   
  162.     while (fgets(sendline, MAXLINE, fp) != NULL)  
  163.     {  
  164.         n = strlen(sendline);  
  165.         if (writen(sock, sendline, n) != n)  
  166.             FatalError("DoClientLoop: writen() error");  
  167.   
  168.         n = readline(sock, recvline, MAXLINE);  
  169.         if (n < 0)  
  170.             FatalError("DoClientLoop: readline() error");  
  171.         recvline[n] = '\0';  
  172.         fputs(recvline, stdout);  
  173.     }  
  174.   
  175.     if (ferror(fp))  
  176.         FatalError("DoClientLoop: error reading file");  
  177. }  



            服务器端:

[cpp]  view plain copy
  1. /* 
  2.  * EchoCli.c 
  3.  * 
  4.  * Sample code for "Multithreading Applications in Win32" 
  5.  * This is from Chapter 6. 
  6.  * 
  7.  * This is a command line client to drive the ECHO server. 
  8.  * Run the server in one Commmand Prompt Window, 
  9.  * then run this program in one or more other windows. 
  10.  * EchoCli will wait for you to type in some text when 
  11.  * it starts up. Each line of text will be sent to the 
  12.  * echo server on TCP port 5554. 
  13.  */  
  14.   
  15. #include <windows.h>  
  16. #include <tchar.h>  
  17. #include <stdio.h>  
  18. #include <stdlib.h>  
  19. #include <string.h>  
  20. #include <winsock.h>  
  21.   
  22. /* Function Prototypes */  
  23. void FatalError(char *s);  
  24. int writen(SOCKET sock, char *ptr, int nBytes);  
  25. int readline(SOCKET sock, char *ptr, int maxlen);  
  26. void DoClientLoop(FILE *fp, SOCKET sock);  
  27.   
  28. /* Constants */  
  29. #define MAXLINE 512  
  30. #define SERVER_TCP_PORT     5554  
  31. #define SERVER_ADDRESS      "127.0.0.1"  
  32.   
  33. /* 
  34.  * Error handler 
  35.  */  
  36. void FatalError(char *s)  
  37. {  
  38.     fprintf(stdout, "%s\n", s);  
  39.     exit(EXIT_FAILURE);  
  40. }  
  41.   
  42. /* 
  43.  * Write bytes to the port with proper block size handling. 
  44.  */  
  45. int writen(SOCKET sock, char *ptr, int nBytes)  
  46. {  
  47.     int nleft;  
  48.     int nwritten;  
  49.   
  50.     nleft = nBytes;  
  51.     while (nleft > 0)  
  52.     {  
  53.        nwritten = send(sock,  
  54.                    ptr,  
  55.                    nBytes,  
  56.                    0  
  57.           );  
  58.   
  59.        if (nwritten == SOCKET_ERROR)  
  60.        {  
  61.           fprintf(stdout, "Send Failed\n");  
  62.           exit(1);  
  63.        }  
  64.   
  65.         nleft -= nwritten;  
  66.         ptr += nwritten;  
  67.     }  
  68.   
  69.     return nBytes - nleft;  
  70. }  
  71.   
  72.   
  73. /* 
  74.  * Read a line of text of the port. This version 
  75.  * is very inefficient, but it's simple. 
  76.  */  
  77. int readline(SOCKET sock, char *ptr, int maxlen)  
  78. {  
  79.     int n;  
  80.     int rc;  
  81.     char c;  
  82.   
  83.     for (n=1; n<maxlen; n++)  
  84.     {  
  85.         if ( ( rc= recv(sock, &c, 1, 0)) == 1)  
  86.         {  
  87.             *ptr++ = c;  
  88.             if (c=='\n')  
  89.                 break;  
  90.         }  
  91.         else if (rc == 0)  
  92.         {  
  93.             if (n == 1)  
  94.                 return 0;  
  95.             else  
  96.                 break;  
  97.         }  
  98.         else  
  99.             return -1;  /* Error */  
  100.     }  
  101.   
  102.     *ptr = '\0';  
  103.     return n;  
  104. }  
  105.   
  106. int main(int argc, char *argv[])  
  107. {  
  108.     WSADATA WsaData;  
  109.     SOCKET sock;  
  110.     struct sockaddr_in  serv_addr;  
  111.     int err;  
  112.   
  113.     puts("EchoCli - client for echo server sample program\n");  
  114.     puts("Type a line of text followed by Return.");  
  115.     puts("Exit this program by typing Ctrl+Z followed by Return.");  
  116.   
  117.     err = WSAStartup(0x0101, &WsaData);  
  118.     if (err == SOCKET_ERROR)  
  119.         FatalError("WSAStartup Failed");  
  120.   
  121.     /* 
  122.      * Bind our local address 
  123.      */  
  124.     memset(&serv_addr, 0, sizeof(serv_addr));  
  125.     serv_addr.sin_family    = AF_INET;  
  126.     // Use the local host  
  127.     serv_addr.sin_addr.s_addr   = inet_addr(SERVER_ADDRESS);  
  128.     serv_addr.sin_port          = htons(SERVER_TCP_PORT);  
  129.   
  130.     /* 
  131.      * Open a TCP socket (an Internet stream socket) 
  132.      */  
  133.   
  134.     sock = socket(AF_INET, SOCK_STREAM, 0);  
  135.     if (sock < 0)  
  136.         FatalError("socket() failed -- do you have TCP/IP networking installed?");  
  137.   
  138.     if (connect(sock, (struct sockaddr *) &serv_addr,  
  139.                 sizeof(serv_addr)) < 0)  
  140.         FatalError("connect() failed -- is the server running?");  
  141.   
  142.     DoClientLoop(stdin, sock);  
  143.   
  144.     closesocket(sock);  
  145.   
  146.     return EXIT_SUCCESS;  
  147. }  
  148.   
  149.   
  150. /* 
  151.  * As long as there is input from "fp", copy it to 
  152.  * the server, read what the server gives back, 
  153.  * and print it. 
  154.  */  
  155. void DoClientLoop(FILE *fp, SOCKET sock)  
  156. {  
  157.     int n;  
  158.     char sendline[MAXLINE];  
  159.     char recvline[MAXLINE+1];  
  160.   
  161.     while (fgets(sendline, MAXLINE, fp) != NULL)  
  162.     {  
  163.         n = strlen(sendline);  
  164.         if (writen(sock, sendline, n) != n)  
  165.             FatalError("DoClientLoop: writen() error");  
  166.   
  167.         n = readline(sock, recvline, MAXLINE);  
  168.         if (n < 0)  
  169.             FatalError("DoClientLoop: readline() error");  
  170.         recvline[n] = '\0';  
  171.         fputs(recvline, stdout);  
  172.     }  
  173.   
  174.     if (ferror(fp))  
  175.         FatalError("DoClientLoop: error reading file");  
  176. }  


            对于完成端口的更多内容可以参考下面一些资料:

           完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三

           完成端口通讯服务器设计

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值