关于 windows IOCP

有人说 windows IOCP 是 windows 上最好的东西。 IOCP 是真正的异步 IO,意味着每次发起一个 IO 请求,该调用本身则立即返回, 而包括 IO 操作和数据从内核缓冲区到用户缓冲区之间的拷贝都由系统完成,直到这个过程结束系统才通知用户进程。 linux 上没有这样的异步 IO。

IOCP 的使用

  1. 创建一个新的完成端口。完成端口被设计成与一个线程池相互合作,线程池的线程并发的用来处理完成的 IO 通知。CreateIoCompletionPort这个 API 用于创建 IOCP, 最后一个参数则是指定线程池中线程个数,一般来说取 CPU * 2 ,这样可以最充分使用多核 CPU ,又降低了线程间的切换。CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, dwNumberOfConcurrentThreads)
  2. 创建工作线程。
  3. 关联一个 IO 设备到完成端口。也是调用CreateIoCompletionPort(API 设计有些太随意了吧,难道有什么历史原因?)。
    HANDLE h = CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, 0);
  4. 使用 overlapped IO ,例如 socket 的 WSARecv/WSASend,甚至 AcceptEx 和 ConnectEx。这些调用都只是发起一个 IO 请求然后立即返回。函数调用都需要初始化一个 OVERLAPPED 结构体,后面有提到其作用。
  5. 工作线程是一个 loop, 阻塞在 GetQueuedCompletionStatus 调用上。GetQueuedCompletionStatus 返回时从 IO 完成队列中取出一个 completion packet。线程池线程阻塞时是由系统负责完成调度的。

IOCP 内部的一些数据结构

  1. Device List:包含所有与完成端口相关联的设备的一个列表。
  2. I/O Completion Queue(FIFO):当一个异步 IO 请求完成了,系统会去检查是否这个 IO 设备与任何 IO 完成端口关联了,如果是,系统会在 IO 完成端口队列的末尾添加一个 completion packet(以 FIFO 的顺序入队),GetQueuedCompletionStatus 就是在这个队列上等待。
  3. IOCP 关联线程等待队列:线程池中的线程调用GetQueuedCompletionStatus时,就会被放进一个等待队列,IO 完成端口内核对象根据此队列知道有哪些线程在等待处理completion packet。线程等待队列是按照 LIFO 的方式入队的,也就是当有一个 completion packet 到来时,系统先唤醒最后调用GetQueuedCompletionStatus进入等待队列的线程。

IOCP 和线程池的相互作用

  1. 任何线程都可以调用GetQueuedCompletionStatus来与一个 IO 完成端口关联起来,但是一个线程只能关联一个 IOCP,当线程退出或者指定了其他的 IOCP或者关闭了 IOCP,线程才与这个 IOCP 解开绑定。
  2. 创建 IOCP 的时候会指定一个并发值,虽然任意个线程可以关联到这个 IOCP,但是并发值限定了可以同时运行的线程数。假设这样的场景,有一个并发值为 1 的 IOCP,但是有多于一个的线程关联到了这个 IOCP,如果完成队列中总是有一个 completion packet 在等待,当正在运行的线程调用GetQueuedCompletionStatus时就会立即返回,该线程处理完这个 completion packet 再次调用GetQueuedCompletionStatus又会立即返回。在处理 completion packet过程中,虽然完成队列中始终有 completion packet 待处理,但是因为并发值为 1 的原因,系统不会去调度其他线程来执行,尽管关联 IOCP 的线程不止一个。同时也避免了线程切换的开销,因为始终都是这一个线程在执行。
  3. 在上述情况中,看起来线程池中关联的其他线程毫无用处,但是其实是没有考虑到正在运行的线程进入等待状态或者因为某种情况与该 IOCP 解除绑定时的情况。如果正在运行的线程调用Sleep, WaitFor*,或者一个同步 IO 函数,或者任何可以引起当前线程从运行状态变为等待状态的函数时,IOCP 就会立即调度其他关联的线程,维持始终有一个线程在运行。

