windows IOCP 实践

关于 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 实践

时间: 2024-11-08 09:30:32

windows IOCP 实践的相关文章

使用Samba实现Linux与Windows文件共享实践

前言 一直以来都以为FTP和NFS是局域网文件共享的常用方式,但是在最近接触Samba之后,了解到一些用户需要简化访问学习成本,满足基础的权限控制管理,并支持实时编辑和保存文件,我才明白这些需求使用之前的方法都是很难满足的,而Samba却可以完美的支持上述需求,虽然在开始接触时花了一些时间学习,但把配置和语法梳理清楚之后就很简单了. Unix与Windows文件共享的最佳方式之一 更新历史 2015年07月11日 - 初稿 阅读原文 - http://wsgzao.github.io/post/

WSL与Windows交互实践

1. WSL是什么 2. WSL新特性 3. WSL管理配置 4. WSL交互 5. 解决方案 ?* 5.1 使用别名 ?* 5.2 多复制一份 ?* 5.3 重定向 ?* 5.4 symlink 6. 其他 ?* 6.1 闲聊 ?* 6.2 参考 1. WSL是什么 ? WSL 是Windows Subsystem for Linux 的简称,主要是为了在Windows 10上原生运行Linux二进制可执行文件(ELF格式),而提供的兼容层. 通俗来讲是在Windows10 嵌入了个Linux

架构设计:系统间通信(5)——IO通信模型和JAVA实践 下篇

接上篇:<架构设计:系统间通信(4)--IO通信模型和JAVA实践 中篇>,我们继续讲解 异步IO 7.异步IO 上面两篇文章中,我们分别讲解了阻塞式同步IO.非阻塞式同步IO.多路复用IO 这三种IO模型,以及JAVA对于这三种IO模型的支持.重点说明了IO模型是由操作系统提供支持,且这三种IO模型都是同步IO,都是采用的"应用程序不询问我,我绝不会主动通知"的方式. 异步IO则是采用"订阅-通知"模式:即应用程序向操作系统注册IO监听,然后继续做自己

Windows API参考大全新编

书名:新编Windows API参考大全 作者:本书编写组 页数:981页 开数:16开 字数:2392千字 出版日期:2000年4月第二次印刷 出版社:电子工业出版社 书号:ISBN 7-5053-5777-8 定价:98.00元 内容简介 作为Microsoft 32位平台的应用程序编程接口,Win32 API是从事Windows应用程序开发所必备的.本书首先对Win32 API函数做完整的概述:然后收录五大类函数:窗口管理.图形设备接口.系统服务.国际特性以及网络服务:在附录部分,讲解如何

Java多线程:Linux多路复用,Java NIO与Netty简述

JVM的多路复用器实现原理 Linux 2.5以前:select/poll Linux 2.6以后: epoll Windows: IOCP Free BSD, OS X: kqueue 下面仅讲解Linux的多路复用. Linux中的IO Linux的IO将所有外部设备都看作文件来操作,与外部设备的操作都可以看做文件操作,其读写都使用内核提供的系统调用,内核会返回一个文件描述符(fd, file descriptor),例如socket读写使用socketfd.描述符是一个索引,指向内核中一个

EQueue - 一个纯C#写的分布式消息队列介绍2

一年前,当我第一次开发完EQueue后,写过一篇文章介绍了其整体架构,做这个框架的背景,以及架构中的所有基本概念.通过那篇文章,大家可以对EQueue有一个基本的了解.经过了1年多的完善,EQueue无论是功能上还是成熟性上都完善了不少.所以,希望再写一篇文章,介绍一下EQueue的整体架构和关键特性. EQueue架构 EQueue是一个分布式的.轻量级.高性能.具有一定可靠性,纯C#编写的消息队列,支持消费者集群消费模式. 主要包括三个部分:producer, broker, consume

异步IO

1. http://blog.csdn.net/ajian005/article/details/18054009 http://janeky.iteye.com/blog/1073695 http://elf8848.iteye.com/blog/1751384 http://elf8848.iteye.com/blog/1751375 http://www.iteye.com/topic/472333 windows iocp异步,linux epoll模拟异步 文件异步IO http://

libevent学习七

Bufferevents:概念和基础 很多时候,一个程序需要处理一些数据的缓存,不止应用在答复event上.例如:当我们需要去写出数据,通常会这样做: 1. 发现有数据需要写出到一条连接上:把这些数据放到buffer里. 2. 等连接变成可写的状态. 3. 尽可能的写入数据. 4. 记住我们写了多少数据,然后如果数据没有全部写完,就等连接再次变为可写的状态. 这种IO缓冲模式已经足够Libevent的日常使用.一个"bufferevent"是由一个底层传输渠道(如socket),一个读

关于Archlinux

既然都开了博客不写点东西总感觉不好 之前装过无数操作系统,,不论是一开始人的windows(实践证明windows打游戏还是很不错的),到kaili,到黑苹果(这是我装过最难的,没有之一!!!),最后到Arch(不要问我为什么不装ubuntu),基本上市面有的系统都在这台坚强的小黑上装过了(简直太坚强了!).就拿我这台机子来讲(thinkpad t440p)Archlinux应该是最好用的,兼容性极佳. 安装过程不想介绍,毕竟网上一抓一大把,跟着wiki装就好(万能的wiki    https: