Linux 多路转接select

news2025/7/17 20:16:36

Linux 多路转接select

1. select

select() 是一种较老的多路转接IO接口,它有一定的缺陷导致它不是实现多路转接IO的最优选择,但 poll()epoll() 都是较新版的Linux系统提供的,一些小型嵌入式设备的存储很小,只能使用老版本的 Linux 系统(老内核的代码编译体积小),select()在这些设备上可能是唯一的选择

2 select的工作原理

调用 select() 会在内核中建立 readfdswritefdsexceptfds 三张位图(如果参数设置为空就不建立),分别对应监听读事件、写事件、异常事件。对位图进行添加、删除等操作都必须要使用系统提供的位图操作系统调用(见函数声明部分)。调用 select() 时,向位图输入的时要关心的文件描述符,当事件就绪时,位图的内容就变文件描述符是否有事件发生

select() 会返回就绪的文件描述符的数量,用户自行设置判断条件,对相应的文件描述符进行读、写、或异常处理操作。

Select1

3. 函数声明

#include <sys/select.h>

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

int nfds 表示文件描述符的范围,需要输入等待的最大值fd + 1。(硬要解释就是遍历的是[ 0, fd + 1 )的左闭右开区间,但是需要手动 +1 就匪夷所思)

fd_set *readfds readfds 表示只关心事件。

fd_set *writefds writefds 只关心事件。

fd_set *exceptfds exceptfds 只关心异常事件。

struct timeval *timeout 表示执行流调用 select() 后等待事件就绪的最大时间。如果等待就绪成功,其内部会存储等待剩余的时间;如果等待超时,执行流会跳出 select()。(详细见下文)

reval 返回值大于0,则返回值表示reval 个文件描述符就绪;返回值等于0表示等待超时;返回值小于0代表 select() 出错(如传入的文件描述符不存在)。

4. fd_set

fd_set 是文件描述符集,是一个数据类型,本质是一个位图结构,它的比特位的位置表示文件描述符编号,如第二个比特位表示二号文件描述符。 fd_set 能存储的上线取决于内核编译它时的取值,在云服务器上测试理论最多存储 4096 4096 4096 个文件描述符,这也是 select() 的缺点之一,它能存储的最大文件描述符是定长的,且对于现代服务器来说很小

输入时,fd_set 比特位的内容表示是否关心该 fd 事件,1 表示关心,0 表示不关心;输出时,比特位的内容表示事件是否发生

如图表示第 0、1、6、8 号文件描述符被存进去。

Select2

由于 fd_set 是位图结构,对位图做操作必须使用系统提供的专门的系统调用

void FD_CLR(int fd, fd_set *set)

将某个文件描述符对应的位清零,表示它不在监听范围内。

int  FD_ISSET(int fd, fd_set *set)

用于检查某个文件描述符是否在集合中被设置。

void FD_SET(int fd, fd_set *set)

将某个文件描述符对应的位设置为 1,表示它在监听范围内。

void FD_ZERO(fd_set *set)

用来清除(初始化)整个 fd_set

fd_set简化源码:

/* Macros for manipulating `fd_set`.  */
#define __FD_SETSIZE        1024    /* fd_set 能保存的最大文件描述符数 */

/* 一组文件描述符,每个比特位表示一个文件描述符的状态。 */
typedef struct {
    unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(unsigned long))];
} fd_set;

/* FD_ZERO: 清空 fd_set 中的所有位。 */
#define FD_ZERO(fdsetp) \
  (memset (fdsetp, 0, sizeof (fd_set)))

/* FD_SET: 将文件描述符 `fd` 在 fd_set 中置位。 */
#define FD_SET(fd, fdsetp) \
  ((fdsetp)->fds_bits[(fd) / (8 * sizeof (unsigned long))] |= \
   (1UL << ((fd) % (8 * sizeof (unsigned long)))))

/* FD_CLR: 清除文件描述符 `fd` 在 fd_set 中的位。 */
#define FD_CLR(fd, fdsetp) \
  ((fdsetp)->fds_bits[(fd) / (8 * sizeof (unsigned long))] &= \
   ~(1UL << ((fd) % (8 * sizeof (unsigned long)))))

/* FD_ISSET: 检查文件描述符 `fd` 在 fd_set 中是否被置位。 */
#define FD_ISSET(fd, fdsetp) \
  (((fdsetp)->fds_bits[(fd) / (8 * sizeof (unsigned long))] & \
    (1UL << ((fd) % (8 * sizeof (unsigned long))))) != 0)

5. struct timeval

timeval 是一个时间戳类结构体,用于存储执行流进入 select() 后等待事件就绪的最大时间。它的源码长这样:

#ifndef _STRUCT_TIMEVAL
#define _STRUCT_TIMEVAL

/* 用于存储时间的结构体,包含秒和微秒。 */
struct timeval {
    long tv_sec;        /* 秒。 */
    long tv_usec;       /* 微秒。 */
};

#endif /* _STRUCT_TIMEVAL */

tv_sec 表示秒, tv_usec 表示微秒,设置为 { n, 0 } ,表示阻塞等待,在 n 秒内反复轮询直到有事件就绪就返回, n 秒后,没有事件就绪也会返回;设置为 { 0, 0 } 表示非阻塞等待,轮询一次就返回;也可以设置为 NULL 表示一直阻塞等待

tv_sectv_usec 等待结束后并不会被销毁或重置,设置为 { n, 0 } 时,如果在 n 秒内有事件就绪,可以查看它的剩余时间。

6. select() 的使用

这里演示 select() 在服务器中的使用,用于管理 accept() 传来的文件描述符,因此其只关心读事件。如果不及时处理 select() ,也没有关系,因为事件就绪但是不处理,select() 就会一直通知,直到就绪被处理。注意select() 要正常工作,需要借助一个辅助数组,来保存所有合法的文件描述符。select() 每次使用都要重置。

//select_server.h
#pragma once

#include <iostream>
#include <sys/select.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace socket_ns;

class SelectServer
{
    const static int gnum = sizeof(fd_set) * 8;	//fd_set的理论最大容量
    const static int gdefaultfd = -1;	//初始化 fd_array 的值

public:
    SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(_port);
    }
    void InitServer()
    {
        for (int i = 0; i < gnum; i++)
        {
            fd_array[i] = gdefaultfd;
        }
        fd_array[0] = _listensock->Sockfd(); // 默认直接添加listensock到数组中
    }
    // 处理新连接的
    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());
            bool flag = false;
            for (int pos = 1; pos < gnum; pos++)
            {
                if (fd_array[pos] == gdefaultfd)
                {
                    flag = true;
                    fd_array[pos] = sockfd;
                    LOG(INFO, "add %d to fd_array success!\n", sockfd);
                    break;
                }
            }
            if (!flag)
            {
                LOG(WARNING, "Server Is Full!\n");
                ::close(sockfd);
            }
        }
    }
    // 处理普通的fd就绪的
    void HandlerIO(int i)
    {
        char buffer[1024];
        ssize_t n = ::recv(fd_array[i], buffer, sizeof(buffer) - 1, 0); // 这里读取不会阻塞
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say# " << buffer << std::endl;
            std::string content = "<html><body><h1>hello bite</h1></body></html>";
            std::string echo_str = "HTTP/1.0 200 OK\r\n";
            echo_str += "Content-Type: text/html\r\n";
            echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";
            echo_str += content;
            // echo_str += buffer;
            ::send(fd_array[i], echo_str.c_str(), echo_str.size(), 0); // 临时方案
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit...\n");
            // 关闭fd
            ::close(fd_array[i]);
            // select 不要在关心这个fd了
            fd_array[i] = gdefaultfd;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            // 关闭fd
            ::close(fd_array[i]);
            // select 不要在关心这个fd了
            fd_array[i] = gdefaultfd;
        }
    }
    // 一定会存在大量的fd就绪,可能是普通sockfd,也可能是listensockfd
    void HandlerEvent(fd_set &rfds)
    {
        // 事件派发
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            // fd一定是合法的fd
            // 合法的fd不一定就绪, 判断fd是否就绪
            if (FD_ISSET(fd_array[i], &rfds))
            {
                // 读事件就绪
                // 1. listensockfd 2. normal sockfd就绪
                if (_listensock->Sockfd() == fd_array[i])
                {
                    Accepter();
                }
                else
                {
                    HandlerIO(i);
                }
            }
        }
    }
    void Loop()
    {
        while (true)
        {
            // 1. 文件描述符进行初始化
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = gdefaultfd;

            // 2. 合法的fd 添加到rfds集合中
            for (int i = 0; i < gnum; i++)
            {
                if (fd_array[i] == gdefaultfd)
                    continue;
                FD_SET(fd_array[i], &rfds);
                // 2.1 更新出最大的文件fd的值
                if (max_fd < fd_array[i])
                {
                    max_fd = fd_array[i];
                }
            }

            struct timeval timeout = {30, 0};

            // _listensock->Accepter();// 不能,listensock && accept 我们把他也看做IO类的函数。只关心新链接到来,等价于读事件就绪!
            int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, nullptr ,&timeout); // 只关心读事件
            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);
                PrintDebug();
                break;
            }
        }
    }

    void PrintDebug()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            std::cout << fd_array[i] << " ";
        }
        std::cout << "\n";
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    // 1. select要正常工作,需要借助一个辅助数组,来保存所有合法fd
    int fd_array[gnum];
};

