IO 多路转接之 select

news2024/9/21 21:27:29

文章目录

  • IO 多路转接之 select
    • 1、初识 select
    • 2、select 函数及其参数解释
    • 3、select 函数返回值
    • 4、select 的执行过程
    • 5、socket 就绪条件
      • 5.1、读就绪
      • 5.2、写就绪
      • 5.3、异常就绪
    • 5、select 的特点
    • 6、select 的缺点
    • 7、select 使用实例
      • 7.1、只检测检测标准输入输出
      • 7.2、使用 select 的服务器响应程序

img

IO 多路转接之 select

1、初识 select

select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的。

程序会停在 select 这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。


2、select 函数及其参数解释

select 函数:

/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 参数 nfds 是需要监视的最大的文件描述符值+1

  • readfds,writefds,exceptfds 分别对应于需要检测的可读文件描述符的集合,可读写文件描述符的集合及异常文件描述符的集合

  • 参数 timeout 为结构 timeval,用来设置 select()的等待时间

struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

timeout 参数:

  • NULL:则表示 select()没有 timeout,select 将一直被阻塞,直到某个文件描述符上发生了事件。

  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

  • 特定的时间值:如果在指定的时间段里没有事件发生,select 将超时返回(比如设置tv_sec=1,tv_usec=1000,那就是每隔1.1秒返回一次等到的文件描述符)。

fd_set 结构:

/* fd_set for select and pselect.  */
typedef struct
 {
   /* XPG4.2 requires this member name.  Otherwise avoid the name
      from the global namespace.  */
#ifdef __USE_XOPEN
   __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
   __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
 } fd_set;

typedef long int __fd_mask;
typedef __fd_mask fd_mask;

其实这个结构就是一个整数数组,更严格的说,是一个 “位图”。使用位图中对应的位来表示要监视的文件描述符,提供了一组操作 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全部位

3、select 函数返回值

下面是执行man select后的查找到的 select 的返回值信息。

RETURN VALUE
      On success, select() and pselect() return the number of file descriptors contained in the three returned descriptor sets (that is,  the  total  number  of
      bits  that  are  set  in  readfds, writefds, exceptfds) which may be zero if the timeout expires before anything interesting happens.  On error, -1 is re‐
      turned, and errno is set to indicate the error; the file descriptor sets are unmodified, and timeout becomes undefined.

ERRORS
      EBADF  An invalid file descriptor was given in one of the sets.  (Perhaps a file descriptor that was already closed, or one on  which  an  error  has  oc‐
             curred.)  However, see BUGS.

      EINTR  A signal was caught; see signal(7).

      EINVAL nfds is negative or exceeds the RLIMIT_NOFILE resource limit (see getrlimit(2)).

      EINVAL The value contained within timeout is invalid.

      ENOMEM Unable to allocate memory for internal tables.
  • 执行成功则返回文件描述词状态已改变的个数
  • 如果返回 0 代表在描述词状态改变前已超过 timeout 时间,没有返回(没有文件描述符就绪)
  • 当有错误发生时则返回-1,错误原因存于 errno,此时参数 readfds,writefds,exceptfds 和 timeout 的值变成不可预测。

错误值可能为:

  • EBADF:文件描述词为无效的或该文件已关闭
  • EINTR:此调用被信号所中断
  • EINVAL:参数 nfds 为负值或者 timeout 无效
  • ENOMEM:核心内存不足

常用的使用场景:

fs_set readset;
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)){……}

4、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)阻塞等待,这里 6 就是 最大文件描述符+1
  • 若 fd=1,fd=2 上都发生可读事件,则 select 返回,此时 set 变为 0000,0011。注意:没有事件发生的 fd=5 被清空,所以在执行 select 之前,需要保存所有需要监听的文件描述符,以便于被清空后,可以重新设置。

5、socket 就绪条件

5.1、读就绪

  • socket 内核中,接收缓冲区中的字节数,大于等于低水位标记 SO_RCVLOWAT (有足够的数据可以读的意思吧),此时可以无阻塞的读该文件描述符,并且返回值大于 0
  • socket TCP 通信中,对端关闭连接,此时对该 socket 读,则返回 0
  • 监听的 socket 上有新的连接请求
  • socket 上有未处理的错误

5.2、写就绪

  • socket 内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记 SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于 0

  • socket 的写操作被关闭(close 或者 shutdown),对一个写操作被关闭的 socket进行写操作, 会触发 SIGPIPE 信号

  • socket 使用非阻塞 connect 连接成功或失败之后

  • socket 上有未读取的错误


