前言

传统socket编程中服务端一般为每一个客户端创建一个线程(一对一)。这样虽然可以使程序的结构简单明了并且方便对数据处理,但是这些都是建立在创建多个线程的基础上,也就是以牺牲线程为代价。一旦有大量数量了客户端连接服务端,我们的服务端需要创建很多线程,这样会造成很大的系统开销这显然是不能被我们所接受的。那么为了解决这个问题就必须采用一种方法令有限的线程去处理所有的客户端连接,利用windows的IOCP完成端口配合线程池就可以帮助我们完成这个操作。

IOCP实现高并发整体思路

IOCP实现高并发原理

我们先通过CreateIoCompletionPort()函数创建一个IOCP完成端口对象,然后每次有客户端连接时在调用CreateIoCompletionPort()函数将请求连接的客户端与IOCP完成端口对象绑定,并且设置完成键为客户端的socket。实际IOCP完成端口对象内部维护者一张设备列表,此列表记录着各个设备的句柄和对应的完成键,对于我们来说就是各个客户端的句柄和各个客户端的完成键(socket)。

然后我们会利用异步I/O函数WASRecv()接收客户端的数据包,当设备驱动程序将异步I/O完成后会这个完成的I/O请求追加到IOCP完成队列中。IOCP完成队列每一项都包含了已完成异步I/O的详细信息,如已传输字节数,完成键值,指向此次I/O的Overlapped结构的地址,错误码。线程池中等待队列中的线程会从IOCP完成队列中取出一项并将此项删除。

现在来看一下线程池是如何工作的,线程池中的线程都使用同一线程函数。首先线程池有三个队列:等待线程队列,已释放线程队列,已暂停线程队列。

我们一开始调用GetQueuedCompletionStatus时会让调用线程进入等待线程队列,当等待线程队列中的线程的GetQueuedCompletionStatus返回时会从IOCP队列中取出一项并将其从IOCP队列中删除,接着其线程就会从等待队列转移到已释放队列中。然后处理完之后为了继续接收客户端的数据包需要再次调用一下异步函数WASRecv(),这时线程会从已释放队列转移到已暂定队列中,这样做IOCP完成端口对象会发现此线程在已暂停队列中,所以会使IOCP完成队列中的其他项利用线程池中等待队列中的其他线程处理,而不会继续使用此线程。接着当此线程WASRecv()函数调用后其又会从已暂停队列回到已释放队列中,然后循环重新开始继续调用GetQueuedCompletionStatus,此线程又从已释放队列中移动到等待队列中。

相关函数

  • WSARecv/WSASend

    WSARecv/WSASend与函数recv/send函数相对应,前者为异步函数,后者为同步函数。以WSARecv( )函数为例,此函数会产生一个异步I/O请求,然后立刻返回而此时并未真正收到数据包,等服务端接收到来自客户端的数据包异步I/O完成时会向IOCP对象的完成队列中添加一项,接着IOCP对象会从线程池中等待队列的线程中选择一个线程来进一步处理。

  • CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);

    此函数为IOCP对象的创建或绑定函数,一开始我们需要借助此函数创建一个IOCP完成端口内核对象,然后每当一个客户端与服务端连接时我们都要将IOCP对象与此客户端的socket绑定。在客户端绑定IOCP完成端口对象时注意第三个参数CompletionKey,此参数为完成键。因为我们客户端的数据包最后都到数量受限的线程池中处理,为了区分是哪个客户端的数据包需要为每个不同的客户端指定不同的完成键,从而标志不同的客户端(通常用客户端的套接字作为完成键)。

  • GetQueuedCompletionStatus()

    哪个线程调用GetQueuedCompletionStatus()函数,其就会被IOCP完成对象认为是线程池中的一个线程。此函数从IOCP完成队列中取出一项,如果最后一个参数指定为INFINITE,则只有当异步I/O完成时也就是IOCP完成队列中有非空项时其会返回,否则一直等待。

  • GetQueuedCompletionStatusEx()

    此函数可以从IOCP完成队列中取出所有的项,这样我们可以避免开启多个线程并调用GetQueuedCompletionStatus()等待增加系统开销。

  • PostQueuedCompletionStatus()

    此函数可以向线程池中每一个工作线程都发送—个特殊的完成数据包,在退出程序的时候可以通过此函数来向线程池中的每一个线程发送一个特定的数据包使其线程安全退出

