【IO】多路转接Select

一、初识 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还是普通的fdif (_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.rhkb.cn/news/439686.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python+Django微信小程序前后端人脸识别登录注册

程序示例精选 PythonDjango微信小程序前后端人脸识别登录注册 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango微信小程序前后端人脸识别登录注册》编写代码&#xff0c;代码整…

基于SpringBoot+Vue+MySQL的在线学习交流平台

系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网技术的飞速发展&#xff0c;在线学习已成为现代教育的重要组成部分。传统的面对面教学方式已无法满足广大学习者的需求&#xff0c;特别是在时间、地点上受限的学习者。因此&#xff0c;构建一个基于SpringBoot、Vue.…

2024年最新大模型LLM学习路径全解析!看完你就是LLM大师

ChatGPT的出现在全球掀起了AI大模型的浪潮&#xff0c;2023年可以被称为AI元年&#xff0c;AI大模型以一种野蛮的方式&#xff0c;闯入你我的生活之中。 从问答对话到辅助编程&#xff0c;从图画解析到自主创作&#xff0c;AI所展现出来的能力&#xff0c;超出了多数人的预料&…

华为eNSP:端口隔离

一&#xff0c;什么是端口隔离 端口隔离是一种网络配置技术&#xff0c;用于将不同的网络设备或用户隔离在不同的虚拟局域网&#xff08;VLAN&#xff09;中&#xff0c;以实现网络流量的隔离和安全性提升。通过在交换机或路由器上配置端口隔离&#xff0c;可以将连接到同一设…

Java多线程(2)—线程创建

Java多线程(2)—线程创建 一、线程创建简介 在Java中&#xff0c;创建线程可以通过两种主要方式&#xff1a;继承 Thread​ 类、实现 Runnable​ 、实现Callable ​接口和线程池。 ​ ‍ 二、创建方式 2.1 继承 Thread 类 示例1 ♠①&#xff1a;创建一个类继承 Thread…

【工程测试技术】第6章 信号处理初步,频谱分析,相关系数

目录 6.1 数字信号处理的基本步骤 6.2 离散信号及其频谱分析 6.2.1 概述 6.2.2 时域采样、混叠和采样定理 6.2.3 量化和量化误差 6.2.4 截断、泄漏和窗函数 6.2.5 频域采样、时域周期延拓和栅栏效应 6.2.6 频率分辨率、整周期截断 6.3 相关分析及其应用 6.3.1 两…

前端学习第二天笔记 CSS选择 盒子模型 浮动 定位 CSS3新特性 动画 媒体查询 精灵图雪碧图 字体图标

CSS学习 CSS选择器全局选择器元素选择器类选择器ID选择器合并选择器 选择器的优先级字体属性背景属性文本属性表格属性表格边框折叠边框表格文字对齐表格填充表格颜色 关系选择器后代选择器子代选择器相邻兄弟选择器通用兄弟选择器 CSS盒子模型弹性盒子模型父元素上的属性flex-…

STM32三种启动模式:【详细讲解】

STM32在上电后&#xff0c;从那里启动是由BOOT0和BOOT1引脚的电平决定的&#xff0c;如下表&#xff1a; BOOT模式选引脚启动模式BOOT0BOOT1X0主Flash启动01系统存储器启动11内置SRAM启动 BOOT 引脚的值在重置后 SYSCLK 的第四个上升沿时被锁定。在重置后,由用户决定是如何设…

基于springboot vue3 在线考试系统设计与实现 源码数据库 文档

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

基于元神操作系统实现NTFS文件操作(六)

1. 背景 本文主要介绍$Root元文件属性的解析。先介绍元文件各属性的属性体构成&#xff0c;然后结合读取到的元文件内容&#xff0c;对测试磁盘中目标分区的根目录进行展示。 2. $Root元文件属性的解析 使用每个属性头偏移0x04-0x07处的值可以从第一个属性开始依次定位下一个…

一款基于 Java 的可视化 HTTP API 接口快速开发框架,干掉 CRUD,效率爆炸(带私活源码)

平常我们经常需要编写 API&#xff0c;但其实常常只是一些简单的增删改查&#xff0c;写这些代码非常枯燥无趣。 今天给大家带来的是一款基于 Java 的可视化 HTTP API 接口快速开发框架&#xff0c;通过 UI 界面编写接口&#xff0c;无需定义 Controller、Service、Dao 等 Jav…

在线教育的未来:SpringBoot技术实现

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

【可答疑】基于51单片机的PWM控制智能台灯设计(含仿真、代码、报告、演示视频等)

✨哈喽大家好&#xff0c;这里是每天一杯冰美式oh&#xff0c;985电子本硕&#xff0c;大厂嵌入式在职0.3年&#xff0c;业余时间做做单片机小项目&#xff0c;有需要也可以提供就业指导&#xff08;免费&#xff09;~ &#x1f431;‍&#x1f409;这是51单片机毕业设计100篇…

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

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

MySQL 用户管理

一.用户信息 MySQL中的用户&#xff0c;都存储在系统数据库mysql的user表中。 host&#xff1a; 表示这个用户可以从哪个主机登陆&#xff0c;如果是localhost&#xff0c;表示只能从本机登陆user&#xff1a; 用户名authentication_string&#xff1a; 用户密码通过password函…

Python | Leetcode Python题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution:def circularArrayLoop(self, nums: List[int]) -> bool:n len(nums)def next(cur: int) -> int:return (cur nums[cur]) % n # 保证返回值在 [0,n) 中for i, num in enumerate(nums):if num 0:continueslow, fas…

中小企业做网站需要考虑哪些因素?

中小企业在建设网站时&#xff0c;需要考虑的因素有很多。以下是一些主要考虑因素的介绍&#xff1a; 明确建站目的&#xff1a;中小企业需要明确自己建立网站的目的。是为了展示企业形象、推广产品&#xff0c;还是提供客户服务&#xff1f;不同的目的将决定网站的设计和功能…

MOELoRA —— 多任务医学应用中的参数高效微调方法

人工智能咨询培训老师叶梓 转载标明出处 在医疗场景中&#xff0c;LLMs可以应用于多种不同的任务&#xff0c;如医生推荐、诊断预测、药物推荐、医学实体识别、临床报告生成等。这些任务的输入和输出差异很大&#xff0c;给统一模型的微调带来了挑战。而且LLMs的参数众多&…

微信朋友圈实况照片需要注意隐私

微信朋友圈现在可以发实况了。 一直不怎么发朋友圈&#xff0c;就这几天发现朋友圈可以发实况照片了。 实况照片可让用户在声音和动作丰富的互动体验中捕捉美好瞬间&#xff0c;为传统的静态照片增添活力感。 当实况照片可用时&#xff0c;“相机” App 可在用户拍照之前和之…

服务器数据恢复—raid磁盘故障导致数据库文件损坏的数据恢复案例

服务器存储数据恢复环境&故障&#xff1a; 存储中有一组由3块SAS硬盘组建的raid。上层win server操作系统层面划分了3个分区&#xff0c;数据库存放在D分区&#xff0c;备份存放在E分区。 RAID中一块硬盘的指示灯亮红色&#xff0c;D分区无法识别&#xff1b;E分区可识别&a…