在windows下开发高性能服务器,通常采用的是iocp的模式。下面讨论几个常见的问题、解决方式。

1、WSANOBUF问题

假如我们有10000个客户端socket连接,为了接收他们发送过来的数据,我们需要预先投递10000个WSARecv。
假如每个异步读需要应用层程序员提供10k的缓冲区,则一共需要的用户缓冲区为 10000*10k=97M 内存。windows要求这97M数据必须被OS“锁定”,意思大体是需要占用大量的OS的资源了。所以程序很可能会因为10000个客户同时连接,而耗尽资源。WSAENOBUF错误同此有关。
解决方法是投递0字节数请求的WSARecv。伪代码如下:
WSABUF DataBuf;
DataBuf.len=0;
DataBuf.buf=0;
WSARecv(socket, &DataBuf, 1,...);
当有数据到来时,这个异步IO会从角色2线程中得到结果。由于它是0字节的读,所以它没有触碰任何socket缓冲区的到来的任何数据。我们付出很小的成本(大约每个连接节省了10k)就能知道哪个客户端的数据到来了。别小看了每个连接节省了这么点资源,连接数大了节约的总量就很可观了。如果客户端数量很少,这个技巧就没什么意思了。

2、OnOrderRead的问题

3、多次投递的处理

4、如何优雅的杀死线程(或者退出iocp)

PostQueuedCompletionStatus函数会向{IOCP完成队列C}中push进去一条记录。这样角色2线程就能获得这个“虚伪或模拟”的异步IO完成事件。为什么要“假冒”一条{IOCP完成队列C}的条目呢?用处吗,程序员自己去想吧(意思是用处多多了)。一般来说,我们用它“优雅的杀死角色2线程”。伪代码如下:
typedef struct
{
   OVERLAPPED Overlapped;
   OP_CODE op_type; 
   ...
} PER_IO_DATA;
PER_IO_DATA* PerIOData = ...
PerIOData->op_type = OP_KILL; //操作类型是杀死线程
PostQueuedCompletionStatus(...PerIOData...); 
//如果有N个角色2线程,则需要调用N次,这样{IOCP完成队列C}中才能有N个这个的条目

5、getqueuedcompletionstatus()是否需要互斥?

这个不需要的。

6、错误处理:包含995、64之类的

GetQueuedCompletionStatus函数的错误处理比较复杂。
1 如果GetQueuedCompletionStatus返回false:
1.1 如果Overlapped指针非空
    恭喜你,你投递的异步IO获得结果了,只不过是失败的结果。好孬也终于回来个信儿了。
    这可能是socket连接断了等等。
    1.1.1 如果GetLastError获得的错误号为ERROR_OPERATION_ABORTED
          一定是有东西调用了CancelIO(socket)了。所有同这个socket相关的异步IO请求都会被取消。
    1.1.2 如果GetLastError 获得的错误号为其他的东西
          可能是IO没成功,如socket连接断开了等等。
1.2 如果Overlapped指针空
    这可不是好消息,因为这意味着IOCP本身有重大故障了。比如我们意外的把IOCP的句柄CloseHandle了。
    1.2.1 如果GetLastError获得的错误号为WAIT_TIMEOUT
          可能GetQueuedCompletionStatus设置的超时参数dwMilliseconds不是INFINITE。我们继续调用GetQueuedCompletionStatus重新等待吧。
    1.2.1 如果GetLastError获得的错误号ERROR_ABANDONED_WAIT_0, 或者其他
          IOCP本身都完蛋了,角色2线程应另找东家了,或者就地自我了断算了。
2 如果GetQueuedCompletionStatus返回true:
  恭喜你,异步IO成功了。
  通过lpNumberOfBytes, lpCompletionKey, and lpOverlapped这三个参数获得详细信息。
  lpNumberOfBytes:实际传输的字节数。(可能比需要传输的字节数少)
  lpCompletionKey:这就是著名的PerHandleData,可以知道这是哪个socket连接的。
  lpOverlapped:   这就是著名的PER_IO_DATA, 同某次异步IO调用关联,
        比如某次WSASend(Overlapped参数=0x123)调用,这里能重新拿到lpOverlapped==0x123。
我们可以根据这个指针,得知这个IO结果是对应着哪次WSASend()调用的结果。         
我满以为这个错误处理天衣无缝,直到有一次测试。我对一个socke投递了100个WSARecv。当我故意把客户端关闭后,这些异步IO不出意外的都在角色2线程的GetQueuedCompletionStatus函数处获得结果了。令我吃惊的是,GetQueuedCompletionStatus返回为TRUE!!!,并且GetLastError()返回值是0!!!
令我欣慰的是lpNumberOfBytes值为0(否则真见鬼了)。所以看到GetQueuedCompletionStatus返回true,不要高兴的太早了。
2.1 把lpOverlapped指针解释成PER_IO_DATA数据结构。如果PerIOData->op_type == OP_KILL,可能这个是PostQueuedCompletionStatus伪造的一个IO完成事件。
2.2 判断是否(lpNumberOfBytes==0)。如果这个IO结果的确是某个WSAxxx()的结果,而不是PostQueuedCompletionStatus伪造的,则这个IO对应的socket可能断了。
2.3 (lpNumberOfBytes>0) ,这才是真正的IO完成的事件呢。可能99.9%的机会,分支跑到这里的。
 
