前言 在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) = ;
};

该类的调用函数很简单,对外接口也很明确。说明该类的职责很清楚,这也符合单一职责原则。

实现步骤说明

AcceptEx不但需要与监听端口绑定,还需要与完成端口绑定。所以程序的第一步是创建完成端口:

a)创建完成端口

m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, );
if (m_hIocp == NULL)
return FALSE;

b)监听端口创建与绑定

    //生成套接字
SOCKET serverSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, , WSA_FLAG_OVERLAPPED);
if (serverSocket == INVALID_SOCKET)
{
return false;
} //绑定
SOCKADDR_IN addr;
memset(&addr, , 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)) != )
{
closesocket(serverSocket);
serverSocket = INVALID_SOCKET;
return false;
} //启动监听
if (listen(serverSocket, SOMAXCONN) != )
{
closesocket(serverSocket);
serverSocket = INVALID_SOCKET;
return false;
} //监听端口与完成端口绑定
if (CreateIoCompletionPort((HANDLE)serverSocket, m_hIocp, (ULONG_PTR)this, ) == NULL)
{
closesocket(serverSocket);
serverSocket = INVALID_SOCKET;
return false;
}

c)投递AcceptEx

struct AcceptOverlapped
{
OVERLAPPED overlap;
INT32 opType;
SOCKET serverSocket;
SOCKET clientSocket; char lpOutputBuf[];
DWORD dwBytes;
}; int IocpAcceptEx::NewAccept(SOCKET serverSocket)
{
//创建socket
SOCKET _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) + ; int bRetVal = AcceptEx(serverSocket, _socket, ov->lpOutputBuf,
,addrLen, addrLen,
&ov->dwBytes, (LPOVERLAPPED)ov);
if (bRetVal == FALSE)
{
int error = WSAGetLastError();
if (error != WSA_IO_PENDING)
{
closesocket(_socket);
return ;
}
} return ;
}
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 = ;
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 ;  
 
DWORD WINAPI IocpAcceptEx::AcceptExThreadPool(PVOID pContext)
{
ThreadPoolParam *param = (ThreadPoolParam*)pContext;
param->pIocpAcceptEx->NewAccept(param->ServeSocket);
delete param;
return ;
} 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, ); delete acceptData;
return ;
}    
后记 采用完成端口是提高IO处理能力的一个途径(广义上讲,通讯操作也是IO)。为了提高IO处理能力,windows提供很多异步操作函数,这些函数都与完成端口关联,所以这一类处理的思路基本一致。学会了AcceptEx的使用,可以做到触类旁通的效果。

