Windows下的IOCP模型(一):介绍与简单使用

article/2025/11/8 10:31:47

一、IOCP简介

	IOCP(I/O Completion Port,I/O完成端口)是Windows操作系统中伸缩性最好的一种I/O模型。

    I/O 完成端口是应用程序使用线程池处理异步 I/O 请求的一种机制。处理多个并发异步I/O请求时,使用 I/O 完成端口比在 I/O 请求时创建线程更快更高效。

二、IOCP的优势

    I/O 完成端口可以充分利用 Windows 内核来进行 I/O 调度,相较于传统的 Winsock 模型,IOCP 在机制上有明显的优势。

模型机制特性
select模型通过select函数来管理I/O,可以确定一个或多个套接字的状态该模型的优势是程序能够在单个线程内同时处理多个套接字连接,避免了阻塞模式下的线程膨胀
WSAAsyncSelect模型WSAAsyncSelect函数把socket设为非阻塞模式,并为socket绑定一个窗口句柄,依靠Windows的消息驱动机制,通过窗口进行消息接收、事件处理该模型最突出的特点是与Windows的消息驱动机制融合在一起,使得开发带GUI界面的网络程序更简单
WSAEventSelect模型该模型与WSAAsyncSelect模型类似,允许应用程序在一个或多个socket上接收基于事件的网络通知,不过该模型是经由事件对象句柄通知的该模型简单易用,也不需要窗口环境,缺点是最多等待64个事件对象的限制,当socket连接数量增加时,必须创建多个线程来处理I/O
重叠I/O模型该模型引入了重叠数据结构,允许应用程序使用重叠结构一次投递一个或多个异步I/O请求该模型使用Winsock 2.0库的API,如:WSASend、WSARecv等,真正做到了“异步处理”
IOCP模型IOCP模型通过socket绑定完成端口,在socket上投递事件,工作线程在完成端口上轮询接收、处理事件IOCP充分利用内核对象的调度,只使用少量的几个线程来处理所有网络通信,消除了无谓的线程上下文切换,最大限度地提高了网络通信的性能

    相较于传统的Winsock模型,IOCP的优势主要体现在两方面:独特的异步I/O方式和优秀的线程调度机制。

独特的异步I/O方式

    IOCP模型在异步通信方式的基础上,设计了一套能够充分利用Windows内核的I/O通信机制,主要过程为:① socket关联iocp,② 在socket上投递I/O请求,③ 事件完成返回完成通知封包,④ 工作线程在iocp上处理事件。
在这里插入图片描述
    IOCP的这种工作模式:程序只需要把事件投递出去,事件交给操作系统完成后,工作线程在完成端口上轮询处理。该模式充分利用了异步模式高速率输入输出的优势,能够有效提高程序的工作效率。

优秀的线程调度机制

    完成端口可以抽象为一个公共消息队列,当用户请求到达时,完成端口把这些请求加入其抽象出的公共消息队列。这一过程与多个工作线程轮询消息队列并从中取出消息加以处理是并发操作。这种方式很好地实现了异步通信和负载均衡,因为它使几个线程“公平地”处理多客户端的I/O,并且线程空闲时会被挂起,不会占用CPU周期。
    IOCP模型充分利用Windows系统内核,可以实现仅用少量的几个线程来处理和多个client之间的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能。

三、IOCP的使用

    初次学习使用IOCP的朋友在熟悉各个API时,建议参看MSDN的官方文档MSDN

IOCP的使用主要分为以下几步:

  1. 创建完成端口(iocp)对象
  2. 创建一个或多个工作线程,在完成端口上执行并处理投递到完成端口上的I/O请求
  3. Socket关联iocp对象,在Socket上投递网络事件
  4. 工作线程调用GetQueuedCompletionStatus函数获取完成通知封包,取得事件信息并进行处理

1 创建完成端口对象

    使用IOCP模型,首先要调用 CreateIoCompletionPort 函数创建一个完成端口对象,Winsock将使用这个对象为任意数量的套接字句柄管理 I/O 请求。函数定义如下:

