异步通信之IOCP详解

article/2025/11/8 10:27:44

一、 概述

学习完网络基础,在写C/S应用程序时,大多童靴写服务器基本都没有用到io模型,基本都是采用“accept同步拥塞通讯和多线程方式”与客户端通讯。但当有成千上万客户端请求连接并与服务器通讯时,多线程的创建与CPU上下文的切换,服务器端压力可想而知,在资源有限的情况在,选择一个好的io模型才能搭建高性能服务器。其中IOCP广泛运用于个高性能服务器程序,apache服务器就是IOCP实现。

同步通讯和异步通信
在写网络程序时,我们知道CPU运行速度非常快,而在与IO设备进行数据交换时速度简直不忍直视。在程序请求一个网络操作,如accept,recv等时,若应用程序一直拥塞等待这些网络操作结束返回结果后才接着执行后面的代码,则这个过程就是同步通讯方式,反之,在提交网络操作请求后,由系统去执行该请求,程序继续往下走(该干嘛干嘛),待网络请求操作完成后,系统在通知程序来处理结果,则就是异步通信方式。
所以异步通讯比同步通讯(accept+多线程)方式高效的多,IOCP通讯模型是比较好的网络通讯模型。一起来学习吧!

二、IOCP执行流程
在这里插入图片描述

三、重要的数据结构

1.单句柄数据:该结构体用于管理具体哪个socket在进行io请求操作。

typedef struct PER_HANDLE_DATA{SOCKET 				socket;SOCKADDR_IN 		addr;
}*pPER_HANDLE_DATA,PER_HANDLE_DATA;

2.单IO数据:该结构体用于每一次客户端socket向IOCP提交请求时提交给系统,在其结构内可定义任意参数(overlapped必须在第一个),当请求完成后,IOCP**“原封不动”**的返回到工作线程,但给相应参数进行了赋值,如用于接收数据的buffer数组。

typedef struct PER_IO_DATA{OVERLAPPED       overlapped;        //类似id,每个io都必须有一个SOCKET			 socket;            //io请求的套接字WSABUF			 wsabuf;            //用于从缓冲区获取数据的结构char			 buffer[LENGTH];    //保存获得的数据OPT_TYPE         opt_type;          //这次io请求的类型,如ACCEPT,RECV,SEND等
}*pPER_IO_DATA,PER_IO_DATA;

解释一下WSABUF结构体,在ws2def.h中定义:

typedef struct _WSABUF{ULONG len;                            /*the length of buffer*/__field_bcount(len) CHAR FAR *buf;    /*  the pointer to the buffer  */
};

四、IOCP详细实现

1、初始化listenSocket等相应库、创建完成端口、创建工作线程。
(1) 创建listenSocket
既然我们使用的是IOCP模型,那么创建的socket必须这样创建,用于异步请求:

SOCKET socket = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
//socket只需要在服务端创建,客户端不需要

(2) 创建完成端口

对,就是这么简单一句代码就将我们完成端口创建完毕了,参数一个-1三个0,简单吧!不过,你懂得,操作系统为我们做了很多。。。

HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE,NULL,0,0);
//最后一个0比较有讨论价值,它表示为了避免CPU上下文切换,创建的工作线程最理想的状态就是一个处理器一个线程。

(3)创建工作线程
完成端口之所以比较高效,能很好处理高并发访问的原因之一就是合理的创建处理请求连接、收发数据等工作线程。

//工作线程数 = CPU数 * 2 + 1
//上面不是说理想状态有多少个处理器创建多少工作线程么,但现实是创建两倍的线程比较高效
//因为万一有个线程被挂起了嘞,那不是有个处理器没有被充分的利用了么,得有个“备胎”啊,哈哈//获得cpu数
SYSTEM_INFO si;
int processors = si.dwNumerOfProcessors;//创建工作线程
HANDLE * workThreadH =  new HANDLE[processors];
for(int number = 0 ; number < processors;number++){workThreadH[number] = (HANDLE) __beginthreadex(NULL,0,workThread,传值,0,NULL);
}

2、将listenSocket与完成端口绑定,并投递第一个accept请求,这样完成端口就可以为我们处理请求了。

