当前位置: 首页 > news >正文

【Linux】从零开始使用多路转接IO --- select

在这里插入图片描述

碌碌无为,则余生太长;
欲有所为,则人生苦短。
--- 中岛敦 《山月记》---

从零开始认识五种IO模型

  • 1 前言
  • 2 认识多路转接select
  • 3 多路转接select等待连接
  • 4 完善代码
  • 5 总结

1 前言

上一篇文章我们讲解了五种IO模型的基本概念,并通过系统调用使用了非阻塞IO。
一般的服务器不会使用非阻塞IO,因为非阻塞IO非常耗费CPU资源,导致CPU发热效率下降!非阻塞IO只有在特定情况下才比较好用!

今天我们来学习多路转接select

我们知道IO = 等 + 拷贝。拷贝的前提是底层有数据,没有数据的时候就需要进行等待。为了提高效率可以等待多个文件描述符。多路转接就是等待文件描述符上的新事件,等到就可以通知程序员事件已经就绪,可以进行拷贝!

这个事件可以是:

  • 读事件就绪:OS底层有数据了
  • 写事件就绪:OS底层有空间了

今天我们要学习的就是多路转接select

2 认识多路转接select

我们先来看其作用与定位:

  • select的定位是:只在IO中只负责等待,不进行拷贝! 并且select可以等待多个文件描述符,有新事件就进行通知。

来看select系统调用:

SELECT(2)                                                                 Linux Programmer's Manual                                                                 SELECT(2)NAMEselect, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexingSYNOPSIS#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);int  FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);Feature Test Macro Requirements for glibc (see feature_test_macros(7)):pselect(): _POSIX_C_SOURCE >= 200112L

select函数中有5个参数,都是用来干什么的呢?

  1. int nfds:输入性参数 ,表示等待的多个文件描述符最大值 加 1。比如等待1 2 5 6 99 这几个文件描述符,那么就要传入100。注意不是文件描述符的个数!
  2. struct timeval *timeout:输入输出性参数 ,这是一个结构体表示微秒级别的时间戳,其中有两个参数分别表示秒和微秒。这个参数告诉select在这个时间戳内进行阻塞式select,超出时间就进行一次返回。如果时间以内等到了新事件,就返回,并把剩余时间返回。传入{0,0}就是非阻塞轮询了。传入nullptr表示一直阻塞等待事件
    在这里插入图片描述

那么现在我们知道了两个参数,我们探索一下返回值:

  • 大于0:有几个就绪了
  • 等于0:超时返回了
  • 小于0:select出错了

那么其他三个参数呢?首先fd_set代表文件描述符集,是用位图进行维护的!位图下标表示文件描述符,该比特位的内容表示对应信息!一共1024比特位,可以表示1024个文件描述符,下面我们就来了解一下这三个参数:
这三个参数都是fd_set,是输入输出参数,分别对应读事件,写事件,异常事件。通过这三个位图的设置,我们就可以对一个文件描述符的操作指明清楚。今天我们以读事件为例进行讲解:

  • 输入时:传入一个读事件文件描述符,就是告诉OS要帮我们关心fd_set集合中的所有fd的读事件。这里比特位的位置表示文件描述符的编号,比特位的内容表示是否关心fd的读事件!
  • 输出时:OS会返回一个读事件文件描述符,表示你让我关心的文件描述符集中哪些已经就绪了!这里比特位的位置表示文件描述符的编号,比特位的内容表示事件是否发生!

OK,现在我们了解了select的基本参数,下面我们就开始使用select进行编程

3 多路转接select等待连接

我们首先把之前的套接字基础的类拷贝过来:

  1. class Socket:实现套接字的创建工作,并进入监听模式。
  2. class InetAddr:网络套接字基本信息类,用于进行网络套接字传参工作。
  3. class Log:进行日志信息的打印,便于调试

然后我们就来设计Selectsever类:

  • 成员变量需要端口号,TcpSocket套接字类
  • 构造函数中进行端口号的初始化,并创建套接字,设置为监听模式
  • 循环函数中不能直接进行accept获取连接,因为底层不一定有数据,直接进行会阻塞式等待。所以我们可以把accept看做IO函数,将等的任务交给select函数。
  • select函数需要对监听套接字进行等待