HANDLE WINAPI CreateIoCompletionPort(_In_     HANDLE    FileHandle,_In_opt_ HANDLE    ExistingCompletionPort,_In_     ULONG_PTR CompletionKey,_In_     DWORD     NumberOfConcurrentThreads
);

此函数的两个不同功能:

  1. 创建一个完成端口对象
  2. 将一个或多个文件句柄(这里是套接字句柄)关联到 I/O 完成端口对象

    最初创建完成端口对象时,唯一需要设置的参数是 NumberOfConcurrentThreads,该参数定义了 允许在完成端口上同时执行的线程的数量。理想情况下,我们希望每个处理器仅运行一个线程来为完成端口提供服务,以避免线程上下文切换。NumberOfConcurrentThreads 为0表示系统允许的线程数量和处理器数量一样多。因此,可以简单地使用以下代码创建完成端口对象,取得标识完成端口的句柄。

HANDLE m_hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);

2 I/O工作线程和完成端口

    I/O 工作线程在完成端口上执行并处理投递的I/O请求。关于工作线程的数量,要注意的是,创建完成端口时指定的线程数量和这里要创建的线程数量不是一回事。CreateIoCompletionPort 函数的 NumberOfConcurrentThreads 参数明确告诉系统允许在完成端口上同时运行的线程数量。如果创建的线程数量多于 NumberOfConcurrentThreads,也仅有NumberOfConcurrentThreads 个线程允许运行。
    但也存在确实需要创建更多线程的特殊情况,这主要取决于程序的总体设计。如果某个线程调用了一个函数,如 Sleep 或 WaitForSingleObject,进入了暂停状态,多出来的线程中就会有一个开始运行,占据休眠线程的位置。
    有了足够的工作线程来处理完成端口上的 I/O 请求后,就该为完成端口关联套接字句柄了,这就用到了 CreateCompletionPort 函数的前3个参数。

FileHandle:要关联的套接字句柄
ExistingCompletionPort:要关联的完成端口对象句柄
CompletionKey:指定一个句柄唯一(per-handle)数据,它将与FileHandle套接字句柄关联在一起

3 完成端口和重叠I/O

    向完成端口关联套接字句柄之后,便可以通过在套接字上投递重叠发送和接收请求处理 I/O。在这些 I/O 操作完成时,I/O 系统会向完成端口对象发送一个完成通知封包。I/O 完成端口以先进先出的方式为这些封包排队。工作线程调用 GetQueuedCompletionStatus 函数可以取得这些队列中的封包。函数定义如下:

BOOL GetQueuedCompletionStatus([in]  HANDLE       CompletionPort,LPDWORD      lpNumberOfBytesTransferred,[out] PULONG_PTR   lpCompletionKey,[out] LPOVERLAPPED *lpOverlapped,[in]  DWORD        dwMilliseconds
);

参数说明

  1. CompletionPort:完成端口对象句柄
  2. lpNumberOfBytesTransferred:I/O操作期间传输的字节数
  3. lpCompletionKey:关联套接字时指定的句柄唯一数据
  4. lpOverlapped:投递 I/O 请求时使用的重叠对象地址,进一步得到 I/O 唯一(per-I/O)数据

    lpCompletionKey 参数包含了我们称为 per-handle 的数据,该数据在套接字第一次关联到完成端口时传入,用于标识 I/O 事件是在哪个套接字句柄上发生的。可以给这个参数传递任何类型的数据。
    lpOverlapped 参数指向一个 OVERLAPPED 结构,结构后面便是我们称为per-I/O的数据,这可以是工作线程处理完成封包时想要知道的任何信息。

per-handle数据和per-I/O数据结构类型示例

#define BUFFER_SIZE 1024
//per-handle 数据
typedef struct _PER_HANDLE_DATA  
{SOCKET s;            //对应的套接字句柄SOCKADDR_IN addr;    //客户端地址信息
}PER_HANDLE_DATA,*PPER_HANDLE_DATA;
//per-I/O 数据
typedef struct _PER_IO_DATA  
{OVERLAPPED ol;            //重叠结构char buf[BUFFER_SIZE];    //数据缓冲区int nOperationType;       //I/O操作类型
#define OP_READ 1
#define OP_WRITE 2
#define OP_ACCEPT 3
}PER_IO_DATA,*PPER_IO_DATA;

