AcceptEx与完成端口(IOCP)结合实例
前言 在windows平台下实现高性能网络服务器,iocp(完成端口)是唯一选择。编写网络服务器面临的问题有:1 快速接收客户端的连接。2 快速收发数据。3 快速处理数据。本文主要解决第一个问题。
AcceptEx函数定义
BOOL AcceptEx(SOCKET sListenSocket,SOCKET sAcceptSocket,PVOID lpOutputBuffer,DWORD dwReceiveDataLength,DWORD dwLocalAddressLength,DWORD dwRemoteAddressLength,LPDWORD lpdwBytesReceived,LPOVERLAPPED lpOverlapped );
为什么要用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外部功能说明
class IocpAcceptEx { public:IocpAcceptEx();~IocpAcceptEx();//设置回调接口。当accept成功,调用回调接口。void SetCallback(IAcceptCallback* callback);// 增加监听端口void AddListenPort(UINT16 port);//启动服务 BOOL Start();void Stop();。。。以下代码省略 } #define POST_ACCEPT 1 //使用IocpAcceptEx类,必须实现该接口。接收客户端的连接 class IAcceptCallback { public:virtual void OnAcceptClient(SOCKET hSocketClient, UINT16 nListenPort) = 0; };
该类的调用函数很简单,对外接口也很明确。说明该类的职责很清楚,这也符合单一职责原则。
实现步骤说明
AcceptEx不但需要与监听端口绑定,还需要与完成端口绑定。所以程序的第一步是创建完成端口:
a)创建完成端口
m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0); if (m_hIocp == NULL)return FALSE;
b)监听端口创建与绑定
//生成套接字SOCKET serverSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);if (serverSocket == INVALID_SOCKET){return false;}//绑定 SOCKADDR_IN addr;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;return false;}//启动监听if (listen(serverSocket, SOMAXCONN) != 0){closesocket(serverSocket);serverSocket = INVALID_SOCKET;return false;}//监听端口与完成端口绑定if (CreateIoCompletionPort((HANDLE)serverSocket, m_hIocp, (ULONG_PTR)this, 0) == NULL){closesocket(serverSocket);serverSocket = INVALID_SOCKET;return false;}
c)投递AcceptEx
struct AcceptOverlapped {OVERLAPPED overlap;INT32 opType;SOCKET serverSocket;SOCKET clientSocket;char lpOutputBuf[128];DWORD dwBytes; };int IocpAcceptEx::NewAccept(SOCKET serverSocket) {//创建socketSOCKET _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); AcceptOverlapped *ov = new AcceptOverlapped();ZeroMemory(ov,sizeof(AcceptOverlapped));ov->opType = POST_ACCEPT;ov->clientSocket = _socket;ov->serverSocket = serverSocket;//存放网络地址的长度int addrLen = sizeof(sockaddr_in) + 16;int bRetVal = AcceptEx(serverSocket, _socket, ov->lpOutputBuf,0,addrLen, addrLen,&ov->dwBytes, (LPOVERLAPPED)ov);if (bRetVal == FALSE){int error = WSAGetLastError();if (error != WSA_IO_PENDING){closesocket(_socket);return 0;}}return 1; }
AcceptEx是非阻塞操作,调用会立即返回。当有客户端连接时,怎么得到通知。答案是通过完成端口返回。注意有一个步骤:监听端口与完成端口绑定,就是serverSocket与m_hIocp绑定,所以当有客户端连接serverSocket时,m_hIocp会得到通知。需要生成线程,等待完成端口的通知。 d)通过完成端口,获取通知
DWORD dwBytesTransferred;ULONG_PTR Key;BOOL rc;int error;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){DWORD lastError = GetLastError();if (lastError == WAIT_TIMEOUT){continue;}else{assert(false);return lastError;}}}if (lpPerIOData != NULL){switch (lpPerIOData->opType){case POST_ACCEPT:{OnIocpAccept(lpPerIOData, dwBytesTransferred, error);}break;}}else { }}return 0;
DWORD WINAPI IocpAcceptEx::AcceptExThreadPool(PVOID pContext) {ThreadPoolParam *param = (ThreadPoolParam*)pContext;param->pIocpAcceptEx->NewAccept(param->ServeSocket);delete param;return 0; }int IocpAcceptEx::OnIocpAccept(AcceptOverlapped *acceptData, int transLen, int error) {m_IAcceptCallback->OnAcceptClient(acceptData->clientSocket, acceptData->serverSocket);//当一个AcceptEx返回,需要投递一个新的AcceptEx。 //使用线程池好像有点小题大做。前文已说过,套接字的创建相对是比较耗时的操作。//如果不在线程池投递AcceptEx,AcceptEx的优点就被抹杀了。ThreadPoolParam *param = new ThreadPoolParam();param->pIocpAcceptEx = this;param->ServeSocket = acceptData->serverSocket;QueueUserWorkItem(AcceptExThreadPool, this, 0);delete acceptData;return 0; }
后记 采用完成端口是提高IO处理能力的一个途径(广义上讲,通讯操作也是IO)。为了提高IO处理能力,windows提供很多异步操作函数,这些函数都与完成端口关联,所以这一类处理的思路基本一致。学会了AcceptEx的使用,可以做到触类旁通的效果。
转载于:https://www.cnblogs.com/yuanchenhui/p/acceptex_socket.html
AcceptEx与完成端口(IOCP)结合实例相关推荐
- SQL:安装多个实例,修改实例端口号,和IP加端口号连接实例
原文:SQL:安装多个实例,修改实例端口号,和IP加端口号连接实例 sql server 安装第一个实例,默认实例的端口是1433, 一个库中如果有多个实例, 从第二个实例开始的端口是动态端口,需 ...
- python调用扫描仪_使用Python编写简单的端口扫描器的实例分享
单线程实现单线程实现道理比较简单,这里尝试Soket连接3389,连接成功说明端口开放,否则说明没有开远程服务.随便修改了一下就ok了,代码如下,最终得到自己的IP地址. #!/usr/bin/env ...
- 思科路由器端口映射配置实例
端口映射是指在路由器开放一个端口,映射到相应的计算机.当外网有数据访问路由器开放的端口时,路由器直接将来自于这个端口的所有数据转发到相应的计算机上,而不是路由器自己接收.这种技术可以把一个公网地址映射 ...
- linux启动redis指定端口,linux redis实现自定义运行多端口、多实例 | 极安全-JiSec
一.redis-server的安装 由于我使用的是ubuntu15 系统,所以这里就用Ubuntu给大家说明下,安装redis-server可以直接使用apt源安装redis-server,cento ...
- linux shell 端口占用,linux 查看端口占用命令实例详解
linux 查看端口占用命令实例详解 端口是系统非常重要的一个东东,我们经常需要查看哪个进程占用了哪个端口,或者哪个端口被哪个进程占用.废话不多说,直接上干货,教大家怎样查看系统端口占用情况. 方法一 ...
- 华为交换机端口组配置实例
华为交换机支持永久端口组和临时端口组,两种端口组功能相同,不同之处在于退出临时端口组之后系统会自动删除临时端口组(不会删除通过临时端口组的配置的配置). 在端口组下配置端口属性就会应用到所有端口组下的 ...
- php iocp,完成端口(iocp)实现高性能网络服务器
在Windows系统里使用完成端口 在Windows系统里,使用完成端口高性能的方法之一,比如把完成端口使用到线程池和网络服务器里.现在就通过线程池的方法来介绍怎么样使用完成端口,高性能的服务器以后再 ...
- python写端口扫描器_使用Python编写简单的端口扫描器的实例分享
#!/usr/bin/env python import socket if __name__=='__main__': port=3389 s=socket.socket() for cnt in ...
- Windows 技术篇-利用telnet方法ping端口通不通实例演示,如何测试服务器端口是否启用,windows启用telnet功能
如果提示 telnet 不是命令的话就是没有启用,需要启用一下. C:\Users\Administrator>telnet 10.10.xx.xx 3332 'telnet' 不是内部或外部命 ...
最新文章
- JavaScript 中运算符的优先级
- Linux 多个cpp文件的编译(Makefile)
- linux 文件系统 vfs,linux虚拟文件系统vfs
- 2020年第十一届蓝桥杯 - 省赛 - Python大学组 - D.蛇形填数
- 【深入理解JVM】JVM字节码指令集
- C++ STL的妙用
- 本科、硕士、博士的区别(终极版,太形象了!)
- c语言写16进制转2进制,[求助]如何实现16进制转2进制
- 都说互联网寒冬,有人却获一线大厂六枚Offer,他是怎么做到的?
- oracle--merge
- 【WEB端移动端】企业官网高保真Axure原型模板
- java下拉刷新上拉加载_使用PullToRefresh实现下拉刷新和上拉加载
- 几个项目管理经典小故事,发人深思
- DP(Nietzsche)的hu测 T1(状压dp)
- stm32f103c8t6视频教程
- Android手电筒开发
- 如何进行PDF文件翻译?PDF翻译怎么操作
- openssl version mismatch. built against 30000010, you have 30100000
- linux下安装nginx出错,Ubuntu安装Nginx服务器出错解决
- 陶哲轩实分析:微积分基本定理剖析