socket鏈接池

轉自:http://blog.csdn.net/nokianasty/article/details/8554577程序員

socket鏈接池編程

SOCKET鏈接池原來注意過,但時間長了,對這個的瞭解有些亂,今天總結一下,趁着天氣比較涼快,心情也比較舒暢。
SOCKET鏈接池產生,目的是爲了減小內核在建立和銷燬SOCKET時所產生的開銷,一個兩個的SOCKET的這個過程是比較容易的,但一旦多了後,特別在一些具體的環境,好比大併發的不斷的登陸和退出時,內核的開銷是很是痛苦的。這裏順便說一句,牛人的經驗和網上的不少文章,都告訴編程人員,內存泄露很是可怕,必定要注意,但據個人編程經驗,一兩次的內存泄露和短期內的小內存泄露,根本是沒有控制的意義的。真正讓人難以忍受的,是服務器類型上的7*24的內存泄露,即便很小的泄露,累積起來,也可能要了人的命,這裏說的不僅這個,還要順便說一下,不但內存是重要的資源,諸如SOCKET,畫筆,畫刷這些都是資源,記住必定要釋放和回收,不然出現問題後,可真是要命。
扯回來,咱們能夠在封裝一個類,在啓動時動態產生一個SOCKET池,這個池的大小和內存池大小的肯定有殊途同歸之妙,看具體的應用和建立,也就是說咱們把動態和靜態結合起來,這樣,就能保證SOCKET的最優化使用,從而防止內核開銷的巨增。這個下面有一個用在客戶端的例子,我以爲主要應用一些網絡購物和遊戲上,同時開多個遊戲或網絡鏈接時有用。
咱們在使用這種鏈接池時,必定要注意的是,SOKCET池中的SOCKET的有效與否,這個在網上有高手寫過,能夠封裝一個布爾變量,使用時將其置爲FALSE,不用時將其置爲TRUE,這樣就能夠在遍歷時根據這個來肯定是否有可用的SOCKET,可是我一直覺着,這樣寫會不會增長程序的複雜性,可能個人理解仍是不夠深入,或許在大併發的狀況下,這個東西就會平衡起來,將複雜和效率達到一個最佳的平衡點。
還有一個SOCKET重用的問題,就是在IOCP中,微軟提供了一種最新的SOCKET重用的機制。TransmitFile函數和DisconnectEx函數,前者用時容易引發死鎖然後者相對來講安全多了,咱們在重用SOCKET後,會從新BindIoCompletionCallback用這個函數綁定一下SOCKET,這時會產生一個錯誤,不用理他,直接略過便可。這個東東我想應該是等於對SOCKET的資源不進行回收而直接再使用,固然一些細節可能會在微軟的底層裏實現咱們只要會用便可。
不過,話說回來,這個東西我也沒有在實際應用過,學習之,有機會在實際中應用一次就行了。緩存

轉載兩篇文章
WinSock2編程之打造完整的SOCKET池2010-04-15 12:39:13|  分類: 電腦編程 閱讀757 評論0   字號:大中小 訂閱 
WinSock2編程之打造完整的SOCKET池
安全

IOCP編程 服務器

在Winodows平臺上,網絡編程的主要接口就是WinSock,目前大多數的Windows平臺上的WinSock平臺已經升級到2.0版,簡稱爲WinSock2。在WinSock2中擴展了不少頗有用的Windows味很濃的SOCKET專用API,爲Windows平臺用戶提供高性能的網絡編程支持。這些函數中的大多數已經再也不是標準的「Berkeley」套接字模型的API了。使用這些函數的代價就是你不能再將你的網絡程序輕鬆的移植到「尤里平臺」(我給Unix +Linux平臺的簡稱)下,反過來由於Windows平臺支持標準的「Berkeley」套接字模型,因此你能夠將大多數尤里平臺下的網絡應用移植到Windows平臺下。網絡

