Linux高级IO——多路转接之select

文章目录

    • 0. 前言
    • 1. 五种IO模型
    • 2. 非阻塞IO
    • 3. select
      • select_server
      • select缺点

0. 前言

在应用层用户调用read或者write方法读写的时候,本质上是是拷贝函数。

例如调用read的时候,如果底层接收缓冲区没有数据,那么就会阻塞式的等待;调用write时,如果发送缓冲区是满的,此时上层也无法将数据拷贝给下层,也会阻塞式等待。

所以IO = 等待 + 拷贝,要进行拷贝,就先要判断读写事件是否就绪。

判断IO是否高效,就是看单位时间内,等的比重占比越小,IO效率越高

1. 五种IO模型

大三

  1. 阻塞IO

    钓鱼佬A:拿着鱼竿钓鱼,在钓鱼的时候,什么也不干,就盯着鱼漂,鱼咬钩就拉杆,不咬钩就一直盯着

  2. 非阻塞IO(非阻塞轮询)

    钓鱼佬B:拿着鱼竿钓鱼,钓鱼的时候,玩一会手机,看一下鱼漂;吃一点东西,看一下鱼漂

  3. 信号驱动IO

    钓鱼佬C:在鱼竿顶部放了一个铃铛,然后开始钓鱼,期间一直干自己的事情,当听见铃铛响的时候,才拉鱼竿

  4. 多路复用IO

    钓鱼佬D:这是一个充值玩家,一次性放很多很多鱼竿,来回走路检测这些鱼竿是否钓到鱼

  5. 异步IO

    钓启强:启强不爱钓鱼,但是喜欢吃鱼,找到老墨,让老墨去钓鱼,跟老墨说:“你去帮我钓鱼吧,钓到鱼跟我打电话,我去公司办点事”

    启强不是执行钓鱼的人,而是钓鱼的发起者,真正执行的老墨

前四种IO都是同步IO

阻塞和非阻塞的区别:

IO = 等 + 拷贝,在本质上,它们等待的时间都一样,说非阻塞效率高,本质上其实是在等待期间,能进行别的操作

同步和异步的区别:

凡是参与了IO(参与等或者拷贝),都属于同步IO;

异步IO只是IO的发起者,并不参与IO的过程。

2. 非阻塞IO

将文件描述符设置为非阻塞,可以采用fcntl接口

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

一下采用F_GETFD,先获取文件描述符的标记,然后再添加非阻塞选项

#include<iostream>
#include<unistd.h>
#include<fcntl.h>
#include<cstdio>
#include<cstring>
#include<cerrno>using namespace std;void SetNOBlock(int fd)
{int fl = fcntl(fd, F_GETFD);	//获取文件描述符标记if(fl < 0)  //获取失败{perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);	//添加非阻塞选项cout << "set " << fd << " non block success" << endl;
}int main()
{char buffer[1024];SetNOBlock(0);sleep(1);while(true){// printf("Please Enter: ");// fflush(stdout);ssize_t n = read(0, buffer, sizeof(buffer)-1);if(n > 0){buffer[n-1] = 0;cout << "echo: " << buffer << endl;}else if(n == 0){cout << "read done" << endl;break;}else{if(errno == EWOULDBLOCK){cout << "0 fd data not ready, please try again" << endl;sleep(1);}else{cerr << "read error, fd = " << n << ", erron code: " << errno << " error str:" << strerror(errno) << endl;break;}}}return 0;
}

read的错误码为11(EWOULDBLOCK)时,表示当前文件描述符数据没有就绪

3. select

select只负责IO当中的等待,它可以等待多个文件描述

#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 nfds:等待多个文件描述符当中值最大的+1,即maxfd+1

  • fd_set *readfds:输入输出型参数,fd_set是内核提供的一种数据类型,是位图,readfds表示关心读事件

    比如说要关心0、1、2、3文件描述符的读事件,将位图设置为0000 1111这是输入的时候

    当其中某个文件描述符已经就绪。假设3号文件描述符就绪,则返回0000 1000

  • fd_set *writefds:关心写事件

  • fd_set *exceptfds:关心异常事件

  • struct timeval *timeout:输入输出型参数,时间结构体,设置等待时长

    struct timeval {time_t      tv_sec;     //时间戳,以秒为单位suseconds_t tv_usec;    //微秒
    };
    

    例如struct timeval timeout = [5,0],表示每隔5秒timeout一次,如果2秒之后有文件描述符就绪,返回[3,0]struct timeval timeout = [0,0],直接返回(非阻塞),也可也设置为null,表示阻塞等待

  • 返回值:
    > 0:有n个fd已经就绪
    == 0:等待超时,没有错误,没有文件描述符就绪
    < 0:等待出错

select_server

#pragma once
#include<iostream>
#include<string>
#include<sys/time.h>
#include"Socket.hpp"
#include"Log.hpp"static const int defaultport = 8089;
static const int fd_max = sizeof(fd_set) * 8;
const int defaultfd = -1;class SelectServer
{
public:SelectServer(uint16_t port = defaultport):_port(port){for(int i = 0; i < fd_max; i++)_rfd_array[i] = defaultfd;}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Accepter(){std::string clientip;uint16_t clientport;int sock = _listensock.Accept(&clientip, &clientport); // 此时并不会阻塞, 因为已经上层通知事件已经就绪if (sock < 0)return;log(Info, "accept success, %s:%d, sockfd:%d", clientip.c_str(), clientport, sock);// 添加到辅助数组int pos = 1;for (; pos < fd_max; pos++){if (_rfd_array[pos] != defaultfd)continue;elsebreak;}if (pos == fd_max) // 文件描述符满了(位图满了){log(Warning, "server is full, close %d", sock);close(sock);}else{_rfd_array[pos] = sock; // 套接字添加到辅助数组PrintFd();              // Debug}}void Recver(int fd, int pos){// 读事件就绪char buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "get a message: " << buffer << std::endl;}else if (n == 0){log(Info, "client quit, me too, close fd:%d", fd);close(fd);_rfd_array[pos] = defaultfd; // 从select中移除}else{log(Warning, " read error, close fd:%d", fd);close(fd);_rfd_array[pos] = defaultfd;}}void Dispatcher(const fd_set &rfds){for (int i = 0; i < fd_max; i++){int fd = _rfd_array[i];if(fd == defaultfd) continue;if (FD_ISSET(fd, &rfds)){if(fd == _listensock.Getfd())     //是监听套接字且已经就绪 获取新链接{Accepter();}else{Recver(fd, i);}//其他的事件...}}}void Start(){int listensock = _listensock.Getfd();_rfd_array[0] = listensock;for( ; ; ){fd_set rfds;    //读事件文件描述符FD_ZERO(&rfds); //位图清零int maxfd = _rfd_array[0];for(int i = 0; i < fd_max; i++) //每次都重新设定{if(_rfd_array[i] == defaultfd)  continue;FD_SET(_rfd_array[i], &rfds);   //该文件描述符设置进位图if(maxfd < _rfd_array[i]){maxfd = _rfd_array[i];log(Info, "maxfd update, maxfd: %d", maxfd);}}//不可直接accept, accept本质是检测并获取listensock上面的事件struct timeval timeout = {2, 0};    //输入输出型参数, 需要周期性重复设置//int s = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);int s = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);switch (s){case 0://等待超时std::cout << "time out: " << timeout.tv_sec << "." << timeout.tv_usec << std::endl;break;case -1://等待出错std::cerr << "select error" << std::endl;break;default://有事件就绪std::cout << "get a link" << std::endl; //如果上层一直不处理,底层则一直触发Dispatcher(rfds);break;}}}//Debugvoid PrintFd(){std::cout << "online fd list: ";for(int i = 0; i < fd_max; i++){if(_rfd_array[i] != defaultfd)std::cout << _rfd_array[i] << " ";}std::cout << std::endl;}~SelectServer(){}
private:MySocket _listensock;uint16_t _port;int _rfd_array[fd_max];
};
  • 服务器启动时,不能直接accpet,因为accept的本质是检测并获取listensock上面的事件
  • struct timeval *timeout为输入输出型参数,需要周期性重复设置
  • fd_set类型有大小,sizeof(fd_set) * 8即为最多设置位图个数
  • fd_set *readfds/writefds/exceptfds都是输入输出参数,设置辅助数组,保留关系的文件描述符,然后重新设置
  • 获取链接之后,不能直接读取,如果直接读取,如果数据没有就绪,那就直接阻塞了,所以要将获取链接的文件描述符添加到辅助数组当中

select缺点

  • fd_set最多就是sizeof(fd_set)*8,所以等待的文件描述符有上限的
  • 输入输出参数较多,会频繁的从用户到内核、从内核到用户,数据拷贝频率较高;而且每次都要对关系的fd进行重置,需要大量的遍历(用户层第三方数组管理需要遍历,内核中检测fd也需要遍历)

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

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

相关文章

Berkeley CS

Eta Kappa Nu (HKN), Mu Chapter61 A计算机科学 61A — 计算机程序的结构和解释&#xff08;4 学分&#xff09; Python4 61 B计算机科学 61B — 数据结构&#xff08;4 学分&#xff09; Java4 61 C计算机科学 61C — 机器结构&#xff08;4 学分&#xff09;4 CS 70计算机…

真--开源个人收款系统方案--部署方案

继上文:真--个人收款系统方案,今天主要推出部署方案 1.下载源码 首先需要下载源码,源码地址:PayServer: 个人收款系统方案 - Gitee.com 并且pip下载依赖库: Flask2.5.1 Flask-Cors3.0.10 gevent23.6.0 websockets10.9 urllib31.26.1 2.修改配置 路径下有两个py文件&#xf…

Docker简单介绍、特点、与虚拟机技术的区别、核心概念及在CentOS 7 中安装卸载Docker

目录 一、什么是Docker 二、特点 三、Docker与虚拟机技术的区别 四、Docker的核心概念 Docker仓库与仓库注册服务器的区别 五、CentOS7在线安装Docker 安装配置 卸载 一、什么是Docker Docker是一个开源的容器化平台&#xff0c;用于打包、部署和运行应用程序。它利用…

C语言——找单身狗1

题目描述&#xff1a; 在一个整形数组中&#xff0c;只有一个数字出现一次&#xff0c;其他数组都是成对出现的&#xff0c;找出那个只出现一次的数字。 例如&#xff1a; 数组中&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;4&#xff0c;3…

【airtest】自动化入门教程(四)Poco元素定位

目录 一、基础操作 1、通过属性名等方式 2、通过属性组合 3、子节点方式 4、子节点加属性组合方式 5、孙节点offspring 6、兄弟节点sibling 7、父节点parent 8、正则表达式 9、直到某个元素出现 10、直到某个元素消失 二、通过局部坐标定位 1、使用局部坐标系的cli…

电商系列之风控安全

> 插&#xff1a;AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

使用Python的SQLite和Tkinter库来创建一个简单的查询

要使用Python的SQLite和Tkinter库来创建一个简单的查询系统&#xff0c;你可以遵循以下步骤&#xff1a; 安装所需的库&#xff1a; 如果你还没有安装sqlite3和tkinter库&#xff0c;可以使用pip进行安装。但通常&#xff0c;sqlite3是Python的标准库&#xff0c;而tkinter在大…

【Spring进阶系列丨第七篇】Spring框架新注解分类及详解

文章目录 一、Spring新注解1.1、Configuration注解1.1.1、定义一个类1.1.2、使用Configuration注解修饰类1.1.3、作用 1.2、Bean注解1.2.1、定义bean1.2.2、在主配置类中注册bean1.2.3、测试容器中是否有该bean1.2.4、注册bean的同时可以指定bean名称1.2.5、补充内容1.2.5.1、案…

JAVA IO流学习

File类&#xff1a; File类是java.io包中很重要的一个类 File类的对象可以代表一个文件或者目录&#xff0c;可以修改文件大小、文件最后修改日期、文件名等 File对象不能操作文件的具体数据&#xff0c;即不能对文件进行读和写的操作 File的构造方法&#xff1a; File&…

什么时候外部依赖接口慢拖死应用?

A应用调用B应用&#xff0c;当B应用的接口响应耗时平均都在3000ms的时&#xff0c;如果当前A调用B的请求数达300/s 那么在3s内A应用在途的请求数 300 * 3 900 &#xff0c;按照servlet原理一个http的请求需要一个线程提供服务&#xff0c;即需要900个线程提供服务&#xff0c…

如何用Python读取Excel中的高亮标注,并统计不同高亮标注的数量

业务场景&#xff1a;当我们对Excel表格标记了不同颜色&#xff0c;我们怎么统计不同颜色的文本框的数量呢&#xff1f; 解决思路&#xff1a; 读取文本框的颜色种类颜色有哪些统计每个种类的个数 from openpyxl import load_workbookdef count_highlighted_colors_in_column…

政安晨【AIGC实践】(一):在Kaggle上部署使用Stable Diffusion

目录 简述 开始 配置 执行 安装完毕&#xff0c;一键运行 结果展示 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 人工智能数字虚拟世界实践 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

MySQL中的redo log 和 undo log

undo log和redo log 先引入两个概念&#xff1a; 当我们做了一些操作 (update/delete/insert)&#xff0c;提交事务后要操作MySql中的数据。 为了能够提升性能&#xff0c;引入了两块区域&#xff1a;内存结构和磁盘结构。 磁盘结构&#xff1a; 主要存储的就是数据页&#x…

【C语言基础】:文件操作详解(前篇:准备知识)

文章目录 一、什么是文件以及文件的分类1.1 程序文件1.2 数据文件1.3 文件名 二、文本文件和二进制文件2.1 数据在文件中的存储 三、文件的打开和关闭3.1 流和标准流3.1.1 流3.1.2 标准流 3.3 文件指针3.5 文件的打开和关闭 一、什么是文件以及文件的分类 文件是指存储在计算机…

编程新手必看,学习python中字符串数据类型内容(8)

1、 Python3 字符串 字符串是 Python 中最常用的数据类型。我们可以使用引号( ’ 或 " )来创建字符串。 创建字符串很简单&#xff0c;只要为变量分配一个值即可。例如&#xff1a; var1 Hello World! var2 "Runoob"Python 访问字符串中的值 Python 不支持单…

Linux(centos7)部署spark

Spark部署模式主要有4种&#xff1a;Local模式&#xff08;单机模式&#xff09;、Standalone模式&#xff08;使用Spark自带的简单集群管理器&#xff09;、Spark On Yarn模式&#xff08;使用YARN作为集群管理器&#xff09;和Spark On Mesos模式&#xff08;使用Mesos作为集…

自动化运维(九)Ansible 之Jinja2 模板

Jinja2 是一个 Python 的模板引擎库,常用于生成动态的文本内容,如配置文件、HTML 页面等。Ansible 使用 Jinja2 作为默认的模板引擎,用于在 playbook 中动态生成文件内容。 Jinja2 模板允许你在文本中使用变量、条件语句、循环等功能,以实现动态内容的生成。模板文件通常以 .j…

【python】模块测试方法三步走

第一步&#xff1a;写 main 函数 # 你的模块...if __name__ __main__:# 你的测试部分from .. import 上级模块名上级模块名.上级模块函数 ...# 测试部分第二步&#xff1a;加 __init__ 略 第三步&#xff1a;python -m 在项目目录下执行 python -m 命令 python -m 包.模块…

机器学习模型——关联规则

目录 关联规则 - 基本概念 关联规则的挖掘步骤: Apriori算法 Apriori算法简介&#xff1a; Apriori算法举例&#xff1a; Apriori算法优缺点&#xff1a; Apriori算法应用 FP-growth算法&#xff1a; FP-growth算法简介&#xff1a; FP-growth的数据结构&#xff1a; …

AI帮助写代码:在python中,如何替换字符串中指定位置的字符

用python编写一段代码&#xff0c;在下面的字符串中:#1#2#3#4#5#6#7#8#9#10#11#12#13#14#15#16#17#18#19#20#21#22#23#24#25#26#27#28#29&#xff0c;从第2个‘#开始&#xff0c;每个#前面加一个字符串 or ‘。 # 原始字符串 s "#1#2#3#4#5#6#7#8#9#10#11#12#13#14#15#…