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