[C++ 网络协议] 重叠I/O模型

目录

1. 什么是重叠I/O模型

2. 重叠I/O模型的实现

2.1 创建重叠非阻塞I/O模式的套接字

2.2 执行重叠I/O的Send函数

2.3 执行重叠I/O的Recv函数

2.4 获取执行I/O重叠的函数的执行结果

2.5 重叠I/O的I/O完成确认

2.5.1 使用事件对象(使用重叠I/O函数的第六个参数)

2.5.2 使用Completion Routine函数(使用重叠I/O的第七个参数)

3. 用重叠I/O实现回声服务器端


1. 什么是重叠I/O模型

重叠I/O模型:

重叠I/O:同一线程内部向多个目标传输(或从多个目标接收)数据引起的I/O重叠现象

所以为了完成这一功能,要求套接字的I/O函数要立即返回,以便于后面的套接字的I/O处理。这就有点像是异步I/O模型。如图:

异步I/O模型: 

所以,从结果上来看,重叠I/O的前提条件就是异步I/O

非阻塞I/O、异步I/O、重叠I/O之间的关系:重叠I/O离不开异步I/O,异步I/O离不开非阻塞I/O,三者之间应该是层层递进的关系。

2. 重叠I/O模型的实现

重叠I/O的重点不在于I/O,因为只要是非阻塞I/O就都能调用并立即返回,我们要关注的是在I/O返回后,我们怎么确认它的执行结果怎么知道它什么时候读取/发送数据结束怎么知道它读取/发送了多少数据?这些问题。

2.1 创建重叠非阻塞I/O模式的套接字

#include<winsock2.h>SOCKET WSASocket(
int af,                                //协议族信息
int type,                              //套接字数据传输方式
int protocol,                          //使用的协议信息
LPWSAPROTOCOL_INFO lpProtocolInfo,     //包含创建的套接字信息的WSAPROTOCOL_INFO结构体变量地址值//不需要时传NULL
GROUP g,                               //为扩展函数而预约的参数,可以使用0
DWORD dwFlags                          //套接字属性信息
);
成功返回套接字句柄
失败返回INVALID_SOCKET

创建进行重叠I/O模式的套接字:

hSocket=WSASocket(PF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);

第五个参数传WSA_FLAG_OVERLAPPED。 

将套接字改为非阻塞I/O模式:

int mode=1;
ioctlsocket(hSocket,FIONBIO,(u_long*)&mode);    //非阻塞I/O的设置

将hSocket句柄引用的套接字I/O模式(FIONBIO)改为变量mode中指定的形式。

当设置为非阻塞模式后:

  • 如果在没有客户端请求连接的情况下,调用accpet函数,将直接返回INVALID_SOCKET,调用WSAGetLastError函数,将返回WSAEWOULDBLOCK
  • 调用accpet函数时创建的套接字同样具有非阻塞属性。

所以如果针对非阻塞套接字调用accept函数时,要判断返回INVALID_SOCKET的理由。有可能是accpet函数未成功,也有可能是没有客户端请求连接。

2.2 执行重叠I/O的Send函数

#include<winsock2.h>int WSASend(
SOCKET s,     //套接字句柄
LPWSABUF lpBuffers,    //WSABUF结构体变量数组的地址值
DWORD dwBufferCount,   //第二个参数中数组长度
LPDWORD lpNumberOfBytesSent,    //保存实际发送字节数的变量地址值
DWORD dwFlags,    //用于更改数据传输特性,如传递MSG_OOB时发送OOB模式的数据
LPWSAOVERLAPPED lpOverlapped,    //WSAOVERLAPPED结构体变量地址值,使用事件对象,用于确认完成数据传输
LPWSAOVERLAPPED_COMPLETION_ROUTING lpCompletionRoutine //传入Completion Routine函数的入口//地址值,可以通过该函数确认是否完成数据传输
);
成功返回0
失败返回SOCKET_ERROR

第二个参数,lpBuffers:

struct __WSABUF
{u_long len;    //待传输数据大小char FAR* buf; //缓冲地址值
}WSABUF,*LPWSABUF;

第四个参数,lpNumberOfBytesSent:

填写了第四个参数会有如下两种情况:

        1.当传输数据不大,函数调用后可以立即完成数据传输时,WSASend函数将返回0,lpNumberOfBytesSent中保存实际传输的数据大小

        2.当传输数据过大,函数调用后不能立即完成数据传输时,WSASend函数将返回SOCKET_ERROR,并将WSA_IO_PENDING注册为错误代码。该代码通过函数WSAGetLastError函数得到:

