完成端口IOCP 流程

参考:《windows网络编程》、度娘、谷歌、还有一篇博客和它的源码http://blog.csdn.net/piggyxp/article/details/6922277,感谢PiggyXP,不过我的代码有更好的可读性,感谢broadcom的嵌入式C++系统带给我的编程经验,不过流程基本相似,可以关注一些细节

HttpServer\targetver.h:           Windows platform header,系统生成的
HttpServer\stdafx.h:                 系统头文件和一些通用define
HttpServer\stdafx.cpp :               预编译 stdafx.h使用的源文件,无内容

HttpServer\Start.cpp:                          console程序入口,目前不必实现stop,因为结束console主线程,该进程的其余线程也将退出
                                                TODO:做界面的时候再实现stop和restart功能,可能需要使用PostQueuedCompletionStatus函数来通知worker线程退出
HttpServer\IOCPServer.h:
HttpServer\IOCPServer.cpp:             完成端口模型的TCP server

HttpServer\SocketHandle.h:
HttpServer\SocketHandle.cpp:      对应于每一个socket的handle类定义,一个socket handle可持有多个IO数据组成的list

HttpServer\PerIOData.h:
HttpServer\PerIOData.cpp:            单个IO数据,包含 overlapped结构,而且该结构必须在类内存空间的最开始

流程图:

源码如下,其他的不细说了,看注释吧,不过是英文的大笑,英文语法就别深究了:
HttpServer\stdafx.h:
[cpp]  view plain copy
  1. // stdafx.h : include file for standard system include files,  
  2. // or project specific include files that are used frequently, but  
  3. // are changed infrequently  
  4. //  
  5.   
  6. #pragma once  
  7.   
  8. #include "targetver.h"  
  9.   
  10. #include <cstdio>  
  11. #include <cassert>  
  12. #include <tchar.h>  
  13. #include <string.h>  
  14.   
  15. // TODO: reference additional headers your program requires here  
  16. // win header and lib  
  17. #include <winsock2.h>  
  18. #include <MSWSock.h>  
  19. #pragma comment(lib,"ws2_32.lib")  
  20.   
  21. #include <list>  
  22. using namespace std;  
  23. #define IOCP_DBUG(format, ...) printf("%s,%d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__);  

