Socket I/O模型之完成端口(completion port)

http://tangfeng.iteye.com/blog/518148

“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择! 

使用完成端口模型的服务器端代码: 
C++代码    收藏代码
  1. // write by larry  
  2. // 2009-8-20  
  3. // This is a server using completion port.  
  4. #include "stdafx.h"  
  5. #include <WINSOCK2.H>  
  6. #include <stdio.h>  
  7. #pragma comment(lib, "ws2_32.lib")  
  8. #define PORT  5150  
  9. #define MSGSIZE  1024  
  10. typedef enum  
  11. {  
  12.     RECV_POSTED  
  13. } OPERATION_TYPE;  
  14. typedef struct   
  15. {  
  16.     WSAOVERLAPPED  overlap;  
  17.     WSABUF         Buffer;  
  18.     char           szMessage[MSGSIZE];  
  19.     DWORD          NumberOfBytesRecvd;  
  20.     DWORD          Flags;  
  21.     OPERATION_TYPE OperationType;  
  22. } PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;  
  23. DWORD WINAPI WorkerThread(LPVOID CompletionPortID);  
  24.   
  25. int main(int argc, char* argv[])  
  26. {  
  27.     WSADATA wsaData;  
  28.     SOCKET sListen, sClient;  
  29.     SOCKADDR_IN local, client;  
  30.     DWORD i, dwThreadId;  
  31.     int iAddrSize = sizeof(SOCKADDR_IN);  
  32.     HANDLE CompletionPort = INVALID_HANDLE_VALUE;  
  33.     SYSTEM_INFO sysinfo;  
  34.     LPPER_IO_OPERATION_DATA lpPerIOData = NULL;  
  35.     // Initialize windows socket library  
  36.     WSAStartup(0x0202, &wsaData);  
  37.     // Create completion port  
  38.     CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);  
  39.     // Create worker thread  
  40.     GetSystemInfo(&sysinfo);  
  41.     for (i = 0; i < sysinfo.dwNumberOfProcessors; i++)  
  42.     {  
  43.         CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);  
  44.     }  
  45.     // Create listening socket  
  46.     sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
  47.     // Bind  
  48.     local.sin_family = AF_INET;  
  49.     local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  
  50.     local.sin_port = htons(PORT);  
  51.     bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));  
  52.     // Listen  
  53.     listen(sListen, 3);  
  54.     while (TRUE)  
  55.     {  
  56.         // Accept a connection  
  57.         sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);  
  58.         printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));        
  59.         // Associate the newly arrived client socket with completion port  
  60.         CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);  
  61.         // Launch an asynchronous operation for new arrived connection  
  62.         lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(  
  63.             GetProcessHeap(),  
  64.             HEAP_ZERO_MEMORY,  
  65.             sizeof(PER_IO_OPERATION_DATA));  
  66.         lpPerIOData->Buffer.len = MSGSIZE;  
  67.         lpPerIOData->Buffer.buf = lpPerIOData->szMessage;  
  68.         lpPerIOData->OperationType = RECV_POSTED;  
  69.         WSARecv(sClient,  
  70.             &lpPerIOData->Buffer,  
  71.             1,  
  72.             &lpPerIOData->NumberOfBytesRecvd,  
  73.             &lpPerIOData->Flags,  
  74.             &lpPerIOData->overlap,  
  75.             NULL);  
  76.     }  
  77.     PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);  
  78.     CloseHandle(CompletionPort);  
  79.     closesocket(sListen);  
  80.     WSACleanup();  
  81.     return 0;  
  82. }  
  83. DWORD WINAPI WorkerThread(LPVOID CompletionPortID)  
  84. {  
  85.     HANDLE CompletionPort = (HANDLE)CompletionPortID;  
  86.     DWORD dwBytesTransferred;  
  87.     SOCKET sClient;  
  88.     LPPER_IO_OPERATION_DATA lpPerIOData = NULL;  
  89.     while (TRUE)  
  90.     {  
  91.         GetQueuedCompletionStatus(  
  92.             CompletionPort,  
  93.             &dwBytesTransferred,  
  94.             (DWORD*)&sClient,  
  95.             (LPOVERLAPPED*)&lpPerIOData,  
  96.             INFINITE);  
  97.         if (dwBytesTransferred == 0xFFFFFFFF)  
  98.         {  
  99.             return 0;  
  100.         }  
  101.         if (lpPerIOData->OperationType == RECV_POSTED)  
  102.         {  
  103.             if (dwBytesTransferred == 0)  
  104.             {  
  105.                 // Connection was closed by client  
  106.                 closesocket(sClient);  
  107.                 HeapFree(GetProcessHeap(), 0, lpPerIOData);  
  108.             }  
  109.             else  
  110.             {  
  111.                 lpPerIOData->szMessage[dwBytesTransferred] = '\0';  
  112.                 send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);  
  113.                   
  114.                 // Launch another asynchronous operation for sClient  
  115.                 memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));  
  116.                 lpPerIOData->Buffer.len = MSGSIZE;  
  117.                 lpPerIOData->Buffer.buf = lpPerIOData->szMessage;  
  118.                 lpPerIOData->OperationType = RECV_POSTED;  
  119.                 WSARecv(sClient,  
  120.                     &lpPerIOData->Buffer,  
  121.                     1,  
  122.                     &lpPerIOData->NumberOfBytesRecvd,  
  123.                     &lpPerIOData->Flags,  
  124.                     &lpPerIOData->overlap,  
  125.                     NULL);  
  126.             }  
  127.         }  
  128.     }  
  129.     return 0;  
  130. }  

服务器端得主要流程: 
1.创建完成端口对象 
2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能) 
3.创建监听套接字,绑定,监听,然后程序进入循环 
4.在循环中,我做了以下几件事情: 
(1).接受一个客户端连接 
(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同), 注意,按道理来讲,此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递; 
(3).触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,此外,还有操作类型等重要信息。 

在工作者线程的循环中,我们 
1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等) 
2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端 
3.再次触发一个WSARecv异步操作

你可能感兴趣的:(socket)