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

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

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

多路复用服务器:通过捆绑并统一管理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;        //seconds
    long 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;        //seconds
    long 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不是一个数组,而是一个结构体,要用“.”或“->”运算符来取里面的套接字文件描述符数组,来进行处理。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue:关于声明式导航中的 跳转、高亮、以及两个类名的定制

声明式导航-导航链接 文章目录 声明式导航-导航链接router-link的两大特点&#xff08;能跳转、能高亮&#xff09;声明式导航-两个类名定制两个高亮类名 实现导航高亮&#xff0c;实现方式其实&#xff0c;css&#xff0c;JavaScript , Vue ,都可以实现。其实关于路由导航&…

Android Studio 的github 工程克隆

上文介绍了Android Studio 里的"Git 建立和简单操作。本文介绍从github 上的工程fork 和clone到本地&#xff0c;然后学习和改进。 本文参考 https://learntodroid.com/how-to-use-git-and-github-in-android-studio/ 克隆clone Github 仓库&#xff1a; 先 Fork 你选择…

stm32之25.FLASH闪存

打开标准库 源码--- int main(void) {uint32_t d;Led_init();key_init();/* 初始化串口1波特率为115200bps&#xff0c;若发送/接收数据有乱码&#xff0c;请检查PLL */usart1_init(115200);printf("this is flash test\r\n");/* 解锁FLASH&#xff08;闪存&#xf…

跨境做独立站,如何低成本引流?你的流量密码在这

大家都知道&#xff0c;海外的消费习惯与国内不同&#xff0c;独立站一向是海外消费者的最喜欢的购物方式之一&#xff0c;这也吸引了许多跨境商家开设独立站。 独立站不同于其他的第三方平台&#xff0c;其他平台可以靠平台自身流量来获得转化&#xff0c;而独立站本身没有流…

java八股文面试[多线程]——ThreadLocal底层原理和使用场景

源码分析&#xff1a; ThreadLocal中定义了ThreadLocalMap静态内部类&#xff0c;该内部类中又定义了Entry内部类。 ThreadLocalMap定了 Entry数组。 Set方法&#xff1a; Get方法&#xff1a; Thread中定义了两个ThreaLocalMap成员变量&#xff1a; Spring使用ThreadLocal解…

虚拟机Ubuntu20.04 网络连接器图标开机不显示怎么办

执行以下指令&#xff1a; sudo service network-manager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service network-manager start

nginx压缩ttf文件 mine.types的作用

最近在运维过程中&#xff0c;前端提到发现在linux上下载某ttl文件&#xff08;字体文件&#xff09;太大&#xff0c;传输过程比较慢&#xff0c;于是就想着使用nginx的gzip进行压缩&#xff0c;经过不断尝试&#xff0c;终于发现在nginx的配置目录/etc/nginx/mine.types 文件…

【UIPickerView-UIDatePicker-应用程序对象 Objective-C语言】

一、今天我们来学习三个东西 1.UIPickerView-UIDatePicker-应用程序对象 1.首先,来看数据选择控件 数据选择控件, 大家对这个数据选择控件,是怎么理解的, 1)数据选择控件,首先,是不是得有数据, 2)然后呢,你还得让用户能够选择, 3)最后,你还得是一个控件儿 那…

国产ETLCloud VS 开源Kettle ETL对比分析

ETLCloud VS Kettle ETLCloud和kettle是目前国内使用最广泛的两款免费ETL工具&#xff0c;本文将从多个角色对ETLCloud和kettle进行对比&#xff0c;方便用户快速了解到两款产品的差异并根据自已的需求选择相应的工具。 ETLCloud提供了对kettle流程的迁移功能&#xff0c;所以…

文件上传漏洞-upload靶场5-12关

文件上传漏洞-upload靶场5-12关通关笔记&#xff08;windows环境漏洞&#xff09; 简介 ​ 在前两篇文章中&#xff0c;已经说了分析上传漏的思路&#xff0c;在本篇文章中&#xff0c;将带领大家熟悉winodws系统存在的一些上传漏洞。 upload 第五关 &#xff08;大小写绕过…

gRPC-Gateway 快速实战

今天来分享一波 gRPC-Gateway &#xff0c; 之前咱们有分享过什么是 gRPC 及其使用方式&#xff0c;可以看看这些关于 gRPC 的历史文章&#xff1a; gRPC介绍 gRPC 客户端调用服务端需要连接池吗&#xff1f; gRPC的拦截器 gRPC的认证 分享一下 gRPC- HTTP网关 I 今天主要是分…

AI:05 - 基于深度学习的道路交通信号灯的检测与识别

随着人工智能的快速发展,基于深度学习的视觉算法在道路交通领域中起到了重要作用。本文将探讨如何利用深度学习技术实现道路交通信号灯的检测与识别,通过多处代码实例展示技术深度。 道路交通信号灯是指示交通参与者行驶和停止的重要信号。准确地检测和识别交通信号灯对于智…

最大子数组和【贪心算法】

最大子数组和 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 class Solution {public int maxSubArray(int[] nums) {//记录最大结果&…

【陈老板赠书活动 - 11期】- 【MySQL从入门到精通】

![ 陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍…

高亮img、pdf重点部分(html2canvas、pdfjs-dist、react-pdf)

可用业务场景 报销单据审批中&#xff0c;高亮发票部分 需求 后台返回一张图片或者pdf、返回一组坐标&#xff0c;坐标类型[number,number,number,number]&#xff0c;分别代表了x、y、width、height。需要根据坐标在图片上高亮出来坐标位置。如下图 高亮的坐标是&#xff1…

UDP 广播

一、UDP 通信图解 UDP通信、本地套接字_呵呵哒(&#xffe3;▽&#xffe3;)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/132523536?spm1001.2014.3001.5501 #include <sys/types.h> #include <sys/socket > ssize_t sendto(in…

【校招VIP】前端算法考点之大数据相关

考点介绍&#xff1a; 大数据的关键技术分为分析技术和处理技术&#xff0c;可用于大数据分析的关键技术主要包括A/B测试&#xff0c;关联规则挖掘&#xff0c;数据挖掘&#xff0c;集成学习&#xff0c;遗传算法&#xff0c;机器学习&#xff0c;自然语言处理&#xff0c;模式…

计算机视觉-YOYO-

目录 计算机视觉-YOYO 目标检测发展历程 区域卷积神经网络(R-CNN) Fast R-CNN Mask R-CNN模型 比如SSD、YOLO(1, 2, 3)、R-FCN 目标检测基础概念 边界框、锚框和交并比 边界框&#xff08;bounding box&#xff09; 锚框&#xff08;Anchor box&#xff09; 交并比 …

Java中的Reference

1. 常用四种引用 快速记忆法&#xff1a;“硬(俗称的强引用) --> 软(SoftReference) --> 弱(WeakReference) --> 虚(PhantomReference)” 此处将常说的“强引用”记忆成“硬引用”可以对应到次席的“软引用”&#xff08;反义词&#xff1a;硬-软&#xff09;这样更容…

Flink 如何定位反压节点?

分析&回答 Flink Web UI 自带的反压监控 —— 直接方式 Flink Web UI 的反压监控提供了 Subtask 级别的反压监控。监控的原理是通过Thread.getStackTrace() 采集在 TaskManager 上正在运行的所有线程&#xff0c;收集在缓冲区请求中阻塞的线程数&#xff08;意味着下游阻…