在利用IOCP完成端口对象时遇到的一些问题

  • 因为WSARecv()在异步接受数据时会指定接受数据的内存,为了通过重叠结构传递这块内存,需要new[]出来这块内存放到堆中,所以记得delete[]。

    因为重叠结构是IOCP完成对象与线程池中线程交互进一步传递来自客户端的数据的,所以这块内存也需要放到堆中new出来最后也要delete。
        DWORD	dwSize;
DWORD dwFlag = 0;
WSABUF stBuffer;
stBuffer.buf = new char[0x1000]();
stBuffer.len = 0x1000; MYOVERLAPPED* lpMyOVERLAPPED = new MYOVERLAPPED;
memset(lpMyOVERLAPPED, 0, sizeof(MYOVERLAPPED));
lpMyOVERLAPPED->nTypr = TYPE_RECV; //表示发送一个收包请求任务
lpMyOVERLAPPED->pBuf = (BYTE *)stBuffer.buf; //使接收到的数据通过重叠结构传递 //异步接受消息(向任务队列投递一个接受请求)
WSARecv(
h,
&stBuffer, //缓冲区结构
1, //缓冲区数组数量
&dwSize,
&dwFlag,
&(lpMyOVERLAPPED->ol), //标准重叠结构
NULL);
  • 因为我们客户端发送消息的形式一般是先发送包头,在发送包尾。所以 GetQueuedCompletionStatus()在从任务队列中取数据也是先获得包头数据再获得包尾数据。然后将其拼接成完整的包后处理。
  • 我们在处理完一个包后需要往任务队列中再发送接收任务,等待下一次客户端的数据到达。这时我们需要在调用WSARecv()异步接受数据时需要重新指定新的内存给IOCP存放接受到的数据使用。所以需要重新new,至于new的大小取决于我们是接下来是接收包头还是接收包尾。接收包头就是new 包头大小,包尾就是new对应的包尾大小。(一个包分两次接收)。

    注意不要采用每次new固定的大小内存来接收,这样会使GetQueuedCompletionStatus()一次获取不完包中数据从而进行多次调用,而在如果在获取包的最后的数据不足new的大小的话,其会把下一个包的数据一起放进来给我们带来不必要的麻烦。
        DWORD	dwSize;
DWORD dwFlag = 0;
WSABUF stBuffer;
if (pClient->stWrap.dwLength == 0) //如果刚收完包尾,继续收下一个包的包头
{
stBuffer.buf = new char[8]();
stBuffer.len = 8;
}
else //如果刚收完包头,则收对应大小的包尾
{
stBuffer.buf = new char[pClient->stWrap.dwLength]();
stBuffer.len = pClient->stWrap.dwLength;
} memset(pMyOVERLAPPED, 0, sizeof(MYOVERLAPPED));
pMyOVERLAPPED->nTypr = TYPE_RECV; //表示发送一个收包请求任务
pMyOVERLAPPED->pBuf = (BYTE*)stBuffer.buf; //使接收到的数据通过重叠结构传递 //异步接受消息(向任务队列投递一个接受请求)
WSARecv(pClient->hSocketClient, &stBuffer, 1, &dwSize, &dwFlag, &(pMyOVERLAPPED->ol), NULL);

参考:《windows核心编程》

