SOCKET编程登峰造极之完成端口

一、什么是完成端口?
完成端口---是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可,还有设备内核对象、事件对象、告警I/0等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。
 
二、完成端口的内部机制

1)创建完成端口
完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:

显示代码打印1 HANDLE CreateIoCompletionPort( 

2     IN HANDLE FileHandle, 

3     IN HANDLE ExistingCompletionPort, 

4     IN ULONG_PTR CompletionKey, 

5     IN DWORD NumberOfConcurrentThreads 

6     );

通常创建工作分两步:
 
第一步,创建一个新的完成端口内核对象,可以使用下面的函数:

显示代码打印1 HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads) 

2 { 

3      return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads); 

4 }; 

第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:

显示代码打印1 bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey) 

2 { 

3     HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0); 

4     return h==hCompPort; 

5 };

 
说明
a)CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄
b)CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。
c)NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU的个数来自动确定。
创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。
 
2)完成端口线程的工作原理
完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。该函数原型:

显示代码打印1 BOOL GetQueuedCompletionStatus( 

2     IN  HANDLE CompletionPort, 

3     OUT LPDWORD lpNumberOfBytesTransferred, 

4     OUT PULONG_PTR lpCompletionKey, 

5     OUT LPOVERLAPPED *lpOverlapped, 

6     IN  DWORD dwMilliseconds 

7 );

 

 

这个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。
完成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。
当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。
基本上情况就是如此,所以我们的线程要想成为完成端口管理的线程,就必须要调用
GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考《Windows高级编程指南》,我们现在知道的知识,已经足够了。

 

写了一下午,终于写完了这个“完成端口”。
到今天为止,写完了Overlapped I\O Event、Overlapped I\O completion Routine和completion Port。一路写过来的确学到了不少东西,也清楚地看到到微软在遇到问题并解决问题的方法;不得不承认,微软~还是很强的。呵呵~

这也让我明白一件事:遇到困难,不要望而却步;只要你勇于探索,一切都将是那么简单。(听起来有点自恋的感觉^_^)
“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!
我们基本上按下述步骤行事:
1) 创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。
2) 判断系统内到底安装了多少个处理器。
3) 创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务。在这个简单的例子中,我们为每个处理器都只创建一个工作者线程。这是由于事先已预计到,到时不会有任何线程进入“挂起”状态,造成由于线程数量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用CreateThread函数时,必须同时提供一个工作者例程,由线程在创建好执行。本节稍后还会详细讨论线程的职责。
4) 准备好一个监听套接字,在端口1234上监听进入的连接请求。
5) 使用accept函数,接受进入的连接请求。
6) 创建一个数据结构,同时在结构中存入接受的套接字句柄。
7) 调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey)参数,将单句柄数据结构传递给CreateIoCompletionPort。
8) 开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求,稍后便会在步骤3)指定的工作者例程
中,体验到这一点。
9) 重复步骤5) ~ 8),直至服务器中止。

源码---------------------------------------------------------------------------------------------

显示代码打印001 #pragma comment(lib,"ws2_32.lib") 

002 #include <winsock2.h> 

003 #include <stdio.h> 

004 ////////////////////////////////////////////////////////////////////////// 

005 //仅供测试软件用 

006 #include "Protocol.h" 

007   

008 #define DATA_BUFSIZE 1024        // 接收缓冲区大小 

009 typedef enum{ IOSEND,IORECV,IOQUIT } IO_TYPE; 

010 typedef struct _SOCKET_INFORMATION { 

011         OVERLAPPED Overlapped; 

012         SOCKET        Socket; 

013         IO_TYPE  IoType; 

014         char                buffer[DATA_BUFSIZE]; 

015         WSABUF        DataBuf; 

016         DWORD        BytesSEND; 

017         DWORD        BytesRECV; 

018 } SOCKET_INFORMATION, * LPSOCKET_INFORMATION; 

019 DWORD   Flags = 0, 

020                 Bytes = 0; 

021 DWORD WINAPI WorkThread(LPVOID CompletionPortID); 

022 DWORD WINAPI AcceptThread(LPVOID lpParameter) 

