[C++网络协议] I/O复用

news/2024/2/28 1:33:21

具有代表性的并发服务器端实现模型和方法:

多进程服务器:通过创建多个进程提供服务。

多路复用服务器:通过捆绑并统一管理I/O对象提供服务。✔

多线程服务器:通过生成与客户端等量的线程提供服务。

目录

1. I/O复用

2. select函数

2.1 select函数的作用

2.2 设置文件描述符

2.3 指定监视范围

2.4 设置超时

2.5 查看调用select函数后的结果

2.7 与Windows系统的区别

3. 实现I/O复用的回声服务器端


1. I/O复用

“在一个通信频道中传递多个数据(信号)的技术。”

“为了提高物理设备的效率,用最少的物理要素传递最多数据时使用的技术。”

举个例子,某个教师里有10名学生,1位老师,这10名学生都非等闲之辈,他们会不停的提问,所以学校没有办法,只能给他们每个人都配一个老师,这样这个教师就有10个老师,10个学生,但这样的话,以后每当有一个新学生进来,就要增加一个新老师,这样下去也不是办法。这时,学校来了个贼牛的老师,他一个人就可以应对所有学生的提问,而且速度很快,所以学校就把其他老师给转移到了其他班。并且,现在学生提问必须举手,老师确认学生的提问再回答问题。现在,这间教师就是以I/O复用方式运行的。

如图是I/O复用在服务端的模型。

2. select函数

#include<sys/select.h>
#include<sys/time.h>int select(    
int maxfd,                     //监视对象文件描述符数量
fd_set* readset,               //将所有关注"是否存在待读取数据"的文件描述符注册到fd_set型//变量,并传递其地址值
fd_set* writeset,              //将所有关注"是否可传输无阻塞数据"的文件描述符注册到fd_set型//变量,并传递其地址值
fd_set* exceptset,             //将所有关注"是否发生异常"的文件描述符注册到fd_set型变量,//并传递其地址值
const struct timeval* timeout  //调用select函数后,为防止进入无限阻塞状态,传递超时信息
);
成功返回>0的值,表示发生上述事件的文件描述符
超时返回0
失败返回-1
struct timaval
{long tv_sec;        //secondslong tv_usec;       //microseonds
}

2.1 select函数的作用

作用:将多个文件描述符集中到一起统一监视。获取发生监视事件的文件描述符,从而与这个文件描述符指定的套接字进行通信。

监视事件:

        1.是否存在套接字接收数据(read)

        2.无需阻塞传输数据的套接字有哪些(write)

        3.哪些套接字有异常(except)

2.2 设置文件描述符

select函数是怎么将多个文件描述符集中到一起监视的?

答:使用fd_set数组。

fd_set数组的结构:

fd_set数组是一个位数组,即只存储0与1。0:表示当前文件描述符未被监视,1:表示当前文件描述符正在被监视。

那么fd_set数组是怎么将文件描述符集中到数组里的?又是怎么设置它的位数的?

答:通过提供的宏来完成。

fd_set数组提供如下宏:

含义
FD_ZERO(fd_set* fdset);将fdset数组里的所有位初始化为0
FD_SET(int fd,fd_set* fdset);将文件描述符fd的信息注册到fdset数组里,即指定一个位置为此文件描述符,并将其位设置为1。
FD_CLR(int fd,fd_set* fdset);将fdset数组里的文件描述符fd的信息清除掉,即设置为0。
FD_ISSET(int fd,fd_set* fdset);判断fdset数组里有没有注册文件描述符fd的信息,即指定位置处,其位的值是否为1。有则返回true,无则返回false。

所以使用select函数前的第一步,就是先把要监视的文件描述符注册到fd_set数组里。

2.3 指定监视范围

select函数通过第一个参数来传递监视对象的文件描述符的数量。因为Linux里文件描述符的值是从3开始递增,所以你只需将最大的文件描述符的值再加1传递给select的第一个参数即可。

2.4 设置超时

struct timaval
{long tv_sec;        //secondslong tv_usec;       //microseonds
}

通过select函数的最后一个参数来设置超时,因为select函数只有在有文件描述符发生变化时,才会返回,否则会一直阻塞住。

所以如果你不想阻塞住程序,那么就可以设置超时时间,传递给select的最后一个参数。

如果你想阻塞住程序,直到有文件描述符发生变化,你可以给select的最后一个参数传nullptr

