【Linux】多路转接select

news2024/9/21 16:17:36

一、select介绍

1.1 初始select

系统提供的select函数来实现多路复用输入/输出模型。

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的
  • 程序会停止在select这里等待,直到被监视的文件描述符有一个或者多个发生了状态改变

       IO = 等 + 拷贝,select函数只负责进行对fd进行等待,有时间就绪,就进行事件的派发,这里可以同时对多个fd进行等待。

1.2 认识一下select函数

函数原型:

函数参数:

  • nfds:需要监视的最大的文件描述符值 + 1
  • rdset/wrset/exset:分别对应于需要检测可读文件描述符的集合,可写文件描述符的集合以及异常文件描述符的集合。其结构是使用位图形式,位图中比特位的位置表示的是文件描述符的值,比特位的值:0表示的是不关心的文件描述符,1表示的是关心的文件描述符。
  • timeout:其结构为timeval,用来设置select()的等待时间

函数功能:

       在多路复用中,select函数用于监控多个文件描述符,以便在这些描述符上的某些事件(比如可读,可写或者异常条件)发生时进行处理:

  • 等待事件:可以等待直到一个或者多个文件描述符变得可读、可写或者发生异常
  • 非阻塞监控:它允许程序在多个文件描述符之间进行选择,而不是阻塞在单一操作中
  • 提高效率:可以有效地处理大量并发连接,避免了使用线程或者进程池来处理每一个连接的开销

函数返回值:

  • 执行成功,则返回文件描述符状态已经改变的个数
  • 如果返回0,则代表在文件描述符状态改变前已经超过timeout时间了,没有返回
  • 当有错误发生时,则返回-1,错误原因存在于errno,此时参数readfds,writefds,exceptfds和timeout的值变得不可预测

错误值可能为:

  • EBADF:文件描述符为无效的或者该文件已经关闭
  • EINTR:此调用被信号所中断
  • EINVAL:参数n为负值
  • ENOMEM:核心内存不足

参数timeout的取值:

  • NULL:表示select()没有timeout,select函数将一直被阻塞,直到某一个文件描述符发生了事件,即阻塞IO
  • 0:仅仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生,即非阻塞IO
  • 特定的时间值:如果在指定的时间段里没有事件的发生,select函数将超时返回

关于timeval结构

       timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发送则函数返回,返回值为0。

关于 fd_set 结构

       这个结构就是一个整数数组,更严格地说是一个“位图”,使用位图中对应的位来表示要监视的文件描述符,同时提供了一组操作fd_set的接口,来比较方便的操作位图。

void FD_CLR(int fd, fd_set *set); // 用来清楚描述符组set中相关的fd的位
void FD_ISSET(int fd, fd_set *set); // 用来测试描述符组set中相关的fd的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述符组set中相关的fd的位
void FD_ZERO(fd_set *set); // 用来清除描述符组set中的全部位

二、select函数的执行和socket的就绪

2.1 理解select函数的执行过程

       理解select模型的关键在于理解fd_set,为了举例方便,我们去fd_set的长度为1字节,fd_set中的每一个bit可以对应一个文件描述符fd,则一个字节的fd_set最大可以对应8个fd。

  • 执行fd_set set;FD_ZERO(&set);则set用位表示是 0000 0000
  • 如果fd = 5,执行FD_SET(fd, &set);set变为了0001 0000(第五位置为1)
  • 如果再加入fd = 2,fd = 1,则set变为 0001 0011
  • 执行select(6,&set, 0, 0, 0)阻塞等待
  • 如果fd = 1,fd = 2上都发生了可读事件,则select返回,此时set变成了0000 0011。注意:没有事件发生的 fd = 5 则被清空了

2.2 Socket就绪条件

2.2.1 读就绪

socket内核中,接收缓冲区中的字节数大于等于

2.2.2 写就绪

2.2.3 异常就绪

三、select的特点

3.1 select的优点

       可以监控的文件描述符个数取决于sizeof(fd_set)的值,这个由服务器决定是多少,我的这个服务器上sizeof(fd_set)的值的大小为512,每一个bit表示一个文件描述符,则这个服务器上支持的最大的文件描述符是 512 * 8 = 4096

       将fd加入select监控集的同时,还要再次使用一个数据结构array保存放到select监控集中的fd:一是用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断;二是select返回后会把以前加入的但是并没有事件发生的fd清空,则每次开始select前,都要重新从array中取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

       fd_set的大小可以调整,但是涉及重新编译内核

