c# AcceptEx与完成端口(IOCP)结合的示例
前言
在windows平台下实现高性能网络服务器,iocp(完成端口)是唯一选择。编写网络服务器面临的问题有:
1快速接收客户端的连接。
2快速收发数据。
3快速处理数据。本文主要解决第一个问题。
AcceptEx函数定义
BOOLAcceptEx( SOCKETsListenSocket, SOCKETsAcceptSocket, PVOIDlpOutputBuffer, DWORDdwReceiveDataLength, DWORDdwLocalAddressLength, DWORDdwRemoteAddressLength, LPDWORDlpdwBytesReceived, LPOVERLAPPEDlpOverlapped );
为什么要用AcceptEx
传统的accept函数能满足大部分场景的需要;但在某些极端条件下,必须使用acceptEx来实现。两个函数的区别如下:
1)accept是阻塞的;在一个端口监听,必须启动一个专用线程调用accept。当然也可以用迂回的方式,绕过这个限制,处理起来会很麻烦,见文章单线程实现同时监听多个端口。acceptEx是异步的,可以同时对很多端口监听(监听端口的数量没有上限的限制)。采用迂回的方式,使用accept监听,一个线程最多监听64个端口。这一点可能不是AcceptEx最大优点,毕竟同时对多个端口监听的情况非常少见。
2)AcceptEx可以返回更多的数据。a)AcceptEx可以返回本地和对方ip地址和端口;而不需要调用函数getsockname和getpeername获取网络地址了。b)AcceptEx可以再接收到一段数据后,再返回。这种做法有利有弊,一般不建议这样做。
3)AcceptEx是先准备套接字(socket)后接收。为了应对突发的连接高峰,可以多次投放AcceptEx。accept是事后建立SOCKET,就是tcp三次握手完成后,accept调用才返回,再生成socket。生成套接字是相对比较耗时的操作,accept的方式无法及时处理突发连接。对于AcceptEx的处理方式为建议做如下处理:一个线程负责创建socket,一个线程负责处理AcceptEx返回。
以上仅仅通过文字说明了AcceptEx的特点。下面通过具体代码,逐一剖析。我将AcceptEx的处理封装到类IocpAcceptEx中。编写该类时,尽量做到高内聚低耦合,使该类可以方便的被其他模块使用。
IocpAcceptEx外部功能说明
classIocpAcceptEx { public: IocpAcceptEx(); ~IocpAcceptEx(); //设置回调接口。当accept成功,调用回调接口。 voidSetCallback(IAcceptCallback*callback); //增加监听端口 voidAddListenPort(UINT16port); //启动服务 BOOLStart(); voidStop(); 。。。以下代码省略 } #definePOST_ACCEPT1 //使用IocpAcceptEx类,必须实现该接口。接收客户端的连接 classIAcceptCallback { public: virtualvoidOnAcceptClient(SOCKEThSocketClient,UINT16nListenPort)=0; };
该类的调用函数很简单,对外接口也很明确。说明该类的职责很清楚,这也符合单一职责原则。
实现步骤说明
AcceptEx不但需要与监听端口绑定,还需要与完成端口绑定。所以程序的第一步是创建完成端口:
a)创建完成端口
m_hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,0); if(m_hIocp==NULL) returnFALSE;
b)监听端口创建与绑定
//生成套接字 SOCKETserverSocket=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED); if(serverSocket==INVALID_SOCKET) { returnfalse; } //绑定 SOCKADDR_INaddr; memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=INADDR_ANY; addr.sin_port=htons(port); if(bind(serverSocket,(sockaddr*)&addr,sizeof(addr))!=0) { closesocket(serverSocket); serverSocket=INVALID_SOCKET; returnfalse; } //启动监听 if(listen(serverSocket,SOMAXCONN)!=0) { closesocket(serverSocket); serverSocket=INVALID_SOCKET; returnfalse; } //监听端口与完成端口绑定 if(CreateIoCompletionPort((HANDLE)serverSocket,m_hIocp,(ULONG_PTR)this,0)==NULL) { closesocket(serverSocket); serverSocket=INVALID_SOCKET; returnfalse; }
c)投递AcceptEx
structAcceptOverlapped { OVERLAPPEDoverlap; INT32opType; SOCKETserverSocket; SOCKETclientSocket; charlpOutputBuf[128]; DWORDdwBytes; }; intIocpAcceptEx::NewAccept(SOCKETserverSocket) { //创建socket SOCKET_socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); AcceptOverlapped*ov=newAcceptOverlapped(); ZeroMemory(ov,sizeof(AcceptOverlapped)); ov->opType=POST_ACCEPT; ov->clientSocket=_socket; ov->serverSocket=serverSocket; //存放网络地址的长度 intaddrLen=sizeof(sockaddr_in)+16; intbRetVal=AcceptEx(serverSocket,_socket,ov->lpOutputBuf, 0,addrLen,addrLen, &ov->dwBytes,(LPOVERLAPPED)ov); if(bRetVal==FALSE) { interror=WSAGetLastError(); if(error!=WSA_IO_PENDING) { closesocket(_socket); return0; } } return1; }
AcceptEx是非阻塞操作,调用会立即返回。当有客户端连接时,怎么得到通知。答案是通过完成端口返回。注意有一个步骤:监听端口与完成端口绑定,就是serverSocket与m_hIocp绑定,所以当有客户端连接serverSocket时,m_hIocp会得到通知。需要生成线程,等待完成端口的通知。
d)通过完成端口,获取通知
DWORDdwBytesTransferred; ULONG_PTRKey; BOOLrc; interror; AcceptOverlapped*lpPerIOData=NULL; while(m_bServerStart) { error=NO_ERROR; rc=GetQueuedCompletionStatus( m_hIocp, &dwBytesTransferred, &Key, (LPOVERLAPPED*)&lpPerIOData, INFINITE); if(rc==FALSE) { error=0; if(lpPerIOData==NULL) { DWORDlastError=GetLastError(); if(lastError==WAIT_TIMEOUT) { continue; } else { assert(false); returnlastError; } } } if(lpPerIOData!=NULL) { switch(lpPerIOData->opType) { casePOST_ACCEPT: { OnIocpAccept(lpPerIOData,dwBytesTransferred,error); } break; } } else { } } return0;
DWORDWINAPIIocpAcceptEx::AcceptExThreadPool(PVOIDpContext) { ThreadPoolParam*param=(ThreadPoolParam*)pContext; param->pIocpAcceptEx->NewAccept(param->ServeSocket); deleteparam; return0; } intIocpAcceptEx::OnIocpAccept(AcceptOverlapped*acceptData,inttransLen,interror) { m_IAcceptCallback->OnAcceptClient(acceptData->clientSocket,acceptData->serverSocket); //当一个AcceptEx返回,需要投递一个新的AcceptEx。 //使用线程池好像有点小题大做。前文已说过,套接字的创建相对是比较耗时的操作。 //如果不在线程池投递AcceptEx,AcceptEx的优点就被抹杀了。 ThreadPoolParam*param=newThreadPoolParam(); param->pIocpAcceptEx=this; param->ServeSocket=acceptData->serverSocket; QueueUserWorkItem(AcceptExThreadPool,this,0); deleteacceptData; return0; }
后记
采用完成端口是提高IO处理能力的一个途径(广义上讲,通讯操作也是IO)。为了提高IO处理能力,windows提供很多异步操作函数,这些函数都与完成端口关联,所以这一类处理的思路基本一致。学会了AcceptEx的使用,可以做到触类旁通的效果。
以上就是c#AcceptEx与完成端口(IOCP)结合的示例的详细内容,更多关于c#AcceptEx与完成端口(IOCP)结合的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。