若是不考慮可移植性(或者所謂的跨平臺性),而是着重於應用的性能時,尤爲是注重服務器性能時,對於Windows的程序,都鼓勵使用WinSock2擴展的一些API,更鼓勵使用IOCP模型,由於這個模型是目前Windows平臺上比較完美的一個高性能IO編程模型,它不但適用於SOCKET編程,還適用於讀寫硬盤文件,讀寫和管理命名管道、郵槽等等。若是再結合Windows線程池,IOCP幾乎能夠利用當今硬件全部可能的新特性(好比多核,DMA,高速總線等等),自己具備先天的擴展性和可用性。多線程

今天討論的重點就是SOCKET池。不少VC程序員也許對SOCKET池很陌生,也有些可能很熟悉,那麼這裏就先討論下這個概念。併發

在Windows平臺上SOCKET實際上被視做一個內核對象的句柄,不少Windows API在支持傳統的HANDLE參數的同時也支持SOCKET,好比有名的CreateIoCompletionPort就支持將SOCKET句柄代替HANDLE參數傳入並調用。熟悉Windows內核原理的讀者,馬上就會發現,這樣的話,咱們建立和銷燬一個SOCKET句柄,實際就是在系統內部建立了一個內核對象,對於Windows來講這牽扯到從Ring3層到Ring0層的耗時操做,再加上覆雜的安全審覈機制,實際建立和銷燬一個SOCKET內核對象的成本仍是蠻高的。尤爲對於一些面向鏈接的SOCKET應用,服務端每每要管理n多個表明客戶端通訊的SOCKET對象,並且由於客戶的變更性,主要面臨的大量操做除了通常的收發數據,剩下的就是不斷建立和銷燬SOCKET句柄,對於一個頻繁接入和斷開的服務器應用來講,建立和銷燬SOCKET的性能代價馬上就會體現出來,典型的例如WEB服務器程序,就是一個須要頻繁建立和銷燬SOCKET句柄的SOCKET應用。這種狀況下咱們一般都但願對於斷開的SOCKET對象,不是簡單的「銷燬」了之(不少時候「斷開」的含義不必定就等價於「銷燬」,能夠仔細思考一下),更多時候但願可以重用這個SOCKET對象,這樣咱們甚至能夠事先建立一批SOCKET對象組成一個「池」,在須要的時候「重用」其中的SOCKET對象,不須要的時候將SOCKET對象從新丟入池中便可,這樣就省去了頻繁建立銷燬SOCKET對象的性能損失。在原始的「Berkeley」套接字模型中,想作到這點是沒有什麼辦app

法的。而幸運的是在Windows平臺上,尤爲是支持WinSock2的平臺上,已經提供了一套完整的API接口用於支持SOCKET池。socket

對於符合以上要求的SOCKET池,首先須要作到的就是對SOCKET句柄的「回收」,由於建立函數不管在那個平臺上都是現成的,而最先可以實現這個功能的WinSock函數就是TransmitFile,若是代替closesocket函數像下面這樣調用就能夠「回收」一個SOCKET句柄,而不是銷燬:(注意「回收」這個功能對於TransmitFile函數來講只是個「副業」。)

TransmitFile(hSocket,NULL,0,0,NULL,NULL,TF_DISCONNECT | TF_REUSE_SOCKET );

注意上面函數的最後一個參數,使用了標誌TF_DISCONNECT和TF_REUSE_SOCKET,第一個值表示斷開,第二個值則明確的表示「重用」實際上也就是回收這個SOCKET,通過這個處理的SOCKET句柄,就能夠直接再用於connect等操做,可是此時咱們會發現,這個回收來的SOCKET彷佛沒什麼用,由於其餘套接字函數無法直接利用這個回收來的SOCKET句柄。

這時就要WinSock2的一組專用API上場了。我將它們按傳統意義上的服務端和客戶端分爲兩組:

1、         服務端:

SOCKET WSASocket(

  __in          int af,

  __in          int type,

  __in          int protocol,

  __in          LPWSAPROTOCOL_INFO lpProtocolInfo,

  __in          GROUP g,

  __in          DWORD dwFlags

);

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

);

BOOL DisconnectEx(

  __in          SOCKET hSocket,

  __in          LPOVERLAPPED lpOverlapped,

  __in          DWORD dwFlags,

  __in          DWORD reserved

);

2、         客戶端:

SOCKET WSASocket(

  __in          int af,

  __in          int type,

  __in          int protocol,

  __in          LPWSAPROTOCOL_INFO lpProtocolInfo,

  __in          GROUP g,

  __in          DWORD dwFlags

);

BOOL PASCAL ConnectEx(

  __in          SOCKET s,

  __in          const struct sockaddr* name,

  __in          int namelen,

  __in_opt      PVOID lpSendBuffer,

  __in          DWORD dwSendDataLength,

  __out         LPDWORD lpdwBytesSent,

  __in          LPOVERLAPPED lpOverlapped

);

BOOL DisconnectEx(

  __in          SOCKET hSocket,

  __in          LPOVERLAPPED lpOverlapped,

  __in          DWORD dwFlags,

  __in          DWORD reserved

);

注意觀察這些函數,彷佛和傳統的「Berkeley」套接字模型中的一些函數「大同小異」,其實仔細觀察他們的參數,就已經能夠發現一些調用他們的「玄機」了。

首先咱們來看AcceptEx函數,與accept函數不一樣,它須要兩個SOCKET句柄做爲參數,頭一個參數的含義與accept函數的相同,而第二個參數的意思就是accept函數返回的那個表明與客戶端通訊的SOCKET句柄,在傳統的accept內部,實際在返回那個表明客戶端的SOCKET時,是在內部調用了一個SOCKET的建立動做,先建立這個SOCKET而後再「accept」讓它變成表明客戶端鏈接的SOCKET,而AcceptEx函數就在這裏「擴展」(其實是「閹割」纔對)

accept函數,省去了內部那個明顯的建立SOCKET的動做,而將這個建立動做交給最終的調用者本身來實現。AcceptEx要求調用者建立好那個sAcceptSocket句柄而後傳進去,這時咱們馬上發現,咱們回收的那個SOCKET是否是也能夠傳入呢?答案是確定的,咱們就是能夠利用這個函數傳入那個「回收」來的SOCKET句柄,最終實現服務端的SOCKET重用。

這裏須要注意的就是,AcceptEx函數必須工做在非阻塞的IOCP模型下,同時即便AcceptEx函數返回了,也不表明客戶端鏈接進來或者鏈接成功了,咱們必須依靠它的「完成通知」才能知道這個事實,這也是AcceptEx函數區別於accept這個阻塞方式函數的最大之處。一般能夠利用AcceptEx的非阻塞特性和IOCP模型的優勢,一次能夠「預先」發出成千上萬個AcceptEx調用,「等待」客戶端的鏈接。對於習慣了accept阻塞方式的程序員來講,理解AcceptEx的工做方式仍是須要費一些周折的。下面的例子就演示瞭如何一次調用多個AcceptEx:

//批量建立SOCKET,並調用對應的AcceptEx

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

