【IO】多路转接Select

news2024/11/29 2:40:57

一、初识 select

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

  • select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在 select 这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;
select 函数原型
C
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set
*exceptfds, struct timeval *timeout);
参数解释:
  • nfds 是文件描述符集合中最大文件描述符加1。
  • readfds 是指向需要监视读操作的文件描述符集合的指针。
  • writefds 是指向需要监视写操作的文件描述符集合的指针。
  • exceptfds 是指向需要监视异常条件的文件描述符集合的指针。
  • timeout 是指定 select 调用应该阻塞的最长时间。
函数返回值:
  • 执行成功则返回文件描述词状态已改变的个数
  • 如果返回 0 代表在描述词状态改变前已超过 timeout 时间,没有返回
  • 当有错误发生时则返回-1,错误原因存于 errno,此时参数 readfds,writefds, exceptfds 和 timeout 的值变成不可预测。

错误值可能为:

  • EBADF 文件描述词为无效的或该文件已关闭
  • EINTR 此调用被信号所中断
  • EINVAL 参数 n 为负值。
  • ENOMEM 核心内存不足
参数 timeout 取值:
  • NULL:则表示 select()没有 timeout,select 将一直被阻塞,直到某个文件 描述符上发生了事件;
  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
  • 特定的时间值:如果在指定的时间段里没有事件发生,select 将超时返回。
关于 fd_set 结构

其实这个结构就是一个整数数组, 更严格的说, 是一个 "位图". 使用位图中对应的位来表示要监视的文件描述符.

提供了一组操作 fd_set 的接口, 来比较方便的操作位图.

C
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组 set 中相关fd 的位
int 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 的全部位

关于 timeval 结构

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

二、理解 select 执行过程

想要理解select最主要要理解fd_set结构,fd_set的本质其实是一张位图,他的每一个比特位可以表示一个文件描述符

我们使用select时,需要手动输入告诉内核,要关心哪一些fd,比特位的位置表示文件描述符的编号,比特位的内容表示是否关心这个fd,例如我们要select等待编号为4 5 6号的fd,此时的fd_set内容 ...0111 0000

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set
*exceptfds, struct timeval *timeout);

其实select中的中间几个参数既是输入型参数,也是输出型参数,输入很容易理解,例如上述的例子,用户需要告诉内核哪些需要关心。对于输出来说,select会等待用户关心的fd,等其中的一个或多个事件就绪的话,select就可以通过这些参数来返回,告诉用户具体是关心的哪些fd事件就绪了,它具体的做法是将传入的fd_set结构除了就绪的fd为1,其他的置为0。

这样可能就会出现一种情况,可能关心的有的fd还没有就绪,但是它却被置为0了,所以每次在调用select时就需要我们再次设置要关心的fd,通常我们需要依赖一种数据结构,通常为数组

三、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 的大小可以调整,可能涉及到重新编译内核. 感兴趣可以自己去收集相关资料.

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

四、select 使用示例

#include <iostream>
#include <sys/select.h>
#include <string>
#include "socket.hpp"
using namespace socket_ns;

class SelectServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    SelectServer(uint16_t port)
        : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(_port);
    }
    ~SelectServer()
    {
    }

    void Init()
    {
        //初始化select辅助数组
        for (int i = 0; i < gnum; i++)
        {
            _select_array[i] = gdefaultfd;
        }
        //这里是直接将listen套接字的fd加入到数组中
        _select_array[0] = _listensock->Sockfd();
    }

    void Accepter()
    {
        InetAddr addr;
        int sockfd = _listensock->Accepter(&addr);
        if (sockfd > 0)
        {
            LOG(DEBUG, "get a new link, client info %s:%d\n", addr.IP().c_str(), addr.Port());
            // 将sockfd添加到select辅助数组中
            bool flag = false;
            for (int pos = 1; pos < gnum; pos++)
            {
                if (_select_array[pos] == gdefaultfd)
                {
                    flag = true;
                    _select_array[pos] = sockfd;
                    LOG(INFO, "add %d to fd_array success!\n", sockfd);
                    break;
                }
            }
            // select可以等待的fd是有限的
            if (!flag)
            {
                LOG(WARNING, "Server Is Full!\n");
                ::close(sockfd);
            }
        }
    }

    void HanderIO(int i)
    {
        char buffer[1024];
        ssize_t n = ::recv(_select_array[i], buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say# " << buffer << std::endl;

            std::string responsestr = "HTTP/1.1 200 OK\r\n";
            responsestr += "Content-Type: text/html\r\n";
            responsestr += "\r\n";
            responsestr += "<html><h1>hello Linux</h1></html>";
            ::send(_select_array[i], responsestr.c_str(), responsestr.size(), 0);
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit...\n");
            // 关闭fd
            ::close(_select_array[i]);
            // select 不要在关心这个fd了
            _select_array[i] = gdefaultfd;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            // 关闭fd
            ::close(_select_array[i]);
            // select 不要在关心这个fd了
            _select_array[i] = gdefaultfd;
        }
    }

    void HandlerEvent(fd_set rfds)
    {
        //事件派发(遍历rfds看哪些fd就绪了,根据fd不同的类型处理不同的事件)
        for (int i = 0; i < gnum; i++)
        {
            if (_select_array[i] == gdefaultfd)
                continue;
            // 是关心的fd,但不一定就绪了,接下来检测他是否在rfds中
            if (FD_ISSET(_select_array[i], &rfds))
            {
                // 检测是listenfd还是普通的fd
                if (_listensock->Sockfd() == _select_array[i])
                {
                    // 此时可以accept了,一定不会等了
                    Accepter();
                }
                else
                {
                    HanderIO(i);
                }
            }
        }
    }

    void Loop()
    {
        while (true)
        {
            // 设置文件描述符
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = gdefaultfd;
            for (int i = 0; i < gnum; i++)
            {
                if (_select_array[i] != gdefaultfd)
                {
                    FD_SET(_select_array[i], &rfds);
                    if (_select_array[i] > max_fd)
                    {
                        max_fd = _select_array[i];
                    }
                }
            }
            struct timeval timeout = {30, 0};
            int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n)
            {
            case 0:
                LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                LOG(ERROR, "select error\n");
                break;
            default:
                LOG(INFO, "haved event ready, n : %d\n", n); // 如果事件就绪,但是不处理,select会一直通知我,直到我处理了!
                HandlerEvent(rfds);
                break;
            }
        }
    }

private:
    uint16_t _port;
    SockSPtr _listensock;
    int _select_array[gnum];
};

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

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

相关文章

u盘拷贝文件管控如何实现?4个方法一举搞定,一文详解!100%纯干货,赶快码住!

数字化办公日益普及&#xff0c;U盘作为便携的数据存储设备&#xff0c;在文件传输和备份中扮演着重要角色。 然而&#xff0c;U盘的使用也带来了数据泄露的风险&#xff0c;如何有效管控U盘拷贝文件呢&#xff1f;u盘拷贝文件管控如何实现&#xff1f; 本文&#xff0c;将详细…

winforms基本操作-将datagridview内容保存为excel文件

title: winforms基本操作-将datagridview内容保存为excel文件 tags: [winforms, windows, datagridview] categories: [客户端, windows, winforms] 这里记录一下将winforms展示的datagridview&#xff0c;导出或保存为excel文件。 这里说一下环境、版本信息&#xff1a; win系…

在线教育系统开发:SpringBoot框架的实战应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

什么软件能指定usb端口禁用?五款电脑USB端口禁用软件!(热门分享)

什么软件能指定usb端口禁用&#xff1f; USB端口&#xff0c;作为电脑与外部设备连接的重要接口&#xff0c;其安全性日益受到企业的重视。 为了有效防止数据泄露和未经授权的设备接入&#xff0c;指定USB端口禁用成为了许多企业的迫切需求。 本文&#xff0c;将介绍五款热门…

京东云主机怎么用?使用京东云服务器建网站(图文教程)

京东云主机怎么用&#xff1f;非常简单&#xff0c;本文京东云服务器网jdyfwq.com使用以使用京东云服务器搭建WordPress博客网站为例&#xff0c;来详细说下京东云主机的使用方法。使用京东云服务器快速搭建WordPress网站教程&#xff0c;3分钟基于应用镜像一键搞定&#xff0c…

程序传入单片机的过程,以Avrdude为例分析

在市场上有各式各样的单片机&#xff0c;例如Arduino&#xff0c;51单片机&#xff0c;STM等。通常&#xff0c;我们都用其对应的IDE软件进行单片机的编程。这些软件既负责将程序代码转写成二进制代码&#xff0c;即机器语言&#xff0c;也负责将该二进制代码导入单片机。与此同…

YOLO11改进|卷积篇|引入空间通道重组卷积ScConv

目录 一、【SCConv】卷积1.1【SCConv】卷积介绍1.2【SCConv】核心代码 二、添加【SCConv】卷积2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【SCConv】卷积 1.1【SCConv】卷积介绍 SCConv 模块提供了一种新的视角来看待CNNs的特征提取…

无人机企业必备运营合格证及甲级服务能力等级证书详解

无人机企业在运营过程中&#xff0c;需要取得一系列资质证书以确保其合法、安全、高效地开展业务。其中&#xff0c;运营合格证和甲级服务能力等级证书是两个重要的资质认证。以下是这两个证书的详细解析&#xff1a; 无人机企业运营合格证 无人机企业运营合格证是由国家相关…

