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

news2024/11/25 19:16:18

目录

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.coloradmin.cn/o/1048465.html

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

相关文章

【x265 源码分析系列】:概述

介绍 x265 也属于 VLC 的 project。 版本&#xff1a; x265-3.5&#xff08;TAG-208&#xff09; git&#xff1a; https://bitbucket.org/multicoreware/x265_git.git 编码特点&#xff1a; 研究了一段时间的 HEVC 编码标准&#xff0c;最近开始研究符合 HEVC 标准的开源…

STM32G070RBT6-MCU温度测量(ADC)

1、借助STM32CubeMX生成系统及外设相关初始化代码。 在以上配置后就可以生成相关初始化代码了。 /* ADC1 init function */ void MX_ADC1_Init(void) {/* USER CODE BEGIN ADC1_Init 0 *//* USER CODE END ADC1_Init 0 */ADC_ChannelConfTypeDef sConfig {0};/* USER COD…

MyBatis-Plus通用Service快速实现赠三改查[MyBatis-Plus系列] - 第489篇

历史文章&#xff08;文章累计480&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 M…

综述 | 关于点云配准的全面综述(二)

原创 | 文 BFT机器人 05 基于优化的配准方法 基于优化的方法的关键思想是开发复杂的优化策略来实现方程&#xff08;1&#xff09;中非线性问题的最优解。 由于同源挑战的影响&#xff0c;这个非线性问题变得具有挑战性。图&#xff08;2a&#xff09;总结了该类别的主要过程。…

从零开始之了解电机及其控制(11)实现空间矢量调制

广泛地说&#xff0c;空间矢量调制只是将电压矢量以及磁场矢量在空间中调制到任意角度&#xff0c;通常同时最大限度地利用整个电压范围。 其他空间矢量调制模式确实存在&#xff0c;并且根据您最关心的内容&#xff0c;它们可能值得研究。 如何实际执行这种所谓的交替反向序列…

看看属猴人性格及近几年的运势怎么样?

属猴的人五行主金&#xff0c;乃是申金之所在&#xff0c;金旺之人&#xff0c;外显懒散&#xff0c;内心富有主见&#xff0c;行事坚定&#xff0c; 有贯彻始终之斗志&#xff0c;与他人合作融洽&#xff0c;且得以财运颇多&#xff1b; 主金&#xff0c;杀伐果决、精明干练&a…

PHP各种老版本下载方式

最近因工作需要&#xff0c;要下载PHP7.3的最新版本版本。 PHP官网上提供了各种老版本下载地址&#xff1a; https://windows.php.net/downloads/releases/archives/ 下载速度不稳定&#xff0c;时快时慢。 使用前&#xff0c;给下载留足时间。 貌似晚上速度快一些。

gif怎么转换成视频MP4?

gif怎么转换成视频MP4&#xff1f;GIF动图已成为一种风靡网络的流行的特殊图片文件&#xff0c;其循环播放和逐帧呈现的特点使其在社交媒体、聊天应用等场合广泛应用&#xff0c;平时我们进行群聊是&#xff0c;大家总会一些gif动态表情的出现而感觉非常的开行&#xff0c;gif动…

Android12之容器类SortedVector、KeyedVector、Vector、VectorImpl总结(一百六十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

极致增长 | NetMarvel 程序化广告最大化广告变现收益

程序化广告已彻底改变广告主触达目标受众的方式。 从早期传统人工售卖流量&#xff0c;到流量平台推出广告联盟&#xff0c;从程序化交易到利用算法和机器学习实时计算买卖广告空间&#xff0c;通过逐渐精微的数据来测评不同渠道、不同受众的广告效果&#xff0c;提高广告主的…

datart:Invalid database configuration. Datart is running in demo mode

datart在IDEA配置好数据库连接之后&#xff0c;启动&#xff0c;报错&#xff1a; 【********* Invalid database configuration. Datart is running in demo mode *********】 原因是缺少一个变量 config 增加即可&#xff1a; 再次启动&#xff0c;就不会报无效数据库配置了…

百华鞋业董事长牛兴华应邀出席德国前总统武尔夫欢迎宴会

2023年9月25日&#xff0c;德国前总统克里斯蒂安-武尔夫&#xff08;Christian Wullff&#xff09;一行来华访问期间&#xff0c;于上海新华联索菲特举办2023中德交流领袖论坛暨武尔夫总统欢迎晚宴。百华鞋业董事长牛兴华先生受邀出席&#xff0c;并受到武尔夫的亲自接见。 山东…

关于NVIC 中断控制器的中断配置。

以下图片均来自NVIC控制器内容。 M3处理器仅实现了每个81个中断&#xff0c;每个中断的优先级由高4位控制。 这里的组优先级我认为是抢占式优先级。

多线程批量下载ERA5逐日数据

介绍 这篇博文主要是整了和ERA5官方参考文档和网上现有的代码&#xff0c;从而实现ERA5逐日数据的批量下载**&#xff08;可指定时区&#xff09;**。 先前准备 在使用python批量下载ERA5逐日数据前我们需要手动配置一下cdsapi 1.访问&#xff1a;CDS官网并注册账号 2.注册好…

掌动智能:UI自动化测试工具的重要性和应用

在软件开发过程中&#xff0c;测试是至关重要的环节。而UI自动化测试工具则成为了测试团队提高效率、降低成本、保证软件质量的重要利器。本文将介绍UI自动化测试工具的概念和重要性&#xff0c;并探讨其在软件开发中的应用和好处。 一、UI自动化测试工具的概念 UI自动化测试工…

获奖作品展示 | 2023嵌入式大赛AidLux系列作品精彩纷呈

第六届&#xff08;2023&#xff09;全国大学生嵌入式芯片与系统设计竞赛应用赛道全国总决赛已于8月下旬圆满结束。 本届赛事中&#xff0c;AidLux是广和通5G智能物联网赛题的唯一软件支持&#xff0c;阿加犀为该赛题学生们提供了全程线上辅导、技术答疑&#xff0c;以及大赛专…

VR庆中秋丨奇幻月景邀您共赏!

中秋佳节&#xff0c; 如何来一场别开生面的云游月景体验&#xff1f; 3DVR技术开启中秋过节新姿势&#xff0c; 嫦娥奔月伴玉兔、 太白花间饮美酒、 吴刚月下伐桂树…… 立体化还原璀璨的传统中秋文化&#xff0c; 还有趣味猜灯谜活动&#xff0c; 丰富豪礼等你来拿&a…

Coovally模型探索:高效下载并使用Hugging Face Transformers预训练模型

Hugging Face Transformers 是一个用于自然语言处理&#xff08;NLP&#xff09;的开源库&#xff0c;提供了各种预训练模型。这些模型被广泛应用于各种任务&#xff0c;如文本分类、命名实体识别、问答、文本生成等。Transformers库易于使用&#xff0c;可方便地集成到现有的深…

归并(merge)排序

归并&#xff08;merge&#xff09;排序也是采用分而治之的思想&#xff0c;其采用二分法将待排列数组分成若干个子数组。然后将相邻的子数组进行归并成新的有序子数组&#xff0c;然后在新的子数组的基础上在进行归并成新的有序数组&#xff0c;直至归并成一个整体有序的数组。…

源码编译postgresql

没什么好研究的了&#xff0c;就试试编译Postgresql源码&#xff0c;按照网站查的资料一步步测试的&#xff0c;方便后期定制数据库时候用&#xff0c;也算是开源的大优势了&#xff0c;只要你愿意折腾&#xff0c;可以自己定制或改进一个数据库来满足特殊业务。后面研究一下他…