IOCP+WinSock2新函數打造高性能SOCKET池(轉)

在前一篇文章《WinSock2編程之打造完整的SOCKET池 》中,介紹了WinSock2的一些新函數,並重點詳細介紹了什麼是SOCKET池,有了這個概念,現在就接着展開更深入的討論。

首先這裏要重點重申一下就是,SOCKET池主要指的是使用面向連接的協議的情況下,最常用的就是需要管理大量的TCP連接的時候。常見的就是Web服務器、FTP服務器等。

下面就分步驟的詳細介紹如何最終實現SOCKET池。

 

一、WinSock2環境的初始化:

 

要使用WinSock2就需要先初始化Socket2.0的環境,不廢話,上代碼:

WSADATA wd = {0};

int iError = WSAStartup(MAKEWORD(2,0), &wd);

if( 0 != iError )

{//出現錯誤,最好跟蹤看下錯誤碼是多少

       return FALSE;

}

if ( LOBYTE(lpwsaData->wVersion) != 2 )

{//非2.0以上環境 退出了事 可能是可憐的WinCE系統

       WSACleanup();

       return FALSE;

}

最後再不使用WinSock之後都要記得調用一下WSACleanup()這個函數;

 

二、裝載WinSock2函數:

 

上一篇文章中給出了一個裝載WinSock2函數的類,這裏分解介紹下裝載的具體過程,要提醒的就是,凡是類裏面演示了動態裝載的函數,最好都像那樣動態載入,然後再調用。以免出現上網發帖跪求高手賜教爲什麼AcceptEx函數無法編譯通過等問題。看完這篇文章詳細你不會再去發帖找答案了,呵呵呵,好了,上代碼:

//定義一個好用的載入函數 摘自CGRSMsSockFun 類

BOOL LoadWSAFun(GUID&funGuid,void*& pFun)

{//本函數利用參數返回函數指針

       DWORD dwBytes = 0;

       pFun = NULL;

       //隨便創建一個SOCKET供WSAIoctl使用 並不一定要像下面這樣創建

       SOCKET skTemp = ::WSASocket(AF_INET,

                     SOCK_STREAM, IPPROTO_TCP, NULL,

                     0, WSA_FLAG_OVERLAPPED);

       if(INVALID_SOCKET == skTemp)

       {//通常表示沒有正常的初始化WinSock環境

              return FALSE;

       }

       ::WSAIoctl(skTemp, SIO_GET_EXTENSION_FUNCTION_POINTER,

                     &funGuid,sizeof(funGuid),&pFun,

                     sizeof(pFun), &dwBytes, NULL,NULL);

       ::closesocket(skTemp);

       return NULL != pFun;

}

//演示如何動態載入AcceptEx函數

......

LPFN_ACCEPTEX pfnAcceptEx; //首先聲明函數指針

GUID GuidAcceptEx = WSAID_ACCEPTEX;

LoadWSAFun(GuidAcceptEx,(void*&)pfnAcceptEx); //載入

......

//使用豐富的參數調用

......

pfnAcceptEx(sListenSocket,sAcceptSocket,lpOutputBuffer,

              dwReceiveDataLength,dwLocalAddressLength,dwRemoteAddressLength,

lpdwBytesReceived,lpOverlapped);

              //或者:

              SOCKET skAccept = ::WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,

                                                 NULL, 0,WSA_FLAG_OVERLAPPED);

              PVOID pBuf = new BYTE[sizeof(sockaddr_in) + 16];

              pfnAcceptEx(skServer, skAccept,pBuf,

0,//將接收緩衝置爲0,令AcceptEx直接返回,防止拒絕服務攻擊

                     sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, NULL,

                                                 (LPOVERLAPPED)pAcceptOL);

......

       以上是一個簡單的演示,如何動態載入一個WinSock2擴展函數,並調用之,其它函數的詳細例子可以看前一篇文章中CGRSMsSockFun類的實現部分。如果使用CGRSMsSockFun 類的話當然更簡單,像下面這樣調用即可:

              CGRSMsSockFun MsSockFun;

              MsSockFun.AcceptEx(skServer, skAccept,pBuf,

0,//將接收緩衝置爲0,令AcceptEx直接返回,防止拒絕服務攻擊

                     sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, NULL,

                     (LPOVERLAPPED)pAcceptOL);