5.3、异常就绪

socket 上收到带外数据。

关于带外数据,和 TCP 紧急模式相关(回忆 TCP 协议头中,有一个紧急指针的字段)。

这个数据需要紧急处理。


5、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 的大小可以调整,可能涉及到重新编译内核。感兴趣的同学可以自己去收集相关资料。

这里的特点感觉像是缺点哈哈哈,后面学习到的 epoll 可以完美解决这些问题。


6、select 的缺点

  • 每次调用 select,都需要手动设置 fd 集合,从接口使用角度来说也非常不便

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

  • 同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大 select 支持的文件描述符数量太小

  • select 最多只能监听 sizeof(fd_set) 个文件描述符,这在有些场景下可能不够


7、select 使用实例

7.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(标准输入)时,因为输入条件只有在你有输入信息的时候,才成立,所以如果一直不输入,就会产生超时信息。


7.2、使用 select 的服务器响应程序

主要的代码:

  • SelectServer.hpp文件
#pragma once

#include <iostream>
#include <memory>
#include <sys/select.h>
#include <sys/time.h>
#include <string>
#include "Socket.hpp"

using namespace socket_ns;

// select需要一个第三方数组保存文件描述符
class SelectServer
{
   const static int N = sizeof(fd_set) * 8;
   const static int defaultfd = -1;

public:
   SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
   {
       InetAddr client("0", port);
       _listensock->BuildListenSocket(client);
       for (int i = 0; i < N; ++i)
           _fd_array[i] = defaultfd;
       _fd_array[0] = _listensock->SockFd(); // listen文件描述符一定是第一个
   }

   void AcceptClient()
   {
       InetAddr clientaddr;
       socket_sptr sockefd = _listensock->Accepter(&clientaddr);
       int fd = sockefd->SockFd();
       if (fd >= 0)
       {
           LOG(DEBUG, "Get new Link ,sockefd is :%d ,client info : %s:%d", fd, clientaddr.Ip().c_str(), clientaddr.Port());
       }
       // 把新到的文件描述符交给select托管,使用辅助数组
       int pos = 1;
       for (; pos < N; pos++)
       {
           if (_fd_array[pos] == defaultfd)
               break;
       }
       if (pos == N)
       {
           ::close(fd);
           // 满了
           LOG(WARNING, "server full ...");
           return;
       }
       else
       {
           _fd_array[pos] = fd;
           LOG(WARNING, "%d sockfd add to select array", fd);
       }
       LOG(DEBUG, "cur fdarr[] fd list : %s", RfdsToStr().c_str());
   }

   void ServiceIO(int pos)
   {
       char buff[1024];
       ssize_t n = ::recv(_fd_array[pos], buff, sizeof(buff) - 1, 0);
       if (n > 0)
       {
           buff[n] = 0;
           LOG(DEBUG, "client # %s", buff);
           std::string message = "Server Echo# ";
           message += buff;
           ::send(_fd_array[pos], message.c_str(), message.size(), 0);
       }
       else if (n == 0)
       {
           LOG(DEBUG, "%d socket closed!", _fd_array[pos]);
           ::close(_fd_array[pos]);
           _fd_array[pos] = defaultfd;
           LOG(DEBUG, "cur fdarr[] fd list : %s", RfdsToStr().c_str());
       }
       else
       {
           LOG(DEBUG, "%d recv error!", _fd_array[pos]);
           ::close(_fd_array[pos]);
           _fd_array[pos] = defaultfd;
           LOG(DEBUG, "cur fdarr[] fd list : %s", RfdsToStr().c_str());
       }
   }

   void HandlerRead(fd_set &rfds)
   {
       for (int i = 0; i < N; i++)
       {
           if (_fd_array[i] == defaultfd)
               continue;
           if (FD_ISSET(_fd_array[i], &rfds))
           {
               if (_fd_array[i] == _listensock->SockFd())
               {
                   AcceptClient();
               }
               else
               {
                   // socket读事件就绪
                   ServiceIO(i);
               }
           }
       }
   }