{//調用1000次

//建立與客戶端通信的SOCKET,注意SOCKET的建立方式

skAccept = ::WSASocket(AF_INET,

                   SOCK_STREAM,

                   IPPROTO_TCP,

                   NULL,

                   0,

                   WSA_FLAG_OVERLAPPED);

if (INVALID_SOCKET == skAccept)

{

    throw CGRSException((DWORD)WSAGetLastError());

}

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

pAcceptOL = new CGRSOverlappedData(GRS_OP_ACCEPT

,this,skAccept,NULL);

pAddrBuf = pAcceptOL->GetAddrBuf();

//四、發出AcceptEx調用

//注意將AcceptEx函數接收鏈接數據緩衝的大小設定成了0,這將致使此函數當即返回,雖然與

//不設定成0的方式而言,這致使了一個較低下的效率,可是這樣提升了安全性,因此這種效率

//犧牲是必須的

if(!AcceptEx(m_skServer,

                   skAccept,

                   pAddrBuf->m_pBuf,

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

                   GRS_ADDRBUF_SIZE,

                   GRS_ADDRBUF_SIZE,

                   NULL,

                   (LPOVERLAPPED)pAcceptOL))

{

int iError = WSAGetLastError();

if( ERROR_IO_PENDING != iError

     && WSAECONNRESET != iError )

{

     if(INVALID_SOCKET != skAccept)

     {

         ::closesocket(skAccept);

         skAccept = INVALID_SOCKET;

     }

     if( NULL != pAcceptOL)

     {

             GRS_ISVALID(pAcceptOL,sizeof(CGRSOverlappedData));

delete pAcceptOL;

     pAcceptOL = NULL;

     }

  }

}

}

以上的例子只是簡單的演示了AcceptEx的調用,尚未涉及到真正的「回收重用」這個主題,那麼下面的例子就演示瞭如何重用一個SOCKET句柄:

if(INVALID_SOCKET == skClient)

{

throw CGRSException(_T("SOCKET句柄是無效的!"));

}

OnPreDisconnected(skClient,pUseData,0);

CGRSOverlappedData*pData

= new GRSOverlappedData(GRS_OP_DISCONNECTEX

,this,skClient,pUseData);

//回收而不是關閉後再建立大大提升了服務器的性能

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

......

      //在接收到DisconnectEx函數的完成通知以後,咱們就能夠重用這個SOCKET了

CGRSAddrbuf*pBuf = NULL;

pNewOL = new CGRSOverlappedData(GRS_OP_ACCEPT

,this,skClient,pUseData);

pBuf = pNewOL->GetAddrBuf();

//把這個回收的SOCKET從新丟進鏈接池

if(!AcceptEx(m_skServer,skClient,pBuf->m_pBuf,

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

                 GRS_ADDRBUF_SIZE, GRS_ADDRBUF_SIZE,

                 NULL,(LPOVERLAPPED)pNewOL))

{

int iError = WSAGetLastError();

    if( ERROR_IO_PENDING != iError

        && WSAECONNRESET != iError )

    {

        throw CGRSException((DWORD)iError);

     }

}

//注意在這個SOCKET被從新利用後,從新與IOCP綁定一下,該操做會返回一個已設置的錯誤,這個錯誤直接被忽略便可

::BindIoCompletionCallback((HANDLE)skClient

,Server_IOCPThread, 0);

 

至此回收重用SOCKET的工做也就結束了,以上的過程實際理解了IOCP以後就比較好理解了,例子的最後咱們使用了BindIoCompletionCallback函數從新將SOCKET丟進了IOCP線程池中,實際還能夠再次使用CreateIoCompletionPort函數達到一樣的效果,這裏列出這一步就是告訴你們,不要忘了再次綁定一下完成端口和SOCKET。

    對於客戶端來講,可使用ConnectEx函數來代替connect函數,與AcceptEx函數相同,ConnectEx函數也是以非阻塞的IOCP方式工做的,惟一要注意的就是在WSASocket調用以後,在ConnectEx以前要調用一下bind函數,將SOCKET提早綁定到一個本地地址端口上,固然回收重用以後,就無需再次綁定了,這也是ConnectEx較之connect函數高效的地方之一。

   與AcceptEx函數相似,也能夠一次發出成千上萬個ConnectEx函數的調用,能夠鏈接到不一樣的服務器,也能夠鏈接到相同的服務器,鏈接到不一樣的服務器時,只需提供不一樣的sockaddr便可。

    經過上面的例子和講解,你們應該對SOCKET池概念以及實際的應用有個大概的瞭解了,固然核心仍然是理解了IOCP模型,不然仍是步履維艱。

