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

news2024/12/27 19:25:04

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

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

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

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

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

相关文章

数据恢复工具推荐,记好这3款!

“我真的总会因为数据丢失而烦恼。有些文件都不知道什么时候删除的&#xff0c;想找的时候就找不到了。各位朋友有什么好的数据恢复工具可以推荐吗&#xff1f;真的很需要&#xff01;” 无论保存在电脑、u盘、还是手机里的数据&#xff0c;都有可能会被误删或由于各种原因而丢…

Vue框架--Vue中的数据绑定

Vue中有两种数据绑定的方式 1.单向数据绑定(v-band):数据只能够从data流向页面 2.双向数据绑定(v-model):数据不仅仅能够从data流向页面&#xff0c;也可以从页面流向data。 备注: 1.双向绑定一般都应用在表单类元素上。(如:input、select等有value属性值的标签上) 2.…

【开发】视频云存储/安防监控视频智能分析网关V3:明烟/明火检测详解

智能分析网关系列是基于边缘AI计算技术&#xff0c;可对前端摄像头采集的视频流进行实时检测分析&#xff0c;能对监控画面中的人、车、物进行识别。我们的AI边缘计算网关硬件——智能分析网关目前有5个版本&#xff1a;V1、V2、V3、V4、V5&#xff0c;每个版本都能实现对监控视…

iOS开发Swift-6-深色模式,类与对象,MVC模式,弹出框,闭包-趣味问答App

1.创建趣味问答App项目 2.创建一个问题文本&#xff0c;水平居中约束。 创建蓝、红两个按钮&#xff0c;放入Stack View中&#xff0c;给StackView水平居中约束&#xff0c;下边约束&#xff0c;设置两按钮间距为20. 设置进度条view与safe View关系为equal width。设置他们的比…

关于在香橙派安装mysql时遇到的坑

前言 基础环境&#xff1a;硬件&#xff1a;香橙派5操作系统&#xff1a;openkylincpu架构&#xff1a;arm 过程&#xff1a;最近有个任务&#xff0c;要在新的环境中验证一些服务是否可用。目的时向全国产化靠拢。 需要在香橙派上安装openkylin&#xff0c;一开始尝试的是香橙…

A. Channel

题目&#xff1a;样例&#xff1a; 输入 4 5 5 3 -- 5 2 3 - 5 4 2 - 5 0 7 -输出 YES NO MAYBE YES 题意&#xff1a; 给出 目的人数n看到通告的数量&#xff0c;初始人数m上线的数量&#xff0c;通知系统上线q条消息&#xff0c;‘-’表示有人下线&#xff0c;‘’表示有人上…

京东API接口解析,实现按关键字搜索商品

京东开放平台提供了丰富的API接口&#xff0c;用于查询商品、用户、订单等信息。以下是一个基本的示例&#xff0c;解析并实现按关键字搜索商品的API接口。 需要访问京东开放平台并注册一个开发者账号。注册完成后&#xff0c;你需要创建一个应用并获取到API的权限。 在获取到…

不看过程看结果 Web3最霸道架构Intent- Centric能解决啥?

开源区块链网络以太坊诞生的8年里&#xff0c;涌现出不计其数的竞争链&#xff0c;同时也为金融、游戏等场景的链上应用发展奠定了底层基础。唯一令人遗憾的是&#xff0c;8年了&#xff0c;绝大多数互联网用户还是摸不到以区块链为底层的Web3大门。 有数据显示&#xff0c;总…

亚马逊测评的风控挑战与解决方案

做跨境电商测评自养号测评的都知道&#xff0c;亚马逊平台是所有跨境平台中风控最严的&#xff0c;做亚马逊测评实操遇到过各种问题&#xff0c;比如&#xff1a;取消订单&#xff08;砍单&#xff09;、掉评、账号批量被封等等 其实这些问题除了亚马逊自身的风控&#xff0c;…

真香:Alibaba开源GitHub星标100K微服务架构全彩进阶手册

前言&#xff1a; 微服务架构作为一种高效灵活的应用架构&#xff0c;正在成为企业级应用开发的主流选择。在众多的微服务架构指南中&#xff0c;阿里巴巴开源的GitHub微服务架构全彩进阶手册备受瞩目&#xff0c;其100star更是证明了其在开发者社区中的重要地位。 这本手册汇…