HttpServer\Start.cpp
[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #include "IOCPServer.h"  
  3.   
  4.   
  5. int _tmain(int argc, _TCHAR* argv[])  
  6. {  
  7.     IOCP_DBUG("enter Start\n");  
  8.     IOCPServer *iocpserver = new IOCPServer();  
  9.     iocpserver->Start();  
  10.   
  11.     while(TRUE)   
  12.     {  
  13.         Sleep(5000);  
  14.     }  
  15.   
  16.     delete iocpserver;  
  17.     return 0;  
  18. }  

HttpServer\IOCPServer.h:
[cpp]  view plain copy
  1. #pragma once  
  2.   
  3. #include "stdafx.h"  
  4. #include "PerIOData.h"  
  5. #include "SocketHandle.h"  
  6.   
  7.   
  8. // this is IOCPServer which use IOCP  
  9. // has no GUI, we provide two public function to call  
  10. // two public function: start() and stop()  
  11. // you can write new interface  
  12. class IOCPServer  
  13. {  
  14. public:  
  15.     IOCPServer(void);  
  16.     virtual ~IOCPServer(void);  
  17.   
  18. public:  
  19.     bool Start(void);  
  20.     bool Stop(void);  
  21.   
  22. // self member hold in whole life of the class object  
  23. //so we have no need to tranfer these in function parameters  
  24. private:  
  25.     HANDLE completeport;  
  26.     SocketHandle *listenhandle;  
  27.   
  28. private:  
  29.     LPFN_ACCEPTEX lpfnacceptex;  
  30.     LPFN_GETACCEPTEXSOCKADDRS lpfngetacceptexsockaddrs;  
  31.   
  32. //Initialize functions, check the cpp file   
  33. //Return value for BOOL function:  
  34. //      true, if succeed;  
  35. //      false, if failed.  
  36. //  
  37. private:  
  38.     BOOL InitializeIOCP(void);  
  39.     void DeInitializeIOCP(void);  
  40.     BOOL InitializeSocket(void);  
  41.     void DeInitializeSocket(void);  
  42.     BOOL GetAcceptExFuction(void);  
  43.   
  44.   
  45. private:  
  46.     // CreateThread only can use static function in class  
  47.     // so must transfer the 'this' pointer to lpParam to use non-static function member  
  48.     static DWORD WINAPI WorkerThread(LPVOID lpParam);  
  49.   
  50.   
  51. // the six function member below has same parameter format and return value  
  52. //  
  53. //Parameter:  
  54. //      connecthandle - the client socket and addr storage struct for the operation  
  55. //      listeniodata/connectiodata - the io data for the operation  
  56. //          this struct PerIOData MUST begin with WSAOVERLAPPED/OVERLAPPED struct,  
  57. //          To convert the PerIOData pointer to OVERLAPPED pointer.  
  58. //          actually, the first address should be the same, so they are compatible  
  59. //  
  60. //Return value:  
  61. //      true, if succeed;  
  62. //      false, if failed.  
  63. //  
  64. private:  
  65.     // post function for AcceptEx  
  66.     // no need for do function, ACCEPT_POSTED signal corresponding to listen socket  
  67.     // listeniodata is hold in whole life of master thread  
  68.     // MUST not release in worker thread  
  69.     void PostAcceptEx(PerIOData *listeniodata);  
  70.     BOOL DoAcceptEx(PerIOData *listeniodata);  
  71.   
  72. private:  
  73.     // post and do function for recv/send   
  74.     // connectiodata is hold in worker thread  
  75.     void PostRecv(SocketHandle *connecthandle, PerIOData *connectiodata);  
  76.     BOOL DoRecv(SocketHandle *connecthandle, PerIOData *connectiodata);  
  77.     void PostSend(SocketHandle *connecthandle, PerIOData *connectiodata);  
  78.     BOOL DoSend(SocketHandle *connecthandle, PerIOData *connectiodata);  
  79. };  

HttpServer\IOCPServer.cpp:
[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #include "IOCPServer.h"  
  3.   
  4.   
  5. // we only post accpetex after do acceptex, if there is no mistake  
  6. // so the MAX_POST_ACCEPT is the number we post accpetex first  
  7. // and it is max client number we can accpet in the same time  
  8. // but we almost can not see difference between 10 and 100 clients, because this program is simple and fast  
  9. // In test, 100 clients seems to accept immediately  in the same time, so this number have no need to be bigger  
  10. #define MAX_POST_ACCEPT 10  
  11.   
  12. //the port  
  13. #define SERVERPORT 5863  
  14.   
  15. //backlog, see Stevens' book, the nginx set this to 511  
  16. //we have no need to set it to be that big  
  17. #define BACKLOG 200  
  18.   
  19.   
  20. //constructor  
  21. IOCPServer::IOCPServer(void):  
  22.     completeport(NULL),  
  23.     listenhandle(NULL),  
  24.     lpfnacceptex(NULL),  
  25.     lpfngetacceptexsockaddrs(NULL)  
  26. {  
  27.     IOCP_DBUG("enter IOCPServer\n");  
  28.   
  29.     //load the win socket lib  
  30.     WSADATA wsaData;  
  31.     if(NO_ERROR != WSAStartup(MAKEWORD(2,2), &wsaData))  
  32.     {  
  33.         IOCP_DBUG("WSAStartup WinSock 2.2 failed!\n");  
  34.         assert(0);  
  35.     }  
  36.   
  37.     //establish listen socket  
  38.     listenhandle = new SocketHandle();  
  39. }  
  40.   
  41. // destructor  
  42. IOCPServer::~IOCPServer(void)  
  43. {  
  44.     IOCP_DBUG("enter ~IOCPServer\n");  
  45.       
  46.     delete listenhandle;  
  47. }  
  48.   
  49. // start the server  
  50. // we use IOCP instead of select events  
  51. // of cource, you can register other method, not implement here  
  52. bool IOCPServer::Start(void)  
  53. {  
  54.     IOCP_DBUG("enter Start\n");  
  55.   
  56.     //Initialize IOCP  
  57.     if(FALSE == InitializeIOCP())  
  58.     {  
  59.         IOCP_DBUG("InitializeIOCP() failed: %d\n", GetLastError());  
  60.         DeInitializeIOCP();  
  61.         return FALSE;  
  62.     }  
  63.     IOCP_DBUG("InitializeIOCP() succeed\n");  
  64.   
  65.     //Initialize Socket, associate it with IOCP  
  66.     if(FALSE == InitializeSocket())  
  67.     {  
  68.         IOCP_DBUG("InitializeSocket() failed: %d\n", GetLastError());  
  69.         DeInitializeSocket();  
  70.         return FALSE;  
  71.     }  
  72.     IOCP_DBUG("InitializeSocket() succeed\n");  
  73.   
  74.     // get AcceptEx func pointer  
  75.     if(FALSE == GetAcceptExFuction())  
  76.     {  
  77.         IOCP_DBUG("GetAcceptExFuction() failed: %d\n", GetLastError());  
  78.         return FALSE;  
  79.     }  
  80.     IOCP_DBUG("GetAcceptExFuction() succeed\n");  
  81.   
  82.     // new listeniodata only here  
  83.     // post some ACCEPT_POSTED signal, let the server start  
  84.     for(int i=0; i<MAX_POST_ACCEPT; ++i)  
  85.     {  
  86.         PerIOData *listeniodata = listenhandle->GetNewIOData();  
  87.         PostAcceptEx(listeniodata);  
  88.     }  
  89.   
  90.     return TRUE;  
  91. }  
  92.   
  93. //Initialize IOCP  
  94. BOOL IOCPServer::InitializeIOCP(void)  
  95. {  
  96.     IOCP_DBUG("enter InitializeIOCP\n");  
  97.   
  98.     // establish completeport  
  99.     completeport = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );  
  100.     if ( NULL == completeport)  
  101.     {  
  102.         IOCP_DBUG("CreateIoCompletionPort failed: %d!\n", WSAGetLastError());  
  103.         return FALSE;  
  104.     }  
  105.   
  106.     // run worker thread  
  107.     SYSTEM_INFO systeminfo;  
  108.     GetSystemInfo(&systeminfo);  
  109.     for(DWORD i=0; i < systeminfo.dwNumberOfProcessors; ++i)  
  110.     {  
  111.         HANDLE threadhandle;  
  112.         threadhandle = CreateThread(NULL, 0, WorkerThread, (LPVOID)this, 0, NULL);  
  113.         CloseHandle(threadhandle);  
  114.     }  
  115.   
  116.     return TRUE;  
  117. }  
  118.   
  119. //DeInitialize IOCP  
  120. void IOCPServer::DeInitializeIOCP(void)  
  121. {  
  122.     IOCP_DBUG("enter DeInitializeIOCP\n");  
  123.   
  124.     CloseHandle(completeport);  
  125. }  
  126.   
  127. //Initialize Socket  
  128. BOOL IOCPServer::InitializeSocket(void)  
  129. {  
  130.     IOCP_DBUG("enter InitializeSocket\n");  
  131.       
  132.     listenhandle->ssocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);  
  133.     if(INVALID_SOCKET == listenhandle->ssocket)  
  134.     {  
  135.         IOCP_DBUG("WSASocket failed: %d.\n", WSAGetLastError());  
  136.         return FALSE;  
  137.     }  
  138.   
  139.     //associate listen socket with IOCP, let IOCP get ACCEPT_POSTED signal  
  140.     if (NULL == CreateIoCompletionPort((HANDLE)listenhandle->ssocket, completeport, (ULONG_PTR)listenhandle, 0))  
  141.     {  
  142.         IOCP_DBUG("CreateIoCompletionPort() failed: %d.\n", WSAGetLastError());  
  143.         return FALSE;  
  144.     }  
  145.   
  146.     // set reuse addr option  
  147.     int reuseaddr = 1;  
  148.     if (SOCKET_ERROR ==   
  149.         setsockopt(listenhandle->ssocket, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuseaddr, sizeof(int)))  
  150.     {  
  151.         IOCP_DBUG("listenhandle->ssocket: setsockopt(SO_REUSEADDR) failed %d\n", WSAGetLastError());  
  152.         closesocket(listenhandle->ssocket);  
  153.         return FALSE;  
  154.     }  
  155.   
  156.     //bind and listen  
  157.     SOCKADDR_IN ServerAddress;  
  158.     ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));  
  159.     ServerAddress.sin_family = AF_INET;  
  160.     ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);                                
  161.     ServerAddress.sin_port = htons(SERVERPORT);       
  162.   
  163.     if (SOCKET_ERROR ==   
  164.         bind(listenhandle->ssocket, (PSOCKADDR) &ServerAddress, sizeof(SOCKADDR_IN)))   
  165.     {  
  166.         IOCP_DBUG("listensocket: bind() failed, %d\n", WSAGetLastError());  
  167.         closesocket(listenhandle->ssocket);  
  168.         return FALSE;  
  169.     }  
  170.   
  171.     if (SOCKET_ERROR == listen(listenhandle->ssocket, BACKLOG))   
  172.     {  
  173.         IOCP_DBUG("listensocket: listen() failed %d\n", WSAGetLastError());  
  174.         closesocket(listenhandle->ssocket);  
  175.         return FALSE;  
  176.     }  
  177.   
  178.     return TRUE;  
  179. }  
  180.   
  181. //DeInitialize Socket  
  182. void IOCPServer::DeInitializeSocket(void)  
  183. {  
  184.     IOCP_DBUG("enter DeInitializeSocket\n");  
  185.   
  186.     closesocket(listenhandle->ssocket);  
  187.     GlobalFree(listenhandle);  
  188. }  
  189.   
  190. //get two function pointer  
  191. //this has a lower occupancy rate than use win lib directlly  
  192. BOOL IOCPServer::GetAcceptExFuction(void)  
  193. {  
  194.     IOCP_DBUG("enter GetAcceptExFuction\n");  
  195.   
  196.     SOCKET s = listenhandle->ssocket;  
  197.     if(INVALID_SOCKET == s)  
  198.     {  
  199.         IOCP_DBUG("WSASocket failed: %d.\n", WSAGetLastError());  
  200.         return FALSE;  
  201.     }  
  202.   
  203.     // get the fuction pointer for AcceptEx  
  204.     DWORD dwBytes = 0;   
  205.     GUID GuidAcceptEx = WSAID_ACCEPTEX;  
  206.     if (SOCKET_ERROR ==   
  207.         WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GUID),  
  208.         &lpfnacceptex, sizeof(LPFN_ACCEPTEX), &dwBytes, NULL, NULL))  
  209.     {  
  210.         IOCP_DBUG("WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, WSAID_ACCEPTEX) failed"  
  211.             ": %d.\n", WSAGetLastError());  
  212.         return FALSE;  
  213.     }  
  214.   
  215.     // get the fuction pointer for GetAcceptExSockAddrs  
  216.     GUID GuidAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;   
  217.     if (SOCKET_ERROR ==   
  218.         WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptExSockAddrs, sizeof(GUID),  
  219.         &lpfngetacceptexsockaddrs, sizeof(LPFN_GETACCEPTEXSOCKADDRS), &dwBytes, NULL, NULL))  
  220.     {  
  221.         IOCP_DBUG("WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, WSAID_ACCEPTEX) failed"  
  222.             ": %d.\n", WSAGetLastError());  
  223.         return FALSE;  
  224.     }  
  225.     return TRUE;  
  226. }  
  227.   
  228. //worker thread, usually not one,  
  229. //they are busy until exit.  
  230. //because these thread is asynchronous,  
  231. //so one socket can have more than one io.  
  232. DWORD WINAPI IOCPServer::WorkerThread(LPVOID lpParam)  
  233. {      
  234.     IOCP_DBUG("enter WorkerThread\n");  
  235.   
  236.     IOCPServer* temp = (IOCPServer*)lpParam;  
  237.     LPWSAOVERLAPPED lpoverlapped = NULL;  
  238.     SocketHandle *sockethandle = NULL;  
  239.     PerIOData *periodata = NULL;  
  240.     DWORD bytestransfered = 0;  
  241.   
  242.     while (TRUE)  
  243.     {  
  244.         // get the status of comleteport  
  245.         BOOL ret =   
  246.             GetQueuedCompletionStatus(temp->completeport, &bytestransfered,  
  247.             (PULONG_PTR)&sockethandle, &lpoverlapped, INFINITE);  
  248.   
  249.         if(FALSE == ret)    
  250.         {    
  251.             int sockerror = WSAGetLastError();  
  252.             IOCP_DBUG("GetQueuedCompletionStatus failed: %d.\n", sockerror);  
  253.             if(64 == sockerror) //host is down, client close abnormally  
  254.             {  
  255.                 if(temp->listenhandle != sockethandle)  
  256.                 {  
  257.                     IOCP_DBUG("client %s:%d is DOWN abnormally, we need delete the handle\n",   
  258.                         inet_ntoa(sockethandle->ssocketaddr.sin_addr),   
  259.                         ntohs(sockethandle->ssocketaddr.sin_port));  
  260.   
  261.                     // the destructor of sockethandle do recycle  
  262.                     delete sockethandle;  
  263.                 }else  
  264.                 {  
  265.                     IOCP_DBUG("something wrong in listenhandle, you need restart the process");  
  266.                 }  
  267.             }  
  268.             continue;    
  269.         }  
  270.           
  271.         // read the data in completeport  
  272.         periodata = CONTAINING_RECORD(lpoverlapped, PerIOData, overlapped);  
  273.         if(NULL == periodata)    
  274.         {    
  275.             IOCP_DBUG("CONTAINING_RECORD failed: %d.\n", WSAGetLastError());  
  276.             continue;    
  277.         }  
  278.   
  279.         // free io and handle, if client close the socket  
  280.         // all normal handle and io free here, if there is no mistake  
  281.         if((0 == bytestransfered) &&  
  282.             ( PerIOData::RECV_POSTED == periodata->operationtype  
  283.             || PerIOData::SEND_POSTED == periodata->operationtype))    
  284.         {    
  285.             IOCP_DBUG("client %s:%d has close the socket, we need close the connection\n",  
  286.                 inet_ntoa(sockethandle->ssocketaddr.sin_addr),   
  287.                 ntohs(sockethandle->ssocketaddr.sin_port));  
  288.   
  289.             // the destructor of sockethandle do recycle  
  290.             delete sockethandle;  
  291.             continue;    
  292.         }  
  293.           
  294.         switch(periodata->operationtype)    
  295.         {    
  296.             // AcceptEx    
  297.             case PerIOData::ACCEPT_POSTED:  
  298.             {  
  299.                 IOCP_DBUG("recv the ACCEPT_POSTED signal\n");  
  300.   
  301.                 if(FALSE == temp->DoAcceptEx(periodata))  
  302.                 {  
  303.                     // here sockethandle is listenhandle  
  304.                     // periodata is listeniodata  
  305.                     // MUST NOT release them here, let master thread do the bad thing  
  306.                     // JUST re post  
  307.                     temp->PostAcceptEx(periodata);  
  308.                 }  
  309.             }  
  310.             break;  
  311.   
  312.             // RECV  
  313.             case PerIOData::RECV_POSTED:  
  314.             {  
  315.                 IOCP_DBUG("recv the RECV_POSTED signal\n");  
  316.   
  317.                 // re post, if failed  
  318.                 // MUST not close socket here  
  319.                 // because one socket may have more io in use  
  320.                 if(FALSE == temp->DoRecv(sockethandle, periodata))  
  321.                 {  
  322.                     //MUST not force to release the periodata which hold overlapped struct  
  323.                     //re post  
  324.                     temp->PostRecv(sockethandle, periodata);  
  325.                 }  
  326.             }  
  327.             break;  
  328.   
  329.             // SEND  
  330.             case PerIOData::SEND_POSTED:  
  331.             {  
  332.                 IOCP_DBUG("recv the SEND_POSTED signal\n");  
  333.   
  334.                 // re post, if failed  
  335.                 // no need to construct new io, send use the same one with recv  
  336.                 if(FALSE == temp->DoSend(sockethandle, periodata))  
  337.                 {  
  338.                     // MUST not close socket here  
  339.                     // because one socket may have more io in use  
  340.                     //MUST not force to release the periodata which hold overlapped struct  
  341.                     temp->PostSend(sockethandle, periodata);  
  342.                 }  
  343.             }  
  344.             break;  
  345.   
  346.             case PerIOData::NULL_POSTED:  
  347.             default:  
  348.                 IOCP_DBUG("MUST exist a problem, the last error: %d.\n", WSAGetLastError());  
  349.             break;  
  350.         } //switch  
  351.     }//while  
  352.     return 0;  
  353. }  
  354.   
  355. // post AcceptEx  
  356. void IOCPServer::PostAcceptEx(PerIOData* listeniodata)  
  357. {  
  358.     IOCP_DBUG("enter PostAcceptEx\n");  
  359.   
  360.     if(NULL == listeniodata)  
  361.     {  
  362.         IOCP_DBUG("this is a fatal mistake, almost no way to reach here\n");  
  363.         return;  
  364.     }  
  365.   
  366.     // need new socket before AcceptEx  
  367.     listeniodata->acceptsocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);  
  368.     if(INVALID_SOCKET == listeniodata->acceptsocket)  
  369.     {  
  370.         IOCP_DBUG("WSASocket failed: %d.\n", WSAGetLastError());  
  371.         return;  
  372.     }  
  373.   
  374.     // set the connect delay for 3 sec  
  375.     // because AcceptEx will not return, if client just connect but send no bytes  
  376.     // win7 do not support SO_CONNECT_TIME to prevent DDOS  
  377.     // instead, dwReceiveDataLength should be ((sizeof(SOCKADDR_IN)+16)*2)  
  378.     // only get the local and remote addr  
  379.     /* 
  380.     INT timeout = 3; 
  381.     if (SOCKET_ERROR ==  
  382.         setsockopt(listeniodata->acceptsocket, SOL_SOCKET, SO_CONNECT_TIME, 
  383.             (const char*)&timeout, sizeof(timeout))) 
  384.     { 
  385.         IOCP_DBUG("setsockopt(SO_CONNECT_TIME) failed: %d.\n", WSAGetLastError()); 
  386.         return; 
  387.     } 
  388.     */  
  389.   
  390.     // do some clean  
  391.     // make operationtype signal be ACCEPT_POSTED  
  392.     listeniodata->ResetIO();  
  393.     listeniodata->operationtype = PerIOData::ACCEPT_POSTED;    
  394.     DWORD dwBytes = 0;   
  395.     if (FALSE ==   
  396.         lpfnacceptex(listenhandle->ssocket, listeniodata->acceptsocket, listeniodata->databuf.buf,   
  397.         0, sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16,  
  398.         &dwBytes, &listeniodata->overlapped))  
  399.     {  
  400.         if (WSA_IO_PENDING != WSAGetLastError())   
  401.         {  
  402.             IOCP_DBUG("AcceptEx() falied: %d.\n", WSAGetLastError());  
  403.             closesocket(listeniodata->acceptsocket);  
  404.             listeniodata->acceptsocket = -1;  
  405.             return;  
  406.         }  
  407.     }  
  408.   
  409.     return;  
  410. }  
  411.   
  412. BOOL IOCPServer::DoAcceptEx(PerIOData* listeniodata)  
  413. {  
  414.     /* SO_UPDATE_ACCEPT_CONTEXT is required for shutdown() to work */  
  415.     // win7 do not support SO_UPDATE_ACCEPT_CONTEXT  
  416.     /* 
  417.     if (SOCKET_ERROR ==  
  418.         setsockopt(listeniodata->acceptsocket, SOL_SOCKET,  
  419.             SO_UPDATE_ACCEPT_CONTEXT, (char *)&listenhandle->ssocket, sizeof(SOCKET))) 
  420.     { 
  421.         IOCP_DBUG("setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed: %d.\n", WSAGetLastError()); 
  422.         return FALSE; 
  423.     } 
  424.     */  
  425.   
  426.     //get the addr for client which connect me  
  427.     //for AcceptEx, Should use GetAcceptExSockAddrs  
  428.     SOCKADDR_IN *lplocalsockaddr = NULL;  
  429.     SOCKADDR_IN *lpremotesockaddr = NULL;  
  430.     int localsockaddrlen = sizeof(SOCKADDR_IN);  
  431.     int remotesockaddrlen = sizeof(SOCKADDR_IN);  
  432.   
  433.     lpfngetacceptexsockaddrs(listeniodata->databuf.buf, 0,  
  434.         sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16,  
  435.         (LPSOCKADDR*)&lplocalsockaddr, &localsockaddrlen,  
  436.         (LPSOCKADDR*)&lpremotesockaddr, &remotesockaddrlen);  
  437.   
  438.     SocketHandle *connecthandle = new SocketHandle();  
  439.     connecthandle->ssocket = listeniodata->acceptsocket;  
  440.     memcpy(&connecthandle->ssocketaddr, lpremotesockaddr, sizeof(SOCKADDR_IN));  
  441.     if (NULL == CreateIoCompletionPort((HANDLE)connecthandle->ssocket, completeport, (ULONG_PTR)connecthandle, 0))  
  442.     {  
  443.         IOCP_DBUG("CreateIoCompletionPort() failed: %d.\n", WSAGetLastError());  
  444.         return FALSE;  
  445.     }  
  446.   
  447.     // usually we need to recv something, so post recv   
  448.     // we new connecthandle and connectiodata here first  
  449.       
  450.     PerIOData *connectiodata = connecthandle->GetNewIOData();  
  451.     PostRecv(connecthandle, connectiodata);  
  452.   
  453.     //post new one  
  454.     PerIOData *newlisteniodata = listenhandle->GetNewIOData();  
  455.     PostAcceptEx(newlisteniodata);  
  456.   
  457.     return TRUE;  
  458. }  
  459.   
  460. // post recv  
  461. void IOCPServer::PostRecv(SocketHandle* connecthandle, PerIOData* connectiodata)  
  462. {  
  463.     IOCP_DBUG("enter PostRecv\n");  
  464.   
  465.     if(NULL == connecthandle || NULL == connectiodata)  
  466.     {  
  467.         IOCP_DBUG("this is a fatal mistake, almost no way to reach here\n");  
  468.         return;  
  469.     }  
  470.   
  471.     // do some clean  
  472.     // make operationtype signal be RECV_POSTED  
  473.     DWORD flags = 0;  
  474.     DWORD recvbytes = 0;  
  475.     connectiodata->ResetIO();  
  476.     connectiodata->operationtype = PerIOData::RECV_POSTED;  
  477.   
  478.     //recv the message  
  479.     int nbytes = WSARecv(connecthandle->ssocket, &connectiodata->databuf, 1,   
  480.         &recvbytes, &flags, &connectiodata->overlapped, NULL );  
  481.     if ((SOCKET_ERROR == nbytes) && (WSA_IO_PENDING != WSAGetLastError()))  
  482.     {  
  483.         IOCP_DBUG("WSARecv() failed: %d.\n", WSAGetLastError());  
  484.         return;  
  485.     }  
  486.   
  487.     return;  
  488. }  
  489.   
  490. // do after recv  
  491. BOOL IOCPServer::DoRecv(SocketHandle* connecthandle, PerIOData* connectiodata)  
  492. {  
  493.     IOCP_DBUG("enter DoRecv\n");  
  494.   
  495.     if(NULL == connecthandle || NULL == connectiodata)  
  496.     {  
  497.         IOCP_DBUG("this is a fatal mistake, almost no way to reach here\n");  
  498.         return FALSE;  
  499.     }  
  500.   
  501.     IOCP_DBUG("receive from %s:%d, message: %s.\n",   
  502.         inet_ntoa(connecthandle->ssocketaddr.sin_addr),   
  503.         ntohs(connecthandle->ssocketaddr.sin_port),connectiodata->databuf.buf);  
  504.   
  505.     //TODO  
  506.     //process the massage  
  507.       
  508.     // usually we need send after recv  
  509.     PostSend(connecthandle, connectiodata);  
  510.   
  511.     // handle recv signal complete, post new one  
  512.     //MUST not force to release the periodata which hold overlapped struct  
  513.     //new one periodata  
  514.     PerIOData *newconnectiodata = connecthandle->GetNewIOData();  
  515.     PostRecv(connecthandle, newconnectiodata);  
  516.   
  517.     return TRUE;  
  518. }  
  519.   
  520. //post send  
  521. void IOCPServer::PostSend(SocketHandle* connecthandle, PerIOData* connectiodata)  
  522. {  
  523.     IOCP_DBUG("enter PostSend\n");  
  524.   
  525.     if(NULL == connecthandle || NULL == connectiodata)  
  526.     {  
  527.         IOCP_DBUG("this is a fatal mistake, almost no way to reach here\n");  
  528.         return;  
  529.     }  
  530.   
  531.     // do some clean  
  532.     // make operationtype signal be SEND_POSTED  
  533.     DWORD flags = 0;  
  534.     DWORD sendbytes = 0;  
  535.     connectiodata->ResetIO();  
  536.     connectiodata->operationtype = PerIOData::SEND_POSTED;  
  537.   
  538.     //TODO  
  539.     //build the message  
  540.     char *temp = "just for test";  
  541.     strncpy_s(connectiodata->databuf.buf, connectiodata->databuf.len, temp, strlen(temp));  
  542.     *(connectiodata->databuf.buf + strlen(temp)) = 0;  
  543.   
  544.     //send the message  
  545.     int nbytes = WSASend(connecthandle->ssocket, &connectiodata->databuf, 1,  
  546.         &sendbytes, flags, &connectiodata->overlapped, NULL );  
  547.     if ((SOCKET_ERROR == nbytes) && (WSA_IO_PENDING != WSAGetLastError()))  
  548.     {  
  549.         IOCP_DBUG("WSASend() failed: %d.\n", WSAGetLastError());  
  550.         return;  
  551.     }  
  552.   
  553.     return;  
  554. }  
  555.   
  556. //DO after send  
  557. BOOL IOCPServer::DoSend(SocketHandle* connecthandle, PerIOData* connectiodata)  
  558. {  
  559.     IOCP_DBUG("enter DoSend\n");  
  560.   
  561.     if(NULL == connecthandle || NULL == connectiodata)  
  562.     {  
  563.         IOCP_DBUG("this is a fatal mistake, almost no way to reach here\n");  
  564.         return FALSE;  
  565.     }  
  566.   
  567.     IOCP_DBUG("send to %s:%d, message: %s.\n",   
  568.         inet_ntoa(connecthandle->ssocketaddr.sin_addr),   
  569.         ntohs(connecthandle->ssocketaddr.sin_port),connectiodata->databuf.buf );  
  570.   
  571.         //just do it in the end of one io here  
  572.         //this is dangerous in somewhere else  
  573.         connecthandle->RemoveIOData(connectiodata);  
  574.     return TRUE;  
  575. }  