#pragma once#include "Socket.hpp"
#include <sys/select.h>
#include "Log.hpp"using namespace socket_ns;
using namespace log_ns;class SelectServer
{
public:SelectServer(uint16_t port) : _port(port),_listensock(std::make_unique<TcpSocket>()){// 建立监听套接字_listensock->BuildListenSocket(_port);}~SelectServer(){};void Initserver(){}void Loop(){//进入服务while(true){//不能直接进行accept 因为底层不一定建立了连接,所以需要等待底层就绪//等待过程交给select//_listensock->Accepter();//创建fd_setfd_set rfds ;FD_ZERO(&rfds);//加入监听套接字文件描述符FD_SET( _listensock->GetSockfd() , &rfds);//创建timeoutstruct timeval timeout = {3 , 0};//进行selectint n = ::select(_listensock->GetSockfd() + 1 , &rfds ,  nullptr , nullptr , &timeout);switch (n){case 0://超时LOG(DEBUG , "timeout : %d.%d\n" , timeout.tv_sec , timeout.tv_usec);break;case -1://出错了LOG(ERROR, "select error\n");break;default://正常LOG(INFO, "have event ready: n = %d\n" , n);//执行任务HandlerEvent(rfds);break;}}}private:uint16_t _port;std::unique_ptr<Socket> _listensock;
};

我们运行程序来看等待效果:
在这里插入图片描述
可以正常的进行等待,当我们进行连接时:
在这里插入图片描述
select函数就能告诉我们有哪些文件描述符就绪,可以进行拷贝。这里可以得到一个现象:

  • 如果事件就绪,但是不处理,select就会一直通知我们,直到我们处理这个事件。

当我们知道底层就绪时,我们就可以进行"拷贝"了:

void HandlerEvent(fd_set& rfds){//判断是否是套接字就绪if(FD_ISSET(_listensock->GetSockfd() , &rfds)){//连接事件就绪//那么这里我们可以进行accept吗?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());//TODO}else{return ;}}}

但是有几个问题:

  1. 在上面的handler函数中,我们已经获取到了连接,那么下面敢不敢直接进行读取呢?
    当然不能,因为建立连接并不代表会有请求传过来!所以还需要等待请求!
  2. 那么怎么知道底层有没有就绪呢?
    还是通过select进行等待,想办法将新的fd添加给select,进行统一管理!
  3. 那么这样select等待的fd不就越来越多,这要怎么进行维护呢?
    通过辅助数据结构进行维护!由于select接口的参数是输入输出性,无法保存文件描述符,所以必然需要额外的数据结构进行维护文件描述符!

4 完善代码

针对上面的三个问题,我们首先要做的就是想办法通过一个数据结构维护需要进行select的文件描述符。每次进入循环进行select时,就要通过这个数据结构初始化rfds!然后在通过对返回值的rfds与辅助数据结构中的文件描述符进行比对,对有新事件的文件描述符进行处理!

对于这个数据结构我们选择最简单的一维C风格数组即可!进行初始化时都设置为默认值-1

	const static int gnum = sizeof(fd_set) * 8;const static int gdefault = -1;//...void Initserver(){// 对数组进行初始化for (int i = 0; i < gnum; i++){fd_array[i] = gdefault;}// 加入监听套接字fd_array[0] = _listensock->GetSockfd();}//...// 辅助数组int fd_array[gnum];

通过这个数组,当我们进行循环时,每次就都需要通过这个数组进行初始化rfds

 void Loop(){// 进入服务while (true){// 创建fd_setfd_set rfds;FD_ZERO(&rfds);int max_fd = 0;// 首先根据fd_array将合法fd加入到rfdsfor (int i = 0; i < gnum; i++){if (fd_array[i] == gdefault)continue;// 加入合法的文件描述符FD_SET(fd_array[i], &rfds);// 维护一个文件描述符最大值if (fd_array[i] > max_fd)max_fd = fd_array[i];}// 创建timeoutstruct timeval timeout = {30, 0};// 进行selectint n = ::select(max_fd + 1, &rfds, nullptr, nullptr, &timeout);switch (n){case 0:// 超时LOG(DEBUG, "timeout : %d.%d\n", timeout.tv_sec, timeout.tv_usec);break;case -1:// 出错了LOG(ERROR, "select error\n");break;default:// 正常LOG(INFO, "have event ready: n = %d\n", n);// 处理事件HandlerEvent(rfds);PrintDebug();break;}}}

接下来我们来看handlerevent函数,进行select之后,如果有事件就绪,程序就会进入handlerevent函数。那么我们要如何判断是哪一个文件操作符的事件就绪了呢?

  • 直接遍历数组,进行FD_ISSET,通过对每一个合法fd进行判断,我们就能够知道是哪一个文件操作符有事件就绪!
  • 如果是listenfd就绪,说明有新连接,需要进行accepter获取新连接的fd,将其存入到文件描述符数组中!
  • 如果是普通fd就绪,我们进行读写操作即可,如果有连接退出了,要及时更新数组。
    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());// 将新获取的fd加入到数组中LOG(INFO, "get new fd :%d\n", sockfd);bool flag = false;for (int i = 0; i < gnum; i++){if (fd_array[i] == gdefault){flag = true;fd_array[i] = sockfd;break;}elsecontinue;}if (flag == false){LOG(WARNING, "fd_array have fill!\n");}}}void HandlerIO(int &fd){char buffer[1024];int n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){// 读取到了数据buffer[n] = 0;std::string echo_str = "[client say]#";echo_str += buffer;std::cout << echo_str << std::endl;// 返回一个报文std::string content = "<html><body><h1>hello bite</h1></body></html>";std::string ret_str = "HTTP/1.0 200 OK\r\n";ret_str += "Content-Type: text/html\r\n";ret_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";ret_str += content;// echo_str += buffer;::send(fd, ret_str.c_str(), ret_str.size(), 0); // 临时方案}else if (n == 0){// 此时fd退出了LOG(INFO, "fd:%d quit!\n", fd);::close(fd);fd = gdefault;}else{LOG(ERROR, "recv error! errno:%d\n", errno);::close(fd);fd = gdefault;}}void HandlerEvent(fd_set &rfds){// 遍历fd_array判断是否有就绪的新事件for (int i = 0; i < gnum; i++){if (fd_array[i] == gdefault)continue;// 如果有新事件if (FD_ISSET(fd_array[i], &rfds)){// 进行判断是scokfd 还是普通fdif (fd_array[i] == _listensock->GetSockfd()){Accepter();}// 普通fd 进行正常读写else{HandlerIO(fd_array[i]);}}}}

这样就使用select完成了对连接的获取读取工作!来看效果:
在这里插入图片描述
可以看到,我们的数组中的有效fd随着客户端连接与中断会动态变化!

5 总结

根据上面的代码,我们可以总结出select的一些优缺点:

  1. 每次调用 select,都需要手动设置 fd 集合, 从接口使用角度来说也非常不便.
  2. 每次调用 select, 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大。这个是多路转接IO无法避免的问题!
  3. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时很大。
  4. select 支持的文件描述符数量太小!虽然操作系统中文件描述符也有限制,但是这是操作系统的缺陷。同样select也是缺点

这里不断的要进行循环遍历数组,造成的性能开销是比较大的!所以就有了其他两种多路转接方案:poll与epoll

相关文章:

【Linux】从零开始使用多路转接IO --- select

碌碌无为&#xff0c;则余生太长&#xff1b; 欲有所为&#xff0c;则人生苦短。 --- 中岛敦 《山月记》--- 从零开始认识五种IO模型 1 前言2 认识多路转接select3 多路转接select等待连接4 完善代码5 总结 1 前言 上一篇文章我们讲解了五种IO模型的基本概念&#xff0c;并…...

ArcGIS Pro SDK (二十一)渲染

ArcGIS Pro SDK (二十一)渲染 文章目录 ArcGIS Pro SDK (二十一)渲染1 定义唯一值呈现器定义2 为最新观测值设置唯一值渲染器3 为先前的观测值设置唯一值渲染器4 设置简单的渲染器以绘制轨迹线5 检查先前的观测值和轨道线可见性6 使轨迹线和先前的观测点可见7 检索当前观测…...

FPGA在物联网边缘计算中的应用!!!

FPGA&#xff08;现场可编程门阵列&#xff09;在物联网边缘计算中的应用正变得越来越重要。边缘计算是一种分布式计算架构&#xff0c;它将数据的处理分散到网络的边缘&#xff0c;靠近数据源&#xff0c;而不是集中在数据中心处理。以下是FPGA在物联网边缘计算中的几个关键应…...

【解决】Linux环境中mysqlclient安装失败问题

问题描述 在Linux系统下安装myslclient报异常。系统为Centos 8 使用 pip install mysqlclient 报出下面的异常 error: subprocess-exited-with-error Getting requirements to build wheel did not run successfully.│ exit code: 1╰─> [30 lines of output]/bin/sh: pkg…...

✨ Midjourney中文版:创意启航,绘梦无界 ✨

Midjourney AI超强绘画 (原生态系统&#xff09;用户端&#xff1a;Ai Loadinghttps://www.mjdiscord.com 项目详细介绍飞书文档&#xff1a;Docshttps://ivqklkndl4k.feishu.cn/docx/GRnMdCbcooWkwTx1RU4cZjGVnzb?fromfrom_copylk &#x1f310; 无缝体验&#xff0c;中文定制…...

软件(1)

软件 常考软件 图像软件 Flash 一款二维动画处理软件 photoshop 图像处理界的“巨无霸” ACDSee ACDSee是常用的图片管理编辑软件&#xff0c;尽管也可以支持WAV格式的音频播放&#xff0c; 但目前主要是作为看图软件 音频软件 Winamp Winamp是数字媒体播放的先驱Audition Audi…...

linux perf 环境部署和基本测试(基于Ubuntu20.04)

1,linux 安装perf sudo apt-ge install linux-tools-common sudo apt-get install linux-tools-$(uname -r) linux-tools-generic -y 2 补充安装 sudo apt-get install python3-q-text-as-data 3&#xff0c;perf常用命令 larkubuntu:~$ perf usage: perf [--version] [--hel…...

【网络面试篇】HTTP(1)(笔记)——状态码、字段、GET、POST、缓存

目录 一、相关问题 1. HTTP请求常见的状态码和字段&#xff1f; &#xff08;1&#xff09;状态码 &#xff08;2&#xff09;字段 ① Host 字段 ② Content-length 字段 ③ Connection 字段 ④ Content-Type 字段 ⑤ Content-Encoding 字段 2. GET 和 POST 的区别&a…...

HTML 基础标签——分组标签 <div>、<span> 和基础语义容器

文章目录 1. `<div>` 标签特点用途示例2. `<span>` 标签特点用途示例3. `<fieldset>` 标签特点用途示例4. `<section>` 标签特点用途示例5. `<article>` 标签特点用途示例总结HTML中的分组(容器)标签用于结构化内容,将页面元素组织成逻辑区域…...

SS928V100 ISP常见问题列表

下载链接&#xff1a; https://download.csdn.net/download/quantum7/89948226 1 FAQ 1 1.1 ISP 1 1.1.1 如何解决整体锐度不足 1 1.1.2 如何解决图像发蒙问题&#xff0c;提高通透性 2 1.1.3 如何解决低照度清晰度差 2 1.1.4 如何解决图像清晰度与物体边缘白边和黑边问题…...

AI写诗:自动版大唐宫体诗

大唐学子&#xff0c;手拿一本小卷&#xff08;类书&#xff09;&#xff0c;从中挑选过去他们&#xff08;权威&#xff09;认为好的词来拼接一首诗&#xff0c;此类诗词称作“宫体诗”&#xff0c;在初唐时期甚是流行。 写“宫体诗”的过程有木有那么一丝丝的熟悉&#xff1f…...

Java复习31(PTA)

sdust-Java-字符串集合求并集 分数 15 全屏浏览 切换布局 作者 张峰 单位 山东科技大学 从键盘接收N个英文字符串&#xff08;其中不同的字符串数量大于10&#xff09;&#xff0c;从头开始取5个不同的字符串放入一个集合S1&#xff0c;然后接着取5个不同的字符串放入另一个…...

【Linux系列】Linux 系统中的软连接管理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

@layer(级联层)

在css样式表(文件)中声明layer为样式添加级联层,其意义在于可以使用它重新定义样式的叠层关系. layer后声明的级联层里面的样式将覆盖前声明的级联层里面的相同属性.在级联层外声明的样式会覆盖级联层里面的相同属性样式,同一层级里面的样式冲突,依然按照优先级来计算. 在级联层…...

nginx代理websocket服务

一、nginx代理websocket服务 一&#xff09;nginx代理ws服务 在nginx中&#xff0c;可以通过proxy_pass指令来代理WebSocket服务。 以下是一个示例配置&#xff1a; map $http_upgrade $connection_upgrade {default upgrade; close; }upstream ws_backend {server 127.0.0.1:…...

第二十七章 Vue异步更新之$nextTick

目录 一、概述 二、完整代码 2.1. main.js 2.2. App.vue 一、概述 需求&#xff1a;编辑标题, 弹出显示编辑框自动聚焦 1. 点击编辑&#xff0c;显示编辑框 2. 让编辑框&#xff0c;立刻获取焦点 我们常规的思路可能会编写如下代码来实现&#xff1a; 问题&#xff1a…...

【51 Pandas+Pyecharts | 深圳市共享单车数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 处理起始时间、结束时间2.4 增加骑行时长区间列2.5 增加骑行里程区间列 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 各…...

【Clikhouse 探秘】ClickHouse 物化视图:加速大数据分析的新利器

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…...

线程相关题(线程池、线程使用、核心线程数的设置)

目录 线程安全的什么情况用&#xff1f;情况下用线程安全的类&#xff1f; 线程池线程方面的优化 线程池调优主要涉及以下几个关键参数。 线程不安全原因? 1. 共享资源访问冲突 2. 缺乏原子操作 3. 内存可见性问题 4. 重排序问题 如何解决线程不安全的问题&#xff1…...

2181、合并零之间的节点

2181、[中等] 合并零之间的节点 1、问题描述&#xff1a; 给你一个链表的头节点 head &#xff0c;该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val 0 。 对于每两个相邻的 0 &#xff0c;请你将它们之间的所有节点合并成一个节点&#xff…...

powerlaw:用于分析幂律分布的Python库

引言 幂律分布在游戏行业中非常重要。在免费游戏模式下&#xff0c;玩家的付费行为往往遵循幂律分布。少数“鲸鱼玩家”贡献了大部分的收入&#xff0c;而大多数玩家可能只进行少量或不进行付费。通过理解和应用幂律分布&#xff0c;游戏开发者可以更好地分析和预测玩家行为&a…...

工作管理实战指南:利用Jira、Confluence等Atlassian工具打破信息孤岛,增强团队协作【含免费指南】

如果工作场所存在超级反派&#xff0c;其中之一可能会被命名为“信息孤岛”&#xff0c;因为它们能够对公司的生产力和协作造成严重破坏。当公司决定使用太多互不关联的工具来完成工作时&#xff0c;“信息孤岛”就会出现&#xff0c;导致团队需要耗费大量时间才能就某件事情达…...

JAVA语言多态和动态语言实现原理

JAVA语言多态和动态语言实现原理 前言invoke指令invokestaticinvokespecialinvokevirtualinvokeintefaceinvokedynamicLambda 总结 前言 我们编码java文件&#xff0c;javac编译class文件&#xff0c;java运行class&#xff0c;JVM执行main方法&#xff0c;加载链接初始化对应…...

阿里云-防火墙设置不当导致ssh无法连接

今天学网络编程的时候&#xff0c;看见有陌生ip连接&#xff0c;所以打开了防火墙禁止除本机之外的其他ip连接&#xff1a; 但是当我再次用ssh的时候&#xff0c;连不上了才发现大事不妙。 折腾了半天&#xff0c;发现阿里云上可以在线向服务器发送命令&#xff0c;所以赶紧把2…...

使用WebAssembly优化Web应用性能

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用WebAssembly优化Web应用性能 引言 WebAssembly 简介 安装工具 创建 WebAssembly 项目 编写 WebAssembly 代码 编译 WebAssem…...

软件测试模型

软件测试模型是在软件开发过程中&#xff0c;用于指导软件测试活动的一系列方法和框架。这些模型帮助测试团队确定何时进行测试、测试什么以及如何测试&#xff0c;从而确保软件的质量和稳定性。 一 V模型 V模型是一种经典的软件测试模型,它由瀑布研发模型演变而来的测试模型…...

动态规划——两个数组的dp问题

目录 一、最长公共子序列 二、不同的子序列 三、通配符匹配 四、正则表达式匹配 五、两个字符串的最小ASCII删除和 六、最长重复子数组 七、交错字符串 一、最长公共子序列 最长公共子序列 第一步&#xff1a;确定状态表示 dp[i][j]&#xff1a;表示字符串 s1 的 [0&am…...

视频QoE测量学习笔记(二)

目录 自适应比特率&#xff08;ABH或ABS&#xff09; HAS:HTTP adaptive streaming 自适应本质&#xff1a; HAS正在解决传统流协议中主要关注的几个方面&#xff1a; DASH标准化原因 HAS发展 编码&#xff1a; 影响HAS系统的四个主要问题&#xff1a; 一个健全的HAS方…...

RSA算法详解:原理与应用

RSA算法详解&#xff1a;原理与应用 RSA算法是现代密码学的基石之一&#xff0c;广泛应用于安全通信、数据加密和身份验证等领域。本文将详细介绍RSA算法的原理、实现步骤以及实际应用。 一、RSA算法概述 RSA&#xff08;Rivest-Shamir-Adleman&#xff09;算法由Ron Rivest…...

YOLOv6-4.0部分代码阅读笔记-effidehead_fuseab.py

effidehead_fuseab.py yolov6\models\heads\effidehead_fuseab.py 目录 effidehead_fuseab.py 1.所需的库和模块 2.class Detect(nn.Module): 3.def build_effidehead_layer(channels_list, num_anchors, num_classes, reg_max16, num_layers3): 1.所需的库和模块 impo…...