前言 在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)结合实例相关推荐

  1. SQL:安装多个实例,修改实例端口号,和IP加端口号连接实例

    原文:SQL:安装多个实例,修改实例端口号,和IP加端口号连接实例 sql server 安装第一个实例,默认实例的端口是1433,   一个库中如果有多个实例, 从第二个实例开始的端口是动态端口,需 ...

  2. python调用扫描仪_使用Python编写简单的端口扫描器的实例分享

    单线程实现单线程实现道理比较简单,这里尝试Soket连接3389,连接成功说明端口开放,否则说明没有开远程服务.随便修改了一下就ok了,代码如下,最终得到自己的IP地址. #!/usr/bin/env ...

  3. 思科路由器端口映射配置实例

    端口映射是指在路由器开放一个端口,映射到相应的计算机.当外网有数据访问路由器开放的端口时,路由器直接将来自于这个端口的所有数据转发到相应的计算机上,而不是路由器自己接收.这种技术可以把一个公网地址映射 ...

  4. linux启动redis指定端口,linux redis实现自定义运行多端口、多实例 | 极安全-JiSec

    一.redis-server的安装 由于我使用的是ubuntu15 系统,所以这里就用Ubuntu给大家说明下,安装redis-server可以直接使用apt源安装redis-server,cento ...

  5. linux shell 端口占用,linux 查看端口占用命令实例详解

    linux 查看端口占用命令实例详解 端口是系统非常重要的一个东东,我们经常需要查看哪个进程占用了哪个端口,或者哪个端口被哪个进程占用.废话不多说,直接上干货,教大家怎样查看系统端口占用情况. 方法一 ...

  6. 华为交换机端口组配置实例

    华为交换机支持永久端口组和临时端口组,两种端口组功能相同,不同之处在于退出临时端口组之后系统会自动删除临时端口组(不会删除通过临时端口组的配置的配置). 在端口组下配置端口属性就会应用到所有端口组下的 ...

  7. php iocp,完成端口(iocp)实现高性能网络服务器

    在Windows系统里使用完成端口 在Windows系统里,使用完成端口高性能的方法之一,比如把完成端口使用到线程池和网络服务器里.现在就通过线程池的方法来介绍怎么样使用完成端口,高性能的服务器以后再 ...

  8. python写端口扫描器_使用Python编写简单的端口扫描器的实例分享

    #!/usr/bin/env python import socket if __name__=='__main__': port=3389 s=socket.socket() for cnt in ...

  9. Windows 技术篇-利用telnet方法ping端口通不通实例演示,如何测试服务器端口是否启用,windows启用telnet功能

    如果提示 telnet 不是命令的话就是没有启用,需要启用一下. C:\Users\Administrator>telnet 10.10.xx.xx 3332 'telnet' 不是内部或外部命 ...

最新文章

  1. JavaScript 中运算符的优先级
  2. Linux 多个cpp文件的编译(Makefile)
  3. linux 文件系统 vfs,linux虚拟文件系统vfs
  4. 2020年第十一届蓝桥杯 - 省赛 - Python大学组 - D.蛇形填数
  5. 【深入理解JVM】JVM字节码指令集
  6. C++ STL的妙用
  7. 本科、硕士、博士的区别(终极版,太形象了!)
  8. c语言写16进制转2进制,[求助]如何实现16进制转2进制
  9. 都说互联网寒冬,有人却获一线大厂六枚Offer,他是怎么做到的?
  10. oracle--merge
  11. 【WEB端移动端】企业官网高保真Axure原型模板
  12. java下拉刷新上拉加载_使用PullToRefresh实现下拉刷新和上拉加载
  13. 几个项目管理经典小故事,发人深思
  14. DP(Nietzsche)的hu测 T1(状压dp)
  15. stm32f103c8t6视频教程
  16. Android手电筒开发
  17. 如何进行PDF文件翻译?PDF翻译怎么操作
  18. openssl version mismatch. built against 30000010, you have 30100000
  19. linux下安装nginx出错,Ubuntu安装Nginx服务器出错解决
  20. 陶哲轩实分析:微积分基本定理剖析

热门文章

  1. linux内核发包工具pktgen
  2. 扫描---实验二:漏洞扫描之Nessus
  3. centos7上搭建php服务器环境
  4. 你应该知道的一些IT名词
  5. JPA - Persistence与EntityManagerFactory
  6. idea创建web项目,并连接数据库,在网页输出
  7. alv 刷新_alv稳定刷新
  8. 市场逆风中,海尔智家如何续写增长故事
  9. 第一、二、三类贝塞尔函数(Python)
  10. 南通大学教务学生管理系统用户体验