IOCP 使用过程中遇到的问题

  1. 因为涉及到多线程会比 epoll + 单线程要编码复杂。
  2. API 设计比较糟糕,这也加大了编码难度。
  3. 文档描述不清晰,甚至没有一个官方的示例程序,非官方文档或者程序或多或少有些错误,让人难以放心使用。以 WSARecv 为例子,MSDN上描述若 WSARecv 能立即返回,返回值为 0。这是不是意味着程序要在两处处理 IO 完成的情况,一处是 IO 立即返回时,一处是工作线程GetQueuedCompletionStatus等待 IOCP 完成队列处。几乎所有的异步 IO 函数都是如此。但是所幸似乎即使立即返回 0 ,完成队列中也会有一个 completion packet,所以只在工作线程中的完成队列中等待 IO 完成也不会出错。
  4. 一般的使用 TCP 进行通讯的网络程序,因为 TCP 流无界的特性,都会自定义成这样的应用网络数据包:前面几个字节代表该包的长度,后面就是该包真正的内容。应用程序在解包的时候,对应的要先获取包的长度,再截取对应长度的包数据。这样的过程在多线程的 IOCP 会比较困难,多个线程取到了各个数据包的不同部分,而且因为 completion packet 的出队顺序并不能保证,各个线程获取的数据包之间的顺序已经丢失了。因此,必须想办法解决包的顺序问题,而且解包过程需要同步各个线程。这样无疑使得代码变得更复杂。
  5. IOCP 作为异步 IO ,可以非常方便的发起 IO ,但是每次发起 IO 时候都必须提交一段用户内存,在 IO 完成之前这段内存必须是被锁住的,既你不能再使用。当然这不是 IOCP 的问题,这是异步 IO特性决定的。

一个收发 TCP 应用协议包的程序示例

  1. 协议包定义成头两个字节保存包长度 len,包头后面 len 字节是包的具体内容。为了简化编码,又能利用到 IOCP 一些特性,决定只启动一个工作线程处理所有的 IO 完成操作,发包和收包都是非阻塞的异步调用。
  2. 提交给异步 IO 的 buffer,都是从一段预先分配的内存中取出来的,这样使得IO 操作使用的内存是可控的,并且不会有内存碎片,充分使用内存。
  3. IOCP 的几个核心 API 都与参数 completionKey, overlapped 有关。在程序中 completionKey 可以对应是对哪个 socket 进行操作,overlapped 则对应成具体哪一个 IO 操作。
  4. 同一时间只允许一个同类的 IO 操作(读或者写)在提交。

代码在此,服务端程序比较简单,可以自己实现并验证。