HttpServer\SocketHandle.h:
[cpp]  view plain copy
  1. #pragma once  
  2.   
  3. #include "stdafx.h"  
  4. #include "PerIOData.h"  
  5.   
  6.   
  7. class SocketHandle  
  8. {  
  9. public:  
  10.     SocketHandle(void);  
  11.     virtual ~SocketHandle(void);  
  12.   
  13. public:  
  14.     //the socket and its addr the handle hold  
  15.     SOCKET ssocket;  
  16.     SOCKADDR_IN ssocketaddr;  
  17.   
  18.     //the io list belong to me  
  19.     list<PerIOData*> sockiodata;  
  20.   
  21. public:  
  22.     PerIOData* GetNewIOData(void);  
  23.       
  24.     // check the cpp source file for the reason  
  25.     void RemoveIOData(PerIOData* speriodata);  
  26. };  

HttpServer\SocketHandle.cpp:
[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #include "SocketHandle.h"  
  3.   
  4. SocketHandle::SocketHandle(void)  
  5. {  
  6.     ssocket = INVALID_SOCKET;  
  7.     ZeroMemory(&ssocketaddr, sizeof(ssocketaddr));   
  8. }  
  9.   
  10. SocketHandle::~SocketHandle(void)  
  11. {  
  12.     //close the socket  
  13.     if( ssocket!=INVALID_SOCKET )  
  14.     {  
  15.         closesocket(ssocket);  
  16.         ssocket = INVALID_SOCKET;  
  17.     }  
  18.   
  19.     // release the list  
  20.     list <PerIOData*>::iterator myIterator = sockiodata.begin();  
  21.     for ( ; myIterator != sockiodata.end(); myIterator++)  
  22.     {  
  23.         delete *myIterator;  
  24.         *myIterator = NULL;  
  25.     }  
  26.     sockiodata.clear();  
  27. }  
  28.   
  29. // new iodata and add it in list  
  30. //return new io which will be used  
  31. PerIOData* SocketHandle::GetNewIOData(void)  
  32. {  
  33.     PerIOData* speriodata = new PerIOData();  
  34.     sockiodata.push_back(speriodata);  
  35.     return speriodata;  
  36. }  
  37.   
  38. //delete the io transfered and erase it form list  
  39. //MUST not force to release the periodata which hold overlapped struct  
  40. //let the deconstruct func do the recycle  
  41. //we need it just in the end of one io's life.  
  42. //just for case, some ugly client keep sending message to me with no interval  
  43.   
  44. void SocketHandle::RemoveIOData(PerIOData* speriodata)  
  45. {  
  46.     if(NULL == speriodata)  
  47.     {  
  48.         IOCP_DBUG("you just transfer a NULL pointer\n");  
  49.         return;  
  50.     }  
  51.   
  52.     list <PerIOData*>::iterator myIterator = sockiodata.begin();  
  53.     for ( ; myIterator != sockiodata.end(); myIterator++)  
  54.     {  
  55.         if(*myIterator == speriodata)  
  56.         {  
  57.             delete *myIterator;  
  58.             *myIterator = NULL;  
  59.             sockiodata.erase(myIterator);                 
  60.             break;  
  61.         }  
  62.     }  
  63. }  

HttpServer\PerIOData.h:
[cpp]  view plain copy
  1. #pragma once  
  2.   
  3. #include "stdafx.h"  
  4.   
  5. // per io data for socket handle  
  6. // one socket can has more than one io  
  7. //WSAOVERLAPPED struct must be the first in memory of this class  
  8. // so this class and its base class MUST not have virtual func  
  9. class PerIOData  
  10. {  
  11. public:  
  12.     PerIOData(void);  
  13.     ~PerIOData(void);  
  14.   
  15. public:  
  16.     // overlapped struct, must be in the first place of class memory  
  17.     WSAOVERLAPPED overlapped;  
  18.   
  19.     //just for ACCEPT_POSTED signal  
  20.     SOCKET acceptsocket;  
  21.   
  22.     //the buffer of me  
  23.     WSABUF databuf;  
  24.   
  25.     //the signal belong to me  
  26.     DWORD operationtype;  
  27.   
  28. public:  
  29.     void ResetIO(void);  
  30.   
  31. // this four signal is the public signal for other class, IOCPServer etc..  
  32. //      ACCEPT_POSTED - signal for accept  
  33. //      RECV_POSTED   - signal for recv  
  34. //      SEND_POSTED   - signal for send  
  35. //      NULL_POSTED   - terminal signal, no meaning  
  36. //  
  37. public:  
  38.     typedef enum    
  39.     {    
  40.         ACCEPT_POSTED,  
  41.         RECV_POSTED,  
  42.         SEND_POSTED,  
  43.         NULL_POSTED  
  44.     }OPERATION_TYPE;  
  45. };  

HttpServer\PerIOData.cpp:
[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #include "PerIOData.h"  
  3.   
  4. // default buffer size  
  5. #define BUFFERSIZE 1024  
  6.   
  7. //new buffer and Initialize  
  8. PerIOData::PerIOData(void):  
  9.     operationtype(NULL_POSTED),  
  10.     acceptsocket(INVALID_SOCKET)  
  11. {  
  12.     ZeroMemory(&overlapped, sizeof(overlapped));    
  13.     databuf.buf = new char[BUFFERSIZE];  
  14.     databuf.len = BUFFERSIZE;  
  15.     ZeroMemory(databuf.buf, databuf.len);  
  16. }  
  17.   
  18. //delete the buffer  
  19. PerIOData::~PerIOData(void)  
  20. {  
  21.     if(NULL != databuf.buf)  
  22.     {  
  23.         delete [] databuf.buf;  
  24.         databuf.buf = NULL;  
  25.     }  
  26. }  
  27.   
  28. //reset buffer and overlapped of io  
  29. void PerIOData::ResetIO(void)  
  30. {  
  31.     databuf.len = BUFFERSIZE;  
  32.     ZeroMemory(databuf.buf, databuf.len);  
  33.     ZeroMemory(&overlapped, sizeof(WSAOVERLAPPED));  
  34. }  
  35. 本文转自:http://blog.csdn.net/crasyangel/article/details/40458123
最近有项目要做一个高性能网络服务器,决定下功夫搞定完成端口IOCP),最终花了一个星期终于把它弄清楚了,并用C++写了一个版本,效率很不错。 但,从项目的总体需求来考虑,最终决定上.net平台,因此又花了一天一夜弄出了一个C#版,在这与大家分享。 一些心得体会: 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值