4 示例程序

    主线程首先创建完成端口对象,创建工作线程处理完成端口对象中的事件;然后创建监听套接字,开始监听服务端口;循环处理到来的连接请求,该过程具体如下:

  1. 调用 accept 函数等待接受未决的连接请求
  2. 接受新连接后,创建 per-handle 数,并将其关联到完成端口对象
  3. 在新接受的套接字上投递一个接收请求,该I/O完成后,由工作线程负责处理
void main()
{int nPort = 4567;HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);    //创建完成端口对象::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0);    //创建工作线程//创建监听套接字,绑定到本地地址,开始监听SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN si;si.sin_family = AF_INET;si.sin_port = ::ntohs(nPort);si.sin_addr.S_un.S_addr = INADDR_ANY;::bind(sListen, (sockaddr*)&si, sizeof(si));::listen(sListen, 5);//循环处理到来的连接while (true) {//等待接受未决的连接请求SOCKADDR_IN saRemote;int nRemoteLen = sizeof(saRemote);SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);//接受到新连接之后,为它创建一个per-handle数据,并将它们关联到完成端口对象PPER_HANDLE_DATA pPerHandle = (PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));pPerHandle->s = sNew;memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (DWORD)pPerHandle, 0);//投递一个接收请求PPER_IO_DATA pPerIO = (PPER_IO_DATA)::GlobalAlloc(GPTR, sizeof(PER_IO_DATA));pPerIO->nOperationType = OP_READ;WSABUF buf;buf.buf = pPerIO->buf;buf.len = BUFFER_SIZE;DWORD dwRecv;DWORD dwFlags = 0;::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIO->ol, NULL);}
}

    I/O 工作线程循环调用 GetQueuedCompletionStatus 函数从 I/O 完成端口移除完成的 I/O 通知封包,解析并进行处理。

DWORD WINAPI ServerThread(LPVOID lpParam)
{   //得到完成端口对象句柄HANDLE hCompletion = (HANDLE)lpParam;DWORD dwTrans;PPER_HANDLE_DATA pPerHandle;PPER_IO_DATA pPerIO;while (true) {//在关联到此完成端口的所有套接字上等待I/O完成BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, &dwTrans, (PULONG_PTR)&pPerHandle, (LPOVERLAPPED*)&pPerIO, WSA_INFINITE);if (!bOK) {//在此套接字上由错误发生::closesocket(pPerHandle->s);::GlobalFree(pPerHandle);::GlobalFree(pPerIO);continue;}if (dwTrans == 0 && (pPerIO->nOperationType == OP_READ || pPerIO->nOperationType == OP_WRITE)) {::closesocket(pPerHandle->s);::GlobalFree(pPerHandle);::GlobalFree(pPerIO);continue;}switch (pPerIO->nOperationType){   //通过per-IO数据中的nOperationType域查看有什么I/O请求完成了case OP_READ:  //完成一个接收请求{pPerIO->buf[dwTrans] = '\0';cout << "接收到数据:" << pPerIO->buf << endl;cout << "共有" << dwTrans << "字符" << endl;//继续投递接收I/O请求WSABUF buf;buf.buf = pPerIO->buf;buf.len = BUFFER_SIZE;pPerIO->nOperationType = OP_READ;DWORD nFlags = 0;::WSARecv(pPerHandle->s, &buf, 1, &dwTrans, &nFlags, &pPerIO->ol, NULL);}break;case OP_WRITE: //本例中没有投递这些类型的I/O请求case OP_ACCEPT: break;}}return 0;
}

5 恰当地关闭IOCP

    关闭 I/O 完成端口时,特别是有多个线程在socket上执行 I/O 时,要避免当重叠操作正在进行时释放它的 OVERLAPPED 结构。阻止该情况发生的最好方法是在每个 socket 上调用 closesocket 函数,确保所有未决的重叠 I/O 操作都会完成。
    一旦所有socket关闭,就该终止完成端口上处理 I/O 事件的工作线程了。可以通过调用 PostQueuedCompletionStatus 函数发送特定的完成封包来实现。所有工作线程都终止之后,可以调用 CloseHandle 函数关闭完成端口。