在上面的例子中,回收SOCKET句柄主要使用了DisconnectEx函數,而不是以前介紹的TransmitFile函數,爲何呢?由於TransmitFile函數在一些狀況下會形成死鎖,沒法正常回收SOCKET,畢竟不是專業的回收重用SOCKET函數,我就遇到過好幾回死鎖,最後偶然的發現了DisconnectEx函數這個專用的回收函數,調用以後發現比TransmitFile專業多了,並且無論怎樣都不會死鎖。

最後須要補充的就是這幾個函數的調用方式,不能像傳統的SOCKET API那樣直接調用它們,而須要使用一種間接的方式來調用,尤爲是AcceptEx和DisconnectEx函數,下面給出了一個例子類,用於演示如何動態載入這些函數

並調用之:

class CGRSMsSockFun

{

public:

CGRSMsSockFun(SOCKET skTemp = INVALID_SOCKET)

{

     if( INVALID_SOCKET != skTemp )

     {

       LoadAllFun(skTemp);

      }

}

public:

virtual ~CGRSMsSockFun(void)

{

}

protected:

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

{

     DWORD dwBytes = 0;

     BOOL bRet = TRUE;

     pFun = NULL;

     BOOL bCreateSocket = FALSE;

     try

     {

       if(INVALID_SOCKET == skTemp)

       {

          skTemp = ::WSASocket(AF_INET,SOCK_STREAM,

             IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);

bCreateSocket = (skTemp != INVALID_SOCKET);

       }

if(INVALID_SOCKET == skTemp)

       {

          throw CGRSException((DWORD)WSAGetLastError());

       }

       if(SOCKET_ERROR == ::WSAIoctl(skTemp,

                SIO_GET_EXTENSION_FUNCTION_POINTER,

                &funGuid,sizeof(funGuid),

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

                NULL))

       {

             pFun = NULL;

             throw CGRSException((DWORD)WSAGetLastError());

       }

  }

  catch(CGRSException& e)

  {

      if(bCreateSocket)

      {

        ::closesocket(skTemp);

      }

  }

  return NULL != pFun;

}

protected:

LPFN_ACCEPTEX m_pfnAcceptEx;

LPFN_CONNECTEX m_pfnConnectEx;

LPFN_DISCONNECTEX m_pfnDisconnectEx;

LPFN_GETACCEPTEXSOCKADDRS m_pfnGetAcceptExSockaddrs;

LPFN_TRANSMITFILE m_pfnTransmitfile;

LPFN_TRANSMITPACKETS m_pfnTransmitPackets;

LPFN_WSARECVMSG m_pfnWSARecvMsg;

protected:

BOOL LoadAcceptExFun(SOCKET &skTemp)

{

     GUID GuidAcceptEx = WSAID_ACCEPTEX;

     return LoadWSAFun(skTemp,GuidAcceptEx

,(void*&)m_pfnAcceptEx);

}

BOOL LoadConnectExFun(SOCKET &skTemp)

{

     GUID GuidAcceptEx = WSAID_CONNECTEX;

     return LoadWSAFun(skTemp,GuidAcceptEx

,(void*&)m_pfnConnectEx);

}

BOOL LoadDisconnectExFun(SOCKET&skTemp)

{

     GUID GuidDisconnectEx = WSAID_DISCONNECTEX;

     return LoadWSAFun(skTemp,GuidDisconnectEx

,(void*&)m_pfnDisconnectEx);

}

BOOL LoadGetAcceptExSockaddrsFun(SOCKET &skTemp)

{

     GUID GuidGetAcceptExSockaddrs

= WSAID_GETACCEPTEXSOCKADDRS;

     return LoadWSAFun(skTemp,GuidGetAcceptExSockaddrs

,(void*&)m_pfnGetAcceptExSockaddrs);

}

BOOL LoadTransmitFileFun(SOCKET&skTemp)

{

     GUID GuidTransmitFile = WSAID_TRANSMITFILE;

     return LoadWSAFun(skTemp,GuidTransmitFile

,(void*&)m_pfnTransmitfile);

}

BOOL LoadTransmitPacketsFun(SOCKET&skTemp)

{

     GUID GuidTransmitPackets = WSAID_TRANSMITPACKETS;

     return LoadWSAFun(skTemp,GuidTransmitPackets

,(void*&)m_pfnTransmitPackets);

}

BOOL LoadWSARecvMsgFun(SOCKET&skTemp)

{

     GUID GuidTransmitPackets = WSAID_TRANSMITPACKETS;

     return LoadWSAFun(skTemp,GuidTransmitPackets

,(void*&)m_pfnWSARecvMsg);

}

public:

BOOL LoadAllFun(SOCKET skTemp)

{//注意這個地方的調用順序,是根據服務器的須要,並結合了表達式反作用

  //而特地安排的調用順序

  return (LoadAcceptExFun(skTemp) &&

             LoadGetAcceptExSockaddrsFun(skTemp) &&

             LoadTransmitFileFun(skTemp) &&

             LoadTransmitPacketsFun(skTemp) &&

             LoadDisconnectExFun(skTemp) &&

             LoadConnectExFun(skTemp) &&

             LoadWSARecvMsgFun(skTemp));

}

 

public:

GRS_FORCEINLINE BOOL AcceptEx (

          SOCKET sListenSocket,

          SOCKET sAcceptSocket,

          PVOID lpOutputBuffer,

          DWORD dwReceiveDataLength,

          DWORD dwLocalAddressLength,

          DWORD dwRemoteAddressLength,

          LPDWORD lpdwBytesReceived,

          LPOVERLAPPED lpOverlapped

          )

{

     GRS_ASSERT(NULL != m_pfnAcceptEx);

     return m_pfnAcceptEx(sListenSocket,

             sAcceptSocket,lpOutputBuffer,

             dwReceiveDataLength,dwLocalAddressLength,

             dwRemoteAddressLength,lpdwBytesReceived,

             lpOverlapped);

}

GRS_FORCEINLINE BOOL ConnectEx(

          SOCKET s,const struct sockaddr FAR *name,

          int namelen,PVOID lpSendBuffer,

          DWORD dwSendDataLength,LPDWORD lpdwBytesSent,

          LPOVERLAPPED lpOverlapped

          )

{

     GRS_ASSERT(NULL != m_pfnConnectEx);

     return m_pfnConnectEx(

             s,name,namelen,lpSendBuffer,

             dwSendDataLength,lpdwBytesSent,

             lpOverlapped

             );

}

GRS_FORCEINLINE BOOL DisconnectEx(

          SOCKET s,LPOVERLAPPED lpOverlapped,

          DWORD  dwFlags,DWORD  dwReserved

          )

{

     GRS_ASSERT(NULL != m_pfnDisconnectEx);

     return m_pfnDisconnectEx(s,

             lpOverlapped,dwFlags,dwReserved);

}

GRS_FORCEINLINE VOID GetAcceptExSockaddrs (

          PVOID lpOutputBuffer,

          DWORD dwReceiveDataLength,

          DWORD dwLocalAddressLength,

          DWORD dwRemoteAddressLength,

          sockaddr **LocalSockaddr,

          LPINT LocalSockaddrLength,

          sockaddr **RemoteSockaddr,

          LPINT RemoteSockaddrLength

          )

{

     GRS_ASSERT(NULL != m_pfnGetAcceptExSockaddrs);

     return m_pfnGetAcceptExSockaddrs(

          lpOutputBuffer,dwReceiveDataLength,

          dwLocalAddressLength,dwRemoteAddressLength,

          LocalSockaddr,LocalSockaddrLength,

          RemoteSockaddr,RemoteSockaddrLength

          );

}

GRS_FORCEINLINE BOOL TransmitFile(

     SOCKET hSocket,HANDLE hFile,

     DWORD nNumberOfBytesToWrite,

     DWORD nNumberOfBytesPerSend,

     LPOVERLAPPED lpOverlapped,

     LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,

     DWORD dwReserved

     )

{

     GRS_ASSERT(NULL != m_pfnTransmitfile);

     return m_pfnTransmitfile(

             hSocket,hFile,nNumberOfBytesToWrite,

             nNumberOfBytesPerSend,lpOverlapped,

             lpTransmitBuffers,dwReserved

             );

}

GRS_FORCEINLINE BOOL TransmitPackets(

     SOCKET hSocket,                            

     LPTRANSMIT_PACKETS_ELEMENT lpPacketArray,                              

     DWORD nElementCount,DWORD nSendSize,               

     LPOVERLAPPED lpOverlapped,DWORD dwFlags                              

     )

{

     GRS_ASSERT(NULL != m_pfnTransmitPackets);

     return m_pfnTransmitPackets(

             hSocket,lpPacketArray,nElementCount,

nSendSize,lpOverlapped,dwFlags

             );

}

GRS_FORCEINLINE INT WSARecvMsg(

          SOCKET s,LPWSAMSG lpMsg,

          LPDWORD lpdwNumberOfBytesRecvd,

          LPWSAOVERLAPPED lpOverlapped,

          LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

          )

{

     GRS_ASSERT(NULL != m_pfnWSARecvMsg);

     return m_pfnWSARecvMsg(

             s,lpMsg,lpdwNumberOfBytesRecvd,

             lpOverlapped,lpCompletionRoutine

             );

}

/*WSAID_ACCEPTEX

  WSAID_CONNECTEX

  WSAID_DISCONNECTEX

  WSAID_GETACCEPTEXSOCKADDRS

  WSAID_TRANSMITFILE

  WSAID_TRANSMITPACKETS

  WSAID_WSARECVMSG

  WSAID_WSASENDMSG */

};