3.2 select的缺点

  • 每次调用select,都需要手动设置fd集合,从接口使用角度来说是非常不便的
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd中很多时会很大
  • 同时每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太少

四、看一看代码

#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"

using namespace socket_ns;

// select服务器要正确的编写,需要借助一个第三方数组来完成,保存合法的,所有的fd到数组中,方便后期批量化统一添加
class SelectServer
{
    const static int N = sizeof(fd_set) * 8;
    const static int defaultfd = -1;

public:
    SelectServer(uint16_t port)
        : _port(port),
          _listensock(std::make_unique<TcpSocket>())
    {
        InetAddr addr("0", _port);
        _listensock->BuildListenSocket(addr);
        for (int i = 0; i < N; i++)
        {
            _fd_array[i] = defaultfd;
        }
        _fd_array[0] = _listensock->SockFd();
    }
    void AcceptClient()
    {
        // 我们今天只关心了读,而读有:listensock 和 nornam sockfd
        InetAddr clientaddr;
        int sockfd = _listensock->Accepter(&clientaddr); // 这里调用accept会不会阻塞呢??不会。因为事件已经就绪了
        if (sockfd < 0)
            return;

        LOG(DEBUG, "Get new Link, sockfd: %d, client info %s:%d\n", sockfd, clientaddr.Ip().c_str(), clientaddr.Port());
        // read/recv(sockfd); send(sockfd)?? 不能. 必须将新的fd,托管给select。如何托管呢???
        // 只要将新的fd添加到辅助数组中即可。
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (_fd_array[pos] == defaultfd)
                break;
        }
        if (pos == N)
        {
            ::close(sockfd); // sockfd->Close();
            LOG(WARNING, "server is full!\n");
            return;
        }
        else
        {
            _fd_array[pos] = sockfd;
            LOG(DEBUG, "%d add to select array!\n", sockfd);
        }
        LOG(DEBUG, "curr fd_array[] fd list : %s\n", RfdsToString().c_str());
    }
    void ServiceIO(int pos)
    {
        char buffer[1024];
        ssize_t n = ::recv(_fd_array[pos], buffer, sizeof(buffer) - 1, 0); // 这里读取会不会被阻塞?不会
        if (n > 0)
        {
            buffer[n] = 0; 
            std::cout << "client say# " << buffer << std::endl;
            std::string echo_string = "[server echo]# ";
            echo_string += buffer;
            ::send(_fd_array[pos], echo_string.c_str(), echo_string.size(), 0);
        }
        else if (n == 0)
        {
            LOG(DEBUG, "%d is closed\n", _fd_array[pos]);
            ::close(_fd_array[pos]);
            _fd_array[pos] = defaultfd;
            LOG(DEBUG, "curr fd_array[] fd list : %s\n", RfdsToString().c_str());
        }
        else
        {
            LOG(DEBUG, "%d recv error\n", _fd_array[pos]);
            ::close(_fd_array[pos]);
            _fd_array[pos] = defaultfd;
            LOG(DEBUG, "curr fd_array[] fd list : %s\n", RfdsToString().c_str());
        }
    }

    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < N; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                if (_fd_array[i] == _listensock->SockFd())
                {
                    AcceptClient();
                }
                else
                {
                    // 这里不就是普通的sockfd读事件就绪了吗?
                    ServiceIO(i);
                }
            }
        }
    }
    void Loop()
    {
        while (true)
        {
            // listensocket 等待新连接到来,等价于对方给我发送数据!我们作为读事件同一处理
            // 新连接到来 等价于 读事件就绪!
            // 首先要将listensock添加到select中!
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = defaultfd;

            for (int i = 0; i < N; i++)
            {
                if (_fd_array[i] == defaultfd)
                    continue;
                FD_SET(_fd_array[i], &rfds); // 将所有合法的fd添加到rfds中
                if (max_fd < _fd_array[i])
                {
                    max_fd = _fd_array[i]; // 更新出最大的fd的值
                }
            }

            struct timeval timeout = {0, 0};

            // select 同时等待的fd,是有上限的。因为fd_set是具体的数据类型,有自己的大小!
            // rfds是一个输入输出型参数,每次调用,都要对rfds进行重新设定!
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                LOG(INFO, "timeout, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                LOG(ERROR, "select error...\n");
                break;
            default:
                LOG(DEBUG, "Event Happen. n : %d\n", n); // 底层有一个事件就绪,select为什么会一直通知我?因为:我们没有处理!
                HandlerEvent(rfds);
                break;
            }
        }
    }
    std::string RfdsToString()
    {
        std::string fdstr;
        for (int i = 0; i < N; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            fdstr += std::to_string(_fd_array[i]);
            fdstr += " ";
        }
        return fdstr;
    }
    ~SelectServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    int _fd_array[N]; // 辅助数组
};

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

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