#include<winsock2.h>int WSAGetLastError(void);
返回错误代码(表示错误原因)

第六个参数,lpOverlapped:

struct __WSAOVERLAPPED
{DWORD Internal;DWORD InternalHigh;DWORD Offset;DWORD offsetHigh;WSAEVENT hEvent;
}WSAOVERLAPPED,*LPWSAOVERLAPPED;

 其中Internal、InternalHigh成员是进行重叠I/O时操作系统内部使用成员,Offset、OffsetHigh是属于具有特殊用途的成员。所以只需关注hEvent成员,前四个成员置零即可。

注意:

        1.为了进行重叠I/O,WSASend函数的lpOverlapped参数中应该传递有效的结构体变量地址值,而不是NULL。否则,SOCKET s将以阻塞模式工作。

        2.向多个目标传输数据时,要分别构建lpOverlapped参数。但如果是同一个目标的接收/发送,就只需构建一次lpOvrelapped参数即可。

第七个参数, lpCompletionRoutine:

这是传入lpCompletionRoutine的函数原型:

void CALLBACK CompletionROUTING(
DWORD dwError,                    //写入错误信息,正常结束写入0
DWORD bdTransferred,              //写入实际收发的字节数
LPWSAOVERLAPPED lpOverlapped,     //写入WSASend\WSARecv函数的参数lpOverlapped
DWORD dwFlags                     //写入调用I/O时传入的特性信息或0
);

其中void返回值类型后面必须要有CALLBACK关键字

2.3 执行重叠I/O的Recv函数

#include<winsock2.h>int WSARecv(
SOCKET s,     //套接字句柄
LPWSABUF lpBuffers,    //WSABUF结构体变量数组的地址值
DWORD dwBufferCount,   //第二个参数中数组长度
LPDWORD lpNumberOfBytesSent,    //保存实际接收字节数的变量地址值
LPDWORD dwFlags,    //用于设置或读取数据传输特性,如接收MSG_OOB时发送的OOB模式的数据
LPWSAOVERLAPPED lpOverlapped,    //WSAOVERLAPPED结构体变量地址值,使用事件对象,用于确认完成数据接收
LPWSAOVERLAPPED_COMPLETION_ROUTING lpCompletionRoutine //传入Completion Routine函数的入口//地址值,可以通过该函数确认是否完成数据接收
);
成功返回0
失败返回SOCKET_ERROR

 这个和WSASend函数没什么区别。

2.4 获取执行I/O重叠的函数的执行结果

#include<winsock2.h>BOOL WSAGetOverlappedResult(
SOCKET s,                        //进行重叠I/O的套接字句柄
LPWSAOVERLAPPED lpOverlapped,    //进行重叠I/O时传递的WSAOVERLAPPED结构体变量的地址值
LPDWORD lpcbTransger,            //保存实际传输的字节数的变量地址值
BOOL fWait,                      //如果调用该函数仍在进行I/O,则//填TRUE时,等待I/O完成//填FALSE时,函数退出并返回FALSE
LPDWORD lpdwFlags                //调用WSARecv函数时,用于获取附加信息(如OOB消息)。//不需要,可以传NULL
);
成功返回TRUE
失败返回FALSE

可以获取实际的传输数据大小。同时还可以通过第四个参数验证接收数据的状态。

2.5 重叠I/O的I/O完成确认

2.5.1 使用事件对象(使用重叠I/O函数的第六个参数)

第六个参数:WSAOVERLAPPED结构体。

当重叠I/O完成时:

  • WSAOVERLAPPED结构体里的事件对象将变为signaled状态。
  • 验证I/O的完成结果需要调用WSAGetOverlappedResult函数。

如:

if(SOCKET_ERROR==WSASend(hSocket,&dataBuf,1,&sendBytes,0,&overlapped,NULL))
{if(WSAGetLastError()==WSA_IO_PENDING)    //说明数据还未传输完成{WSAWaitForMultipleEvents(1,&evObj,TRUE,WSA_INFINITE,FALSE);    //等待事件对象结束WSAGetOverlappedResult(hSocket,&overlapped,&sendBytes,FALSE,NULL); //得到结果}else{......}
}
//说明数据传输完成
......

2.5.2 使用Completion Routine函数(使用重叠I/O的第七个参数)

规则:只有请求I/O的线程处于alertable wait状态时才能调用Completion Routine函数