   void Loop()
   {
       while (true)
       {
           // int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
           fd_set rfds;
           FD_ZERO(&rfds);
           int maxfd = defaultfd;
           for (int i = 0; i < N; ++i)
           {
               if (_fd_array[i] == defaultfd)
                   continue;
               FD_SET(_fd_array[i], &rfds);
               if (_fd_array[i] > maxfd)
                   maxfd = _fd_array[i];
           }
           struct timeval t = {2, 0}; // 隔一段时间阻塞
           // struct timeval t = {0, 0}; // 不阻塞
           // int n = select(maxfd + 1, &rfds, nullptr, nullptr, &t);
           int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // 永久阻塞
           if (n > 0)
           {
               // 处理读文件描述符
               HandlerRead(rfds);
           }
           else if (n == 0)
           {
               //  时间到了
               LOG(DEBUG, "time out ...");
           }
           else
           {
               // 错误
               LOG(FATAL, "select error ...");
           }
       }
   }

   std::string RfdsToStr()
   {
       std::string rfdstr;
       for (int i = 0; i < N; ++i)
       {
           if (_fd_array[i] != defaultfd)
           {
               rfdstr += std::to_string(_fd_array[i]);
               rfdstr += " ";
           }
       }
       return rfdstr;
   }

   ~SelectServer() {}

private:
   uint16_t _port;
   std::unique_ptr<TcpSocket> _listensock;
   int _fd_array[N];
};

整体服务代码


OKOK,IO 多路转接之 select就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

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

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

相关文章

YOLOv8改进 | 自定义数据集训练 | AirNet助力YOLOv8检测

目录 一、本文介绍 二、AirNet原理介绍 2.1 对比基降解编码器&#xff08;CBDE&#xff09; 2.2 降解引导修复网络&#xff08;DGRN&#xff09; 三、yolov8与AirNet结合修改教程 3.1 核心代码文件的创建与添加 3.1.1 AirNet.py文件添加 3.1.2 __init__.py文件添加 3…

ONES 与华为云深度合作,共同打造企业智能研发管理平台

9月20日&#xff0c;在华为全联接大会&#xff08;HUAWEI CONNECT 2024&#xff09;上&#xff0c;深圳复临科技有限公司&#xff08;以下简称“ONES”&#xff09;与华为云计算技术有限公司&#xff08;以下简称“华为云”&#xff09;正式签署合作协议&#xff0c;双方将在企…

Java线程池实现父子线程上下文传递以及MDC追踪

文章目录 1. 总览2. 代码实现2.1. 日志配置2.2. ThreadPoolExecutor 父子线程信息不传递2.3. ContextPassThreadPoolExecutor 父子线程信息传递 3. 整体架构 1. 总览 在公司的项目中&#xff0c;为了解决慢接口问题&#xff0c;主线程经常会引入线程池并发调用下游的 RPC&…

pg_start_backup

pg_start_backup()函数在主库上发起一个在线备份&#xff0c;命令执行成功后&#xff0c;将数据文件拷贝到备份接口中 select pg_start_backup(full0918,false,false); 以上会话不要关闭&#xff0c;复制数据目录。 cp -r /pgdata/data/postgres-f66f5f7a/ /opt/qfusion/mnt/st…

Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 DiffUtil 是一个用于计算两个列表之间差异的实用程序类&#xff0c;它可以帮助 RecyclerView 以更高效的方式更新数据。使用 DiffUtil 可以减少…

行业落地分享:大模型 RAG 难点与创新应用

最近这一两周不少互联网公司都已经开始秋招面试了 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解惑答疑…

网络信息传输安全

目录 机密性-加密 对称加密 非对称加密 身份认证 摘要算法和数据完整性 数字签名 签名验签 数字证书 申请数字证书所需信息 数字证书的生成 数字证书的应用 https协议 数字证书的申请 数据在网络中传输过程中&#xff0c;怎么做到 数据没有被篡改&#xff1f;hash算…

【Python深度学习系列】基于Flask将深度学习模型部署到web应用上(完整案例)

这是我的第356篇原创文章。 一、引言 使用 Flask 在 10 分钟内将您自己训练的模型或预训练的模型&#xff08;VGG、ResNet、Densenet&#xff09;部署到网络应用程序中。以图像分类模型为例&#xff0c;本地直接部署和本地使用docker部署两种方式实现。 二、实现过程 2.1 准备…

扎克伯格的未来愿景:用智能眼镜引领数字社交新时代

Meta Connect 2024大会前夕&#xff0c;创始人马克扎克伯格的90分钟播客访谈&#xff0c;为我们描绘了Meta未来的蓝图。这场访谈&#xff0c;不仅是大会的热身&#xff0c;更是对科技未来的一次深刻洞察。 人工智能 - Ai工具集 - 未来办公人的智能办公生活导航网 扎克伯格的未…