http://chatgpt.dhexx.cn/article/LZBdHKET.shtml

相关文章

完成端口(IOCP)编程探讨

FW: http://www.zhuaxia.com/item/473350387 完成端口(IOCP)编程探讨 2007-08-26 16:06:00 来自&#xff1a;C博客-首页原创精华区 新建目录...根目录新手试用频道 本文主要探讨一下windows平台上的完成端口开发及其与之相关的几个重要的技术概念&#xff0c;这些概念都是与…

异步通信之IOCP详解

一、 概述 学习完网络基础&#xff0c;在写C/S应用程序时&#xff0c;大多童靴写服务器基本都没有用到io模型&#xff0c;基本都是采用“accept同步拥塞通讯和多线程方式”与客户端通讯。但当有成千上万客户端请求连接并与服务器通讯时&#xff0c;多线程的创建与CPU上下文的切…

IOCP详解

IOCP详解 IOCP&#xff08;I/O Completion Port&#xff0c;I/O完成端口&#xff09;是性能最好的一种I/O模型。它是应用程序使用线程池处理异步I/O请求的一种机制。在处理多个并发的异步I/O请求时&#xff0c;以往的模型都是在接收请求是创建一个线程来应答请求。这样就有很多…

IOCP技术详解

这几周我接触了Windows网络通讯中的IOCP模型,自己在网上找了相关的知识进行学习&#xff0c;自己又下了好多服务器端的代码&#xff0c;但都运行不了&#xff0c;也是自己菜&#xff0c;能力还需加强。幸好我师父资助了我一个能运行的服务端IOCP代码&#xff0c;自己参照网上的…

Python正则表达式模式

在 Python 程序中&#xff0c;模式字符串使用如下特殊的语法来表示一个正则表达式&#xff1a; 字母和数字表示它们自身&#xff0c;一个正则表达式模式中的字母和数字匹配同样的字符串&#xff1b;当大多数字母和数字前加一个反斜杠时&#xff0c;它们会拥有不同的含义&#…

Python正则表达式实例详解

一、正则表达式语法 正则表达式是用匹配或者描述字符串的工具。 用处&#xff1a; a.判断字符串是否满足某个条件—判断输入的字符串是否是邮箱/手机号码。是否是ip地址 b.提取满足条件的字符串 c.字符串替换 Python中通过re模块中相应的方法来支持正则表达式的匹配、查找和替…

Python正则表达式语法快速入门

文章目录 1 正则符号初阶代码举例1&#xff1a;不同符号的组合代码举例2&#xff1a;符号加&#xff0c;代表连续的一个或多个代码举例3&#xff1a;匹配字符串开始代码举例4&#xff1a;匹配字符串结束代码举例5&#xff1a;匹配单词边界 2 正则符号进阶代码举例1&#xff1a;…

python正则表达式入门

&#x1f64a;今天我们来学习python的正则表达式的部分&#xff0c;先说下为什么要学习这一部分呢&#xff0c;当然是因为正则表达式处理文本类型的数据实在是太方便了。为以后进入nlp领域打打基础&#xff01; 先给大家推荐一个网站: 用于正则表达式验证. 大致就长这个样子。…

python使用正则表达式

一、使用正则表达式步骤 1、寻找规律&#xff1b; 2、使用正则符号表示规律&#xff1b; 3、提取信息&#xff0c;如果每一个字符都能匹配&#xff0c;则匹配成功&#xff1b;一旦有匹配不成功的字符则匹配失败。 二、正则表达式中常见的基本符号 1&#xff0e;点号“.”一…

Python 正则表达式

1.正则表达式的定义: 正则表达式是对字符串进行解析&#xff08;获取一大串字符串信息中&#xff0c;你想要的部分&#xff09;。 正则表达式是一种文本模式&#xff0c;模式描述在搜索文本时要匹配的一个或多个字符串。 正则表达式&#xff0c;又成正规表示式&#xff0c;正规…