【在同一个socket上一次投递多个异步IO】
一次投递多个WSASend(1234,&Buff1,...); WSASend(1234,&Buff2,...); ... 好像没问题。
如果一次投递多个WSARecv(1234,&Buff1,...);WSARecv(1234,&Buff2,...);好像有些需要阐明的问题。
第一:Windows保证按照你投递WSARecv的顺序,把网络上到达的数据按先后顺序放入Buff1,Buff2。
      如果网络上到来的数据为 AAAAUUUU, 假设Buff1长度4,Buff2长度4,
      则保证Buff1获得AAAA,Buff2获得UUUU
第二:如果有多个角色2线程,可能由于线程调度的“竞争条件race condition”,
      某线程首先执行Buff2的完成处理过程。
      如果我在角色2线程中,打印出收到的数据,可能打印出如下结果:UUUUAAAA。这绝不是违反了TCP协议,       而是多线程的问题。其实解决方案很简单。说者费事,上伪代码
typedef struct
{
   OVERLAPPED Overlapped;
   ...
   int Package_Number; //我对每一次IO,夹带本次调用顺序号
   ...
} PER_IO_DATA;
PER_IO_DATA* PerIOData1=...
PerIOData1->Package_Number = 1 ; //第一次调用
WSARecv(1234, &Buff1,...PerIOData1...);
PER_IO_DATA* PerIOData2=...
PerIOData1->Package_Number = 2 ; //第二次调用
WSARecv(1234, &Buff2,...PerIOData2...);
我们需要维护某种数据结构,记住我们发出了两个WSARecv。
当收到IO结果后,程序需要判断,只有1,2两个调用都从角色2线程获得结果后,才能按顺序把Buff1和Buff2拼接,就是符合顺序的AAAAUUUU。当然,还有其他更好的方式,这里只展示基本原理。
第三:真有必对同一个socket一次投递多个WSARecv吗?
      这个问题同【IOCP系统资源耗尽的问题】,不矛盾。我们假设在投递多个WSARecv时,已经预见到网络上将到来某个socket的大量数据。 根据网络资料介绍,这样可以充分发挥多CPU并发运算的能力。我想在双核CPU机器上,一个CPU处理Buff1,同时另一个CPU处理Buff2。
      如果是少量客户端连接,每个连接可能突然发生大量数据的传送,这个做法可能能加快从Socket缓冲区拷贝数据到应用程序Buff的速度(个人揣测)。
      如果是大量客户端(10000)连接,每个连接传送的数据量很少,这个做法我个人认为没什么意义。我想CPU数量就2个,不会轻易就闲下来吧?
      有一个重要原因,需要投递多个buffer给windows。假如我预计到某个socket一次传过来2M的数据,而
我没有2M大小的buffer,我只有1M大小的buffer。我需要先调用一次WSARecv,等待收完这1M数据后,再发一个
WSARecv。或者我用其他方法,提供给windows系统2个1M的buff。
第四:假设我们真需要一次投递多个Buff,接收数据,有必要用多次WSARecv调用吗?
      这里有个可能的替代做法,上伪代码:
      char *raw1 = new char[BUFF_SIZE];
      WSABUF[2] wsabuf;
      wsabuf[0].buf = raw1 ;
      wsabuf[0].len = BUFF_SIZE;
      char *raw2 = new char[BUFF_SIZE];
      wsabuf[1].buf = raw2 ;
      wsabuf[1].len = BUFF_SIZE;
      WSARecv(1234, &wsabuf, 2 ... );  
      //重点在参数2上,指示了WSABUF结构体的个数是2个。一般大量IOCP的例子里这个参数都是1
      
      这个方法我认为更简单,不知道是我自己“2”还是网上的其他人“2”,一次发出多个WSARecv,把这些分散的IO收集起来也是费事的事。UNIX系统的scatter-gather IO类似于这个机制
 
结论:如果出现995等错误,就可能是socket本身出现了问题,这时候需要在错误的地方将这个socket释放;
7、如何释放overlapped的数据、防止多次释放??
如果出现错误,我们在App层释放,但这个消息会在getqueuedcompletionstatus()也出现,这样就可能出现多次释放,产生错误。
解决方式是:统一处理资源的释放,在getqueuedcompletionstatus()来做处理。
 
8、accept, wsaaccept, acceptex的区别?
经常在win下面做网络通信的开发,除了accept外还真是很少用另外2个。看来是有必要了解清楚。
简单的理解为:
accept: 遵循bsd规范,是标准的bsd socket。仅仅是接受连接来的socket,是同步的操作;
WSAAccept: 是微软对accept的封装,提供了条件判断函数,也是同步操作;
AcceptEx:支持先创建socket,然后associate;支持一次Accept同时能得到客户端发送来的第一次数据;支持IOCP,异步操作。