//结构原型
HANDLE WINAPI CreateIoCompletionPort(  __in         HANDLE  FileHandle,               // 监听套接字句柄  __in_opt     HANDLE  ExistingCompletionPort,   // 前面创建的完成端口  __in         ULONG_PTR CompletionKey,         // 绑定自定义的结构体PRE_IO_DATA,传递到Worker线程中,相当于参数的传递  __in        DWORD NumberOfConcurrentThreads  // 置0  
); 
//绑定
//投递第一个acceptEx请求
acceptEx(...);

3、投递AcceptEx
完成端口使用的是AcceptEx接收客户端连接,而不是accept。但AcceptEx比较特别,是微软为重叠I/0机制提供的函数。在mswsock.dll中提供,所以可以通过mswsock.lib静态链接库获得AcceptEx。但是 但是 但是不推荐这样获取,而是应该用WSAIoctl来获取AcceptEx指针,因为没有获得指针的情况下就是用AcceptEx的开销很大,并且不适用所用平台。

//1、获取AcceptEx
LPFN_ACCEPTEX 	pAcceptEx;			//AcceptEx指针
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes = 0;
WSAIoctl(socket,//只要有效的socket就可以SIO_GET_EXTENSION_FUNCTION_POINTER,&GuidAcceptEx,sizeof(GuidAcceptEx),&pAcceptEx,sizeof(pAcceptEx),&dwBytes,NULL,NULL
);
//2调用AcceptEx,投递请求
//这里向IOCP投递的AcceptEx请求,需要绑定刚才的PER_IO_DATA数据结构,当IOCP处理好请求返回后,我们将在work线程中获得这个已经赋值好了的结构体,并且最重要的是在那么多的返回请求中是通过overlapped来标记的,要不你怎么去找到你现在投递的结构体^_^。
bool AcceptEx(SOCKET       listenSocket,//监听socketSOCKET       clientSocket,//提前创建好的接收连接的socket,这是AcceptEx高效性的关键PVOID        lpOutputBuffer,//接收缓冲区,第一部分是client发来的第一组数据,第二是服务端地址,第三是客户端地址DWORD        dwReceiveDataLength,//lpOutputBuffer的长度,若不为0,则表示等待客户端第一组数组发过来后才返回,否则阻塞在这里,若为0,则表示不用等待,直接返回。DWORD        dwLocalAddressLength,//存放本地地址的大小DWORD        dwRemoteAddressLength,//存放远程地址的大小LPVOID       lpdwBytesReceived,//不用管LPOVERLAPPED lpOverlapped//I/O重叠结构
);

4、投递WSARecv请求

//传递参数,调用就行
int WSARecv(  SOCKET socket,                       // 投递的套接字  LPWSABUF lpBuffers,                 // 接收缓冲区WSABUF结构构成的数组  DWORD dwBufferCount,                // 数组中WSABUF结构的数量,设置为1  LPDWORD lpNumberOfBytesRecvd,       // 返回函数调用所接收到的字节数  LPDWORD lpFlags,                    // 设置为0  LPWSAOVERLAPPED lpOverlapped,       // Socket对应的重叠结构  NULL                                // 设置完成例程模式,这里设置为NULL 
); 

5、投递WSASend请求
//和WSARecv类似,不解释