如果要使用這個類,那麼需要一些修改,主要是異常處理部分,自己註釋掉,或者用其它異常代替掉即可,這個對於有基礎的讀者來說不是什麼難事。

 

三、定義OVERLAPPED結構:

 

要想“IOCP”就要自定義OVERLAPPED,這是徹底玩轉IOCP的不二法門,可以這麼說:“江湖上有多少種自定義的OVERLAPPED派生結構體,就有多少種IOCP的封裝!”

OVERLAPPED本身是Windows IOCP機制內部需要的一個結構體,主要用於記錄每個IO操作的“完成狀態”,其內容對於調用者來說是沒有意義的,但是很多時候我們把它當做一個“火車頭”,因爲它可以方便的把每個IO操作的相關數據簡單的“從調用處運輸到完成回調函數中”,這是一個非常有用的特性,哪麼如何讓這個火車頭髮揮運輸的作用呢?其實很簡單:讓它成爲一個自定義的更大結構體的第一個成員。然後用強制類型轉換,將自定義的結構體轉換成OVERLAPPED指針即可。當然不一定非要是新結構體的第一個成員,也可以是任何第n個成員,這時使用VC頭文件中預定義的一個宏CONTAINING_RECORD再反轉回來即可。

說到這裏一些C++基礎差一點的讀者估計已經很頭暈了,更不知道我再說什麼,那麼我就將好人做到底吧,來解釋下這個來龍去脈。

首先就以我們將要使用的AcceptEx函數爲例子看看它的原型吧(知道孫悟空的火眼金睛用來幹嘛的嗎?就是用來看原型的,哈哈哈):

BOOL AcceptEx(

  __in          SOCKET sListenSocket,

  __in          SOCKET sAcceptSocket,

  __in          PVOID lpOutputBuffer,

  __in          DWORD dwReceiveDataLength,

  __in          DWORD dwLocalAddressLength,

  __in          DWORD dwRemoteAddressLength,

  __out         LPDWORD lpdwBytesReceived,

  __in          LPOVERLAPPED lpOverlapped

);

注意最後一個參數,是一個OVERLAPPED結構體的指針(LP的意思是Long Pointer,即指向32位地址長指針,注意不是“老婆”拼音的縮寫),本身這個參數的意思就是分配一塊OVERLAPPED大小的內存,在IOCP調用方式下傳遞給AcceptEx函數用,調用者不用去關心裏面的任何內容,而在完成過程中(很多時候是另一個線程中的事情了),通常調用GetQueuedCompletionStatus函數後,會再次得到這個指針,接着讓我們也看看它的原型:

BOOL WINAPI GetQueuedCompletionStatus(

  __in          HANDLE CompletionPort,

  __out         LPDWORD lpNumberOfBytes,

  __out         PULONG_PTR lpCompletionKey,

  __out         LPOVERLAPPED* lpOverlapped,

  __in          DWORD dwMilliseconds

);

注意這裏的LPOVERLAPPED多了一個*變成了指針的指針,並且前面的說明很清楚Out!很明白了吧,不明白就真的Out了。這裏就可以重新得到調用AcceptEx傳入的LPOVERLAPPED指針,也就是得到了這個“火車頭”,因爲只是一個指針,並沒有詳細的限定能有多大,所以可以在火車頭的後面放很多東西。

再仔細觀察GetQueuedCompletionStatus函數的參數,會發現,這時只能知道一個IO操作結束了,但是究竟是哪個操作結束了,或者是哪個SOCKET句柄上的操作結束了,並沒有辦法知道。通常這個信息非常重要,因爲只有在IO操作實際完成之後才能釋放發送或接收等操作的緩衝區。

這些信息可以定義成如下的一個擴展OVERLAPPED結構:

struct MYOVERLAPPED

{

    OVERLAPPED m_ol;          

    int                    m_iOpType;      

//操作類型 0=AcceptEx 1=DisconnectEx       2=ConnectEx 3=WSARecv等等

    SOCKET          m_skServer;       //服務端SOCKET

    SOCKET         m_skClient;        //客戶端SOCKET

    LPVOID           m_pBuf;            //本次IO操作的緩衝指針

    ......                                           //其它需要的信息

};

使用時:

MYOVERLAPPED* pMyOL = new MYOVERLAPPED;

ZeroMemory(pMyOL,sizeof(MYOVERLAPPED));

pMyOL->m_iOpType = 0;        //AcceptEx操作

pMyOL->m_skServer = skServer;

pMyOL->m_skClient = skClient;