windows IOCP 实践的更多相关文章

  1. Docker for Windows(五)实践搭建SqlServer服务&执行数据库操作

    上一篇我们已经搭建了一个mysql数据库服务了:Docker for Windows(四)实践搭建&删除MySQL服务,发现用Docker确实是方便且容易,但上一篇主要是服务的搭建删除等基础操 ...

  2. windows—IOCP

    一.重叠I/O回声服务器端 服务端: #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #d ...

  3. 【笨嘴拙舌WINDOWS】实践检验之剪切板查看器【Delphi】

    该程序能够监视Windows剪切板的内容(文字和图片) 其思路是 先调用SetClipBoardViewer(Self.Handle),让Windows剪切板内容发生改变之后,通知本程序: 然后截获W ...

  4. 【笨嘴拙舌WINDOWS】实践检验之按键精灵【Delphi】

    通过记录键盘和鼠标位置和输入信息,然后模拟发送,就能够创建一个按键精灵! 主要代码如下: library KeyBoardHook; { Important note about DLL memory ...

  5. 【笨嘴拙舌WINDOWS】实践检验之屏幕取色

    实践是检验真理的唯一标准 要取得屏幕的颜色,首先需要创建一个屏幕DC,然后使用该DC,调用GetPixel就可以了 "Note:GetPixel传入的DC应该是屏幕的DC,而不是桌面的DC, ...

  6. WSL与Windows交互实践

    1. WSL是什么 2. WSL新特性 3. WSL管理配置 4. WSL交互 5. 解决方案  * 5.1 使用别名  * 5.2 多复制一份  * 5.3 重定向  * 5.4 symlink 6 ...

  7. windows IOCP入门的一些资料

    最近需要看一个中间件的IOCP处理模块,需要了解这方面的知识 https://www.codeproject.com/articles/13382/a-simple-application-using ...

  8. Docker for Windows(四)实践搭建&删除MySQL服务

    我们已经下载安装好了Docker for Windows:Docker for Windows(一)下载与安装,也简单了解了Docker常用命令:Docker for Windows(三)Docker ...

  9. 使用 IIS 在 Windows 上托管 ASP.NET Core(Windows安装实践)

    原文地址 https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.0&tabs= ...

随机推荐

  1. Centos7对外开放端口

    (1)查看对外开放的端口状态 查询已开放的端口 netstat -anp 查询指定端口是否已开 firewall-cmd --query-port=666/tcp 提示 yes,表示开启:no表示未开 ...

  2. 【代码周边】npm是What?

    社区 程序员自古以来就有社区文化: 社区的意思是:拥有共同职业或兴趣的人们,自发组织在一起,通过分享信息和资源进行合作.虚拟社区的参与者经常会在线讨论相关话题,或访问某些网站.前端程序员也有社区,世界 ...

  3. git 只提交部分文件 拉取pull远程仓库

    正常的团队开发中,在提交代码push之前要先pull拉取远程仓库的代码.而拉取pull操作需要先commit修改的文件才能成功. 那么问题来了,当我只想push提交部分代码的时候,同样需要pull拉取 ...

  4. Git全栈开发者使用指南

    一.Git基础 1.Git简介 Git是一种分布式版本控制系统,由Linux之父Linus开发. 所谓分布式版本管理系统,就是在每一台机器上都有一个完整的仓库. 2.Git官网 Git官网:git 在 ...

  5. reactor模式前序(二):NIO WEB服务器设计

    前文介绍了传统IO的WEB经典服务器 reactor模式前序:传统IO的WEB服务器设计 下面看看JAVA NIO的WEB服务器设计 NIO是基于事件驱动的,对于NIO来说,重要组件是Selector ...

  6. [ABP教程]第六章 作者:领域层

    Web开发教程6 作者:领域层 关于此教程 在这个教程系列中,你将要构建一个基于ABP框架的应用程序 Acme.BookStore.这个应用程序被用于甘丽图书页面机器作者.它将用以下开发技术: Ent ...

  7. Thread线程控制之sleep、join、setDaemon方法的用处

    Thread线程控制之sleep.join.setDaemon方法的用处 1. sleep方法 public static void sleep(long millis) throws Interru ...

  8. WPF学习笔记02_布局

    布局原则 WPF窗口只能包含单个元素.如果要放置多个元素,需要放置一个容器,然后在容器中添加元素. 不应显示的设定元素的尺寸 不应该使用屏幕坐标指定元素的位置 布局容器的子元素"共享&quo ...

  9. IE浏览器兼容问题总结

    IE浏览器兼容问题总结 引自掘金:https://juejin.cn/post/6844903825854185480 一.标准盒模型和怪异盒模型 浏览器的盒子模型分为两类: 标准的W3C盒子模型. ...

  10. Java安全之ysoserial-JRMP模块分析(一)

    Java安全之ysoserial-JRMP模块分析(一) 首发安全客:Java安全之ysoserial-JRMP模块分析(一) 0x00 前言 在分析到Weblogic后面的一些绕过方式的时候,分析到 ...