alertable wait状态指:等待接收操作系统消息的线程状态。

调用以下函数将进入alertable wait状态:

  • WaitForSingleObjectEx
  • WaitForMultipleObjectsEx
  • WSAWaitForMultipleEvents
  • SleepEx
  • WSA为前缀的上述函数

上述函数和去掉Ex的函数相同,只是上述函数增加了一个参数,为TURE那么就进入alertable wait状态,反之,则不进入。

为什么设定了这个规则?

因为:如果在执行重要任务时,突然调用Completion Routine函数,将破坏程序的正常执行流,所以要定义这个规则。

所以你可以在执行完重要任务后,调用上述任一函数,验证I/O完成与否,如果有已完成的I/O,则操作系统会调用响应的Completion Routine函数。调用结束后,上述函数会返回WAIT_IO_COMPLETION,并继续执行。

int main()
{......//进入alertable wait状态,调用CompRoutine函数int idx=WSAWaitForMultipleEvents(1,&evObj,FALSE,WSA_INFINITE,TRUE);if(inx==WAIT_TO_COMPLETION){}......
}void CALLBACK CompRoutine(......)
{......
}

使用Completion Routine方式的一个小知识点:

        使用Completion Routine就可以无需事件对象了,所以在WSASend/WSARecv的第六个参数填写WSAOVERLAPPED结构体时,里面的事件对象(hEvent),可以存储写入其他信息,这个数据类型会被传送到CALLBACK的函数里的第三个参数,此时直接对hEvent进行强制转换,就可以得到相应的信息了。

struct message
{......;
}
message msg;
overlapped.hEvent=(HANDLE)&msg;    //HANDLE是指针类型
//记住使用的是Completion Routine方式哦void CALLBACK Completion(DWORD error, DWORD transfer, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{message mesg=(message)(lpOverlapped->hEvent);......
}

3. 用重叠I/O实现回声服务器端

实现一:使用事件对象的方式来完成确认

这个比较简单,故省略。

实现二:使用Completion Routine的方式来完成确认

变量:

EventMessage结构体:用以存储连接的SOCKET套接字和对应客户端发送过来的消息内容。

为什么要创建一个这样的结构体?

因为:程序的运行是异步的,在while循环里,每连接一个客户端,SOCKET对应的客户端套接字变量,就会被重新赋值,就会导致写的RecvCompletion和SendCompletion函数里的SOCKET值会变化,这样发送的套接字就不正确了。所以必须要一个套接字对应一个客户端发送来的内容。

思路要点:

  1. 要保证客户端与服务器之间不只是发送一次数据,就要在RecvCompletion里面调用WSASend函数,在SendCompletion里面调用WSARecv函数,来达成循环。
  2. 使用EventMessage结构体,存储套接字和消息内容,把结构体地址值写入WSAOVERLAPPED的hEvent变量里,就可以在RecvCompletion和SendComplition里传递套接字和消息内容信息。