BYTE* pBuf = new BYTE[256];//一個緩衝

.................. //朝緩衝中寫入東西

pMyOL->m_pBuf = pBuf;

...............//其它的代碼

AcceptEx(skServer, skClient,pBuf,

0,//將接收緩衝置爲0,令AcceptEx直接返回                     

256,256,NULL,(LPOVERLAPPED)pMyOL));//注意最後這個強制類型轉換

 

       在完成過程回調線程函數中,這樣使用:

 

UINT CALLBACK Client_IOCPThread(void* pParam)

       {//IOCP線程函數

              .....................

              DWORD dwBytesTrans = 0;

              DWORD dwPerData = 0;

              LPOVERLAPPED lpOverlapped = NULL;

             

              while(1)

              {//又見死循環 呵呵呵

                     BOOL bRet = GetQueuedCompletionStatus(

                            pThis->m_IOCP,&dwBytesTrans,&dwPerData,

                            &lpOverlapped,INFINITE);

                     if( NULL == lpOverlapped )

                     {//沒有真正的完成

                            SleepEx(20,TRUE);//故意置成可警告狀態

                            continue;

                     }

                     //找回“火車頭”以及後面的所有東西

                     MYOVERLAPPED*  pOL = CONTAINING_RECORD(lpOverlapped

, MYOVERLAPPED, m_ol);

                     switch(pOL->m_iOpType)

{

case 0: //AcceptEx結束

{//有鏈接進來了 SOCKET句柄就是 pMyOL->m_skClient

   

}

break;

............................

}

........................

} //end while

........................

       }//end fun

 

至此,關於這個“火車頭”如何使用,應該是看明白了,其實就是從函數傳入,又由函數返回。只不過其間可能已經轉換了線程環境,是不同的線程了。

這裏再補充一個AcceptEx容易被遺漏的一個細節問題,那就是在AcceptEx完成返回之後,如下在那個連入的客戶端SOCKET上調用一下:

       int nRet = ::setsockopt(

              pOL->m_skClient,SOL_SOCKET,SO_UPDATE_ACCEPT_CONTEXT,

              (char *)&pOL->m_skServer,sizeof(SOCKET));

這樣纔可以繼續在這個代表客戶端連接的pOL->m_skClient上繼續調用WSARecv和WSASend。

另外,在AcceptEx完成之後,通常可以用:

LPSOCKADDR addrHost = NULL;      //服務端地址

LPSOCKADDR addrClient = NULL;     //客戶端地址

int lenHost = 0;

int lenClient = 0;

GetAcceptExSockaddrs(

       pOL->m_pBuf,0,sizeof(sockaddr_in) + 16,sizeof(sockaddr_in) + 16,

       (LPSOCKADDR*) &addrHost,&lenHost,(LPSOCKADDR*) &addrClient,&lenClient);

這樣來得到連入的客戶端地址,以及連入的服務端地址,通常這個地址可以和這個客戶端的SOCKET綁定在一起用map或hash表保存,方便查詢,就不用再調用那個getpeername得到客戶端的地址了。要注意的是GetAcceptExSockaddrs也是一個WinSock2擴展函數,專門配合AcceptEx使用的,需要像AcceptEx那樣動態載入一下,然後再調用,詳情請見前一篇文章中的CGRSMsSockFun類。

至此AcceptEx算討論完整了,OVERLAPPED的派生定義也講完了,讓我們繼續下一步。

 

四、編寫線程池回調函數:

 

在討論擴展定義OVERLAPPED結構體時,給出了非線程池版的線程函數的大概框架,也就是傳統IOCP使用的自建線程使用方式,這種方式要自己創建完成端口句柄,自己將SOCKET句柄綁定到完成端口,這裏就不在贅述,主要介紹下調用BindIoCompletionCallback函數時,應如何編寫這個線程池的回調函數,其實它與前面那個線程函數是很類似的。先來看看回調函數長個什麼樣子:

VOID CALLBACK FileIOCompletionRoutine(

  [in]                 DWORD dwErrorCode,

  [in]                 DWORD dwNumberOfBytesTransfered,

  [in]                 LPOVERLAPPED lpOverlapped

);

第一個參數就是一個錯誤碼,如果是0恭喜你,操作一切ok,如果有錯也不要慌張,前一篇文章中已經介紹瞭如何翻譯和看懂這個錯誤碼。照着做就是了。