IOCP实现高并发以及与传统socke编程的对比的更多相关文章

  1. 配置开发支持高并发TCP连接的Linux应用程序全攻略

    http://blog.chinaunix.net/uid-20733992-id-3447120.html http://blog.chinaunix.net/space.php?uid=16480 ...

  2. [转载] Linux下高并发socket最大连接数所受的各种限制

    原文: http://mp.weixin.qq.com/s?__biz=MzAwNjMxNjQzNA==&mid=207772333&idx=1&sn=cfc8aadb422f ...

  3. Linux下高并发socket最大连接数所受的各种限制

    http://blog.csdn.net/guowake/article/details/6615728 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行 ...

  4. Linux配置支持高并发TCP连接(socket最大连接数)

    Linux配置支持高并发TCP连接(socket最大连接数) Linux配置支持高并发TCP连接(socket最大连接数)及优化内核参数 2011-08-09 15:20:58|  分类:LNMP&a ...

  5. Linux下高并发socket最大连接数

    http://soft.chinabyte.com/os/285/12349285.shtml (转载时原文内容做个修改) 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是 ...

  6. Linux下高并发socket最大连接数各种限制的调优

    1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每 ...

  7. Linux下高并发socket最大连接数所受的各种限制(转)

    1.修改用户进程可打开文件数限制在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个 ...

  8. 高并发TCP连接数目问题

    linux可通过五元组唯一确定一个链接:源IP,源端口,目的IP,目的端口,传输层协议.而一个端口不允许被两个及以上进程占用(一个进程可同时占用多个端口),据此是否可以推测一台linux服务器最多可以 ...

  9. Linux下高并发socket最大连接数所受的各种限制(详解)

    1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每 ...

随机推荐

  1. 多种细分方式浏览销售数据,IAP助您有效洞察市场收益效果

    华为应用内支付服务是直接在应用程序内提供购买商品或订阅等功能,为了能够让开发者更好的了解应用内的销售额及商品购买.订阅的市场收益效果,华为应用内支付服务提供的消费数据统计和数据报表,支持多种细分方式浏 ...

  2. python3 输出字符

    import string'''whitespace -- a string containing all ASCII whitespaceascii_lowercase -- a string co ...

  3. RabbitMQ 入门 (Go) - 1. 简介和安装

    Message Broker(消息代理) 维基百科对 Message Broker 的定义是:Message broker 是一种中介程序模块,它把消息从发送方的正式消息传递协议转化为接收方的正式消息 ...

  4. 痞子衡嵌入式:从头开始认识i.MXRT启动头FDCB里的lookupTable

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT启动头FDCB里的lookupTable. 一个MCU内部通常有很多外设模块,这些外设模块是各MCU厂商做差异化产品的本质, ...

  5. [Fundamental of Power Electronics]-PART I-2.稳态变换器原理分析-2.2 伏秒平衡/安秒平衡 小纹波近似

    2.2 电感伏秒平衡.电容充放电平衡以及小纹波近似 让我们更加仔细地观察图2.6中的buck变换器的电感和电容的波形.我们是不可能设计一个滤波器能够只允许直流分量通过而完全滤除开关频率次谐波的.所以, ...

  6. Java代码格式化规范实践总结

    目标说明 统一良好的代码格式规范可以有效提升开发团队之间的「协作效率」,如果不同的开发团队或者开发人员采用不同的代码格式规范,那么每次Format代码都会导致大量的变化,在Code Review及Me ...

  7. Python基础之:struct和格式化字符

    目录 简介 struct中的方法 格式字符串 字节顺序,大小和对齐方式 格式字符 格式数字 格式字符 格式字符串 填充的影响 复杂应用 简介 文件的存储内容有两种方式,一种是二进制,一种是文本的形式. ...

  8. "Unmapped Spring configuration files found.Please configure Spring facet."解决办法

    最近在学习使用IDEA工具,觉得与Eclipse相比,还是有很多的方便之处. 但是,当把自己的一个项目导入IDEA之后,Event Log提示"Unmapped Spring configu ...

  9. 【项目】手写FTP服务器-C++实现FTP服务器

    X_FTP_server 手写FTP服务器-C++实现FTP服务器 项目Gitee链接:https://gitee.com/hsby/ftp_Server 简介 一个基于libevent的高并发FTP ...

  10. Python中的多进程、多线程和协程

    本文中的内容来自我的笔记.撰写过程中参考了胡俊峰老师<Python程序设计与数据科学导论>课程的内容. 并发处理:多进程和多线程 前置 概念: 并发:一段时间内同时推进多个任务,但不一定要 ...