相关文章

JavaDS —— LRUCache

概念 LRU是Least Recently Used的缩写&#xff0c;意思是最近最少使用&#xff0c;它是一种Cache替换算法。 什么是Cache&#xff1f;狭义的Cache指的是位于CPU和主存间的快速RAM&#xff0c; 通常它不像系统主存那样使用DRAM技术&#xff0c;而使用昂贵但较快速的SRAM技术。…

SpringBoot实现房产销售系统全解析

第二章关键技术的研究 2.1相关技术 房产销售系统是在Java MySQL开发环境的基础上开发的。Java是一种服务器端脚本语言&#xff0c;易于学习&#xff0c;实用且面向用户。全球超过35&#xff05;的Java驱动的互联网站点使用Java。MySQL是一个数据库管理系统&#xff0c;因为它的…

灌区信息化发展趋势展望

灌区信息化作为现代农业发展的重要组成部分&#xff0c;正逐渐成为提升水资源管理效率、保障粮食安全与促进农业可持续发展的关键途径。随着信息技术的飞速进步和智能化技术的广泛应用&#xff0c;灌区信息化的未来发展趋势展现出多维度、深层次的变革与创新&#xff0c;其发展…

C语言 13 指针

指针可以说是整个 C 语言中最难以理解的部分了。 什么是指针 还记得在前面谈到的通过函数交换两个变量的值吗&#xff1f; #include <stdio.h>void swap(int, int);int main() {int a 10, b 20;swap(a, b);printf("a %d, b %d", a, b); }void swap(int …

SSH 弱密钥交换算法 通过禁用CBC模式解决SSH服务器CBC加密模式漏洞(CVE-2008-5161)

自查方法 查看当前支持的加密算法 man sshd_config |grep -A 40 -w KexAlgorithms 修复方法 Linux平台 修改sshd_config配置文件&#xff0c;删除不安全的加密算法 重启服务 systemctl restart sshd 3.查看修改后的配置文件 sshd -T | grep -w kexalgorithms SSH 弱密…

【Python基础】Python迭代器与生成器(两种强大工具)

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、迭代器2.1 创建迭代器2.2 自定义迭代器2.3 处理大型文件 三、生成器四、生成器表达式五、实际应用…

【数据结构初阶】队列接口实现及用队列实现栈超详解

文章目录 1. 概念1. 1 队列底层结构选型1. 2 队列定义 2. 接口实现2. 1 初始化2. 2 判空2. 3 入队列2. 4 出队列2. 5 队尾元素和队头元素和队列元素个数2. 6 销毁2. 7 接口的意义 3. 经典OJ题3. 1 用队列实现栈3. 1. 1 栈的定义3. 1. 2 栈的初始化3. 1. 3 入栈3. 1. 4 出栈3. 1…

计算机视觉(二)—— MDPI特刊推荐

特刊征稿 01 期刊名称&#xff1a; Applied Computer Vision and Pattern Recognition: 2nd Volume 截止时间&#xff1a; 摘要提交截止日期&#xff1a;2024年10月30日 投稿截止日期&#xff1a;2024年12月30日 目标及范围&#xff1a; 包括但不限于以下领域&#xff1a…

C++:线程库

