IOCP--Socket IO模型终结篇

完成端口程序的执行步骤:

 1) 创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。

 2) 判断系统内到底安装了多少个处理器。

 3) 创建工作者线程,根据步骤2 )得到的处理器信息,在完成端口上,为已完成的I / O请求提供服务。在这个简单的例子中,我们为每个处理器都只创建一个工作者线程。这是由于事先已预计到,到时不会有任何线程进入“挂起”状态,造成由于线程数量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用C r e a t e T h r e a d函数时,必须同时提供一个工作者例程,由线程在创建好执行。本节稍后还会详细讨论线程的职责。

4) 准备好一个监听套接字,在端口5 1 5 0上监听进入的连接请求。

5) 使用a c c e p t函数,接受进入的连接请求。

6) 创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。

7) 调用C r e a t e I o C o m p l e t i o n P o r t,将自a c c e p t返回的新套接字句柄同完成端口关联到一起。通过完成键(C o m p l e t i o n K e y)参数,将单句柄数据结构传递给C r e a t e I o C o m p l e t i o n P o r t。

 8) 开始在已接受的连接上进行I / O操作。在此,我们希望通过重叠I / O机制,在新建的套接字上投递一个或多个异步W S A R e c v或W S A S e n d请求。这些I / O请求完成后,一个工作者线程会为I / O请求提供服务,同时继续处理未来的I / O请求,稍后便会在步骤3 )指定的工作者例程中,体验到这一点。

9) 重复步骤5 ) ~ 8 ),直至服务器中止。

 

 

如果一个应用程序同时要管理众多的socket,那么采用IOCP是比较好的办法。从本质上说,完成端口模型要求我们创建一个Wi n 3 2完成端口对象,通过指定数量的线程,对重叠I / O请求进行管理,以便为已经完成的重叠I / O请求提供服务。

1) 创建完成端口

HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

 

2) 把一个IO句柄和完成端口关联起来,这里的句柄是一个socket 句柄

CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);

 

其中第一个参数是句柄,可以是文件句柄、SOCKET句柄。
第二个就是我们上面创建出来的完成端口,这里就把两个东西关联在一起了。
第三个参数很关键,叫做PerHandleData,就是对应于每个句柄的数据块。我们可以使用这个参数在后面取到与这个SOCKET对应的数据。
最后一个参数给0,意思就是根据CPU的个数,允许尽可能多的线程并发执行。

 


3) 从完成端口中取得结果
GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)

第一个参数是完成端口
第二个参数是表明这次的操作传递了多少个字节的数据
第三个参数是OUT类型的参数,就是前面CreateIoCompletionPort传进去的单句柄数据,这里就是前面的SOCKET句柄以及与之相对应的数据,这里操作系统给我们返回,让我们不用自己去做列表查询等操作了
第四个参数就是进行IO操作的结果,是我们在投递 WSARecv / WSASend 等操作时传递进去的,这里操作系统做好准备后,给我们返回了。非常省事!