023 { 

024         WSADATA wsaData; 

025         HANDLE hCompPort; 

026         DWORD ThreadID; 

027         DWORD Ret; 

028         if ((Ret = WSAStartup(0x0202, &wsaData)) != 0) 

029         { 

030                 printf("WSAStartup failed with error %d\n", Ret); 

031                 return FALSE; 

032         } 

033         if ((hCompPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL) 

034         { 

035                 printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError()); 

036                 return FALSE; 

037         } 

038         //根据CPU个数来创建线程,以达到最佳性能 

039         SYSTEM_INFO SystemInfo; 

040         GetSystemInfo(&SystemInfo); 

041         for(unsigned int i=0; i<SystemInfo.dwNumberOfProcessors*2; i++) 

042         { 

043                 HANDLE ThreadHandle; 

044                 if ((ThreadHandle = CreateThread(NULL, 0, WorkThread, hCompPort, 0, &ThreadID)) == NULL) 

045                 { 

046                         printf("CreateThread() failed with error %d\n", GetLastError()); 

047                         return FALSE; 

048                 } 

049                 CloseHandle(ThreadHandle); 

050         } 

051         SOCKET ListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, NULL, WSA_FLAG_OVERLAPPED); 

052         SOCKADDR_IN ServerAddr; 

053         ServerAddr.sin_family = AF_INET; 

054         ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 

055         ServerAddr.sin_port = htons(1234); 

056         bind(ListenSocket,(LPSOCKADDR)&ServerAddr,sizeof(ServerAddr)); 

057         listen(ListenSocket,100); 

058         printf("listenning...\n"); 

059         SOCKADDR_IN ClientAddr; 

060         int addr_length=sizeof(ClientAddr); 

061         while (TRUE) 

062         { 

063                 LPSOCKET_INFORMATION  SI = new SOCKET_INFORMATION; 

064                 if ((SI->Socket = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length)) != INVALID_SOCKET) 

065                 { 

066                         printf("accept ip:%s port:%d\n",inet_ntoa(ClientAddr.sin_addr),ClientAddr.sin_port); 

067                         //相关参数初始化 

068                         memset(&SI->Overlapped,0,sizeof(WSAOVERLAPPED)); 

069                         memset(SI->buffer, 0, DATA_BUFSIZE); 

070                         SI->DataBuf.buf = SI->buffer; 

071                         SI->DataBuf.len = DATA_BUFSIZE; 

072                         SI->BytesRECV        = 0; 

073                         SI->BytesSEND        = 0; 

074                         SI->IoType                = IORECV; 

075                         ////////////////////////////////////////////////////////////////////////// 

076                         //仅供测试软件用 

077                         HeaderMessage recvMsg; 

078                         if (recv(SI->Socket, (char*)&recvMsg, sizeof(recvMsg), 0) <= 0)  

079                         { 

080                                 printf("初始参数交互失败"); 

081                         } 

082                         if (CreateIoCompletionPort((HANDLE)SI->Socket, hCompPort, (DWORD)SI, 0) == NULL) 

083                         { 

084                                 printf("CreateIoCompletionPort failed with error %d\n", GetLastError()); 

085                                 return FALSE; 

086                         } 

087                         //发出一个重叠I\O请求 

088                         if(WSARecv(SI->Socket, &SI->DataBuf, 1, &Bytes, &Flags, &SI->Overlapped, NULL) == SOCKET_ERROR) 

089                         { 

090                                 if(WSAGetLastError() != WSA_IO_PENDING) 

091                                 { 

092                                         printf("disconnect\n"); 

093                                         closesocket(SI->Socket);  

094                                         delete SI; 

095                                         continue; 

096                                 } 

097                         } 

098                 } 

099                   

100         } 

101 return FALSE; 

102 } 

103 DWORD WINAPI WorkThread(LPVOID CompletionPortID) 