10个令人惊叹的AI工具

AI 确实改变了游戏规则&#xff1b;它彻底改变了我们工作、创造和与技术互动的方式。虽然 ChatGPT、DALLE 和 Midjourney 等巨头占据了大部分头条新闻&#xff0c;但还有很多其他不为人知的 AI 工具和技术&#xff0c;大多数都同样令人惊叹。 以下是十种你可能没有听说过但绝对…

【AI知识点】正则化(Regularization)

正则化&#xff08;Regularization&#xff09; 是机器学习和统计学中的一种技术&#xff0c;用于防止模型过拟合。在训练模型时&#xff0c;模型可能会过度拟合训练数据&#xff0c;导致在新数据上的表现较差。正则化通过在优化过程中引入额外的约束或惩罚项&#xff0c;使模型…

python如何比较字符串

Python可使用cmp()方法来比较两个对象&#xff0c;相等返回 0 &#xff0c;前大于后&#xff0c;返回 1&#xff0c;小于返回 -1。 a "abc" b "abc" c "aba" d "abd" print cmp(a,b) print cmp(a,c) print cmp(a,d) //返回 0 1 …

pWnos1.0 靶机渗透 (Perl CGI 的反弹 shell 利用)

靶机介绍 来自 vulnhub 主机发现 ┌──(kali㉿kali)-[~/testPwnos1.0] …

解决 OpenCloudOS 中 yum 安装 yum-utils 命令报错的问题

目录 前言1. 问题背景与错误分析2. 深入分析错误原因2.1 OpenCloudOS 与 CentOS 之间的区别2.2 文件冲突的具体分析 3. 解决方案3.1 使用 --replacefiles 强制安装3.2 使用 yum swap 替换冲突包3.3 手动调整冲突包 4. 预防与优化建议4.1 确保软件源的兼容性4.2 定期更新系统 结…

爆赞!豆瓣9.6,多语言版本全球发行,程序员入门大模型必读之作!

当一本书的内容足够好&#xff0c;它就会拥有多个语言版本 我已将这本大模型书免费分享出来&#xff0c;需要的小伙伴可以扫取。 在这个信息全球化的时代&#xff0c;一本书籍的卓越内容往往能够跨越语言的界限&#xff0c;触及世界各地读者的心灵。今天&#xff0c;我们庆祝…

617、合并二叉树

1、题目描述 . - 力扣&#xff08;LeetCode&#xff09; 规则&#xff1a;一个二叉树覆盖到另一颗二叉树上。 (1)重复的节点就将节点值做累加 (2)不重复的节点就取并集。 最终得到一个全新的二叉树&#xff0c;如下图所示。 2、分析 分析&#xff1a;也属于构造二叉树&#x…

Llama 3.2 安卓手机安装教程

在刚刚结束的Meta开发者大会上&#xff0c;Llama 3.2惊艳亮相。此次&#xff0c;它不仅拥有多模态能力&#xff0c;还与Arm等公司合作&#xff0c;推出了专门针对高通、联发科硬件优化的“移动”版本。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 -…

Centos Stream 9备份与恢复、实体小主机安装PVE系统、PVE安装Centos Stream 9

最近折腾小主机&#xff0c;搭建项目环境&#xff0c;记录相关步骤 数据无价&#xff0c;丢失难复 1. Centos Stream 9备份与恢复 1.1 系统备份 root权限用户执行进入根目录&#xff1a; cd /第一种方式备份命令&#xff1a; tar cvpzf backup.tgz / --exclude/proc --exclu…

参数标准+-db和-db

-db是因为比值是相近的&#xff0c;值越进行越好&#xff0c;正负db代表两个值差异不大&#xff0c;可以分子比分母大或者分母比分子大-db代表串扰&#xff0c;分子比分母小&#xff0c;所以负db的值越小越好

探索 GraphRAG:从存储到查询,深入解析 NebulaGraph 与传统 SQL 的对比

近年来&#xff0c;图数据库逐渐成为大数据和人工智能领域的热议话题。特别是随着 GraphRAG 技术的火爆&#xff0c;如何高效存储和查询大规模图数据成为很多开发者关心的问题。出于好奇&#xff0c;我最近尝试了 GraphRAG 并研究其存储结构&#xff0c;因此决定进一步探索图数…

(笔记)第三期书生·浦语大模型实战营(十一卷王场)–书生基础岛第3关---浦语提示词工程实践

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/ZcgkwqteZi9s4ZkYr0Gcayg1n1g?open_in_browsertrue 课程视频&#xff1a;https://www.bilibili.com/video/BV1cU411S7iV/ 课程文档&#xff1a; https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/Prompt 关…