第二個參數就是說這次IO操作一共完成了多少字節的數據傳輸任務,這個字段有個特殊含義,如果你發現一個Recv操作結束了,並且這個參數爲0,那麼就是說,客戶端斷開了連接(注意針對的是TCP方式,整個SOCKET池就是爲TCP方式設計的)。如果這個情況發生了,在SOCKET池中就該回收這個SOCKET句柄。

第三個參數現在不用多說了,立刻就知道怎麼用它了。跟剛纔調用GetQueuedCompletionStatus函數得到的指針是一個含義。

下面就來看一個實現這個回調的例子:

VOID CALLBACK MyIOCPThread(DWORD dwErrorCode

,DWORD dwBytesTrans,LPOVERLAPPED lpOverlapped)

       {//IOCP回調函數

              .....................

              if( NULL == lpOverlapped )

              {//沒有真正的完成

                     SleepEx(20,TRUE);//故意置成可警告狀態

                     return;

              }

              //找回“火車頭”以及後面的所有東西

              MYOVERLAPPED*  pOL = CONTAINING_RECORD(lpOverlapped

, MYOVERLAPPED, m_ol);

              switch(pOL->m_iOpType)

{

case 0: //AcceptEx結束

{//有鏈接進來了 SOCKET句柄就是 pMyOL->m_skClient

   

}

break;

............................

}

........................

       }//end fun

 

看起來很簡單吧?好像少了什麼?對了那個該死的循環,這裏不用了,因爲這個是由線程池回調的一個函數而已,線程的活動狀態完全由系統內部控制,只管認爲只要有IO操作完成了,此函數就會被調用。這裏關注的焦點就完全的放到了完成之後的操作上,而什麼線程啊,完成端口句柄啊什麼的就都不需要了(甚至可以忘記)。

這裏要注意一個問題,正如在《IOCP編程之“雙節棍”》中提到的,這個函數執行時間不要過長,否則會出現掉線啊,連接不進來啊等等奇怪的事情。

另一個要注意的問題就是,這個函數最好套上結構化異常處理,儘可能的多攔截和處理異常,防止系統線程池的線程因爲你糟糕的回調函數而壯烈犧牲,如果加入了併發控制,還要注意防止死鎖,不然你的服務器會“死”的很難看。

理論上來說,你儘可以把這個函數看做一個與線程池函數等價的函數,只是他要儘可能的“短”(指執行時間)而緊湊(結構清晰少出錯)。

最後,回調函數定義好了,就可以調用BindIoCompletionCallback函數,將一個SOCKET句柄丟進完成端口的線程池了:

BindIoCompletionCallback((HANDLE)skClient,MyIOCPThread,0);

注意最後一個參數到目前爲止,你就傳入0吧。這個函數的神奇就是不見了CreateIoCompletionPort的調用,不見了CreateThread的調用,不見了GetQueuedCompletionStatus等等的調用,省去了n多繁瑣且容易出錯的步驟,一個函數就全部搞定了。

 

五、服務端調用:

 

以上的所有步驟在完全理解後,最終讓我們看看SOCKET池如何實現之。

1、按照傳統,要先監聽到某個IP的指定端口上:

SOCKADDR_IN    saServer = {0};

//創建監聽Socket

SOCKET skServer = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP

, NULL, 0, WSA_FLAG_OVERLAPPED);

//把監聽SOCKET扔進線程池,這個可以省略               ::BindIoCompletionCallback((HANDLE)skServer,MyIOCPThread, 0);

//必要時打開SO_REUSEADDR屬性,重新綁定到這個監聽地址

BOOL   bReuse=TRUE;                  ::setsockopt(m_skServer,SOL_SOCKET,SO_REUSEADDR

,(LPCSTR)&bReuse,sizeof(BOOL));

saServer.sin_family = AF_INET;

saServer.sin_addr.s_addr = INADDR_ANY;

// INADDR_ANY這個值的魅力是監聽所有本地IP的相同端口

saServer.sin_port = htons(80);      //用80得永生

::bind(skServer,(LPSOCKADDR)&saServer,sizeof(SOCKADDR_IN));

//監聽,隊列長爲默認最大連接SOMAXCONN

listen(skServer, SOMAXCONN);

 

2、就是發出一大堆的AcceptEx調用:

for(UINT i = 0; i < 1000; i++)

{//調用1000次

//創建與客戶端通訊的SOCKET,注意SOCKET的創建方式

skAccept = ::WSASocket(AF_INET,

                                              SOCK_STREAM,

                                              IPPROTO_TCP,

                                              NULL,

                                              0,

                                              WSA_FLAG_OVERLAPPED);

//2011-07-28:以上爲原文,下面爲改寫後的代碼

skClient = ::WSASocket(AF_INET,SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED );

//丟進線程池中

BindIoCompletionCallback((HANDLE)skAccept ,MyIOCPThread,0);

//2011-07-28:以上爲原文寫法,下面爲修改後代碼,主要爲了更正變量名,方便大家理解

BindIoCompletionCallback((HANDLE)skClient ,MyIOCPThread,0);

//創建一個自定義的OVERLAPPED擴展結構,使用IOCP方式調用

pMyOL= new MYOVERLAPPED;

pMyOL->m_iOpType = 0;        //AcceptEx操作

pMyOL->m_skServer = skServer;

pMyOL->m_skClient = skClient;

BYTE* pBuf = new BYTE[256];//一個緩衝

ZeroMemory(pBuf,256*sizeof(BYTE));

pMyOL->m_pBuf = pBuf;

//發出AcceptEx調用

//注意將AcceptEx函數接收連接數據緩衝的大小設定成了0

//這將導致此函數立即返回,雖然與不設定成0的方式而言,

//這導致了一個較低下的效率,但是這樣提高了安全性

//所以這種效率犧牲是必須的

//=================================================================================

//2011-07-28日修改了下面的代碼 把原來的第二個參數skAccept 改爲 skClient 爲方便大家閱讀和理解

AcceptEx(skServer, skClient,pBuf,

    0,//將接收緩衝置爲0,令AcceptEx直接返回,防止拒絕服務攻擊

    256,256,NULL,(LPOVERLAPPED)pMyOL);

 

}

這樣就有1000個AcceptEx在提前等着客戶端的連接了,即使1000個併發連接也不怕了,當然如果再BT點那麼就放1w個,什麼你要放2w個?那就要看看你的這個IP段的端口還夠不夠了,還有你的系統內存夠不夠用。一定要注意同一個IP地址上理論上端口最大值是65535,也就是6w多個,這個要合理的分派,如果併發管理超過6w個以上的連接時,怎麼辦呢?那就再插塊網卡租個新的IP,然後再朝那個IP端綁定並監聽即可。因爲使用了INADDR_ANY,所以一監聽就是所有本地IP的相同端口,如果服務器的IP有內外網之分,爲了安全和區別起見可以明確指定監聽哪個IP,單IP時就要注意本IP空閒端口的數量問題了。

 

3、AcceptEx返回後,也就是線程函數中,判定是AcceptEx操作返回後,首先需要的調用就是:

GetAcceptExSockaddrs(pBuf,0,sizeof(sockaddr_in) + 16,

       sizeof(sockaddr_in) + 16,(LPSOCKADDR*) &addrHost,&lenHost,

       (LPSOCKADDR*) &addrClient,&lenClient);

int nRet = ::setsockopt(pOL->m_skClient, SOL_SOCKET,

       SO_UPDATE_ACCEPT_CONTEXT,(char *)&m_skServer,sizeof(m_skServer));

之後就可以WSASend或者WSARecv了。

       4、這些調用完後,就可以在這個m_skClient上收發數據了,如果收發數據結束或者IO錯誤,那麼就回收SOCKET進入SOCKET池:

       DisconnectEx(m_skClient,&pData->m_ol, TF_REUSE_SOCKET, 0);

       5、當DisconnectEx函數完成操作之後,在回調的線程函數中,像下面這樣重新讓這個SOCKET進入監聽狀態,等待下一個用戶連接進來,至此組建SOCKET池的目的就真正達到了:

//創建一個自定義的OVERLAPPED擴展結構,使用IOCP方式調用

pMyOL= new MYOVERLAPPED;

pMyOL->m_iOpType = 0;        //AcceptEx操作

pMyOL->m_skServer = skServer;

pMyOL->m_skClient = skClient;

BYTE* pBuf = new BYTE[256];//一個緩衝

ZeroMemory(pBuf,256*sizeof(BYTE));

pMyOL->m_pBuf = pBuf;

AcceptEx(skServer, skClient,pBuf , 0,256,256,NULL,

    (LPOVERLAPPED)pMyOL);