fd_array[gnum] 是一个用来存储所有合法 fd 的辅助数组,它里面所有的元素最初被初始化为 -10 个元素默认存储 listensockfd,其他位置存储 accept() 传递过来的合法 fd 。当连接关闭时,再 close(fd_array[i]) 关闭对应位置的 fd ,并将 fd_array[i] = -1 重置为初始化状态。

这里 select() 用来多路转接 accept() 传递过来的文件描述符, listensockaccept() 也可以看作 IO 类的函数,但这两个函数只关心新连接到来,也就是只关心读事件就绪,所以 select() 中只有 rfds ,其他被置为了 NULL

7. select() 缺点

  1. 每次调用 select(), 都需要手动设置 fd 集合,接口使用不方便。

  2. 每次调用select(), 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大。

  3. 同时每次调用 select() 都需要在内核遍历传递进来的所有 fd, 这个开销在 fd 很多时也很大。

  4. select()支持的文件描述符数量是定长的,而且太小。

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

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

相关文章

【实践案例】使用Dify构建文章生成工作流【在线搜索+封面图片生成+内容标题生成】

文章目录 概述开始节点图片封面生成关键词实时搜索主题参考生成文章详情和生成文章标题测试完整工作流运行测试结果 概述 使用Dify构建文章生成工作流&#xff0c;使用工具包括&#xff1a;使用 Tavily 执行的搜索查询&#xff0c;使用Flux生成封面图片&#xff0c;使用Stable…

Web3 如何赋能元宇宙,实现虚实融合的无缝对接

随着技术的飞速发展&#xff0c;元宇宙作为一个未来数字世界的概念&#xff0c;正在吸引全球范围内的关注。而 Web3 技术的兴起&#xff0c;为元宇宙的实现提供了强大的支撑。Web3 是基于区块链技术的去中心化网络&#xff0c;它在改变互联网的同时&#xff0c;也推动着虚拟世界…

LangChain的开发流程

文章目录 LangChain的开发流程开发密钥指南3种使用密钥的方法编写一个取名程序 LangChain表达式 LangChain的开发流程 为了更深人地理解LangChain的开发流程&#xff0c;本文将以构建聊天机器人为实际案例进行详细演示。下图展示了一个设计聊天机器人的LLM应用程序。 除了Wb服务…

电商系统-用户认证(四)Oauth2授权模式和资源服务授权

本文章介绍&#xff1a;Oauth2.0 常见授权模式&#xff0c;资源服务授权 。 准备工作 搭建认证服务器之前&#xff0c;先在用户系统表结构中增加如下表结构&#xff1a; CREATE TABLE oauth_client_details (client_id varchar(48) NOT NULL COMMENT 客户端ID&#xff0c;主…

[答疑]DDD伪创新哪有资格和仿制药比

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 远航 2025-1-24 10:40 最近的热门话题仿制药&#xff0c;想到您经常批评的伪创新&#xff0c;这两者是不是很像&#xff1f; UMLChina潘加宇 伪创新哪有资格和仿制药比。 仿制药的…

图漾相机——Sample_V1示例程序

文章目录 1.SDK支持的平台类型1.1 Windows 平台1.2 Linux平台 2.SDK基本知识2.1 SDK目录结构2.2 设备组件简介2.3 设备组件属性2.4 设备的帧数据管理机制2.5 SDK中的坐标系变换 3.Sample_V1示例程序3.1 DeviceStorage3.2 DumpCalibInfo3.3 NetStatistic3.4 SimpleView_SaveLoad…

系统架构设计师教材:信息系统及信息安全

信息系统 信息系统的5个基本功能&#xff1a;输入、存储、处理、输出和控制。信息系统的生命周期分为4个阶段&#xff0c;即产生阶段、开发阶段、运行阶段和消亡阶段。 信息系统建设原则 1. 高层管理人员介入原则&#xff1a;只有高层管理人员才能知道企业究竟需要什么样的信…

Kafka 深入客户端 — 事务

Kafka 事务确保了数据在写入Kafka时的原子性和一致性。 1 幂等 幂等就是对接口的多次调用所产生的结果和调用一次是一致的。 Kafka 生产者在进行重试的时候可能会写入重复的消息&#xff0c;开启幂等性功能后就可以避免这种情况。将生产者客户端参数enable.idempotence设置为…

ZZNUOJ(C/C++)基础练习1011——1020(详解版)