104 { 

105         HANDLE hCompPort = (HANDLE)CompletionPortID; 

106         while (TRUE) 

107         { 

108                 DWORD BytesTransferred = 0; 

109                 LPSOCKET_INFORMATION SI = NULL; 

110                 LPWSAOVERLAPPED Overlapped = NULL; 

111                 //线程进入线程池,等待被唤醒 

112                 if (GetQueuedCompletionStatus(hCompPort, &BytesTransferred, (LPDWORD)&SI, &Overlapped, INFINITE)) 

113                 { 

114                         if (0 == BytesTransferred && IOQUIT != SI->IoType) 

115                         { 

116                                 printf("disconnect\n"); 

117                                 closesocket(SI->Socket);  

118                                 delete SI; 

119                                 continue; 

120                         } 

121                         switch(SI->IoType) 

122                         { 

123                         case IORECV: 

124                                 { 

125                                         //目前的功能是将接收到的数据原封不动的返回 

126                                         SI->DataBuf.len = BytesTransferred; 

127                                         SI->BytesRECV = BytesTransferred; 

128                                         SI->IoType = IOSEND; 

129                                         if (WSASend(SI->Socket, &SI->DataBuf, 1, &Bytes, Flags, &SI->Overlapped, NULL) == SOCKET_ERROR) 

130                                         { 

131                                                 if(WSAGetLastError() != WSA_IO_PENDING) 

132                                                 { 

133                                                         printf("disconnect\n"); 

134                                                         closesocket(SI->Socket);  

135                                                         delete SI; 

136                                                         continue; 

137                                                 } 

138                                         } 

139                                 break; 

140                                 } 

141                         case IOSEND: 

142                                 { 

143                                         SI->BytesSEND += BytesTransferred; 

144                                         //返回是否彻底,若未发完,接着发 

145                                         if (SI->BytesSEND < SI->BytesRECV) 

146                                         { 

147                                                 SI->DataBuf.buf += BytesTransferred;  

148                                                 SI->DataBuf.len -= BytesTransferred;  

149                                                 SI->IoType = IOSEND; 

150                                                 if (WSASend(SI->Socket, &SI->DataBuf, 1, &Bytes, Flags, &SI->Overlapped, NULL) == SOCKET_ERROR) 

151                                                 { 

152                                                         if(WSAGetLastError() != WSA_IO_PENDING) 

153                                                         { 

154                                                                 printf("disconnect\n"); 

155                                                                 closesocket(SI->Socket);  

156                                                                 delete SI; 

157                                                                 continue; 

158                                                         } 

159                                                 } 

160                                         } 

161                                         else if (SI->BytesSEND > SI->BytesRECV) 

162                                         { 

163                                                 printf("BytesSEND:%d > BytesRECV:%d\n",SI->BytesSEND,SI->BytesRECV); 

164                                                 memset(SI->buffer, 0, DATA_BUFSIZE); 

165                                                 SI->BytesRECV = 0; 

166                                                 SI->BytesSEND = 0; 

167                                                 SI->IoType = IORECV; 

168                                                 SI->DataBuf.len = DATA_BUFSIZE; 

169                                                 SI->DataBuf.buf = SI->buffer; 

170                                         } 

171                                         else

172                                         { 

173                                                 memset(SI->buffer, 0, DATA_BUFSIZE); 

174                                                 SI->BytesRECV = 0; 

175                                                 SI->BytesSEND = 0; 

176                                                 SI->IoType = IORECV; 

177                                                 SI->DataBuf.len = DATA_BUFSIZE; 

178                                                 SI->DataBuf.buf = SI->buffer; 

179                                                 if (WSARecv(SI->Socket, &SI->DataBuf, 1, &Bytes, &Flags, &SI->Overlapped, NULL) == SOCKET_ERROR) 

180                                                 { 

181                                                         if(WSAGetLastError() != WSA_IO_PENDING) 

182                                                         { 

183                                                                 printf("disconnect\n"); 

184                                                                 closesocket(SI->Socket);  

185                                                                 delete SI; 

186                                                                 continue; 

187                                                         } 

188                                                 } 

189                                         } 

190                                 break; 

191                                 } 

192                         case IOQUIT: 

193                                 { 

194                                         //让线程安全退出 

195                                         return FALSE; 

196                                 break; 

197                                 } 

198                                   

199                         default: 

200                                 break; 

201                         } 

202                 }         

203         }         

204 return FALSE; 

205 } 

206 void main()    

207 { 

208         HANDLE hThreads = CreateThread(NULL, 0, AcceptThread, NULL, NULL, NULL);  

209           

210         WaitForSingleObject(hThreads,INFINITE); 

211         printf("exit\n"); 

212         CloseHandle(hThreads); 

213 }

文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/3_program/c++/cppjs/2007927/74932.html

原文链接: https://www.cnblogs.com/jianqiang2010/archive/2010/11/22/1883891.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    SOCKET编程登峰造极之完成端口

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/17741

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午6:22
下一篇 2023年2月7日 下午6:24

相关推荐