当达到超时时间而没有文件描述符改变时,select函数返回0。

2.5 查看调用select函数后的结果

select函数在调用时,除发生变化的文件描述符对应位外,会把传递的fd_set数组里的其余位全部置为0。

如图,你传入select函数的fd_set数组里,要求监视的是文件描述符fd1、fd2、fd3等,然后,select函数就会将fd0、fd1、fd2、fd3等,没有发生变化的文件描述符置为0,发生变化的文件描述符就不改变。

所以,你调用select函数后,获取到的fd_set数组里的值,位为1的都是发生变化的文件描述符,然后你根据fd_set数组是第几个参数,从而可以对它进行指定的操作,例如:你传递的fd_set数组的参数是select函数的第二个参数,那么说明,这个fd_set数组里位为1的文件描述符,此时有输入流,你就可以通过read函数来将里面的数据取出了。

2.7 与Windows系统的区别

上述都是在Linux系统下的,select函数在Linux和Windows系统上,完全相同。只是:

        1.在Windows系统上,select函数的第一个参数是无意义的,只是为了保持与Linux系统的兼容性。

        2.Windows系统的fd_set与Linux的有区别。如下所示是Windows的fd_set结构体,其中数组也是位数组,并且使用的宏和Linux也是一样的。

typedef struct fd_set
{u_int fd_count;                //套接字句柄数SOCKET fd_array[FD_SETSIZE];   //保存套接字句柄
}fd_set

为什么Windows要这样?

因为:在Linux中,文件描述符是递增的,所以你注册的时候,系统可以很好的找出当前文件描述符和最后生成的文件描述符之间的关系。但是在Windows中,套接字句柄(SOCKET)的生成并非是从0开始的,值之间也没有规律,所以需要直接保存句柄的数组和记录句柄数的变量。

3. 实现I/O复用的回声服务器端

实现思路:

创建一个fdset数组,里面要存放所有要监视的文件描述符。首先把server文件描述符注册到里面,接着select监听这个server文件描述符,因为TCP的连接,也是以数据接收的形式进行的,所以当有连接请求时,select函数里的readfdset数组里注册的server文件描述符就会置为1。紧接着就处理这个连接请求,把后面每连接的一个客户端的文件描述符就注册到fdset数组里,用循环,来遍历,只要是server文件描述符为1,就处理连接请求,不是,就是客户端文件描述符,就处理读写。

代码:

#include<iostream>
#include<sys/socket.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<cstring>
#include<sys/time.h>
#include<sys/select.h>int main()
{int socketfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);if(socketfd==-1){std::cout<<"socket fail!"<<std::endl;return 0;}int bReuse=true;socklen_t reuseLen=sizeof(bReuse);setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,(void*)&bReuse,reuseLen);sockaddr_in serverAddr;memset(&serverAddr,0,sizeof(serverAddr));serverAddr.sin_family=AF_INET;serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);serverAddr.sin_port=htons(9130);if(-1==bind(socketfd,(sockaddr*)&serverAddr,sizeof(serverAddr))){std::cout<<"bind fail!"<<std::endl;return 0;}if(-1==listen(socketfd,1)){std::cout<<"listen fail!"<<std::endl;return 0;}fd_set fdset;FD_ZERO(&fdset);FD_SET(socketfd,&fdset);timeval timeout;timeout.tv_sec=5;timeout.tv_usec=0;fd_set tempset;int fdmax=socketfd;while(1){tempset=fdset;int result=select(fdmax+1,&tempset,0,0,&timeout);if(result==0){continue;}else if(result==-1){std::cout<<"select fail"<<std::endl;break;}else{for(int i=0;i<fdmax+1;++i){if(FD_ISSET(i,&tempset)){if(i==socketfd)     //服务器文件描述符发生了变化,意味着有连接请求{sockaddr_in clientAddr;memset(&clientAddr,0,sizeof(clientAddr));socklen_t clientLen=sizeof(clientAddr);int clientfd=accept(i,(sockaddr*)&clientAddr,&clientLen);std::cout<<"客户端IP地址:"<<inet_ntoa(clientAddr.sin_addr)<<std::endl;if(-1==clientfd){std::cout<<"accept fail!"<<std::endl;}else{FD_SET(clientfd,&fdset);               //成功接收请求把客户端文件描述符信息注册到fdset里if(fdmax<clientfd)fdmax=clientfd;}}else        //客户端文件描述符发生了变化,意味着有读取{char buff[1024];int readlen=read(i,buff,sizeof(buff));if(readlen==0)          //客户端EOF,断开连接不再发送数据{FD_CLR(i,&fdset);   //把fdset里的客户端文件描述符给注销掉close(i);           //关闭客户端套接字}else{std::cout<<"客户端发来的消息:"<<buff<<std::endl;write(i,buff,readlen);}}}}}}close(socketfd);return 0;
}

Windows系统:

要注意,其fdset不是一个数组,而是一个结构体,要用“.”或“->”运算符来取里面的套接字文件描述符数组,来进行处理。


http://www.ppmy.cn/news/1080013.html

相关文章

CSS3常用的新功能总结

CSS3常用的新功能包括圆角、阴渐变、2D变换、3D旋转、动画、viewpor和媒体查询。 圆角、阴影 border-redius 对一个元素实现圆角效果&#xff0c;是通过border-redius完成的。属性为两种方式&#xff1a; 一个属性值&#xff0c;表示设置所有四个角的半径为相同值&#xff…

Ubuntu22.04上下左右全方位美化教程

Ubuntu22.04上下左右全方位美化教程 以Plank替代Dock甲板安装使用优化除了Plank之外还有Ubuntu-Launchpad可以替代Dock Tweak-Tool配置主题Theme的配置下载解压配置 Icon文件夹显示风格的配置Cursors鼠标风格优化Background背景、Lock锁屏以及登陆页面的更换过渡动画配置安装 E…

SAP接口调用方式总结

目录 一、RFC调用/JCO调用二、Restful调用三、Webservice调用四、直联接口五、PI接口&#xff1a;具体不做赘述&#xff0c;可以百度总结&#xff1a; 前言&#xff1a;跟外围系统对接&#xff0c;首要要确认好接口的调用方式&#xff0c;然后再根据相关的调用方式进行相应的操…

23年8月工作笔记整理(前端)

目录 一、css知识二、echarts知识三、vue3知识 一、css知识 1.flex布局让某个子元素靠右 margin-left: auto; 靠左 margin-right: auto; 2.不清楚子元素多少个&#xff0c;又要等分占空间&#xff1b;栅格布局方法为&#xff1a; display: grid; grid-template-columns: repea…

数据结构:排序解析

文章目录 前言一、常见排序算法的实现1.插入排序1.直接插入排序2.希尔排序 2.交换排序1.冒泡排序2.快速排序1.hoare版2.挖坑版3.前后指针版4.改进版5.非递归版 3.选择排序1.直接选择排序2.堆排序 4.归并排序1.归并排序递归实现2.归并排序非递归实现 5.计数排序 二、排序算法复杂…

音视频入门基础理论知识

文章目录 前言一、视频1、视频的概念2、常见的视频格式3、视频帧4、帧率5、色彩空间6、采用 YUV 的优势7、RGB 和 YUV 的换算 二、音频1、音频的概念2、采样率和采样位数①、采样率②、采样位数 3、音频编码4、声道数5、码率6、音频格式 三、编码1、为什么要编码2、视频编码①、…

OCR多语言识别模型构建资料收集

OCR多语言识别模型构建 构建多语言识别模型方案 合合&#xff0c;百度&#xff0c;腾讯&#xff0c;阿里这四家的不错 调研多家&#xff0c;发现有两种方案&#xff0c;但是大多数厂商都是将多语言放在一个字典里&#xff0c;构建1w~2W的字典&#xff0c;训练一个可识别多种语…

YOLOV8模型使用-检测-物体追踪

这个最新的物体检测模型&#xff0c;很厉害的样子&#xff0c;还有物体追踪的功能。 有官方的Python代码&#xff0c;直接上手试试就好&#xff0c;至于理论&#xff0c;有想研究在看论文了╮(╯_╰)╭ 简单介绍 YOLOv8 中可用的模型 YOLOv8 模型的每个类别中有五个模型用于检…

JavaScript【转】

以下内容转载和参考自&#xff1a;w3school的JavaScript学习内容&#xff0c;HTML JavaScript。 JavaScript 使 HTML 页面更具动态性和交互性&#xff0c;前面我们都是在代码中一开始就将元素的值、属性、style样式写死&#xff0c;使用JavaScript 的话就可以对这些内容动态的更…

除法的效率 arm处理器

除法的效率 性能结论&#xff1a; 除法&#xff1a;545ms 乘法&#xff1a;93ms 差了3倍 代码&#xff1a; #define CNT (1000) #define factor (0.666667) // 乘法0.666667 &#xff0c;除法时 1/1.5 void parse3AState(Pack1* in, Pack2* out) {for (int i 0; i < CNT…

经纬恒润预期功能安全(SOTIF)解决方案为自动驾驶安全保驾护航

近年来&#xff0c;“安全”被普遍认为是智能驾驶汽车被用户接受或者得到商业应用最大的问题&#xff0c;ISO26262功能安全旨在避免由E/E系统功能失效导致的不可接受的风险&#xff0c;主要是针对系统性失效/随机硬件失效导致的风险进行分析和控制&#xff0c;然而传感器和感知…

Springboot动态修改日志级别

在开发和运维过程中&#xff0c;我们经常需要调整日志级别来查看不同级别的日志信息。传统的做法是修改配置文件&#xff0c;然后重启应用程序。但是&#xff0c;在分布式系统中&#xff0c;重启应用程序可能比较麻烦&#xff0c;而且也影响了业务的正常运行。 Springboot提供…

mysql8忘记密码的步骤

1.关闭服务器&#xff0c;要管理员打开 cmd net stop mysql 2.进入 到mysql\bin中。跳过密码 mysqld --console --skip-grant-tables --shared-memory 3.新打开一个窗口 mysql -u root -p 密码不输入&#xff0c;直接回车 4.修改密码为空 use mysql; update user set a…

13 Dropout(丢弃法)

正则项只在训练时使用&#xff0c;他们在更新权重时让模型复杂度降低一点点&#xff0c; dropout正则项方法之一&#xff0c;深度学习崛起前&#xff0c;最早提出的算法。 动机&#xff1a;一个好的模型&#xff0c;需要对你的输入数据鲁棒&#xff0c;无论加入多少噪音都有稳…

DEAP库文档教程二-----创建类型

本节将展示如何通过creator创建类型以及如何使用toolbox进行初始化。 1、Fitness 已经提供的Fitness类是一个抽象类&#xff0c;它需要weight来使得它成为一个函数。一个最小化的适应度是通过负权重构建的&#xff0c;而一个最大化适应度则需要正权重。 creator.create(&quo…

【Postman】postman生成测试报告完整步骤(包含命令与newman安装教程链接)

文章目录 一、前提二、导出Postman脚本三、生成测试报告 一、前提 前提准备&#xff1a; 已安装好Newman 指引文章&#xff1a;Newman安装与环境配置完整版文章 Newman是一款基于nodejs开发的可以运行Postman脚本的工具&#xff0c;并可以生成测试报告。 二、导出Postman脚本…

ARDUINO STM32 SSD1306

STM32F103XX系列SPI接口位置 在ARUDINO 下&#xff0c;&#xff08;不需要设置引脚功能&#xff0c;不需要开启时钟设置&#xff0c;ARDUINO已经帮我们处理了&#xff09; stm32f103c6t6 flash不足&#xff0c;不足以运行U8G2,产生错误 改用U8X8&#xff0c;后将字体改为u8x8_…

Python之父加入微软三年后,Python嵌入Excel!

近日&#xff0c;微软传发布消息&#xff0c;Python被嵌入Excel&#xff0c;从此Excel里可以平民化地进行机器学习了。只要直接在单元格里输入“PY”&#xff0c;回车&#xff0c;调出Python&#xff0c;马上可以轻松实现数据清理、预测分析、可视化等等等等任务&#xff0c;甚…

mysql使用st_distance_sphere函数报错Incorrect arguments to st_distance_sphere

最近发现执行mysql st_distance_sphere报错了。 报错的信息是Incorrect arguments to st_distance_sphere。 最开始以为是跟mysql的版本有关系&#xff0c;所以看了下自己本地的mysql版本&#xff0c;执行一下sql select version(); 发现自己本地的mysql版本是 5.7.30 这…

thinkphp 操作远程oracle遇到的相关坑

坑一&#xff1a;没有内置oracle 解决方法&#xff1a; 1&#xff0c;下载think-oracle 扩展&#xff0c;资源很多&#xff0c;百度即可下载&#xff0c;分别放置于db下的connector 和 builder 文件夹下 2&#xff0c;安装oracle本地客户端&#xff0c;一搜一大把&#xff0c;核…
最新文章