AcceptEx与完成端口(IOCP)结合实例的更多相关文章

  1. [原]php远程odbc连接sqlsvr数据库,自定义端口,命名实例的连接方式

    远程odbc连接sqlsvr数据库,自定义端口,命名实例的连接方式,默认如果不修改的话sqlsvr的端口号是1433,默认实例名就是机器名,,如果既用了命名实例,又改了默认端口,改怎么连接数据库呢? ...

  2. DELPHI中完成端口(IOCP)的简单分析(4)

    DELPHI中完成端口(IOCP)的简单分析(4)   在我以前写的文章中,一直说的是如何接收数据.但是对于如何发送数据却一点也没有提到.因为从代码量上来说接收的代码要比发送多很多.今天我就来写一下如 ...

  3. DELPHI中完成端口(IOCP)的简单分析(3)

    DELPHI中完成端口(IOCP)的简单分析(3)   fxh7622关注4人评论7366人阅读2007-01-17 11:18:24   最近太忙,所以没有机会来写IOCP的后续文章.今天好不容易有 ...

  4. DELPHI中完成端口(IOCP)的简单分析(2)

    DELPHI中完成端口(IOCP)的简单分析(2)   今天我写一下关于DELPHI编写完成端口(IOCP)的工作者线程中的东西.希望各位能提出批评意见.上次我写了关于常见IOCP的代码,对于IOCP ...

  5. DELPHI中完成端口(IOCP)的简单分析(1)

    DELPHI中完成端口(IOCP)的简单分析(1)   用DELPHI开发网络代码已经有一段时间了! 我发现在网上用VC来实现完成端口(IOCP)的代码很多,但是使用DELPHI来实现的就比较少了.对 ...

  6. 使用Python编写简单的端口扫描器的实例分享【转】

    转自 使用Python编写简单的端口扫描器的实例分享_python_脚本之家 http://www.jb51.net/article/76630.htm -*- coding:utf8 -*- #!/ ...

  7. Socket网络编程(TCP/IP/端口/类)和实例

    Socket网络编程(TCP/IP/端口/类)和实例 原文:C# Socket网络编程精华篇 转自:微冷的雨 我们在讲解Socket编程前,先看几个和Socket编程紧密相关的概念: TCP/IP层次 ...

  8. 关于完成端口IOCP异步接收连接函数AcceptEx注意事项

    AcceptEx方法有一个参数dwReceiveDataLength,指明了在收到连接后是否需要收到第一包数据才返回.需要注意的是,如果 dwReceiveDataLength=0,则当接收到一个连接 ...

  9. 完成端口IOCP详解

    修改自: http://blog.csdn.net/piggyxp/article/details/6922277 ps: 原作者很厉害了, 把一个iocp模型讲解的这么形象,不过在实践过程中发现一些 ...

随机推荐

  1. git 使用遇到的问题

    本博客只记录遇到的问题和解决方案 问题一:git上与本地不同步无法上传 先git pull origin master再git push -u origin master(实在不行或者清空本地,或者清 ...

  2. PHP并发之Swoole

    <?php /** * Created by PhpStorm. * User: zhezhao * Date: 2016/10/20 * Time: 10:51 */ $url_arr = a ...

  3. [原]CentOS 7.2 1511部署L2TP/IPsec服务器及客户端

    快过年了,感觉从去年开始,我们公司就变成了“别人的公司”,基本上提前一星期就放假了,好开心.正好可以利用这一段时间,把前段时间一些疑惑的问题解决下:) 然而挡在面前的一个拦路虎是:很多时候不能愉快的G ...

  4. MySQL开发——【高级操作、五子句】

    高级新增操作 ①基本插入操作: insert into 数据表名称(字段) values (值); ②批量插入操作: insert into 数据表名称(字段) values (值1),(值2),(值 ...

  5. 电子商务系统+java+web+完整项目+包含源码和数据库Java实用源码

    鸿鹄云商大型企业分布式互联网电子商务平台,推出PC+微信+APP+云服务的云商平台系统,其中包括B2B.B2C.C2C.O2O.新零售.直播电商等子平台. 分布式.微服务.云架构电子商务平台 java ...

  6. ps高级磨皮的7个步骤

    1.打开图片 2. 进入通道看红绿蓝哪个通道痘比较明显拖拽复制拷贝哪个通道! 3.选中拷贝的通道--执行滤镜--其他--高反差保留 4.在执行图像--计算(混合模式强光)--计算三次 5.选alpha ...

  7. pyinstaller spec

    pyinstaller options..script.py pyi-makespec options script.py [other scripts ...] pyinstaller option ...

  8. exchange 2010 数据库管理

    1. 查看数据库中空白空间 Get-MailboxDatabase databasename -Status | FL AvailableNewMailboxSpace 2.卸载数据库 Dismoun ...

  9. [JAVA]JAVA章2 IOC与AOP是啥

    使用Spring框架的过程中,其实就是为了使用IOC(依赖注入),和AOP(面向切面编程),这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式. IOC就是典型的工厂模式,通过ses ...

  10. ABP框架系列之三十四:(Multi-Tenancy-多租户)

    What Is Multi Tenancy? "Software Multitenancy refers to a software architecture in which a sing ...