python正则表达式详解

正则表达式是一个很强大的字符串处理工具&#xff0c;几乎任何关于字符串的操作都可以使用正则表达式来完成&#xff0c;作为一个爬虫工作者&#xff0c;每天和字符串打交道&#xff0c;正则表达式更是不可或缺的技能&#xff0c;正则表达式的在不同的语言中使用方式可能不一样…

详解Python正则表达式(含丰富案例)

前言&#xff1a;正则表达式在网络爬虫、数据分析中有着广泛使用&#xff0c;掌握正则表达式能够达到事半功倍的效果。本文详细介绍正则表达式中各种规则及其符号含义&#xff0c;并结合Python中的Re库进行演示&#xff0c;由浅入深&#xff0c;即学即练即用&#xff0c;内容丰…

Python超详细的正则表达式

目录 介绍 常用的正则表达式匹配规则表格 1.match() .group() 常用知识点 1.通用匹配 2.贪婪与非贪婪 3.修饰符 4.转义匹配 2.search() 3.findall() 4.sub() 总结 Hello大家好我是&#xff0c;PYmili&#xff01;今天给大家带来正则表达式的使用教程即匹配规则表格…

Python正则表达式(一看就懂)

目录 哈喽O(∩_∩)O&#x1f604; 什么是正则表达式(⊙_⊙) 简单说&#xff0c;正则表达式是… 正则表达式怎么用❓ sreach的用法&#x1f34a; 匹配连续的多个数值&#x1f349; 字符""重复前面一个匹配字符一次或者多次&#x1f349; 字符"*"重复前…

Python正则表达式详解 (超详细,看完必会!)

正则表达式详解 正则表达式 英文名称叫 Regular Expression简称RegEx&#xff0c;是用来匹配字符的一种工具&#xff0c;它常被用在网页爬虫&#xff0c;文稿整理&#xff0c;数据筛选等方面&#xff0c;最常用的就是用在网页爬虫&#xff0c;数据抓取。 一、正则表达式的各种…

Python 正则表达式详解(建议收藏!)

目录 match 匹配字符串 单字符匹配 . 匹配任意一个字符 \d 匹配数字 \D 匹配非数字 \s 匹配特殊字符&#xff0c;如空白&#xff0c;空格&#xff0c;tab等 \S 匹配非空白 \w 匹配单词、字符&#xff0c;如大小写字母&#xff0c;数字&#xff0c;_ 下划线 \W 匹配非…

Android工程中方法数超过65536解决方法(Kotlin)

Android Studio报错&#xff1a; The number of method references in a .dex file cannot exceed 64K. Caused by: com.android.tools.r8.utils.AbortException: Error: Cannot fit requested classes in a single dex file (# methods: 68815 > 65536) 解决方案 build.…

postman/EOLINKER测试报错 RangeError:Port should be 0 and 65536. Received 80892.

postman/EOLINKER测试报错 RangeError&#xff1a;Port should be > 0 and < 65536. Received 80892. RangeError&#xff1a;范围错误 RangeError是当一个只超出有效范围时发生的错误。主要的有几种情况&#xff0c;第一是数组长度为负数&#xff0c;第二是Number对象…

Android Studio Cannot fit requested classes in a single dex file (# methods: 72633 > 65536)解决办法

今天在Android Studio中构建Android工程时&#xff0c;出现了这样报错&#xff1a;“Cannot fit requested classes in a single dex file (# methods: 72633 &#xff1e; 65536)” 在网上找了一圈儿&#xff0c;总结个简单快速的解决方法 问题&#xff1a; Cannot fit req…

计算机端口号65536,65536端口能不能用

楼主asdd3000(asdd) 16位计算机端口65536个,32位计算机端口是65536/2个还是65536*2个?为什么? 问题点数:10、回复次数:20 Top 1 楼EvilOctal(冰血封情) 回复于 2004-04-08 20:41:04 得分 0 这个问题我真的不太清楚. 但是 好象和位数没关系吧? 服务端口是国际组织规定的,不是…