【在Linux世界中追寻伟大的One Piece】多路转接select
目录
1 -> I/O多路转接之select
1.1 -> 初识select
1.2 -> select函数原型
1.3 -> 关于fd_set结构
1.4 -> 关于timeval结构
2 -> 理解select执行过程
2.1 -> Socket就绪条件
2.2 -> select特点
2.3 -> select缺点
3 -> select使用示例
3.1 -> 只检测标准输入
3.2 -> 实现字典服务器
1 -> I/O多路转接之select
1.1 -> 初识select
系统提供select函数来实现多路复用输入/输出模型。
- select系统调用是用来让我们的程序监视多个文件描述符的状态变化的。
- 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。
1.2 -> select函数原型
select函数原型如下:
#include <sys/select.h>int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
参数解释:
- 参数nfds是需要监视的最大的文件描述符值+1。
- rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。
- 参数timeout为结构timeval,用来设置select()的等待时间。
参数timeout取值:
- NULL:则表示 select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。
- 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
- 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
1.3 -> 关于fd_set结构
其实这个结构就是一个整数数组,更严格的说,是一个"位图"。使用位图中对应的位来表示要监视的文件描述符。
提供了一组操作fd_set的接口,来比较方便的操作位图。
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 的全部位
1.4 -> 关于timeval结构
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
函数返回值:
- 执行成功则返回文件描述词状态已改变的个数。
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回。
- 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
错误值可能为:
- EBADF文件描述词为无效的或该文件已关闭。
- EINTR此调用被信号所中断。
- EINVAL参数n为负值。
- ENOMEM核心内存不足。
常见的程序片段如下:
fs_set readset;
FD_SET(fd, &readset);
select(fd + 1, &readset, NULL, NULL, NULL);
if (FD_ISSET(fd, readset)) { …… }
2 -> 理解select执行过程
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
- 执行fd_set set,FD_ZERO(&set);则set用位表示是0000,0000。
- 若fd=5,执行 FD_SET(fd,&set);后set变为 0001,0000(第5位置为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.1 -> Socket就绪条件
读就绪
- socket内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RCVLOWAT。此时可以无阻塞的读该文件描述符,并且返回值大于0。
- socket TCP通信中,对端关闭连接,此时对该socket读,则返回0。
- 监听的socket上有新的连接请求。
- socket上有未处理的错误。
写就绪
- socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0。
- socket的写操作被关闭(close或者shutdown)。对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号。
- socket使用非阻塞connect连接成功或失败之后。
- socket上有未读取的错误。
2.2 -> 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的第一个参数。
2.3 -> select缺点
- 每次调用select,都需要手动设置fd集合,从接口使用角度来说也非常不便。
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
- select支持的文件描述符数量太小。
3 -> select使用示例
3.1 -> 只检测标准输入
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>int main()
{fd_set read_fds;FD_ZERO(&read_fds);FD_SET(0, &read_fds);for (;;) {printf("> ");fflush(stdout);int ret = select(1, &read_fds, NULL, NULL, NULL);if (ret < 0) {perror("select");continue;}if (FD_ISSET(0, &read_fds)) {char buf[1024] = { 0 };read(0, buf, sizeof(buf) - 1);printf("input: %s", buf);}else {printf("error! invaild fd\n");continue;}FD_ZERO(&read_fds);FD_SET(0, &read_fds);}return 0;
}
说明:
当只检测文件描述符0(标准输入)时,因为输入条件只有在你有输入信息的时候,才成立,所以如果一直不输入,就会产生超时信息。
3.2 -> 实现字典服务器
tcp_select_server.hpp
#pragma once#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/select.h>
#include "tcp_socket.hpp"// 必要的调试函数
inline void PrintFdSet(fd_set* fds, int max_fd)
{printf("select fds: ");for (int i = 0; i < max_fd + 1; ++i) {if (!FD_ISSET(i, fds)) {continue;}printf("%d ", i);}printf("\n");
}typedef std::function<void(const std::string& req, std::string*resp)> Handler;// 把 Select 封装成一个类. 这个类虽然保存很多 TcpSocket 对象指针, 但是不管理内存
class Selector
{
public:Selector() {// [注意!] 初始化千万别忘了!!max_fd_ = 0;FD_ZERO(&read_fds_);}bool Add(const TcpSocket& sock) {int fd = sock.GetFd();printf("[Selector::Add] %d\n", fd);if (fd_map_.find(fd) != fd_map_.end()) {printf("Add failed! fd has in Selector!\n");return false;}fd_map_[fd] = sock;FD_SET(fd, &read_fds_);if (fd > max_fd_) {max_fd_ = fd;}return true;}bool Del(const TcpSocket& sock) {int fd = sock.GetFd();printf("[Selector::Del] %d\n", fd);if (fd_map_.find(fd) == fd_map_.end()) {printf("Del failed! fd has not in Selector!\n");return false;}fd_map_.erase(fd);FD_CLR(fd, &read_fds_);// 重新找到最大的文件描述符, 从右往左找比较快for (int i = max_fd_; i >= 0; --i) {if (!FD_ISSET(i, &read_fds_)) {continue;}max_fd_ = i;break;}return true;}// 返回读就绪的文件描述符集bool Wait(std::vector<TcpSocket>* output) {output->clear();// [注意] 此处必须要创建一个临时变量, 否则原来的结果会被覆盖掉fd_set tmp = read_fds_;// DEBUGPrintFdSet(&tmp, max_fd_);int nfds = select(max_fd_ + 1, &tmp, NULL, NULL, NULL);if (nfds < 0) {perror("select");return false;}// [注意!] 此处的循环条件必须是 i < max_fd_ + 1for (int i = 0; i < max_fd_ + 1; ++i) {if (!FD_ISSET(i, &tmp)) {continue;}output->push_back(fd_map_[i]);}return true;}private:fd_set read_fds_;int max_fd_;// 文件描述符和 socket 对象的映射关系std::unordered_map<int, TcpSocket> fd_map_;
};class TcpSelectServer
{
public:TcpSelectServer(const std::string& ip, uint16_t port) : ip_(ip),port_(port) {}bool Start(Handler handler) const {// 1. 创建 socketTcpSocket listen_sock;bool ret = listen_sock.Socket();if (!ret) {return false;}// 2. 绑定端口号ret = listen_sock.Bind(ip_, port_);if (!ret) {return false;}// 3. 进行监听ret = listen_sock.Listen(5);if (!ret) {return false;}// 4. 创建 Selector 对象Selector selector;selector.Add(listen_sock);// 5. 进入事件循环for (;;) {std::vector<TcpSocket> output;bool ret = selector.Wait(&output);if (!ret) {continue;}// 6. 根据就绪的文件描述符的差别, 决定后续的处理逻辑for (size_t i = 0; i < output.size(); ++i) {if (output[i].GetFd() == listen_sock.GetFd()) {// 如果就绪的文件描述符是 listen_sock, 就执行 accept, 并加入到 select 中TcpSocket new_sock;listen_sock.Accept(&new_sock, NULL, NULL);selector.Add(new_sock);}else {// 如果就绪的文件描述符是 new_sock, 就进行一次请求的处理std::string req, resp;bool ret = output[i].Recv(&req);if (!ret) {selector.Del(output[i]);// [注意!] 需要关闭 socketoutput[i].Close();continue;}// 调用业务函数计算响应handler(req, &resp);// 将结果写回到客户端output[i].Send(resp);}} // end for} // end for (;;)return true;}private:std::string ip_;uint16_t port_;
};
感谢各位大佬支持!!!
互三啦!!!
相关文章:

【在Linux世界中追寻伟大的One Piece】多路转接select
目录 1 -> I/O多路转接之select 1.1 -> 初识select 1.2 -> select函数原型 1.3 -> 关于fd_set结构 1.4 -> 关于timeval结构 2 -> 理解select执行过程 2.1 -> Socket就绪条件 2.2 -> select特点 2.3 -> select缺点 3 -> select使用示例…...

补一下 二维 平面直角坐标系 到三维
上一篇帖子写到 二维的平面直角坐标系,是那样的,这次补充一下三维的。首先需要,安装一个包,如下: 然后,把参数输入,输入这个坐标系的参数,如下: 这样就可以输出如下的三…...

如何学习Python编程?
如何学习Python编程? 了解基础概念: 学习Python的基本语法,包括变量、数据类型、运算符等。了解控制结构,如条件语句(if语句)和循环(for和while循环)。 选择学习资源: 在…...

使用EasyExcel实现导出excel文件时生成多级下拉选
前言 公司有个需求本来只涉及到两个下拉选项,后面就想能不能实现多个下拉选,当然我这里说的多个下拉选是联动的,比如省、地市、区县这种。 实现步骤 1、添加EasyExcel的Maven依赖 <dependency><groupId>com.alibaba</group…...

微信小程序 高校教材征订系统
文章目录 项目介绍具体实现截图技术介绍mvc设计模式小程序框架以及目录结构介绍错误处理和异常处理java类核心代码部分展示详细视频演示源码获取 项目介绍 系统分为三个角色,分别是教材科、系教学秘书、教研室主任。系统主要完成功能是教材科要发布教材征订信息&am…...

从0开始的STM32 定时器(I):聊一聊基本定时器
目录 时钟源 控制器 时基单元 关于HAL库如何配置基本定时器 HAL是如何初始化我们的定时器句柄的 HAL_TIM_Base_Init 开始定时 如何处理句柄? 在我们使用STM32解决一些问题的时候,常常会遇到说:我想要以一个周期做一些事情:…...

vue常见题型(1-10)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 2.2双向绑定的原理是什么vue框架采用的是数据双向绑定的方式,由三个重要部分构成2.2.1.ViewModel2.2.2 双向绑定2.2.3.1.编译Compile2.2.3.2.依赖收集 3…...

【SpringBoot】使用注解进行XSS防御
在Spring Boot中,我们可以使用注解的方式来进行XSS防御。注解是一种轻量级的防御手段,它可以在方法或字段级别对输入进行校验,从而防止XSS攻击。 引入相关依赖 maven依赖: <!--JSR-303/JSR-380用于验证的注解 --> <de…...

华为海思招聘-芯片与器件设计工程师-模拟芯片方向- 机试题-真题套题题目——共8套(每套四十题)
华为海思招聘-芯片与器件设计工程师-模拟芯片方向- 机试题-真题套题题目分享——共九套(每套四十题) 岗位——芯片与器件设计工程师 岗位意向——模拟芯片 真题题目分享,完整题目,无答案(共8套) 实习岗位…...

vscode 下载慢的解决方法
下载链接示例:https://az764295.vo.msecnd.net/stable/ccbaa2d27e38e5afa3e5c21c1c7bef4657064247/code1.62.3-1637137107amd64.deb 解决方法: 把 az764295.vo.msecnd.net 替换成 vscode.cdn.azure.cn...

STM32ZET6-USART使用
一、原理说明 STM32自带通讯接口 通讯目的 通信方式: 全双工:通信时可以双方同时通信。 半双工:通信时同一时间只能一个设备发送数据,其他设备接收。 单工:只能一个设备发送到另一个设备,例如USART只有…...

es自动补全(仅供自己参考)
elasticssearch提供了CompletionSuggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询效率,对于文档中字段的类型有一些约束: 查询类型必须是:completion 字段内容是多个补全词条形成的数组 P…...

13-综合排序:Function Score Query 优化算分
使用了 function_score 查询来根据某个字段的值对查询结果进行打分。以下是该查询的主要部分: query: 包含了实际执行搜索的部分,在这里包括一个 multi_match 查询。 multi_match:用于在多个字段上执行相同的查询。 query:设置…...

鸿蒙应用App测试-专项测试(DevEco Testing)
注意:大家记得先学通用测试在学专项测试 鸿蒙应用App测试-通用测试-CSDN博客 注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下 如果大家觉得博主文章写的好的话,可以点下关注&am…...

RabbitMQ设置消息过期时间
RabbitMQ设置消息过期时间 1、过期消息(死信)2、设置消息过期的两种方式2.1、设置单条消息的过期时间2.1.1、配置文件application.yml2.1.2、配置类RabbitConfig2.1.3、发送消息业务类service(核心代码)2.1.4、启动类2.1.5、依赖文…...

大数据-209 数据挖掘 机器学习理论 - 梯度下降 梯度下降算法调优
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...

粒子群优化双向深度学习!PSO-BiTCN-BiGRU-Attention多输入单输出回归预测
粒子群优化双向深度学习!PSO-BiTCN-BiGRU-Attention多输入单输出回归预测 目录 粒子群优化双向深度学习!PSO-BiTCN-BiGRU-Attention多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现PSO-BiTCN-BiGRU-Attention粒子…...

排序算法简介
直接插入排序: 将第一个元素视为已排序的序列,其余元素视为未排序序列。 逐个处理:从第二个元素开始,逐个将当前元素插入到已排序序列的适当位置,直到所有元素都被插入。 插入过程:对于每个待…...

(没有跳过联网激活)导致使用微软账号激活电脑---修改为本地账户和英文名字
修改为本地账户和英文名字 前言微软账号,本地账号与用户名基本知识账户管理方式一方式2 查看账户的sid并且修改文件夹名字和系统变量修改注册表和建立软件路径超链接注意事项总结 前言 当没有联网激活新买的电脑时候,这个就不用看了 当你是联网激活的时…...

[论文粗读][REALM: Retrieval-Augmented Language Model Pre-Training
引言 今天带来一篇检索增强语言模型预训练论文笔记——REALM: Retrieval-Augmented Language Model Pre-Training。这篇论文是在RAG论文出现之前发表的。 为了简单,下文中以翻译的口吻记录,比如替换"作者"为"我们"。 语言模型预训练…...

flink 内存配置(五):网络缓存调优
flink 内存配置(一):设置Flink进程内存 flink 内存配置(二):设置TaskManager内存 flink 内存配置(三):设置JobManager内存 flink 内存配置(四)…...

set和map的使用
目录 1.关联式容器 2.键值对 3.set 3.1set的模版参数列表 3.2对set的修改 3.2.1insert 3.2.2 erase 3.2.3clear 3.2.4swap 3.2.5 find 3.3set的迭代器 3.4set的容量 4.map 4.1对map的修改 4.1.1insert 4.1.2erase 4.1.3swap 4.1.4clear 4.2map的迭代器 4.3opera…...

LCL三相并网逆变器simulink仿真+说明文档
背景描述: 详细解析了LCL三相并网逆变器的工作原理,强调了准PR比例谐振控制的重要性,讨论了电感、电容参数选择及保护电路设计。通过仿真结果展示了逆变器性能优化的方法,以提升系统效率和稳定性。 模型介绍: 整体模…...

从0开始深度学习(24)——填充和步幅
1 填充 在上一节中,我们的卷积步骤如下: 可以发现输入是 3 3 3\times3 33,输出是 2 2 2\times2 22,这样可能会导致原始图像的边界丢失了许多有用信息,如果应用多层卷积核,累积丢失的像素就更多了&#…...

CPU Study - Instructions Fetch
参考来源:《超标量处理器设计》—— 姚永斌 N-Way CPU 取指问题 如果CPU可以在每个周期内同时解码N条指令,则此类CPU为N-Way超标量处理器。 N-Way超标量处理器需要每个周期从I-Cache中至少取得N条指令,这N条指令成为一组Fetch Group。 为了…...

GJ Round (2024.9) Round 1~7
前言: 点此返回 GJ Round 目录 博客园可能食用更佳 Round 1 (9.10) A 洛谷 P10059 Choose 不难发现结论:记长度为 L L L 时对应的 X X X 最大值为 f ( L ) f(L) f(L),则 f ( L ) f(L) f(L) 单调不降 那么就可以考虑使用二分求出最小的…...

【CMCL】多模态情感识别的跨模态对比学习
abstract 近年来,多模态情感识别因其能够通过整合多模态信息来提高情感识别的准确性而受到越来越多的关注。然而,模态差异导致的异质性问题对多模态情感识别提出了重大挑战。在本文中,我们提出了一个新的框架——跨模态对比学习(…...

输入/输出系统
一、I/O 系统基本概念(了解即可) 1. 输入/输出系统 【总结】: “I/O” 就是 “输入 / 输出”(Input/Output),I/O 设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备。 输…...

asp.net+uniapp养老助餐管理系统 微信小程序
文章目录 项目介绍具体实现截图技术介绍mvc设计模式小程序框架以及目录结构介绍错误处理和异常处理java类核心代码部分展示详细视频演示源码获取 项目介绍 以往流浪猫狗的救助网站相关信息的管理,都是工作人员手工统计。这种方式不但时效性低,而且需要查…...

部署istio应用未能产生Envoy sidecar代理
1. 问题描述及原因分析 在部署Prometheus、Grafana、Zipkin、Kiali监控度量Istio的第2.2章节,部署nginx应用,创建的pod并没有产生Envoy sidecar代理,仅有一个应用容器运行中 故在随后的prometheus中也没有产生指标istio_requests_total。通…...