這個類的使用很是簡單,只須要聲明一個類的對象,而後調用其成員AcceptEx、DisconnectEx函數等便可,參數與這些函數的MSDN聲明方式徹底相同,除了本文中介紹的這些函數外,這個類還包含了不少其餘的Winsock2函數,那麼都應該按照這個類中演示的這樣來動態載入後再行調用,若是沒法載入一般說明你的環境中沒有Winsock2函數庫,或者是你初始化的不是2.0版的Winsock環境。這個類是本人完整類庫的一部分,如要使用須要自行修改一些地方,若是不知如何修改或遇到什麼問題,能夠直接跟帖說明,我會不按期回答你們的問題,這個類能夠無償使用、分發、修改,能夠用於任何商業目的,可是對於使用後引發的任何問題,本人概不負責,有問題請跟帖。關於AcceptEx以及其餘一些函數,包括本文中沒有介紹到得函數,我會在後續的一些專題文章中進行詳細深刻的介紹,敬請期待。若是你有什麼疑問,或者想要了解什麼也請跟帖說明,我會在後面的文章中儘可能說明。


如何建立和使用socket連接池


做者:吳康彬


    採用CS方式的程序不可避免都要碰到socket鏈接的問題,不少時候,使用編程語言當中自帶的socket庫,使用起來多少有些不習慣,雖然系統自帶的庫在不少異常處理,穩定性上下了不少功夫,可是要去理解和使用那些庫,好比作socket鏈接池難免要走不少彎路。在這裏我和你們討論下怎麼樣建立和使用socket連接池。 
    通常socket連接有如下兩種方式:長(常)連接和短連接。 
    長連接:當數據發送完成後socket連接不斷開。一直保留到異常或者是程序退出爲止,這種方式的好處是不用每次去發起鏈接斷開,在速度上能夠比短鏈接要快一些,可是相對來講對服務器的資源壓力也要大些。長連接用的範圍很廣,好比遊戲系統,qq等等,長(常)連接通常還須要定時向服務器ping數據,以保證socket連接暢通。當ping不通服務器時,須要從新開啓連接。 
    短連接:當一次數據發送完畢後,主動斷開連接,每次發送數據都要一次連接、斷開操做,這種方式的好處是:對服務器的資源佔用相對來講比較小,可是因爲每次都要從新連接,速度開銷上也比較大,這種方式對於那種不須要常常與服務器交互的狀況下比較適用。 
    上面兩種方法在用戶量很是大的狀況下都存在着很大的不足,所以,咱們考慮能夠用一種折衷的辦法,那就是使用socket的鏈接池。 
    程序一開始初始化建立若干數量的長連接。給他們設置一個標識位,這個標識位表示該連接是否空閒的狀態。當須要發送數據的時候,系統給它分配一個當前空閒的連接。同時,將獲得的連接設置爲「忙」,當數據發送完畢後,把連接標識位設置爲「閒」,讓系統能夠分配給下個用戶,這樣使得兩種方式的優勢都充分的發揮出來了。杭州攜購網絡科技有限公司旗下的攜購獨立購物網(http://www.shopxg.com)系統採用的就是這種方式。用戶數量足夠多的時候,只須要動態增長連接池的數量便可。 
    下面咱們用具體的程序來說解下: 
    首先咱們聲明一個socket類:

public class XieGouSocket
{
  public Socket m_socket;  //Socket對象
  public bool m_isFree;  //判斷是否空閒
  public int m_index;  //在連接緩存池中的索引值
}

    下面的函數是建立socket連接池,這裏爲了使代碼更加清晰,我特意把異常處理部分所有取掉了。  public XieGouSocket[] m_socket; //先定義個緩衝池
public void  CreateSocketPool()
{
  string ip= 「127.0.0.1」; 
  string port= 2003; 
  IPAddress serverIp=IPAddress.Parse(ip); 
  int serverPort=Convert.ToInt32(port); 
  IPEndPoint iep=new IPEndPoint(serverIp,serverPort); 
  m_socket = new XieGouSocket[200]; 
  for(int i =0; i < 200 ; i ++)
  {
   m_socket[i] = new XieGouSocket();
   m_socket[i].m_index = i ;
   m_socket[i].m_isFree = true;
   m_socket[i].m_socket =new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
   m_socket[i].m_socket.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.SendTimeout,1000);  
   m_socket[i].m_socket.Connect(iep);
  }
}

    下面的函數是獲取當前空閒的socket連接: 
    由於是多線程,因此咱們須要加一個原子操做,定義一個原子變量,以防止多個線程之間搶佔資源問題的發生。  private   static   Mutex   m_mutex=new   Mutex();
public static  XieGouSocket GetFreeConnection()
{ 
  m_mutex.WaitOne(); //先阻塞
  for(int i =0; i < m_socket.Length ; i ++)
  {
   if(m_socket[i].m_isFree) //若是找到一個空閒的
   {
    m_socket[i].m_isFree = false;
    m_mutex.ReleaseMutex();//釋放資源          
    return m_socket[i];
   }
  }
  //若是沒有空閒的連接,要麼等待,要麼程序再動態建立一個連接。
  m_mutex.ReleaseMutex();//釋放資源
     
  return null;
}

    當數據發送完畢後,程序必須將m_isFree 設置爲 False。不然只使用不釋放,程序很快就溢出了。 
    基本的思路就是這樣的,你們能夠在此基礎上好好的改進下,這樣運行的效率就比較高了。 
        歡迎你們與我交流。QQ:8814730 Email:wkb@xiegoo.com

相關文章
相關標籤/搜索