C&#xff1a;线程库 threadthreadthis_threadchrono 引用拷贝问题 mutexmutextimed_mutexrecursive_mutexlock_guardunique_lock atomicatomicCAS condition_variablecondition_variable thread 操作线程需要头文件<thread>&#xff0c;头文件包含线程相关操作&#xf…

上班炒股会被开除吗?公司是如何发现员工上班炒股?一文告诉你答案!

随着互联网金融的发展&#xff0c;股票交易变得越来越便捷&#xff0c;不少上班族选择利用工作之余的时间来进行股票投资。 然而&#xff0c;这种行为是否合规&#xff1f;公司又是如何发现并处理这种情况的呢&#xff1f;本文将为您解答这些问题。 一、上班炒股是否合规&…

JAVA毕业设计175—基于Java+Springboot+vue3的医院预约挂号管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的医院预约挂号管理系统(源代码数据库)175 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、医生、管理员三种角色 1、用户&#x…

交换机最常用的网络变压器分为DIP和SM

华强盛电子导读&#xff1a;交换机通用网络变压器插件48PIN最为常见 您好&#xff01;今天我要给您介绍一款真正能为您的工业生产带来变革的产品——华强盛工业滤波器。在如今这个高度数字化的工业时代&#xff0c;可靠的网络连接至关重要&#xff0c;而华强盛工业网络变压器就…

Smartbi体验中心新增系列Demo,用户体验更丰富

为进一步提升用户体验&#xff0c;让大家更直观地了解Smartbi产品在数据分析方面的功能优势&#xff0c;Smartbi体验中心近期新增了一系列Demo。这些更新旨在优化产品操作流程&#xff0c;并为用户提供更多真实场景下的应用参考。接下来&#xff0c;我们一起简要浏览此次体验中…

KEIL仿真时弹窗 “Cannot access target.”

现象 仿真时&#xff0c;点击暂停就会弹出下图窗口。 Cannot access target. Shutting down debug session. 解决方法 开启STM32的Debug&#xff0c;如下图。

基于WIFI的开关控制器设计与实现

本设计基于STC89C52RC单片机设计的WiFi开关器系统&#xff0c;旨在通过软硬件设计实现按键和手机APP远程同步控制4个继电器驱动3个LED灯、1个风扇&#xff0c;并且具备时间显示、开关状态显示、定时驱动&#xff0c;时间设定及保存等功能。该设计在硬件方面采用STC89C52单片机作…

软考中项(第三版) 项目成本管理总结

前言 系统集成项目管理工程师考试&#xff08;简称软考中项&#xff09;&#xff0c;其中案例分析也是很大一部分考试内容&#xff0c;目前正在学习中&#xff0c;现总结一些可能会考到的知识点供大家参考。 1.1、项目成本管理总线索 1、项目成本失控的原因 &#xff08;1&a…

python库安装失败问题

pip install XXXX 报错信息如下 D:\Dev>pip install D:\Dev\robotlib-0.0.33.tar.gz DEPRECATION: Loading egg at d:\app\dev\python\lib\site-packages\fs11a3_package-1.3-py3.11.egg is deprecated. pip 24.3 will enforce this behaviour change. A possible replace…

【机器学习】使用Numpy实现神经网络训练全流程

文章目录 网络搭建前向传播反向传播损失计算完整代码 曾经在面试一家大模型公司时遇到的面试真题&#xff0c;当时费力写了一个小时才写出来&#xff0c;自然面试也挂了。后来复盘&#xff0c;发现反向传播掌握程度还是太差&#xff0c;甚至连梯度链式传播法则都没有弄明白。 网…

solidity-19-fallback

接收ETH receive和fallback receive和callback是solidity中两个特殊的回调函数&#xff0c;一个处理接收ETH,一个处理不存在的函数调用。本质上就是吧fallback拆成了两个回调函数。我暂时不知道什么是fallback fallback调用不存在的函数时会被调用也就是这个函数是不是等价于…

视频转音频,分享这六种转换操作

视频转音频&#xff0c;随着多媒体技术的发展&#xff0c;人们越来越频繁地需要将视频中的音频部分提取出来单独使用。无论是为了制作播客、获取音乐片段还是其他需求&#xff0c;视频转音频都是一项非常实用的技能。为了让你轻松应对各种场合的需求&#xff0c;下文将为你详细…