1011 : 圆柱体表面积 题目描述 输入圆柱体的底面半径r和高h&#xff0c;计算圆柱体的表面积并输出到屏幕上。要求定义圆周率为如下宏常量 #define PI 3.14159 输入 输入两个实数&#xff0c;表示圆柱体的底面半径r和高h。 输出 输出一个实数&#xff0c;即圆柱体的表面积&…

Baklib探索内容中台的核心价值与实施策略

内容概要 在数字化转型的背景下&#xff0c;内容中台逐渐成为企业数字化策略中的关键组成部分。内容中台是一个集成的内容管理体系&#xff0c;旨在打破信息孤岛&#xff0c;使内容能够在各个业务部门和平台之间高效流通。这种管理体系不仅能够提升内容的生产效率&#xff0c;…

网络安全攻防实战:从基础防护到高级对抗

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在信息化时代&#xff0c;网络安全已经成为企业、政府和个人必须重视的问题。从数据泄露到勒索软件攻击&#xff0c;每一次…

论文阅读(十三):复杂表型关联的贝叶斯、基于系统的多层次分析:从解释到决策

1.论文链接&#xff1a;Bayesian, Systems-based, Multilevel Analysis of Associations for Complex Phenotypes: from Interpretation to Decision 摘要&#xff1a; 遗传关联研究&#xff08;GAS&#xff09;报告的结果相对稀缺&#xff0c;促使许多研究方向。尽管关联概念…

“““【运用 R 语言里的“predict”函数针对 Cox 模型展开新数据的预测以及推理。】“““

主题与背景 本文主要介绍了如何在R语言中使用predict函数对已拟合的Cox比例风险模型进行新数据的预测和推理。Cox模型是一种常用的生存分析方法&#xff0c;用于评估多个因素对事件发生时间的影响。文章通过具体的代码示例展示了如何使用predict函数的不同参数来获取生存概率和…

Oracle Primavera P6 最新版 v24.12 更新 1/2

目录 引言 P6 PPM 更新内容 1. 在提交更新基线前预览调整 2. 快速轻松地取消链接活动 3. 选择是否从 XER 文件导入责任经理 4. 提高全局变更报告的清晰度 5. 将整个分层代码值路径导出到 CPP 6. 里程碑活动支持所有关系类型 6. 时间表批准 7. 性能改进 8. 安装改进 …

AI大模型开发原理篇-2:语言模型雏形之词袋模型

基本概念 词袋模型&#xff08;Bag of Words&#xff0c;简称 BOW&#xff09;是自然语言处理和信息检索等领域中一种简单而常用的文本表示方法&#xff0c;它将文本看作是一组单词的集合&#xff0c;并忽略文本中的语法、词序等信息&#xff0c;仅关注每个词的出现频率。 文本…

本地部署deepseek模型步骤

文章目录 0.deepseek简介1.安装ollama软件2.配置合适的deepseek模型3.安装chatbox可视化 0.deepseek简介 DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于打造高性能、低成本的 AI 模型&#xff0c;其目标是让 AI 技术更加普惠&#xff0c;让更多人能够用上强…

【deepseek】deepseek-r1本地部署-第二步:huggingface.co替换为hf-mirror.com国内镜像

一、背景 由于国际镜像国内无法直接访问&#xff0c;会导致搜索模型时加载失败&#xff0c;如下&#xff1a; 因此需将国际地址替换为国内镜像地址。 二、操作 1、使用vscode打开下载路径 2、全局地址替换 关键字 huggingface.co 替换为 hf-mirror.com 注意&#xff1a;务…

sunrays-framework配置重构

文章目录 1.common-log4j2-starter1.目录结构2.Log4j2Properties.java 新增两个属性3.Log4j2AutoConfiguration.java 条件注入LogAspect4.ApplicationEnvironmentPreparedListener.java 从Log4j2Properties.java中定义的配置读取信息 2.common-minio-starter1.MinioProperties.…

【大模型】Ollama+AnythingLLM搭建RAG大模型私有知识库

文章目录 一、AnythingLLM简介二、搭建本地智能知识库2.1 安装Ollama2.2 安装AnythingLLM 参考资料 一、AnythingLLM简介 AnythingLLM是由Mintplex Labs Inc.开发的一个全栈应用程序&#xff0c;是一款高效、可定制、开源的企业级文档聊天机器人解决方案。AnythingLLM能够将任…

代理模式 -- 学习笔记

代理模式学习笔记 什么是代理&#xff1f; 代理是一种设计模式&#xff0c;用户可以通过代理操作&#xff0c;而真正去进行处理的是我们的目标对象&#xff0c;代理可以在方法增强&#xff08;如&#xff1a;记录日志&#xff0c;添加事务&#xff0c;监控等&#xff09; 拿一…