前言

传统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. 攻防世界 reverse debug

    debug  XCTF 3rd-GCTF-2017 .net程序,这里我用的dnspy,当然.net Reflector也很好用. 查看程序,发现是明文比较,下断,debug,完成. flag{967 ...

  2. warpperspective 透视变化的opencv实现

    warpperspective 透视变化的opencv2.0实现 1st-------2nd | | | | | |3rd-------4th 原始代码              cv::Mat sr ...

  3. 了解 Vue 的 Compsition API

    在这篇文章中,我将讲讲 Vue 的 Composition API 为什么比之前的 Options API 要好,以及它是如何工作的. Options API 有什么问题 首先,这里不是要大家放弃 O ...

  4. [二分匹配]Asteroids

    A s t e r o i d s Asteroids Asteroids 题目描述 Bessie wants to navigate her spaceship through a dangerou ...

  5. 【ProLog - 3.0 进阶:递归】

    [ProLog中的递归] 如果递归中的一个或多个规则引用谓词本身,则对该谓词使用"递归"定义 在使用时,这往往像一条食物链或者族谱的构成(A的爸爸的爸爸,即A的爷爷,是A的长辈) ...

  6. 个人阅读作业#2——软工模式&CI/CD

    项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 个人阅读作业#2 我在这个课程的目标是 从实践中学习软件工程相关知识(结构化分析和设计方法.敏捷开发方法.软 ...

  7. 记一次metasploitable2内网渗透之512,513,514端口攻击

    512,513,514端口都是R服务: TCP端口512,513和514为著名的rlogin提供服务.在系统中被错误配置从而允许远程访问者从任何地方访问(标准的,rhosts + +). 默认端口:5 ...

  8. 自动化kolla-ansible部署ubuntu20.04+openstack-victoria之镜像制作centos7.8-15

    自动化kolla-ansible部署ubuntu20.04+openstack-victoria之镜像制作centos7.8-15 欢迎加QQ群:1026880196 进行交流学习   制作OpenS ...

  9. Android+Spring Boot 选择+上传+下载文件

    2021.02.03更新 1 概述 前端Android,上传与下载文件,使用OkHttp处理请求,后端使用Spring Boot,处理Android发送来的上传与下载请求.这个其实不难,就是特别多奇奇 ...

  10. linux 更新python3.8

    1 下载源码 地址 选版本下载即可,目前最新为3.8.2版本. 2 解压 tar -zxvf Python-3.8.2.tgz cd Python-3.8.2 3 新建安装目录 安装目录在/usr/l ...