高性能服务器开发-iocp相关推荐

  1. Linux 高性能服务器开发笔记:Reactor 模型定时器 | 网络编程定时器

    本文主要根据游双书本 Linux 高性能服务器开发 学习分析 linux 网络编程常用到的定时器模型,配备详细理解和分析,同时分析了 Linux 内核中定时器的低精度时间轮和高精度定时器实现思路还有 ...

  2. 高性能服务器开发 2018 年原创汇总

    2018 年就这样过去了,总结一下 2018 年『高性能服务器开发』公众号发表的一些原创文章,欢迎读者鉴阅. 面试求职 写给那些傻傻想做服务器开发的朋友 『腾讯后台开发』实习生技能要求 去BAT,你应 ...

  3. 好教程推荐系列:张小方的《高性能服务器开发》以及《30天自制C++服务器》

    张小方的<高性能服务器开发> 首先感谢技术达人张小方的原创资料分享!!!欢迎关注[高性能服务器开发]微信公众号 1.张小方整理的优质的C++后端开发进阶学习资料 CppGuide: 优质的 ...

  4. Linux高性能服务器开发——进程篇

    本文主要是学习Linux高性能服务器开发需要提前了解的知识,后续还会涉及到虚拟内存方面的内容,各位看官可以多了解了解,看到文章内有将的不清楚或者讲错的地方请各位一定留言,我看到后会第一时间验证并修正的 ...

  5. linux高性能服务器开发十大必须掌握的核心技术

    推荐视频: 全网最详细epoll讲解,6种epoll的设计,让你吊打面试官 150行代码,带你手写线程池,自行准备linux环境 40k技术专家的linux服务器性能优化方法论,异步的效率 c/c++ ...

  6. python高性能服务器编写,Tornado的高性能服务器开发常用方法

    最近一直开发AI人脸识别相关的项目,需要提供给客户一些服务,所以我需要开发一些服务端程序.由于AI算法都是用python3写的,所以我就索性用起了python开发服务端,毕竟速度也快,以前用过Flas ...

  7. 图解高性能服务器开发两种模式,第四章 NETTY高性能架构设计

    目录 一.NIO存在问题以及Netty的优点 二.线程模型基本介绍 三.工作原理图(传统同步阻塞式IO) 四.Reactor模式 五.单Reactor单线程 六.单Reactor多线程 七.主从REA ...

  8. c++高性能服务器开发01-环境搭建,相关基础概念,Linux系统相关函数

    1.安装Ubuntu18 openssh-server net-tools pwd ls vscode 插件:remote c++ 配置公钥 windows 公钥复制到Linux的./ssh/auth ...

  9. 001.从零到1之Linux高性能服务器开发

    作者之前也写了很多关于socket编程以及一些多进程多线程的博客了,所以作者打算在这个新的专栏开始.完成一个完整的项目. 涉及的内容:(有可能后续会添加或者删除,按照项目的进度来定) shell so ...

最新文章

  1. django两个服务器之间的通讯
  2. PHP的抽象类的一段简单代码示例
  3. mysql mongo关联查询语句_MongoDB 集合间关联查询后通过$filter进行筛选
  4. 以图搜图 图像匹配_图像匹配,基于深度学习DenseNet实现以图搜图功能
  5. git-工作区与暂存区
  6. java调用数据库存储过程_Java调用SQL Server的存储过程详解
  7. 一种语音控制PPT翻页系统的制作方法
  8. 项目成本管理---控制成本
  9. seo教程之对搜索引擎的研究
  10. Spring框架---全面详解【无比详细,学习总结】
  11. SQL SERVER2008 R2 ,求教,故障日志不断增大,撑爆硬盘,谢谢。
  12. 人工智能方向本科生如何查看论文?
  13. td-agent windows 安装步骤
  14. 使用git工具提交上传代码到GitHub上或者远程仓库
  15. 强化学习精要-第二部分-蒙特卡罗、TD、DQN
  16. 攻防世界web进阶区Web_php_wrong_nginx_config详解
  17. 【调剂】中国矿业大学接收调剂研究生,资源与环境矿业工程
  18. Appium-实现手势密码登陆
  19. python通过pil为png图片填充上背景颜色的代码
  20. 一天一道LeetCode(61-90)

热门文章

  1. File类遍历(文件夹)目录功能
  2. 忽略SQL改造等价性
  3. 方法引用_通过类名引用静态成员方法
  4. android10如何设置打开方式,Android启动方式
  5. java 手写缓存,java手写多级缓存
  6. Shell多线程实现
  7. C语言与Java怎么沟通_c语言初学指针,对于java面向对象的初理解
  8. igs时间和utc_UTC和GMT时间
  9. 【SpringBoot零基础案例07】【IEDA 2021.1】多环境下.yml/.yaml配置文件的使用
  10. Leetcode PHP题解--D5 804. Unique Morse Code Words