前言

传统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. Ignatius and the Princess III HDU - 1028

    题目传送门:https://vjudge.net/problem/HDU-1028 思路:整数拆分构造母函数的模板题 1 //#include<bits/stdc++.h> 2 #incl ...

  2. Java 添加数字签名到Excel以及检测、删除签名

    Excel中可添加数字签名以供文档所有者申明文档的所有权或有效性.文本以Java代码示例介绍如何在Excel文档中对数字签名功能进行相关操作,包括如何添加签名到Excel.检测Excel文档是否已签名 ...

  3. Django 模板 render传参不转码

    今天通过Django后端向前端页面传递一行js代码,却发现符号被转码了导致代码不能执行 Django代码 HTML代码 实际生成页面代码 我们可以看到实际代码中的引号被转义,导致代码不能执行, 解决方 ...

  4. 【RTOS】堆栈与任务栈

    目录 前言 概念 双堆栈指针 要点 Cortex-M3寄存器介绍 寄存器图 简要介绍 知识 出入栈 入栈(压栈) 出栈 重点知识 异常的响应序列* 入栈 取向量 更新寄存器 小结知识* FreeRTO ...

  5. Flutter原理简介

    Flutter 是怎么运转的? 与用于构建移动应用程序的其他大多数框架不同,Flutter 是重写了一整套包括底层渲染逻辑和上层开发语言的完整解决方案.这样不仅可以保证视图渲染在 Android 和 ...

  6. ATMS中去pause Activity A.

    上文写完之后,感觉这个部分写的还是不清晰,本文继续补充一下. 首先还是看堆栈. obtain:78, PauseActivityItem (android.app.servertransaction) ...

  7. 【Azure 微服务】Service Fabric中微服务在升级时,遇见Warning - System.Collections.Generic.KeyNotFoundException 服务无法正常运行

    问题描述 使用.Net Framework 4.5.2为架构的Service Fabric微服务应用,在升级后发布到Azure Fabric中,服务无法运行.通过Service Fabric Expl ...

  8. CIE标准色度系统(下)

    四.色温与相关色温 根据绝对黑体光谱分布特性的普朗克定律,由普朗克公式可以计算出黑体对应于某一温度的光谱分布,并由此应用CIE标准色度系统可获得该温度下黑体发光的三刺激值和色品坐标,从而在色品图上得到 ...

  9. OO第二章总结

    OO第二章总结 电梯作业终于结束了!!! 这三周作业用多线程模拟搭建电梯的运行,我从开始对多线程的一无所知到结束时的能够完成一些多线程任务的水平,进步还是蛮大的,尽管过程有点艰难. 一.复杂度与UML ...

  10. NPM 与 NPX 区别

    NPM 和 NPX 区别 NPM Node Package Manager npm 是 Node.js 的软件包管理器,其目标是自动化的依赖性和软件包管理 NPX npx 是执行 Node 软件包的工 ...