实操学习——题目的管理

实操学习——题目的管理 一、基础配置二、权限控制三、分页1. PageNumberPagination分页器2. LimitOffsetPagination分页器3.总结 四、题目操作模块1. 考试2. 题目练习——顺序刷题3. 模拟考试 补充&#xff1a;前端调用接口写法 本文主要讲解题目的管理案例 1.题目的基本增删改…

FastAPI 的隐藏宝石:自动生成 TypeScript 客户端

在现代 Web 开发中&#xff0c;前后端分离已成为标准做法。这种架构允许前端和后端独立开发和扩展&#xff0c;但同时也带来了如何高效交互的问题。FastAPI&#xff0c;作为一个新兴的 Python Web 框架&#xff0c;提供了一个优雅的解决方案&#xff1a;自动生成客户端代码。本…

必知!5大AI生成模型

大数据文摘授权转载自数据分析及应用 随着Sora、diffusion等模型的大热&#xff0c;深度生成模型再次成为了研究的焦点。这类模型&#xff0c;作为强大的机器学习工具&#xff0c;能够从输入数据中学习其潜在的分布&#xff0c;并生成与训练数据高度相似的新样本。其应用领域广…

【IDEA】使用IDEA连接MySQL数据库并自动生成MySQL的建表SQL语句

前言&#xff1a; 在软件开发过程中&#xff0c;数据库的设计与实现是至关重要的一环。IntelliJ IDEA作为一款强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供了丰富的数据库工具&#xff0c;使得连接MySQL数据库并自动生成建表SQL语句变得简单快捷。本文将详细…

ansible远程自动化运维、常用模块详解

一、ansible是基于python开发的配置管理和应用部署工具&#xff1b;也是自动化运维的重要工具&#xff1b;可以批量配置、部署、管理上千台主机&#xff1b;只需要在一台主机配置ansible就可以完成其它主机的操作。 1.操作模式&#xff1a; 模块化操作&#xff0c;命令行执行…

竹云赋能“中国·贵州”全省统一移动应用平台建设,打造政务服务“新引擎”

近日&#xff0c;2024中国国际大数据产业博览会在贵州贵阳圆满落幕。会上&#xff0c;由贵州省政府办公厅牵头建设的“中国贵州”全省统一移动应用平台正式发布&#xff0c;聚焦民生办事、政务公开、政民互动、扁平高效、数据赋能五大模块&#xff0c;旨在打造公平普惠的服务平…

解决Python Debug没有反应的问题

应该有伙伴和我一样&#xff0c;用的2024版本的VS code&#xff0c;但是用到的python解释器是3.6.x&#xff0c;或者是更旧版本的Python. 想要进行Debug就会在扩展里面安装 一般安装就会安装最新版本&#xff0c;但是debug时又没有反应&#xff0c;其主要原因是Python的版本与…

基于Springboot的助学金管理系统设计与实现

文未可获取一份本项目的java源码和数据库参考。 一、研究背景 利用计算机来实现助学金管理系统&#xff0c;已经成为一种趋势&#xff0c;相比传统的手工管理方式&#xff0c;利用软件进行助学金管理系统&#xff0c;有着执行快&#xff0c;可行性高、容量存储大&#xff0c;…

前端入门:HTML+CSS

引言: 前端三大件:HTML、CSS、JS,每一个部分都很重要,我听过比较形象的比喻就是HTML(HYPER TEXT MARKUP LANGUAGE)相当于骨架,而CSS就是装饰渲染,JS则是动作功能实现。 之前的文章我已经讲过HTML,这篇文章我将讲解HTML和CSS的案例。 网页开发: 我开发出来的网页如…

DAMODEL——Llama3.1的部署与使用指南

Llama3.1的部署与使用指南 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;大模型&#xff08;LLM&#xff09;是基于深度学习算法训练而成的重要工具&#xff0c;应用范围包括自然语言理解和生成。随着技术的发展&#xff0c;开源的LLM不断涌现&#xff0c;涵盖了…

数字人直播带货火了,只要有了这个工具,就可以打造数字人,建议新手小白赶紧尝试!

经济下行&#xff0c;普通人应该尽早认清一个事实&#xff0c;没有一技之长&#xff0c;没有核心竞争力&#xff0c;即便是打工皇帝&#xff0c;年入百万也只是浮云。 一定要保证主业的稳定&#xff0c;再探索新的机会&#xff0c;要多从”1-10"&#xff0c;而不是反复”…