个人感觉完成端口就是操作系统为我们包装了很多重叠IO的不爽的地方,让我们可以更方便的去使用,下篇我将会尝试去讲述完成端口的原理。

 

  1 #include "winerror.h"
  2 #include "Winsock2.h"
  3 #pragma comment(lib, "ws2_32")
  4 
  5 #include "windows.h"
  6 
  7 #include <iostream>
  8 using namespace std;
  9 
 10 /// 宏定义
 11 #define PORT 5050
 12 #define DATA_BUFSIZE 8192
 13 
 14 #define OutErr(a) cout << (a) << endl
 15 << "出错代码:" << WSAGetLastError() << endl
 16 << "出错文件:" << __FILE__ << endl  
 17 << "出错行数:" << __LINE__ << endl
 18 
 19 #define OutMsg(a) cout << (a) << endl;
 20 
 21 
 22 /// 全局函数定义
 23 
 24 ///////////////////////////////////////////////////////////////////////
 25 //
 26 // 函数名       : InitWinsock
 27 // 功能描述     : 初始化WINSOCK
 28 // 返回值       : void
 29 //
 30 ///////////////////////////////////////////////////////////////////////
 31 void InitWinsock()
 32 {
 33     // 初始化WINSOCK
 34     WSADATA wsd;
 35     if( WSAStartup(MAKEWORD(22), &wsd) != 0)
 36 
 37 }
 38 
 39 ///////////////////////////////////////////////////////////////////////
 40 //
 41 // 函数名       : BindServerOverlapped
 42 // 功能描述     : 绑定端口,并返回一个 Overlapped 的Listen Socket
 43 // 参数         : int nPort
 44 // 返回值       : SOCKET
 45 //
 46 ///////////////////////////////////////////////////////////////////////
 47 SOCKET BindServerOverlapped(int nPort)
 48 {
 49     // 创建socket
 50     SOCKET sServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
 51 
 52     // 绑定端口
 53     struct sockaddr_in servAddr;
 54     servAddr.sin_family = AF_INET;
 55     servAddr.sin_port = htons(nPort);
 56     servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 57 
 58     if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
 59     {
 60         OutErr("bind Failed!");
 61         return NULL;
 62     }
 63 
 64     // 设置监听队列为200
 65     if(listen(sServer, 200!= 0)
 66     {
 67         OutErr("listen Failed!");
 68         return NULL;
 69     }
 70     return sServer;
 71 }
 72 
 73 
 74 /// 结构体定义
 75 typedef struct
 76 {
 77     OVERLAPPED Overlapped;
 78     WSABUF DataBuf;
 79     CHAR Buffer[DATA_BUFSIZE];
 80 } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
 81 
 82 
 83 typedef struct
 84 {
 85     SOCKET Socket;
 86 } PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
 87 
 88 
 89 DWORD WINAPI ProcessIO(LPVOID lpParam)
 90 {
 91     HANDLE CompletionPort = (HANDLE)lpParam;
 92     DWORD BytesTransferred;
 93     LPPER_HANDLE_DATA PerHandleData;
 94     LPPER_IO_OPERATION_DATA PerIoData;
 95 
 96     while(true)
 97     {
 98 
 99         if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
100         {
101             if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
102             {
103                 cout << "closing socket" << PerHandleData->Socket << endl;
104 
105                 closesocket(PerHandleData->Socket);
106 
107                 delete PerIoData;
108                 delete PerHandleData;
109                 continue;
110             }
111             else
112             {
113                 OutErr("GetQueuedCompletionStatus failed!");
114             }
115             return 0;
116         }
117 
118         // 说明客户端已经退出
119         if(BytesTransferred == 0)
120         {
121             cout << "closing socket" << PerHandleData->Socket << endl;
122             closesocket(PerHandleData->Socket);
123             delete PerIoData;
124             delete PerHandleData;
125             continue;
126         }
127 
128         // 取得数据并处理
129         cout << PerHandleData->Socket << "发送过来的消息:" << PerIoData->Buffer << endl;
130 
131         // 继续向 socket 投递WSARecv操作
132         DWORD Flags = 0;
133         DWORD dwRecv = 0;
134         ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
135         PerIoData->DataBuf.buf = PerIoData->Buffer;
136         PerIoData->DataBuf.len = DATA_BUFSIZE;
137         WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1&dwRecv, &Flags, &PerIoData->Overlapped, NULL); 
138     }
139 
140     return 0;
141 }
142 
143 void main()
144 {
145     InitWinsock();
146 
147     HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 00);
148 
149     // 根据系统的CPU来创建工作者线程
150     SYSTEM_INFO SystemInfo;
151     GetSystemInfo(&SystemInfo);
152 
153     for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
154     {
155         HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, CompletionPort, 0, NULL);
156         //if(hProcessIO)
157         CloseHandle(hProcessIO);
158     }
159 
160     // 创建侦听SOCKET
161     SOCKET sListen = BindServerOverlapped(PORT);
162 
163 
164     SOCKET sClient;
165     LPPER_HANDLE_DATA PerHandleData;
166     LPPER_IO_OPERATION_DATA PerIoData;
167     while(true)
168     {
169         // 等待客户端接入
170         //sClient = WSAAccept(sListen, NULL, NULL, NULL, 0);
171         sClient = accept(sListen, 00);
172 
173         cout << "Socket " << sClient << "连接进来" << endl;
174 
175         PerHandleData = new PER_HANDLE_DATA();
176         PerHandleData->Socket = sClient;
177 
178         // 将接入的客户端和完成端口联系起来
179         CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);
180 
181         // 建立一个Overlapped,并使用这个Overlapped结构对socket投递操作
182         PerIoData = new PER_IO_OPERATION_DATA();
183 
184         ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
185         PerIoData->DataBuf.buf = PerIoData->Buffer;
186         PerIoData->DataBuf.len = DATA_BUFSIZE;
187 
188         // 投递一个WSARecv操作
189         DWORD Flags = 0;
190         DWORD dwRecv = 0;
191         WSARecv(sClient, &PerIoData->DataBuf, 1&dwRecv, &Flags, &PerIoData->Overlapped, NULL);
192     }
193 
194     DWORD dwByteTrans;
195     PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 00);
196     closesocket(sListen);
197 }

 

 

  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 enum
 10 {
 11     RECV_POSTED
 12 }OPERATION_TYPE;
 13 
 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 
 24 DWORD WINAPI WorkerThread(LPVOID);
 25 
 26 int main()
 27 {
 28     WSADATA                 wsaData;
 29     SOCKET                  sListen, sClient;
 30     SOCKADDR_IN             local, client;
 31     DWORD                   i, dwThreadId;
 32     int                     iaddrSize = sizeof(SOCKADDR_IN);
 33     HANDLE                  CompletionPort = INVALID_HANDLE_VALUE;
 34     SYSTEM_INFO             systeminfo;
 35     LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
 36     // Initialize Windows Socket library
 37     WSAStartup(0x0202&wsaData);
 38     // Create completion port
 39     CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 00);
 40     // Create worker thread
 41     GetSystemInfo(&systeminfo);
 42     for (i = 0; i < systeminfo.dwNumberOfProcessors; i++)
 43     {
 44         CreateThread(NULL, 0, WorkerThread, CompletionPort, 0&dwThreadId);
 45     }
 46 
 47     // Create listening socket
 48     sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 49     // Bind
 50     local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 51     local.sin_family = AF_INET;
 52     local.sin_port = htons(PORT);
 53     bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
 54     // Listen
 55     listen(sListen, 3);
 56     while (TRUE)
 57     {
 58         // Accept a connection
 59         sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
 60         printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
 61         // Associate the newly arrived client socket with completion port
 62         CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
 63 
 64         // Launch an asynchronous operation for new arrived connection
 65         lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
 66             GetProcessHeap(),
 67             HEAP_ZERO_MEMORY,
 68             sizeof(PER_IO_OPERATION_DATA));
 69         lpPerIOData->Buffer.len = MSGSIZE;
 70         lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
 71         lpPerIOData->OperationType = RECV_POSTED;
 72         WSARecv(sClient,
 73             &lpPerIOData->Buffer,
 74             1,
 75             &lpPerIOData->NumberOfBytesRecvd,
 76             &lpPerIOData->Flags,
 77             &lpPerIOData->overlap,
 78             NULL);
 79     }
 80     PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF0, NULL);
 81     CloseHandle(CompletionPort);
 82     closesocket(sListen);
 83     WSACleanup();
 84     return 0;
 85 }
 86 DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
 87 {
 88     HANDLE                  CompletionPort=(HANDLE)CompletionPortID;
 89     DWORD                   dwBytesTransferred;
 90     SOCKET                  sClient;
 91     LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
 92     while (TRUE)
 93     {
 94         GetQueuedCompletionStatus(
 95             CompletionPort,
 96             &dwBytesTransferred,
 97             &sClient,
 98             (LPOVERLAPPED *)&lpPerIOData,
 99             INFINITE);
100         if (dwBytesTransferred == 0xFFFFFFFF)
101         {
102             return 0;
103         }
104 
105         if (lpPerIOData->OperationType == RECV_POSTED)
106         {
107             if (dwBytesTransferred == 0)
108             {
109                 // Connection was closed by client
110                 closesocket(sClient);
111                 HeapFree(GetProcessHeap(), 0, lpPerIOData);       
112             }
113             else
114             {
115                 lpPerIOData->szMessage[dwBytesTransferred] = '\0';
116                 send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);
117 
118                 // Launch another asynchronous operation for sClient
119                 memset(lpPerIOData, 0sizeof(PER_IO_OPERATION_DATA));
120                 lpPerIOData->Buffer.len = MSGSIZE;
121                 lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
122                 lpPerIOData->OperationType = RECV_POSTED;
123                 WSARecv(sClient,
124                     &lpPerIOData->Buffer,
125                     1,
126                     &lpPerIOData->NumberOfBytesRecvd,
127                     &lpPerIOData->Flags,
128                     &lpPerIOData->overlap,
129                     NULL);
130             }
131         }
132     }
133     return 0;
134 }

 

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


在工作者线程的循环中,我们

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

 

 


posted on 2008-08-17 19:29  风荷小筑  阅读(5231)  评论(0编辑  收藏  举报