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

【linux高级IO(二)】多路转接之select详解

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Linux从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学更多操作系统知识
  🔝🔝


Linux高级IO

  • 1. 前言
  • 2. 初识select
  • 3. 理解select的执行过程
  • 4. select的简使用示例
  • 5. 总结

1. 前言

多路转接一共有三种实现方案, 分别是select,poll和epoll. 本系列文章会一一讲解它们的优缺点和使用方法. 如果你还不清楚这些IO模型, 请先阅读这篇文章: Linux高级IO

本章重点:

本篇文章着重讲解select函数的原型和用法, 并且会带大家实现一个简单的select多路转接代码


2. 初识select

系统提供select函数来实现多路复用输入/输出模型.

select函数的原型:

在这里插入图片描述

参数解释:

  • 参数nfds是需要监视的最大的文件描述符值+1;
  • rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合;
  • 参数timeout为结构timeval,用来设置select()的等待时间

就绪事件通常分为可读事件,可写事件和异常事件

参数timeout的意义:

  • NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
  • 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

select函数参数中, 使用了fd_set类型变量, 如果你对信号部分比较熟悉, 那么你一定能猜到, fd_set是个位图, 你可以把你需要关心的所有fd都设置到这个位图中, 让select函数帮你关心, 比如你想要关心0号文件描述符(stdin)的读事件, 那么就在readfd中将0设置进去.

在这里插入图片描述
select函数的返回值以及错误码:

在这里插入图片描述


3. 理解select的执行过程

timel结构体参数,fd_set类型参数都是输入输出型参数

  • timeval类型参数: 假如你设置阻塞时间为5秒, 但是等待了三秒后就有事件就绪, 函数就返回了, 那么timeval类型参数的值会被设置成为2秒.
  • fd_set类型参数, 输入你想要关心的fd集合, 输出时, 此结构中存放, 已经事件就绪的fd集合. 比如你想要关心0~10号文件描述符的读事件, 函数返回时, 此集合中可能只有1.3.5号fd被返回了, 也就是说只有1.3.5号fd的事件就绪了

综上所述: 每一次调用完select后,都需要我们重置参数

while(1)
{//处理完重置参数fs_set readset;FD_SET(fd,&readset);select(fd+1,&readset,NULL,NULL,NULL);if(FD_ISSET(fd,readset)){……}
}

什么叫做事件就绪?拿读事件来说, 客户端和服务器建立连接后, 客户端就一定会向服务器发送数据吗? 不一定! 所以服务器只能等待客户端发数据, 一旦接收缓冲区有数据到来, 那么就是读事件就绪了, 可以直接调用recv函数从对应的fd中将数据拿到内存. 写事件也是如此, 可能此时的发送缓冲区已满, 那么就需要等待

select的特点:

在这里插入图片描述

fd_set的大小可变,有兴趣可自行查资料

select的缺点:

在这里插入图片描述

正因为select的这些缺陷, 才会有后面的poll和epoll来代替它


4. select的简使用示例

多路转接的编程本质上也是网络编程, 所以我们可以先定义一个sock.h文件用于网络通信的套接字编程:

class Sock
{const static int gbacklog = 20;
public:Sock(){}static int Socket(){}static void Bind(int sock, uint16_t port, string ip = "0.0.0.0"){}static void Listen(int sock){}static int Accept(int listensock,string* ip,uint16_t* port){}   static bool Connect(int sock,string serverip,uint16_t serverport)//客户端要连接谁就传谁的ip和port{}~Sock(){}
};

接下来是select的编程:

#ifndef __SELECT_SVR_H__
#define __SELECT_SVR_H__
#include <iostream>
#include <sys/select.h>
#include <vector>
#include "log.hpp"
#include "sock.hpp"
#include <string>
#include <sys/time.h>
using namespace std;#define BITS 8
#define NUM (sizeof(fd_set) * BITS)
#define FD_NONE -1// 只完成读取,写入和异常暂时不做处理,单进程,可以同时为多个人服务
class Select_Server
{
public:Select_Server(uint16_t port = 9090): _port(port){_listensock = Sock::Socket();Sock::Bind(_listensock, port);Sock::Listen(_listensock);logMessage(DEBUG, "创建基础套接字成功!");for (int i = 0; i < NUM; i++)_array[i] = FD_NONE; // 规定array[0] = _listensock_array[0] = _listensock;}void Start(){while (1){DebugPrint();// struct timeval timeout = {5, 0};//  如何看待当前唯一的套接字?获取新连接,我们把它依旧看作IO,input事件,如果没有连接到来,调用accept就会阻塞,不能直接调用accept//  FD_SET(_listensock, &readfd); // 将listensock添加到文件描述符集合中//  int n = select(_listensock + 1, &readfd, nullptr, nullptr, &timeout);//  随着获取的sock越来越多,注定了nfds每一次都可能要发送变化,需要对它进行动态计算,并且rfds,writefds是输入输出型参数,每次输入输出可能不同,注定了每次都要对rfds进行重新添加//  应该将自己所有的文件描述符都单独保存起来,用来支持: 1. 更新最大值 2. 更新位图结构fd_set readfd;FD_ZERO(&readfd); // 将这个位图清空int maxfd = _listensock;for (int i = 0; i < NUM; i++) // 更新位图,寻找最大值{if (_array[i] == FD_NONE)continue;FD_SET(_array[i], &readfd);if (_array[i] > maxfd)maxfd = _array[i];}int n = select(maxfd + 1, &readfd, nullptr, nullptr, nullptr);if (n == 0) // 没有文件描述符就绪//logMessage(DEBUG, "time out");else if (n == -1)//logMessage(DEBUG, "select error");else{//logMessage(DEBUG, "select获取到一个链接");HandlerEvent(readfd);}}}~Select_Server(){if (_listensock >= 0)close(_listensock);}private:void Accepter(){}void Recver(int sock, int pos){}void HandlerEvent(const fd_set &readfd) // fd_set是一个集合,里面可能存在多个sock{}
private:uint16_t _port;int _listensock;int _array[NUM];
};
#endif

关于代码的解释都在注释中, 如果你想查看完整的代码, 可以在gitee: select编程示例中找到你想要的一切

需要注意的是, 读事件就绪有两种情况, 一种是新来fd的连接了, 另外一种是已有的连接的数据就绪了, 所以这两种情况需要分开讨论


5. 总结

为什么多路转接在实际生活中运用如此之多? 答案就是它一次性可以等待多个文件描述符, 效率很高. 但是select多路转接方案有局限性, 所以后面的epoll才是学习的重点


🔎 下期预告:多路转接之poll 🔍

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 离线语音识别芯片在智能生活中的应用
  • liunx作业笔记1
  • Elasticsearch7.5.2 常用rest api与elasticsearch库
  • 昇思25天学习打卡营第9天|MindSpore静态图加速
  • 【MySQL基础篇】事务
  • RabbitMq,通过prefetchCount限制消费并发数
  • 排序之冒泡排序
  • 掌握构建魔法:Gradle中Groovy插件的配置秘籍
  • 深度解析 PostgreSQL Protocol v3.0(三)— 流复制(上)
  • i7-13700K负载过高时出现无故自动重启(蓝屏问题)
  • 小白的OS Copilot 产品测评
  • 微信小程序毕业设计-学习资料库系统项目开发实战(附源码+论文)
  • ETL数据集成丨主流ETL工具(ETLCloud、DataX、Kettle)数据传输性能大PK
  • 启动完 kubelet 日志显示 failed to get azure cloud in GetVolumeLimits, plugin.host: 1
  • 2024辽宁省大学数学建模竞赛试题思路
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • css选择器
  • Golang-长连接-状态推送
  • gops —— Go 程序诊断分析工具
  • HTTP中的ETag在移动客户端的应用
  • Javascript编码规范
  • MySQL QA
  • PAT A1092
  • PHP变量
  • Vue.js 移动端适配之 vw 解决方案
  • 彻底搞懂浏览器Event-loop
  • 关于List、List?、ListObject的区别
  • 入门级的git使用指北
  • 设计模式(12)迭代器模式(讲解+应用)
  • 数组大概知多少
  • 用Canvas画一棵二叉树
  • 智能合约Solidity教程-事件和日志(一)
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​​​​​​​​​​​​​​Γ函数
  • ​马来语翻译中文去哪比较好?
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • #565. 查找之大编号
  • #define,static,const,三种常量的区别
  • #Spring-boot高级
  • #vue3 实现前端下载excel文件模板功能
  • #每日一题合集#牛客JZ23-JZ33
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (floyd+补集) poj 3275
  • (Java入门)学生管理系统
  • (LeetCode 49)Anagrams
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (pojstep1.3.1)1017(构造法模拟)
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (五)activiti-modeler 编辑器初步优化