void CALLBACK RecvCompletion(DWORD error, DWORD transfer, LPWSAOVERLAPPED lpOverlapped, DWORD flags);
void CALLBACK SendCompletion(DWORD error, DWORD transfer, LPWSAOVERLAPPED lpOverlapped, DWORD flags);struct EventMessage
{WSABUF recvBuf;SOCKET client;
};int main()
{......//这里和方式一都是一样的while (1){SleepEx(100, TRUE);    //进入alertable wait状态sockaddr_in clientAddr;memset(&clientAddr, 0, sizeof(clientAddr));int clientAddrLen = sizeof(clientAddr);SOCKET client = accept(server, (sockaddr*)&clientAddr, &clientAddrLen);if (INVALID_SOCKET == client){if (WSAGetLastError() == WSAEWOULDBLOCK){std::cout << "没有客户端连接" << std::endl;}else{std::cout << "accept fail!" << std::endl;}continue;}EventMessage eventMsg;char buff[1024];eventMsg.recvBuf.buf = buff;eventMsg.recvBuf.len = sizeof(buff);eventMsg.client = client;WSAOVERLAPPED recvOverlapeed;memset(&recvOverlapeed, 0, sizeof(recvOverlapeed));recvOverlapeed.hEvent = (HANDLE)&eventMsg;DWORD recvLen;DWORD recvFlag=0;WSARecv(client, &eventMsg.recvBuf, 1, &recvLen, &recvFlag, &recvOverlapeed, RecvCompletion);}
}void CALLBACK RecvCompletion(DWORD error, DWORD transfer, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{if (error == 0)		//说明是正确结束{EventMessage eventMsg =*(EventMessage*)lpOverlapped->hEvent;int recvLen = transfer;	//获取接收的字节数if (recvLen == 0){std::cout << "客户端已断开!" << std::endl;closesocket(eventMsg.client);return;}std::cout << "客户端发来的信息:" << eventMsg.recvBuf.buf << std::endl;WSASend(eventMsg.client, &eventMsg.recvBuf, 1, &transfer, flags, lpOverlapped, SendCompletion);}
}void CALLBACK SendCompletion(DWORD error, DWORD transfer, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{if (error == 0){EventMessage eventMsg = *(EventMessage*)lpOverlapped->hEvent;char buff[1024];eventMsg.recvBuf.buf = buff;eventMsg.recvBuf.len = sizeof(buff);DWORD recvLen;DWORD recvFlag = 0;WSARecv(eventMsg.client, &eventMsg.recvBuf, 1, &recvLen, &recvFlag, lpOverlapped, RecvCompletion);}
}

执行结果:

因为是异步执行的,所以线程不会等待,会持续往下执行,当有消息传来时,就会执行Complition函数进行处理,线程不会阻塞住。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/91001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用C++开发一个迷你的英文单词录入和测试小程序-增强功能

小玩具基本完成之后&#xff0c;在日常工作中&#xff0c;记录一些单词&#xff0c;然后定时再复习下&#xff0c;还真的有那么一点点用&#xff08;毕竟自己做的小玩具&#xff09;。 在使用过程中&#xff0c;遇到不认识的单词&#xff0c;总去翻译软件翻译&#xff0c;然后…

React 全栈体系(十五)

第八章 React 扩展 一、setState 1. 代码 /* index.jsx */ import React, { Component } from reactexport default class Demo extends Component {state {count:0}add ()>{//对象式的setState/* //1.获取原来的count值const {count} this.state//2.更新状态this.set…

嵌入式Linux应用开发-第十一章设备树的引入及简明教程

嵌入式Linux应用开发-第十一章设备树的引入及简明教程 第十一章 驱动进化之路&#xff1a;设备树的引入及简明教程11.1 设备树的引入与作用11.2 设备树的语法11.2.1 1Devicetree格式11.2.1.1 1DTS文件的格式11.2.1.2 node的格式11.2.1.3 properties的格式 11.2.2 dts文件包含 d…

Flask框架【before_first_request和before_request详解、钩子函数、Flask_信号机制】(七)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

期权定价模型系列【7】:Barone-Adesi-Whaley定价模型

期权定价模型系列第7篇文章 1.前言 目前大连商品交易所、郑州商品交易所、以及上海期货交易所的所有商品期权都为美式期权&#xff0c;并且大商所的所有期权合约会根据BAW(Barone-Adesi-Whaley)美式期权定价模型计算新上市期权合约的挂牌基准价。 BAW模型(Barone-Adesi and W…

马尔萨斯《人口原理》读后

200 多年前的书&#xff0c;很多人都说旧的东西过时了&#xff0c;但我觉得它只是被修正了&#xff0c;内核并不过时。毕竟&#xff0c;静态存量分析这本身就不符合现实&#xff0c;用现在的话说&#xff0c;建模就错了&#xff0c;但马尔萨斯的理论核心并不仅仅是一个模型&…

微信、支付宝、百度、抖音开放平台第三方代小程序开发总结

大家好&#xff0c;我是小悟 小伙伴们都开启小长假了吧&#xff0c;值此中秋国庆双节之际&#xff0c;小悟祝所有的小伙伴们节日快乐。 支付宝社区很用心&#xff0c;还特意给寄了袋月饼&#xff0c;愿中秋节的圆月带给你身体健康&#xff0c;幸福团圆&#xff0c;国庆节的旗帜…

聊聊并发编程——Condition

目录 一.synchronized wait/notify/notifyAll 线程通信 二.Lock Condition 实现线程通信 三.Condition实现通信分析 四.JUC工具类的示例 一.synchronized wait/notify/notifyAll 线程通信 关于线程间的通信&#xff0c;简单举例下&#xff1a; 1.创建ThreadA传入共享…

(一)NIO 基础

&#xff08;一&#xff09;NIO 基础 non-blocking io&#xff1a;非阻塞 IO 1、三大组件 1.1、Channel & Buffer Java NIO系统的核心在于&#xff1a;通道&#xff08;Channel&#xff09;和缓冲&#xff08;Buffer&#xff09;。通道表示打开到 IO 设备&#xff08;例…

【golang】调度系列之sysmon

调度系列 调度系列之goroutine 调度系列之m 调度系列之p 掉地系列之整体介绍 在golang的调度体系中&#xff0c;除了GMP本身&#xff0c;还有另外一个比较重要的角色sysmon。实际上&#xff0c;除了GMP和sysmon&#xff0c;runtime中还有一个全局的调度器对象。但该对象只是维护…

浅谈AVL树

文章目录 1.介绍1.1定义1.2来源1.3概念1.特性2.平衡因子[ Balance Factor-- _bf ] 2.BST>AVL1.示例分析2.情况分类3.代码剖析3.1左左型-右单旋3.2右右型-左单旋3.3左右型-左右旋3.4右左型:右左旋3.5总图 3.完整代码3.1AVLTree.h3.2Test.cpp 1.介绍 1.1定义 AVL树 – 平衡二…

RabbitMQ(15672) 消息中间件 NOTE

目录 1、初识 RabbitMQ 消息队列 1.1 MQ 四大核心概念 1.2 消息的发送&#xff08;无交换机态&#xff09; 1.3 关于消息自动重新入队 1.3.1 消息的常见应答方法&#xff08;R&#xff09; 1.4 关于 RabbitMQ 的持久化、不公平分发以及预取值 2、RabbitMQ 消息的发布确认…

centos7用docker安装WireGuard教程

PS:本文章用于帮助组建自己内网或者公司组网操作,该教程不涉及翻墙操作. 1、 检查centos内核版本 uname -r2、升级内核 下载脚本上传到服务器运行脚本进行升级内核 链接&#xff1a;https://pan.baidu.com/s/1vYmqVy2St3nFnJWGPIwdOw 提取码&#xff1a;owac 3、安装WireG…

云原生Kubernetes:K8S安全机制

目录 一、理论 1.K8S安全机制 2.Authentication认证 3.Authorization授权 4.Admission Control准入控制 5.User访问案例 6.ServiceAccount访问案例 二、实验 1.Admission Control准入控制 2.User访问案例 3.ServiceAccount访问案例 三、问题 1.生成资源报错 2.镜…

7.2 怎样定义函数

7.2.1 为什么要定义函数 主要内容&#xff1a; 为什么要定义函数 C语言要求所有在程序中用到的函数必须“先定义&#xff0c;后使用”。这是因为在调用一个函数之前&#xff0c;编译系统需要知道这个函数的名字、返回值类型、功能以及参数的个数与类型。如果没有事先定义&…

多叉树+图实现简单业务流程

文章目录 场景整体架构流程业务界面技术细节小结 场景 这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念. 整体架构流程 方案管理、预案管理构成任务流程的基础条…

nginx 多层代理 + k8s ingress 后端服务获取客户真实ip 配置

1.nginx http 七层代理 修改命令空间&#xff1a; namespace: nginx-ingress : configmap&#xff1a;nginx-configuration kubectl get cm nginx-configuration -n ingress-nginx -o yaml添加如上配置 compute-full-forwarded-for: “true” forwarded-for-header: X-Forwa…

.balckhoues-V-XXXXXXX勒索病毒数据怎么处理|数据解密恢复

引言&#xff1a; 随着网络犯罪的不断演进&#xff0c;勒索病毒已成为当前数字时代的威胁之一&#xff0c;其中包括.balckhoues-V-XXXXXXX勒索病毒。本文将深入介绍.balckhoues-V-XXXXXXX勒索病毒的特点、数据恢复方法以及预防措施&#xff0c;以帮助您更好地理解和应对这一威…

NPDP产品经理认证怎么报名?考试难度大吗?

PMDA&#xff08;Product Development and Management Association&#xff09;是美国产品开发与管理协会&#xff0c;在中国由中国人才交流基金会培训中心举办NPDP&#xff08;New Product Development Professional&#xff09;考试&#xff0c;该考试是产品经理国际资格认证…

Spring实例化源码解析之ComponentScanAnnotationParser(四)

上一章我们分析了ConfigurationClassParser&#xff0c;配置类的解析源码分析。在ComponentScans和ComponentScan注解修饰的候选配置类的解析过程中&#xff0c;我们需要深入的了解一下ComponentScanAnnotationParser的parse执行流程&#xff0c;SpringBoot启动类为什么这么写&…