//注意在這個SOCKET被重新利用後,後面的再次捆綁到完成端口的操作會返回一個已設置//的錯誤,這個錯誤直接被忽略即可

         ::BindIoCompletionCallback((HANDLE)skClient,Server_IOCPThread, 0);

       至此服務端的線程池就算搭建完成了,這個SOCKET池也就是圍繞AcceptEx和DisconnectEx展開的,而創建操作就全部都在服務啓動的瞬間完成,一次性投遞一定數量的SOCKET進入SOCKET池即可,這個數量也就是通常所說的最大併發連接數,你喜歡多少就設置多少吧,如果連接多數量就大些,如果IO操作多,連接斷開請求不多就少點,剩下就是調試了。

 

六、客戶端調用:

 

1、  主要是圍繞利用ConnectEx開始調用:

SOCKET skConnect = ::WSASocket(AF_INET,SOCK_STREAM,IPPROTO_IP,

                            NULL,0,WSA_FLAG_OVERLAPPED);

//把SOCKET扔進IOCP

BindIoCompletionCallback((HANDLE)skConnect,MyIOCPThread,0);

//本地隨便綁個端口

SOCKADDR_IN LocalIP = {};

LocalIP.sin_family = AF_INET;

LocalIP.sin_addr.s_addr = INADDR_ANY;

LocalIP.sin_port = htons( (short)0 );    //使用0讓系統自動分配

int result =::bind(skConnect,(LPSOCKADDR)&LocalIP,sizeof(SOCKADDR_IN));

pMyOL= new MYOVERLAPPED;

pMyOL->m_iOpType = 2;            //ConnectEx操作

pMyOL->m_skServer = NULL;    //沒有服務端的SOCKET

pMyOL->m_skClient = skConnect;

ConnectEx(skConnect,(const sockaddr*)pRemoteAddr,sizeof(SOCKADDR_IN),

       NULL,0,NULL,(LPOVERLAPPED)pOL) )

如果高興就可以把上面的過程放到循環裏面去,pRemoteAddr就是遠程服務器的IP和端口,你可以重複連接n多個,然後瘋狂下載東西(別說我告訴你的哈,人家的服務器宕機了找你負責)。注意那個綁定一定要有,不然調用會失敗的。

2、  接下來就在線程函數中判定是ConnectEx操作,通過判定m_iOpType == 2就可以知道,然後這樣做:

setsockopt( pOL->m_skClient, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT,

                     NULL, 0 );

然後就是自由的按照需要調用WSASend或者WSARecv。

3、  最後使用和服務端相似的邏輯調用DisconnectEx函數,收回SOCKET並直接再次調用ConnectEx連接到另一服務器或相同的同一服務器即可。

至此客戶端的SOCKET池也搭建完成了,創建SOCKET的工作也是在一開始的一次性就完成了,後面都是利用ConnectEx和DisconnectEx函數不斷的連接-收發數據-回收-再連接來進行的。客戶端的這個SOCKET池可以用於HTTP下載文件的客戶端或者FTP下載的服務端(反向服務端)或者客戶端,甚至可以用作一個網遊的機器人系統,也可以作爲一個壓力測試的客戶端核心的模型。

 

七、總結和提高:

以上就是比較完整的如何具體實現SOCKET池的全部內容,因爲篇幅的原因就不貼全部的代碼了,我相信各位看客看完之後心中應該有個大概的框架,並且也可以進行實際的代碼編寫工作了。可以用純c來實現也可以用C++來實現。但是這裏要說明一點就是DisconnectEx函數和ConnectEx函數似乎只能在XP SP2以上和2003Server以上的平臺上使用,對於服務端來說這不是什麼問題,但是對於客戶端來說,使用SOCKET池時還要考慮一個兼容性問題,不得已還是要放棄在客戶端使用SOCKET池。

SOCKET池的全部精髓就在於提前創建一批SOCKET,然後就是不斷的重複回收再利用,比起傳統的非SOCKET池方式,節省了大量的不斷創建和銷燬SOCKET對象的內核操作,同時借用IOCP函數AcceptEx、ConnectEx和DisconnectEx等的異步IO完成特性提升了整體性能,非常適合用於一些需要大規模TCP連接管理的場景,如:HTTP Server FTP Server和遊戲服務器等。

SOCKET池的本質就是充分的利用了IOCP模型的幾乎所有優勢,因此要用好SOCKET池就要深入的理解IOCP模型,這是前提。有問題請跟帖討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章