Linux 多路转接select
Linux 多路转接select
1. select
select() 是一种较老的多路转接IO接口,它有一定的缺陷导致它不是实现多路转接IO的最优选择,但 poll() 和 epoll() 都是较新版的Linux系统提供的,一些小型嵌入式设备的存储很小,只能使用老版本的 Linux 系统(老内核的代码编译体积小),select()在这些设备上可能是唯一的选择。
2 select的工作原理
调用 select() 会在内核中建立 readfds、writefds、exceptfds 三张位图(如果参数设置为空就不建立),分别对应监听读事件、写事件、异常事件。对位图进行添加、删除等操作都必须要使用系统提供的位图操作系统调用(见函数声明部分)。调用 select() 时,向位图输入的时要关心的文件描述符,当事件就绪时,位图的内容就变文件描述符是否有事件发生。
select() 会返回就绪的文件描述符的数量,用户自行设置判断条件,对相应的文件描述符进行读、写、或异常处理操作。

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 号文件描述符被存进去。
由于 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_sec 和 tv_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,也可能是listensockfdvoid 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要正常工作,需要借助一个辅助数组,来保存所有合法fdint fd_array[gnum];
};
fd_array[gnum]是一个用来存储所有合法 fd 的辅助数组,它里面所有的元素最初被初始化为-1,第0个元素默认存储listensockfd,其他位置存储accept()传递过来的合法 fd 。当连接关闭时,再close(fd_array[i])关闭对应位置的 fd ,并将fd_array[i] = -1重置为初始化状态。这里
select()用来多路转接accept()传递过来的文件描述符,listensock和accept()也可以看作 IO 类的函数,但这两个函数只关心新连接到来,也就是只关心读事件就绪,所以select()中只有rfds,其他被置为了NULL。
7. select() 缺点
-
每次调用
select(), 都需要手动设置 fd 集合,接口使用不方便。 -
每次调用
select(), 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大。 -
同时每次调用
select()都需要在内核遍历传递进来的所有 fd, 这个开销在 fd 很多时也很大。 -
select()支持的文件描述符数量是定长的,而且太小。
相关文章:
Linux 多路转接select
Linux 多路转接select 1. select select() 是一种较老的多路转接IO接口,它有一定的缺陷导致它不是实现多路转接IO的最优选择,但 poll() 和 epoll() 都是较新版的Linux系统提供的,一些小型嵌入式设备的存储很小,只能使用老版本的…...
最近最少使用算法(LRU最近最少使用)缓存替换算法
含义 最近最少使用算法(LRU)是一种缓存替换算法,用于在缓存空间有限的情况下,选择最少使用的数据项进行替换。该算法的核心思想是基于时间局部性原理,即刚被访问的数据在未来也很有可能被再次访问。 实现 LRU算法的…...
信息学奥赛一本通 1342:【例4-1】最短路径问题
【题目描述】 平面上有n个点(n<100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。 若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点间的直线距离。现在的任务是…...
【实践案例】使用Dify构建文章生成工作流【在线搜索+封面图片生成+内容标题生成】
文章目录 概述开始节点图片封面生成关键词实时搜索主题参考生成文章详情和生成文章标题测试完整工作流运行测试结果 概述 使用Dify构建文章生成工作流,使用工具包括:使用 Tavily 执行的搜索查询,使用Flux生成封面图片,使用Stable…...
MyBatis 写法
MyBatis 高效使用技巧 常见 MyBatis 使用技巧,这些技巧有助于简化数据库操作,提高开发效率,并增强系统的性能。 1. 动态 SQL 动态 SQL 让开发者能够依据参数灵活地构建 SQL 语句,避免了手动拼接字符串带来的复杂性和错误风险。…...
Web3 如何赋能元宇宙,实现虚实融合的无缝对接
随着技术的飞速发展,元宇宙作为一个未来数字世界的概念,正在吸引全球范围内的关注。而 Web3 技术的兴起,为元宇宙的实现提供了强大的支撑。Web3 是基于区块链技术的去中心化网络,它在改变互联网的同时,也推动着虚拟世界…...
C#常考随笔3:对象比较obj1.Equals(obj2)== true时候,hashcode是否相同?
一般情况下是相同的,但在自定义类型中,重写了Equals方法,就可能不同。 Equals: 1. 首先,关于Equals方法: Equals 方法最初定义在 Object 类中,所有的类都直接或间接地继承自 Object 类&#…...
深入探索SQL中修改表字段属性的技巧与策略
摘要 在SQL中,修改表字段属性是一项常见的数据库管理任务。用户可以调整字段的数据类型、长度、默认值或注释,而无需更改字段名称。例如,varchar类型可转换为mediumtext或text,NVARCHAR2类型可转换为NCLOB。若需同时变更字段名称及…...
gif动画图像优化,相同的图在第2,4,6帧中重复出现,会增加图像体积吗?
对于 GIF 图像,情况与 Git 文件存储有所不同。GIF 是一种图像格式,其体积主要取决于图像的内容、颜色数量、优化设置等因素。如果在 GIF 动画中,相同的图像在第 2、4、6 帧中重复出现,是否会增加图像体积,取决于以下几…...
LangChain的开发流程
文章目录 LangChain的开发流程开发密钥指南3种使用密钥的方法编写一个取名程序 LangChain表达式 LangChain的开发流程 为了更深人地理解LangChain的开发流程,本文将以构建聊天机器人为实际案例进行详细演示。下图展示了一个设计聊天机器人的LLM应用程序。 除了Wb服务…...
电商系统-用户认证(四)Oauth2授权模式和资源服务授权
本文章介绍:Oauth2.0 常见授权模式,资源服务授权 。 准备工作 搭建认证服务器之前,先在用户系统表结构中增加如下表结构: CREATE TABLE oauth_client_details (client_id varchar(48) NOT NULL COMMENT 客户端ID,主…...
[答疑]DDD伪创新哪有资格和仿制药比
DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 远航 2025-1-24 10:40 最近的热门话题仿制药,想到您经常批评的伪创新,这两者是不是很像? 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个基本功能:输入、存储、处理、输出和控制。信息系统的生命周期分为4个阶段,即产生阶段、开发阶段、运行阶段和消亡阶段。 信息系统建设原则 1. 高层管理人员介入原则:只有高层管理人员才能知道企业究竟需要什么样的信…...
Kafka 深入客户端 — 事务
Kafka 事务确保了数据在写入Kafka时的原子性和一致性。 1 幂等 幂等就是对接口的多次调用所产生的结果和调用一次是一致的。 Kafka 生产者在进行重试的时候可能会写入重复的消息,开启幂等性功能后就可以避免这种情况。将生产者客户端参数enable.idempotence设置为…...
TensorFlow 2基本功能和示例代码
TensorFlow 2.x 是 Google 开源的一个深度学习框架,广泛用于构建和训练机器学习模型。 一、核心特点 1. Keras API 集成 TensorFlow 2.x 将 Keras 作为其核心 API,简化了模型的构建和训练流程。Keras 提供了高层次的 API,易于使用和理解。…...
ZZNUOJ(C/C++)基础练习1011——1020(详解版)
1011 : 圆柱体表面积 题目描述 输入圆柱体的底面半径r和高h,计算圆柱体的表面积并输出到屏幕上。要求定义圆周率为如下宏常量 #define PI 3.14159 输入 输入两个实数,表示圆柱体的底面半径r和高h。 输出 输出一个实数,即圆柱体的表面积&…...
Python 字典:快速掌握高效的数据存储方式
文章目录 一、什么是字典?字典的定义二、字典的基本操作1. 访问字典的值2. 修改字典中的值3. 添加新的键值对4. 删除键值对5. 获取字典长度三、字典的遍历1. 遍历键2. 遍历值3. 遍历键值对四、字典的常用方法1. `keys()`:获取所有键2. `values()`:获取所有值3. `items()`:获…...
Baklib探索内容中台的核心价值与实施策略
内容概要 在数字化转型的背景下,内容中台逐渐成为企业数字化策略中的关键组成部分。内容中台是一个集成的内容管理体系,旨在打破信息孤岛,使内容能够在各个业务部门和平台之间高效流通。这种管理体系不仅能够提升内容的生产效率,…...
网络安全攻防实战:从基础防护到高级对抗
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 引言 在信息化时代,网络安全已经成为企业、政府和个人必须重视的问题。从数据泄露到勒索软件攻击,每一次…...
论文阅读(十三):复杂表型关联的贝叶斯、基于系统的多层次分析:从解释到决策
1.论文链接:Bayesian, Systems-based, Multilevel Analysis of Associations for Complex Phenotypes: from Interpretation to Decision 摘要: 遗传关联研究(GAS)报告的结果相对稀缺,促使许多研究方向。尽管关联概念…...
13.zookeeper开机自启动配置
要在Linux(RHEL7.7)系统中设置zookeeper开机自启动,可以创建一个系统服务单元文件。以下是为详细配置部署,假设你已经安装了zookeeper并且可以通过zkServer.sh命令启动它。 1.进入/lib/systemd/system目录 命令: cd /lib/systemd/system [root@rhel77 system]# cd /lib/…...
“““【运用 R 语言里的“predict”函数针对 Cox 模型展开新数据的预测以及推理。】“““
主题与背景 本文主要介绍了如何在R语言中使用predict函数对已拟合的Cox比例风险模型进行新数据的预测和推理。Cox模型是一种常用的生存分析方法,用于评估多个因素对事件发生时间的影响。文章通过具体的代码示例展示了如何使用predict函数的不同参数来获取生存概率和…...
Oracle Primavera P6 最新版 v24.12 更新 1/2
目录 引言 P6 PPM 更新内容 1. 在提交更新基线前预览调整 2. 快速轻松地取消链接活动 3. 选择是否从 XER 文件导入责任经理 4. 提高全局变更报告的清晰度 5. 将整个分层代码值路径导出到 CPP 6. 里程碑活动支持所有关系类型 6. 时间表批准 7. 性能改进 8. 安装改进 …...
AI大模型开发原理篇-2:语言模型雏形之词袋模型
基本概念 词袋模型(Bag of Words,简称 BOW)是自然语言处理和信息检索等领域中一种简单而常用的文本表示方法,它将文本看作是一组单词的集合,并忽略文本中的语法、词序等信息,仅关注每个词的出现频率。 文本…...
JavaWeb学习-SpringBotWeb开发入门(HTTP协议)
(一)SpringBotWeb开发步骤 (1)创建springboot工程,并勾选开发相关依赖 (2)定义HelloController类,添加方法hello,并添加注解 (3)运行测试 (二)HTTP入门概述 创建请求页面 package com.itheima.demo3; /*请求处理类,加上注解标识为请求处理类*/import org.spr…...
网站结构优化:加速搜索引擎收录的关键
本文来自:百万收录网 原文链接:https://www.baiwanshoulu.com/9.html 网站结构优化对于加速搜索引擎收录至关重要。以下是一些关键策略,旨在通过优化网站结构来提高搜索引擎的抓取效率和收录速度: 一、合理规划网站架构 采用扁…...
本地部署deepseek模型步骤
文章目录 0.deepseek简介1.安装ollama软件2.配置合适的deepseek模型3.安装chatbox可视化 0.deepseek简介 DeepSeek 是一家专注于人工智能技术研发的公司,致力于打造高性能、低成本的 AI 模型,其目标是让 AI 技术更加普惠,让更多人能够用上强…...
【deepseek】deepseek-r1本地部署-第二步:huggingface.co替换为hf-mirror.com国内镜像
一、背景 由于国际镜像国内无法直接访问,会导致搜索模型时加载失败,如下: 因此需将国际地址替换为国内镜像地址。 二、操作 1、使用vscode打开下载路径 2、全局地址替换 关键字 huggingface.co 替换为 hf-mirror.com 注意:务…...
sunrays-framework配置重构
文章目录 1.common-log4j2-starter1.目录结构2.Log4j2Properties.java 新增两个属性3.Log4j2AutoConfiguration.java 条件注入LogAspect4.ApplicationEnvironmentPreparedListener.java 从Log4j2Properties.java中定义的配置读取信息 2.common-minio-starter1.MinioProperties.…...