微服务演变及怎么拆分微服务

微服务演变 ⼀、微服务演变过程 版本1.0 ⼏年前&#xff0c;⼩明和⼩⽪⼀起创业做⽹上超市。⼩明负责程序开发&#xff0c;⼩⽪负责其他事宜。当时互联⽹还不发 达&#xff0c;⽹上超市还是蓝海。只要功能实现了就能随便赚钱。所以他们的需求很简单&#xff0c;只需要⼀个⽹站…

一文5000字从0到1使用Jmeter实现轻量级的接口自动化测试(图文并茂)

接口测试虽然作为版本的一环&#xff0c;但是也是有一套完整的体系&#xff0c;有接口的功能测试、性能测试、安全测试&#xff1b;同时&#xff0c;由于接口的特性&#xff0c;接口的自动化低成本高收益的&#xff0c;使用一些开源工具或一些轻量级的方法&#xff0c;在测试用…

文心一言 VS 讯飞星火 VS chatgpt (84)-- 算法导论8.2 1题

一、用go语言&#xff0c;参照图8-2 的方法&#xff0c;说明 COUNTING-SORT 在数组 A(6&#xff0c;0&#xff0c;2&#xff0c;0&#xff0c;1&#xff0c;3&#xff0c;4&#xff0c;6&#xff0c;1&#xff0c;3&#xff0c;2)上的操作过程。 文心一言&#xff1a; Counti…

2023-8-31 有边数限制的最短路(bellman-ford)

题目链接&#xff1a;有边数限制的最短路 #include <iostream> #include <cstring> #include <algorithm>using namespace std;const int N 510, M 10010;int n, m, k; int dist[N], backup[N];// 存放边的信息 struct Edge {int a, b, w; }edges[M];void …

mysql group by 字段 与 select 字段

表数据如下&#xff1a; 执行SQL语句1&#xff1a; SELECT * FROM z_course GROUP BY NAME,SEX 结果&#xff1a; 执行SQL语句2&#xff1a; SELECT * FROM z_course GROUP BY NAME sql 1 根据 name&#xff0c;sex 两个字段分组&#xff0c;查询 所有字段&#xff0c;返回结…

GitHub打不开解决方法——授人以渔

打不开GitHub的原因之一&#xff0c;DNS地址解析到了无法访问的ip。&#xff08;为什么无法访问&#xff1f;&#xff09; 1、打开GitHub看是哪个域名无法访问&#xff0c;F12一下 2、DNS解析看对应的域名目前哪个IP可以访问 DNS解析的网址&#xff1a; &#xff08;1&#x…

OpenBSD新版将增加KARL功能:重启换内核

在 OpenBSD 的测试快照中加入了一个新的功能&#xff0c;每次当 OpenBSD 用户重启或升级计算机时都会创建一个独特的内核。该功能被称之为 KARL&#xff08;内核地址随机化链接Kernel Address Randomized Link&#xff09;&#xff0c;即以随机的顺序重新链接其内部的内核文件&…

深度学习技巧应用26-CNN中多种注意力机制的嵌入方法,终于搞懂注意力机制了

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用26-CNN中多种注意力机制的嵌入方法,终于搞懂注意力机制了。CNN是一种能够有效处理图像和其他二维数据的深度学习模型。在传统的CNN中,每个卷积核都会对输入的所有位置进行相同的操作,这可能导致网络无法针对特定区域…

Oracle中LEFT JOIN后AND与WHERE的异同

1、AND 过滤之后再连接 2、WHERE 连接之后再过滤 下面以具体例子来说明&#xff1a; (1)、建表及插入测试数据 --建测试表 create table FACT_TAB ( ID INTEGER,STATUS VARCHAR2(8) ); create table DIM_STATUS ( STSTUS_CLASS VARCHAR2(8),STATUS_CODE VARCHAR2(8),S…

VMware tools的安装以及按钮灰色的解决方法

VMware tools的安装 ** 注意&#xff1a;** 新版本的 VMware 会自动安装的 VMware tools&#xff0c;如何测试 VMware tools 呢&#xff1f;在Windows 系统里复制一段话&#xff0c;能粘贴到 Ubuntu 系统里终端里&#xff0c;说明 VMware tools 已经安装了。 没有安装的请参考…