{//调用1000次
//创建与客户端通讯的SOCKET,注意SOCKET的创建方式
skClient = ::WSASocket(AF_INET,SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED );
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);
intnRet = ::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,(constsockaddr*)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模型,这是前提。有问题请跟帖讨论。 (本文原创,转载请注明出处。)
相关推荐: