Linux高级IO之select

 (。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

点个关注不迷路⌯'▾'⌯

目录

一、五种IO模型 

1.IO效率的问题

2.阻塞IO是最常见的IO模型.

3.非阻塞IO

4.信号驱动IO

5.IO多路转接

6.异步IO

7.小结

二、非阻塞IO

1.fcntl

三、I/O多路转接之select

1.快速认识select接口

2.fd_set

3.接口挑一个重点参数,细致分析

4.编写代码

5.select的优缺点


网络的本质就是:IO,是一次输入和输出

一、五种IO模型 

1.IO效率的问题

IO的效率是很低效的,毕竟对方在千里之外。

IO为什么低效,以读为例:

  1. 当我们read/recv的时候,如果底层缓冲区没有数据,read/recv会怎么办?-----阻塞
  2. 当我们read/recv的时候,如果底层缓冲区有数据,read/recv会怎么办?-----拷贝
  3. 所以一次IO=等+数据拷贝
  4. 所以read、recv、write、send等都是在等io就绪,然后发起拷贝

那么什么叫做低效的IO呢?

单位时间,大部分时间这些io类的接口都在等数据!

那么如何提高IO的效率呢?

想办法在单位时间等的比重变低,那么IO的效率就高

2.阻塞IO是最常见的IO模型.

阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式

3.非阻塞IO

如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码

非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一 般只有特定场景下才使用

4.信号驱动IO

内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作

5.IO多路转接

虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件 描述符的就绪状态

6.异步IO

由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)

7.小结

任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往 往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

二、非阻塞IO

1.fcntl

一个文件描述符, 默认都是阻塞IO.

函数原型如下:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

传入的cmd的值不同, 后面追加的参数也不相同. fcntl函数有5种功能

  • 复制一个现有的描述符(cmd=F_DUPFD).
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
#include <iostream>
#include <cstring>
#include <ctime>
#include <cassert>
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>#include <sys/time.h>bool SetNonBlock(int fd)
{int fl = fcntl(fd, F_GETFL); // 在底层获取当前fd对应的文件读写标志位if (fl < 0)return false;fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 设置非阻塞return true;
}int main()
{// 0SetNonBlock(0); //只要设置一次,后续就都是非阻塞了char buffer[1024];while (true){sleep(1);errno = 0;// 非阻塞的时候,我们是以出错的形式返回,告知上层数据没有就绪:// a. 我们如何甄别是真的出错了// b. 还是仅仅是数据没有就绪呢?// 数据就绪了的话,我们就正常读取就行ssize_t s = read(0, buffer, sizeof(buffer) - 1); //出错,不仅仅是错误返回值,errno变量也会被设置,表明出错原因if (s > 0){buffer[s-1] = 0;std::cout << "echo# " << buffer << " errno[---]: " << errno << " errstring: " << strerror(errno) << std::endl;}else{// 如果失败的errno值是11,就代表其实没错,只不过是底层数据没就绪//std::cout << "read \"error\" " << " errno: " << errno << " errstring: " << strerror(errno) << std::endl;if(errno == EWOULDBLOCK || errno == EAGAIN){std::cout << "当前0号fd数据没有就绪, 请下一次再来试试吧" << std::endl;continue;}else if(errno == EINTR){std::cout << "当前IO可能被信号中断,在试一试吧" << std::endl;continue;}else{//进行差错处理}}}return 0;

三、I/O多路转接之select

select其实就是帮助用户一次等待多个文件sock,当那些文件sock就绪了,只要通知用户,对应的sock文件就绪了,然后用户在调用如read、recv等接口进行数据读取!

1.快速认识select接口

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
  • 参数nfds是需要监视的最大的文件描述符值+1;
  • rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描 述符的集合;
  • 参数timeout为结构timeval,用来设置select()的等待时间
  • 返回值为就绪的fd的个数
  • 至少有一个fd数据就绪or空间就绪,此时就可以进行返回

参数timeout取值:

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

2.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的全部位

3.接口挑一个重点参数,细致分析

假设我们今天是一个读文件描述符集

readfds:

在输入时:用户告诉内核我的比特位中,比特位的位置,表示文件描述符值,比特位的内容表示,是否关心,如0100,这个表示,我们关心3这个文件描述符,124,这三个文件描述符我们并不关心

在输出时:内核告诉用户,我是OS,用户你让我关心的多个fd有结果了,比特位的位置依旧表示文件描述符值。比特位的内容表示是否就绪,如0100,表示我们的3号文件描述符已经就绪了,如果是0000,则表示3号文件描述符还未就绪,就绪之后就代表,用户可以直接读取3号而不被阻塞

注意,用户和OS都会修改同一个位图结构,这个参数被用一次之后,就需要被重新设定!

4.编写代码

详情讲解看代码

#ifndef __SELECT_SVR_H__
#define __SELECT_SVR_H__#include <iostream>
#include <string>
#include <vector>
#include <sys/select.h>
#include <sys/time.h>
#include "Log.hpp"
#include "Sock.hpp"#define BITS 8
#define NUM (sizeof(fd_set)*BITS)
#define FD_NONE -1using namespace std;
// select 我们只完成读取,写入和异常不做处理 -- epoll(写完整)
class SelectServer
{
public:SelectServer(const uint16_t &port = 8080) : _port(port){_listensock = Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);logMessage(DEBUG,"%s","create base socket success");for(int i = 0; i < NUM; i++) _fd_array[i] = FD_NONE;// 规定 : _fd_array[0] = _listensock;_fd_array[0] = _listensock;}void Start(){while (true){// struct timeval timeout = {0, 0};// 如何看待listensock? 获取新连接,我们把它依旧看做成为IO,input事件,如果没有连接到来呢?阻塞// int sock = Sock::Accept(listensock, ...); //不能直接调用accept了// 将listensock添加到读文件描述符集中// FD_SET(_listensock, &rfds); // int n = select(_listensock + 1, &rfds, nullptr, nullptr, &timeout);// 1. nfds: 随着我们获取的sock越来越多,随着我们添加到select的sock越来越多,注定了nfds每一次都可能要变化,我们需要对它动态计算// 2. rfds/writefds/exceptfds:都是输入输出型参数,输入输出不一定以一样的,所以注定了我们每一次都要对rfds进行重新添加// 3. timeout: 都是输入输出型参数,每一次都要进行重置,前提是你要的话// 1,2 => 注定了我们必须自己将合法的文件描述符需要单独全部保存起来 用来支持:1. 更新最大fd 2.更新位图结构DebugPrint();fd_set rfds;FD_ZERO(&rfds);int maxfd = _listensock;for(int i = 0; i < NUM; i++){if(_fd_array[i] == FD_NONE) continue;FD_SET(_fd_array[i], &rfds);if(maxfd < _fd_array[i]) maxfd = _fd_array[i];}// rfds未来,一定会有两类sock,listensock,普通sock// 我们select中,就绪的fd会越来越多!int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);switch (n){case 0:// printf("hello select ...\n");logMessage(DEBUG, "%s", "time out...");break;case -1:logMessage(WARNING, "select error: %d : %s", errno, strerror(errno));break;default:// 成功的logMessage(DEBUG, "get a new link event..."); // 为什么会一直打印连接到来呢?连接已经建立完成,就绪了,但是你没有取走,select要一直通知你!HandlerEvent(rfds);break;}}}~SelectServer(){if (_listensock >= 0)close(_listensock);}
private:void HandlerEvent(const fd_set &rfds) // fd_set 是一个集合,里面可能会存在多个sock{for(int i = 0; i < NUM; i++){// 1. 去掉不合法的fdif(_fd_array[i] == FD_NONE) continue;// 2. 合法的就一定就绪了?不一定if(FD_ISSET(_fd_array[i], &rfds)){//指定的fd,读事件就绪// 读事件就绪:连接时间到来,acceptif(_fd_array[i] == _listensock) Accepter();else Recver(i);}}}void Accepter(){string clientip;uint16_t clientport = 0;// listensock上面的读事件就绪了,表示可以读取了// 获取新连接了int sock = Sock::Accept(_listensock, &clientip, &clientport); // 这里在进行accept会不会阻塞?不会!if(sock < 0){logMessage(WARNING, "accept error");return;}logMessage(DEBUG, "get a new line success : [%s:%d] : %d", clientip.c_str(), clientport, sock);// read / recv? 不能!为什么不能?我们不清楚该sock上面数据什么时候到来, recv、read就有可能先被阻塞,IO = 等+数据拷贝// 谁可能最清楚呢?select!// 得到新连接的时候,此时我们应该考虑的是,将新的sock托管给select,让select帮我们进行检测sock上是否有新的数据// 有了数据select,读事件就绪,select就会通知我,我们在进行读取,此时我们就不会被阻塞了// 要将sock添加 给 select, 其实我们只要将fd放入到数组中即可!int pos = 1;for(; pos < NUM; pos++){if(_fd_array[pos] == FD_NONE) break;}if(pos == NUM){logMessage(WARNING, "%s:%d", "select server already full,close: %d", sock);close(sock);}else{_fd_array[pos] = sock;}}void Recver(int pos){// 读事件就绪:INPUT事件到来、recv,readlogMessage(DEBUG, "message in, get IO event: %d", _fd_array[pos]);// 暂时先不做封装, 此时select已经帮我们进行了事件检测,fd上的数据一定是就绪的,即 本次 不会被阻塞// 这样读取有bug吗?有的,你怎么保证以读到了一个完整包文呢?char buffer[1024];int n = recv(_fd_array[pos], buffer, sizeof(buffer)-1, 0);if(n > 0){buffer[n] = 0;logMessage(DEBUG, "client[%d]# %s", _fd_array[pos], buffer);}else if(n == 0){logMessage(DEBUG, "client[%d] quit, me too...", _fd_array[pos]);// 1. 我们也要关闭不需要的fdclose(_fd_array[pos]);// 2. 不要让select帮我关心当前的fd了_fd_array[pos] = FD_NONE;}else{logMessage(WARNING, "%d sock recv error, %d : %s", _fd_array[pos], errno, strerror(errno));// 1. 我们也要关闭不需要的fdclose(_fd_array[pos]);// 2. 不要让select帮我关心当前的fd了_fd_array[pos] = FD_NONE;}}void DebugPrint(){cout << "_fd_array[]: ";for(int i = 0; i < NUM; i++){if(_fd_array[i] == FD_NONE) continue;cout << _fd_array[i] << " ";}cout << endl;}
private:uint16_t _port;int _listensock;int _fd_array[NUM];// int _fd_write[NUM];// std::vector<int> arr;
};#endif

5.select的优缺点

优点:任何一个多路转接都具有

  • 效率高,在单位时间内,等的比重大大减小
  • 省资源,应用场景:有大量的连接,但是只有少量的活跃

缺点:

  • 为了维护第三方数组,服务器会充满大量的遍历,时间复杂度时On的,在OS底层也要关心fd,也要进行遍历
  • 每一次都要对select输出参数重新设定
  • 能够同时管理的fd个数是有上限的
  • 因为几乎每一个参数都是输入输出型的,select一定会频繁的进行用户到内核,内核到用户的参数数据拷贝
  • 编码复杂

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

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

相关文章

no main manifest attribute,in xxx.jar(关于Spring项目,无法在云服务器上运行jar包的解决方法)

目录 问题详情 解决方法 问题详情 项目可以打包正常&#xff0c;但是云服务器上无法运行&#xff0c;报错&#xff1a;no main manifest attribute&#xff0c;in xxx.jar 解决方法 1.查看pom.xml配置文件&#xff0c;检查以下代码&#xff0c;没有则加上&#xff1a; <…

Linux网络基础3之数据链路层

(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨&#xff01;你好这里是ky233的主页&#xff1a;这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ ip协议通过子网划分&#xff0c;目的IP地址&#xf…

eFuse在汽车域控制器架构中如何提供更智能的保护?

汽车应用的电气化和自动化趋势推动了域控制器的兴起&#xff0c;用以减轻线缆重量并将车辆架构简化为多个局部化的电源中心。设计人员可以利用这种新兴架构&#xff0c;将传统保险丝和机械继电器替换为更紧凑的电子保险丝 (eFuse)&#xff0c;以提供更先进的保护功能&#xff0…

基于springboot+vue实现高校学生党员发展管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现高校学生党员发展管理系统演示 摘要 随着高校学生规模的不断扩大&#xff0c;高校内的党员统计及发展管理工作面临较大的压力&#xff0c;高校信息化建设的不断优化发展也进一步促进了系统平台的应用&#xff0c;借助系统平台可以实现更加高效便捷的党员信息…

javase day01笔记

第一天课堂笔记 Java第三代高级语言中的面向对象的语言 b/s 浏览器/服务器c/s 客户端/服务端 1991年詹姆斯高斯林在sun公司开发的Java 常用的dos命令 磁盘操作系统&#xff1a;dos win &#xff0b; r -》 cmd dos命令 切换盘符&#xff1a;直接输入对应盘符目录操作&#x…

2024护网面试题精选(二)完

0x02. 内网渗透篇 00- 内网渗透的流程 拿到跳板后&#xff0c;先探测一波内网存活主机&#xff0c;用net user /domian命令查看跳板机是否在域 内&#xff0c;探测存活主机、提权、提取hash、进行横向移动&#xff0c;定位dc位置&#xff0c;查看是否有能直接提权域 管的漏洞…

【微服务】SpringBoot整合Resilience4j使用详解

目录 一、前言 二、熔断器出现背景 2.1 几个核心概念 2.1.1 熔断 2.1.2 限流 2.1.3 降级 2.2 为什么会出现熔断器 2.3 断路器介绍 2.3.1 断路器原理 三、Resilience4j介绍 3.1 Resilience4j概述 3.1.1 Resilience4j是什么 3.1.2 Resilience4j功能特性 3.2 Resilie…

LeetCode59:螺旋矩阵Ⅱ

题目描述 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 代码 class Solution { public:vector…

Canal的入门操作记录

文章目录 1.主从数据库同步原理2.canal使用步骤2.1 开启binlog2.2 配置canalcanal.propertiesinstance.properties区别 3.创建Canal用户4.取信息5.SpringBoot整合 canal其实就是假装自己是从数据库&#xff0c;来监听主数据库的binlog得到数据的变化信息 canal 模拟 MySQL slav…

浪潮信息InManage升级发布 三大功能释放数据中心运维管理压力

近日&#xff0c;浪潮信息官网开放了数据中心管理平台InManage全新版本的开放体验渠道&#xff0c;升级后的InManage拥有更强大的功能体验&#xff0c;可以有效解决大模型等AIGC应用对于数据中心的运维管理压力&#xff0c;通过全新功能的加持&#xff0c;浪潮信息将让数据中心…

docker安装和使用kafka

1. 启动zookeeper Kafka依赖zookeeper, 首先安装zookeeper -p&#xff1a;设置映射端口&#xff08;默认2181&#xff09; docker run --name zookeeper \--network app-tier \-e ALLOW_ANONYMOUS_LOGINyes \--restartalways \-d bitnami/zookeeper:latest2. 启动kafka docker…

C#上位机与欧姆龙PLC的通信13----【又爆肝】上位机应用开发(云端版)

1、概念背景 随着物联网技术的快速发展&#xff0c;工业互联网应运而生。工业互联网云平台作为连接智能制造和智慧工厂的重要技术手段&#xff0c;为制造业提供了更高效、更安全、更便捷的生产模式。工业互联网是指将互联网和物联网技术应用于工业生产和制造过程中&#xff0c;…