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

news/2025/2/15 5:47:35/

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

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

多路复用服务器:通过捆绑并统一管理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 模型的每个类别中有五个模型用于检…