Linux网络——IO模型和多路转接

通常所谓的IO,其本质就是等待通信和进行通信,即IO = 等 + 拷贝。

那么想要做到高效的IO,就要在单位时间内,减少“等”的比重。


一.五种IO模型

  1. 阻塞 IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式。阻塞 IO 是最常见的 IO 模型。
  2. 非阻塞 IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK 错误码. 非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对 CPU 来说是较大的浪费, 一般只有特定场景下才使用。
  3. 信号驱动 IO: 内核将数据准备好的时候, 使用 SIGIO 信号通知应用程序进行 IO 操作.
  4. IO 多路转接: 和阻塞 IO 类似. 核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态。
  5. 异步 IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

此处展开分享一下非阻塞IO。 


二.非阻塞IO

系统和网络中的文件描述符,其默认情况下都是阻塞IO,下面来看怎么将其设置为非阻塞IO。


1.fcntl函数

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

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).

此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞。


2.代码实现非阻塞

void SetNoBlock(int fd)
{int fl = fcntl(fd, F_GETFL);if (fl < 0){perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

使用 F_GETFL 将当前的文件描述符的属性取出来(这是一个位图)。

然后再使用 F_SETFL 将文件描述符设置回去. 设置回去的同时, 加上一个 O_NONBLOCK 参数,表示为非阻塞。

前边提到,在非阻塞IO下,如果数据没有就绪,那么IO就会以出错的形式返回,那么如何区分到底是数据没有就绪,还是真的出错了呢???

通过判断errno错误码,如果错误码为EWOULDBLOCK,表示数据没有就绪,此时可以设计程序去做其他事,并通过轮询方式去检测数据是否就绪,反之则为真的出错,程序退出。

此外,如果进程长期阻塞,可能会收到系统的信号,中断程序运行,此时返回的错误码为EINTR,所以如果不想程序被系统中断,就可以通过此错误码在做判断。


三.多路转接

多路转接,即等待多个fd上的新事件就绪,然后通知程序员,事件已经就绪,可以进行IO拷贝了。


1.select

(1)概述

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

  • select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在 select 这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

IO = 等 + 拷贝,select负责的就是等待,并且是等待多个新事件的到了。


(2)接口

#include <sys/select.h>

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_set结构:

这个结构就是一个整数数组, 更严格的说,是一个 "位图",使用位图中对应的位来表示要监视的文件描述符。

  • 输入时,比特位的位置表示文件描述符的编号,比特位的内容表示是否关心该fd事件。
  • 输出时,比特位的位置表示文件描述符的编号,比特位的内容表示对应的fd事件是否发生。

下面是OS提供了一组操作 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 的全部位

timeval结构:

struct timeval

{

        __time_t tv_sec;//秒

        __suseconds_t tv_usec;//微秒
}

timeval 结构用于描述一段时间长度,比如(5,0)则表示在0-5秒内;如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为 0。

参数 timeout 取值:

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

函数返回值:

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

(3)缺点

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

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

同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。

select 支持的文件描述符数量太小。


2.poll

(1)概述

poll的作用与select完全相同,也是等待多个fd,等待fd上的新事件就绪,随后派发事件,可以理解为是select的优化版本。


(2)接口

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:

  •  fds是一个 poll 函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合。

  • nfds 表示 fds 数组的长度。

  • timeout:以毫秒为单位,设定的超时时间,设为0表示非阻塞,-1表示阻塞。

pollfd结构体:

struct pollfd {

int

fd; /* file descriptor */

short events; /* requested events */

short revents; /* returned events */

};

同样是位图结构,short16位,每一位可代表一个事件,events和revents的取值: 

 这些事件都是宏,且分别表示为不同的二进制位,因此可以自由组合搭配,形成事件集合。

返回值:

  • 大于0,表示有几个fd就绪。
  • 等于0,超时。
  • 小于0,poll出错。

(3)优点

pollfd 结构包含了要监视的 event 和发生的 event,不再使用 select“参数-值”传递的方式. 接口使用比 select 更方便。

poll 并没有最大数量限制 (但是数量过大后性能也是会下降)。


3.epoll

(1)概述

epoll是除了select和poll之外,公认为 Linux 下性能最好的多路 I/O 就绪通知方法。


(2)接口

使用epoll接口需要包含头文件 #include<sys/epoll.h>。 

int epoll_create(int size);

创建一个 epoll 的句柄.

  • size 参数可以被忽略。
  • 用完之后, 必须调用 close()关闭。

返回值epfd供接下来的函数使用。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll 的事件注册函数. 它不同于 select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型。

epoll_ctl在底层会将用户让内核关心的fd及其事件添加进由内核构成的红黑树中进行维护。

  • 第一个参数是 epoll_create()的返回值(epoll 的句柄).
  • 第二个参数表示动作,用三个宏来表示.
  • 第三个参数是需要监听的 fd.
  • 第四个参数是告诉内核需要监听什么事.

第二个参数的取值:

  • EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
  • EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件;
  • EPOLL_CTL_DEL:从 epfd 中删除一个 fd;

struct epoll_event 结构如下:

typedef union epoll_data {
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
} epoll_data_t;

        struct epoll_event {
        uint32_t events;/* Epoll events */
        epoll_data_t data;/* User data variable */
};

需要关注一下epoll_data_t结构体中的fd成员,其要存放事件的fd,当后续事件就绪时,需要通过该fd来获取事件。

其中events同样为位图结构,可以是以下几个宏的集合:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端 SOCKET 正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将 EPOLL 设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个 socket 的话, 需要再次把这个 socket 加入到 EPOLL 队列里.

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

收集在 epoll 监控的事件中已经发送的事件.

epoll_wait会检测内核中构成的就绪队列中是否有事件已经就绪, 并将已经就绪的事件按照严格顺序放入我们定义的用户缓冲区数组中。

  • 参数 events 是分配好的 epoll_event 结构体数组. epoll 将会把发生的事件赋值到 events 数组中 (events 不可以是空指针,内核只负责把数据复制到这个 events 数组中,不会去帮助我们在用户态中分配内存).
  • maxevents 告诉内核这个 events 有多大,这个 maxevents 的值不能大于创建 epoll_create()时的 size.
  • 参数 timeout 是超时时间 (毫秒,0 会立即返回,-1 是永久阻塞).
  • 如果函数调用成功,返回对应 I/O 上已准备好的文件描述符数目,如返回 0 表示已超时, 返回小于 0 表示函数失败.

对应的事件节点,会同时包含红黑树和就绪队列两个指针,从而使得该节点既可以存在于红黑树中,也可以存在于就绪队列中,从而无需新建新节点来进行转移。 


(3)LT工作模式

LT即水平触发 Level Triggered 工作模式。

epoll 默认状态下就是 LT 工作模式.

当 epoll 检测到 socket 上事件就绪的时候, 可以不立刻进行处理,或者只处理一部分,当缓冲区还有事件未处理时,epoll_wait 会不断地立刻返回并通知 socket 读事件就绪,直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回。

支持阻塞读写和非阻塞读写。


(4)ET工作模式

ET即边缘触发 Edge Triggered 工作模式。

在第 1 步将 socket 添加到 epoll 描述符的时候使用 EPOLLET 标志, epoll 将进入 ET 工作模式。

当 epoll 检测到 socket 上事件就绪时, 必须立刻处理。如果未处理或未一次性处理完,在第二次调用epoll_wait 的时候, epoll_wait 不会再返回了。

也就是说, ET 模式下, 文件描述符上的事件就绪后, 只有一次处理机会。

ET 的性能比 LT 性能更高( epoll_wait 返回的次数少了很多). Nginx 默认采用 ET 模式使用 epoll。

只支持非阻塞的读写。


LT 是 epoll 的默认行为.

使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完.

相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的.

另一方面, ET 的代码复杂程度更高。


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

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

相关文章

VM Virutal Box的Ubuntu虚拟机与windows宿主机之间设置共享文件夹(自动挂载,永久有效)

本文参考如下链接 How to access a shared folder in VirtualBox? - Ask Ubuntu &#xff08;1&#xff09;安装增强功能&#xff08;Guest Additions&#xff09; 首先&#xff0c;在网上下载VBoxGuestAdditions光盘映像文件 下载地址&#xff1a;Index of http://…

AI的魔力:如何为开源软件注入智慧,开启无限可能

“AI的魔力&#xff1a;如何为开源软件注入智慧&#xff0c;开启无限可能” 引言&#xff1a; 在科技发展的浪潮中&#xff0c;开源软件生态一直扮演着推动创新与共享的重要角色。从Linux到Python&#xff0c;开源项目赋予了开发者全球协作的机会&#xff0c;推动了技术的飞速…

uniapp条件注释/跨端兼容

文章目录 条件编译处理多端差异1.组件中条件编译2.API条件编译 条件编译处理多端差异 #为什么选择条件编译处理跨端兼容 uni-app 已将常用的组件、API封装到框架中&#xff0c;开发者按照 uni-app 规范开发即可保证多平台兼容&#xff0c;大部分业务均可直接满足。 但每个平…

【拥抱AI】一文讲述如何配置Milvus?

配置Milvus是一个重要的步骤&#xff0c;它可以帮助你更好地管理和优化向量数据库的性能。以下是一些常见的配置选项和步骤&#xff0c;帮助你设置和优化Milvus。 1. 安装Milvus 首先&#xff0c;确保你已经安装了Milvus。你可以使用Docker来快速部署Milvus。以下是一个基本的…

【网络安全】CSRF

一、什么是CSRF CSRF&#xff08;Cross-Site Request Forgery&#xff09;是一种web应用程序安全漏洞&#xff0c;它利用了用户在已登录的状态下的信任&#xff0c;通过欺骗用户发送未经授权的请求来执行恶意操作。这种攻击的危害性取决于受害者在目标网站上的权限。 二、CSR…

OpenCV相机标定与3D重建(5)鱼眼镜头畸变校正的函数estimateNewCameraMatrixForUndistortRectify()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 估计用于去畸变或校正的新相机内参矩阵。 cv::fisheye::estimateNewCameraMatrixForUndistortRectify 是 OpenCV 中用于鱼眼镜头畸变校正的一个函…

IThenticate 查重有无免费午餐?深度解析

经历过论文“折磨”的过来人&#xff0c;深知查重工具是写论文不可或缺的助手。而 iThenticate 查重系统&#xff0c;深受出版商、学术机构和研究人员喜爱。不过&#xff0c;每次看到它那昂贵的价格&#xff0c;就让很多小伙伴直呼&#xff0c;IThenticate查重系统就没有免费的…

启动SpringBoot

前言&#xff1a;大家好我是小帅&#xff0c;今天我们来学习SpringBoot 文章目录 1. 环境准备2. Maven2.1 什么是Maven2.2 创建⼀个Maven项⽬2.3 依赖管理2.3.1 依赖配置2.3.2 依赖传递2.3.4 依赖排除2.3.5 Maven Help插件&#xff08;plugin&#xff09; 2.4 Maven 仓库2.6 中…

DHCP服务(包含配置过程)

目录 一、 DHCP的定义 二、 使用DHCP的好处 三、 DHCP的分配方式 四、 DHCP的租约过程 1. 客户机请求IP 2. 服务器响应 3. 客户机选择IP 4. 服务器确定租约 5. 重新登录 6. 更新租约 五、 DHCP服务配置过程 一、 DHCP的定义 DHCP&#xff08;Dynamic Host Configur…

使用 Certbot 为 Nginx 自动配置 SSL 证书

1.安装Certbot和Nginx插件 sudo apt-get update sudo apt-get install certbot python3-certbot-nginx 2.获取和安装证书 运行Certbot自动安装SSL证书。注意替换 your_domain sudo certbot --nginx -d your_domain Certbot将自动与Lets Encrypt的服务器通信&#xff0c;验证域…

cgroup简介

1、什么是Cgroup cgroups,其名称源自控制组群(control groups)的缩写,是内核的一个特性,用于限制、记录和隔离一组进程的资源使用(CPU、内存、磁盘 I/O、网络等) 资源限制:可以配置 cgroup,从而限制进程可以对特定资源(例如内存或 CPU)的使用量 优先级 :当资源发…

[Go] slice切片详解

切片详解 切片的实现 Go 中的切片本质上是一个结构体&#xff0c;包含以下三个部分&#xff1a; 指向底层数组的指针&#xff08;array&#xff09;&#xff1a;切片指向一个底层数组&#xff0c;数组中存储着切片的数据。切片的长度&#xff08;len&#xff09;&#xff1a…

ros2键盘实现车辆: 简单的油门_刹车_挡位_前后左右移动控制

参考: ROS python 实现键盘控制 底盘移动 https://blog.csdn.net/u011326325/article/details/131609340游戏手柄控制 1.背景与需求 1.之前实现过 键盘控制 底盘移动的程序, 底盘是线速度控制, 效果还不错. 2.新的底盘 只支持油门控制, 使用线速度控制问题比较多, 和底盘适配…

DICOM医学影像应用篇——窗宽窗位概念、原理及实现详解

目录 窗宽窗位调整&#xff08;Windowing&#xff09;在DICOM医学影像中的应用 窗宽窗位的基本概念 窗宽&#xff08;Window Width, WW&#xff09; 窗位&#xff08;Window Level, WL&#xff09; 窗宽窗位调整的基本原理 映射逻辑 数学公式 窗宽窗位调整的C实现 代码…

天锐绿盾加密软件与Ping32联合打造企业级安全保护系统,确保敏感数据防泄密与加密管理

随着信息技术的飞速发展&#xff0c;企业在日常经营过程中产生和处理的大量敏感数据&#xff0c;面临着越来越复杂的安全威胁。尤其是在金融、医疗、法律等领域&#xff0c;数据泄漏不仅会造成企业巨大的经济损失&#xff0c;还可能破坏企业的信誉和客户信任。因此&#xff0c;…

HarmonyOS:@Provide装饰器和@Consume装饰器:与后代组件双向同步

一、前言 Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Provi…

【Spring MVC】如何运用应用分层思想实现简单图书管理系统前后端交互工作

前言 &#x1f31f;&#x1f31f;本期讲解关于SpringMVC的编程思想之应用分层~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那…

【Linux】项目自动化构建工具-make/Makefile

【Linux】项目自动化构建工具-make/Makefile make 和 makefile 的概念如何清理项目推导过程Linux第⼀个小程序−倒计时 &#x1f30f;个人博客主页&#xff1a;个人主页 make 和 makefile 的概念 make是一个命令工具&#xff0c;是一个解释makefile中指令的命令工具&#xf…

爬虫技术全解析:从入门到精通

引言 在互联网时代&#xff0c;数据已成为最宝贵的资源之一。爬虫技术作为一种自动化获取网页数据的工具&#xff0c;被广泛应用于数据采集、信息聚合、市场分析等多个领域。本文将带你从零开始&#xff0c;全面了解爬虫技术&#xff0c;包括其基本原理、常用工具、编程实践以…

量化交易系统开发-实时行情自动化交易-8.1.TradingView平台

19年创业做过一年的量化交易但没有成功&#xff0c;作为交易系统的开发人员积累了一些经验&#xff0c;最近想重新研究交易系统&#xff0c;一边整理一边写出来一些思考供大家参考&#xff0c;也希望跟做量化的朋友有更多的交流和合作。 接下来会对于TradingView平台介绍。 T…