轉自: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