int WSASend(_In_   SOCKET			 socket,_In_   LPWSABUF		 lpBuffers,_In_   DWORD 			 dwBufferCount,_Out_  LPDWORD		 lpNumberOfBytesSent,_In_   DWORD			 dwFlags,_In_   LPWSAOVERLAPPED lpOverlapped,_In_   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

6、work线程工作机制
work线程主要监听完成端口的状态(GetQueuedCompletionStatus),若完成端口有返回请求,则处理,若没有则挂起。

函数原型如下:

BOOL WINAPI GetQueuedCompletionStatus(  __in   HANDLE          CompletionPort,    // 建立的完成端口  __out  LPDWORD         lpNumberOfBytes,   //返回的字节数  __out  PULONG_PTR      lpCompletionKey,   // 与完成端口的时候绑定的那个socket对应的自定义结构体PER_HANDLE_DATA__out  LPOVERLAPPED    *lpOverlapped,     // 重叠结构  __in   DWORD           dwMilliseconds     // 等待完成端口的超时时间,一般设置INFINITE  ); 

(1)监听GetQueuedCompletionStatus

pPER_HANDLE_DATA 			 *pPerHandleData = NULL;  
OVERLAPPED        *pOverlapped = NULL;  
DWORD              dwBytesTransfered = 0;  BOOL bReturn  =  GetQueuedCompletionStatus(  pIOCPModel->m_hIOCompletionPort,  &dwBytesTransfered,  (LPDWORD)&pPerHandleData,  &pOverlapped,  INFINITE );  

(2)获得PPER_IO_DATA
通过宏CONTAINING_RECORD获得,将lpOverlapped变量传到宏中,找到和结构体PER_IO_DATA中overlapped成员对应的数据,就是刚才投递PER_IO_DATA结构体,现在请求IO操作完成,返回给work线程的。

PPER_IO_DATA  pPerIoData = CONTAINING_RECORD(lpOverlapped, PER_IO_DATA  , overlapped);

(3)switch判断完成请求的类型,然后做相应的处理
大概逻辑代码如下:是不是太抽象了,具体代码看后面更新的整体工程代码

switch(pPerIoData.opt_type){case OPT_ACCEPT://这里大概做两件是//一是将连接的socket与IOCP绑定//二是在接着投递下一个AcceptEx,这样AcceptEx接循环起来了,可以不断的监听。break;case OPT_RECV://处理接收到的来自客户端的数据,然后在投递WSARecv请求//当然若还没有完全接收完客户端的一组数据,那么得在投递WASRecv请求,而不做任何数据处理。//比如若客户端发送了1000个字节,但是现在dwBytesTransfered参数值为800,则说明还有200字节还在缓冲区中,得继续去缓冲区中取数据break;case OPT_SEND://这里主要是服务端向客户端发送数据返回后,处理各种情况//1、若向客户端发送的数据全部发送完毕,那么释放PER_IO_DATA结构所占内存,服务器长时间运行,不可能让内存溢出崩溃,是不。//2、若向客户端发送的数据部分发送完毕,则需要在投递WSAsend请求,将数据发送完毕break;
}

7、关闭完成端口
work线程创建后一直在while循环中监听完成端口状态,要么处于挂起状态,要么处于处理数据状态,那么当要关闭服务器时,如何让work线程温文尔雅的退出嘞?我们知道让线程安全退出的最好方法是让线程自己退出,即return。
使用PostQueuedCompletionStatus通知线程退出:

BOOL WINAPI PostQueuedCompletionStatus(  __in      HANDLE CompletionPort,  //当初创建的完成端口__in      DWORD dwNumberOfBytesTransferred, //可做为通知线程退出的一个标示码,其对应于GetQueuedCompletionStatus中的参数lpNumberOfBytes,所以可做文章__in      ULONG_PTR dwCompletionKey,  //PER_HANDLE_DATA结构体__in_opt  LPOVERLAPPED lpOverlapped  
);

这是我对IOCP简单的理解,若有不对的地方,希望各位指正,相互交流,共同成长_


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

相关文章

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 这个问题我真的不太清楚. 但是 好象和位数没关系吧? 服务端口是国际组织规定的,不是…

为什么89C51单片机里面有TH0=(65536-50000)/256;TL0=(65536-50000)%256;

由于89C51的晶振频率为12MHZ&#xff0c;机器周期是1us,则每1us产生一次计数&#xff0c;例如需要进行50ms的计时&#xff0c;对机器周期进行50000计数就可以得到&#xff0c;T0可以对机器周期进行65536次计数&#xff0c;为了不让T0溢出&#xff0c;我们需要对定时器/计数器赋…

NXP MCUXPresso - cc1plus.exe: out of memory allocating 65536 bytes

文章目录 NXP MCUXPresso - cc1plus.exe: out of memory allocating 65536 bytes概述实验结论补充END NXP MCUXPresso - cc1plus.exe: out of memory allocating 65536 bytes 概述 在尝试迁移 openpnp - Smoothieware project 从gcc命令行 MRI调试方式 到NXP MCUXpresso工程…