深入解析select、poll、epoll:Linux网络编程的三种IO模型

news2024/11/22 15:42:57

文章目录

  • IO模型的分类
  • 多路复用型IO的分类
    • select
      • select系统调用
      • 缺点
    • poll
      • poll系统调用
      • 缺点
    • epoll
      • epoll系统调用
      • epoll模型
      • 优点
      • LT模式与ET模式

IO模型的分类

大家都知道,一个完整的IO操作所花费的时间在计算机中是非常多的(速度非常慢),那么这些时间都花费在哪里呢?

IO = 等待数据就绪 + 数据拷贝

而等待数据就绪所花费的时间占了整个IO时间的99%,数据拷贝所花费的时间仅占1%。

IO模型分为同步IO和异步IO。

  • 同步IO:用户发起IO,用户阻塞或轮训的查看数据是否就绪,用户进行内核态到用户态的数据拷贝。
  • 异步IO:用户发起IO,数据是否就绪以及数据拷贝的过程全交给内核,当IO完成时内核通知用户。

同步IO分为阻塞IO、非阻塞IO、信号驱动型IO、多路复用型IO。

  • 阻塞IO:用户发起IO后,若数据还未就绪,该线程就一直阻塞等待着数据的就绪。
  • 非阻塞IO:用户发起IO后,若数据还未就绪,该线程不会阻塞,而是继续向后执行代码,所以需要配合循环来一直检查数据是否就绪,而这也叫做轮询。
  • 信号驱动型IO:用户发起IO后,若数据还未就绪,该线程不会阻塞,而是继续向后执行代码,由内核来检查数据是否就绪,当数据就绪时,内核通知用户。
  • 多路复用型IO:由一个线程来监视(等待数据就绪)多个fd,当某个fd上的数据就绪时,就通知用户。

多路复用型IO和前三者的区别就是,多路复用型IO能同时监视多个fd,从而大大提高了效率。

本篇讲述多路复用型IO。

多路复用型IO的分类

select

select系统调用

函数原型:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

参数解析:

  • nfds:需要监视的文件描述符的最大值+1(告诉操作系统要查找的文件描述符的范围);

  • readfds、writefds、exceptfds:分别都是一个位图,表示要监视的某些fd的读事件、写事件、异常事件。它们都是输入输出型参数,输入时,将需要监视的文件描述符和事件添加到位图中,输出时,就是已经就绪的文件描述符和事件。

  • timeout:

    • 取值为NULL时,表示select阻塞式调用;
    • 取值为0时,表示select非阻塞式调用;
    • 取值>0时,表示在timeout时间内阻塞式调用。
  • 返回值:

    • >0时,表示已就绪的文件描述符的数量;
    • ==0时,表示timeout时间到期返回了;
    • <0时,出错返回。
  • fd_set:

    typedef long int __fd_mask;
    
    typedef struct
    {
        __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
    } fd_set;
    
  • struct timeval:

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

还有一批系统调用用来控制fd_set类型的参数。

void FD_CLR(int fd, fd_set *set);// 将fd从set中移除
int  FD_ISSET(int fd, fd_set *set);// 判断fd是否在set中
void FD_SET(int fd, fd_set *set);// 将fd设置进set中
void FD_ZERO(fd_set *set);// 将set设置为0

缺点

  • 由于中间三个参数是输入输出型参数,所以函数每次返回时,原来设置的内容就都已经被覆盖掉了,所以需要重新设置。这就导致编码难度上升;
  • 每次调用select都会把fd_set从内核态拷贝到用户态,效率较低;
  • 由于采用位图结构,所以底层选用轮询的方法来检测文件描述符是否就绪,效率较低;
  • fd_set是一个固定的类型,那么单个线程能够监视的文件描述符的数量就是固定的(centos7.6上是1024个)。

poll

poll系统调用

函数原型:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数解析:

  • fds:是一个指针(也可以说是一个数组),它里面存放着需要监视的fd以及事件(是一个输入输出型参数);

  • nfds:表示监视的fd的数量,也就是fds的长度;

  • timeout与select一样;

  • 返回值与select一样;

  • struct pollfd:

    struct pollfd {
       int   fd;         /* file descriptor */
       short events;     /* requested events */
       short revents;    /* returned events */
    };
    
    • fd:需要监视的文件描述符;
    • events:该fd上需要监视的事件(输入型参数);
    • revents:该fd上已经就绪的事件(输出型参数)。

    这两个事件也分别都是位图结构,通过宏来设置对应事件:

    事件描述是否可作为输入是否可作为输出
    POLLIN数据(包括普通数据和优先数据)可读
    POLLRDNORM普通数据可读
    POLLRDBAND优先级带数据可读(Linux不支持)
    POLLPRI高优先级数据可读,比如TCP带外数据
    POLLOUT数据(包括普通数据和优先数据)可写
    POLLWRNORM普通数据可写
    POLLWRBAND优先级带数据可写
    POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作,它由GNU引入
    POLLERR错误
    POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
    POLLNVAL文件描述符没有打开

缺点

  • 每次调用都会将fds从内核态拷贝到用户态(因为填写了revents);
  • 底层也是采用轮询的方式来检测文件描述符是否就绪,效率较低。

epoll

epoll系统调用

epoll有最基础的三个系统调用。

函数原型:

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epoll_create用来创建一个epoll模型,返回创建好的epoll模型的句柄(是一个文件描述符)。参数size在旧版本中悲剧,在新版本(2.6.8及之后的版本)被用作一个提示,提示内核为epoll实例预留size个文件描述符,建议设置成大于0的数字。

  • epoll_ctl用来控制(增删改)epoll模型中文件描述符及其事件

    • epfd:epoll模型的句柄;

    • op:控制类型

      op效果
      EPOLL_CTL_ADD将文件描述符及其事件添加到epoll模型中
      EPOLL_CTL_MOD改变文件描述符对应的事件
      EPOLL_CTL_DEL将文件描述符及其事件从epoll模型中移除
    • 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 */
      };
      
    • 返回值:调用成功返回0,调用失败返回-1,同时errno被设置;

  • epoll_wait:等待IO事件就绪

    • epfd:同上;
    • events:一个指针(一个数组),里面都是已经就绪的fd和事件;
    • maxevents:指定本次返回的events的最大数量,通常设置为epoll_create的参数。
    • timeout:同上;
    • 返回值:跟select、poll一样。

epoll模型

当调用epoll_create时,内核会创建一个eventpoll结构体。

struct eventpoll {
    /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
    struct rb_root  rbr;
    /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
    struct list_head rdlist;
};

也就是创建一颗红黑树,而后每次通过epoll_ctl添加进来的fd和事件都会组织成一个结构体挂载到这颗红黑树上。

struct epitem{
    struct rb_node  rbn;//红黑树节点
    struct list_head    rdllink;//双向链表节点
    struct epoll_filefd  ffd;  //事件句柄信息
    struct eventpoll *ep;    //指向其所属的eventpoll对象
    struct epoll_event event; //期待发生的事件类型
}

epoll_ctl中的ADD就是往红黑树中添加结点、DEL就是删除红黑树中的结点、MOD就是修改红黑树中结点内部的event。

每个添加进来的事件都会与设备驱动建立回调关系,当事件触发时就会调用回调函数将其添加到就绪队列中

image-20230918114748088

每个结点是可能同时在多个数据结构中的!

红黑树中就绪的结点同时会被组织成一个就绪队列,epoll_wait返回的就是该队列。

优点

  • 跟poll一样,解决了select中fd有上限的问题;
  • 解决了select、poll中的轮询遍历的问题;
  • 减少了用户态向内核态的拷贝(每次调用select、poll都会拷贝,而epoll仅在合适的时候用epoll_ctl添加结点)

LT模式与ET模式

  • LT(Level Triggered)水平触发模式:只要当fd上有数据时,每次调用epoll_wait都会返回该fd;
  • ET(Edge Triggered)边缘触发模式:只有当fd上的数据发生变化时(从零到有,从少到多),调用epoll_wait才会返回该fd。

select、poll、epoll都是默认处于LT模式,而epoll可以选择ET模式。

假设有一种场景:第一次从fd上读数据,没有读全,需要读第二次,如果处于LT模式下,第二次调用epoll_wait是能够继续从该fd上读数据的,而如果处于ET模式下,第二次调用epoll_wait就不能够从该fd上继续读数据了。这时就会出现问题,所以需要程序员能够一次性读全数据,无论处于LT还是ET,程序员都能够选择一次性读全数据,但是处于ET模式下,程序员就必须要一次性读全数据。

一次性读全数据的优势:能够更快速的将数据从内核拷贝到用户,从而能够拥有更大的TCP窗口,提高底层的数据发送效率,提高吞吐量。

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

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

相关文章

Python定义一个接收3个列表的函数

class CustomFunction:def __getitem__(self, slices):slice1, slice2, slice3 slices# 在这里执行你的操作print("第一个切片&#xff1a;", slice1)print("第二个切片&#xff1a;", slice2)print("第三个切片&#xff1a;", slice3)# 创建一…

黄金代理是什么?和黄金平台有什么不同?

有现货黄金投资需求的朋友都知道&#xff0c;最近我们在网上能看到越来越多的黄金代理进行活动。这个现货黄金代理跟现货黄金投资&#xff0c;又有什么关系呢&#xff1f;投资者如何处理黄金代理与黄金投资以及黄金平台之间的关系呢&#xff1f; 黄金代理在现货黄金市场中类似证…

分享一个基于微信小程序的高校大学生心理咨询与测试小程序(源码lw调试)java+Python双版本

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

使用Cpolar 内网穿透工具,实现公网访问SeaFile搭建的私有云盘

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 现在我们身边的只能设备越来越多&#xff0c…

情侣飞行棋 情侣小游戏 2023 抖音

飞行棋网站地址:https://effect.guoyaxue.top/fxq/index.html#/ 以及各种新版来袭&#xff1a; 以及各种情侣小游戏合集 https://fxnew.guoyaxue.top/#/

一文了解语音合成技术(TTS)

TTS是Text To Speech的缩写&#xff0c;即“从文本到语音”。 它将计算机自己产生的、或外部输入的文字信息转变为可以听得懂的、流利的汉语口语&#xff08;或者其他语言语音&#xff09;输出的技术&#xff0c;隶属于语音合成&#xff08;SpeechSynthesis&#xff09;。 语音…

【前端实习生备战秋招】—计算机网络面试题汇总,建议收藏系列

【前端实习生备战秋招】—计算机网络面试题汇总&#xff0c;建议收藏系列 一、HTTP协议 1. GET和POST的请求的区别 Post 和 Get 是 HTTP 请求的两种方法&#xff0c;其区别如下&#xff1a; 应用场景&#xff1a;GET 请求是一个幂等的请求&#xff0c;一般 Get 请求用于对服…

Linux软件包管理:yum和apt比较

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

elasticsearch16-聚合API

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

多媒体隧道

多媒体隧道使压缩的视频数据能够通过硬件视频解码器直接传送到显示器&#xff0c;而无需通过应用程序代码或 Android 框架代码进行处理。 Android 堆栈下方的设备特定代码通过将视频帧呈现时间戳与以下类型的内部时钟之一进行比较来确定将哪些视频帧发送到显示器以及何时发送它…

郑州大学图书馆许少辉《乡村振兴战略下传统村落文化旅游设计》中文文献——2023学生开学季辉少许

郑州大学图书馆许少辉《乡村振兴战略下传统村落文化旅游设计》中文文献——2023学生开学季辉少许

MySQL 几种导数据的方法与遇到的问题

零、说在前面 MySQL导数据通常使用第三方工具和MySQL自身的工具&#xff0c;本文分别就这两类方法分别介绍。 一、第三方工具之 Navicat 1.1、Navicat的“数据传输”工具 打开Navicat&#xff0c;点击“工具”标签&#xff0c;找到“数据传输”&#xff0c;即可看到操作界面。…

SQL Server 日期范围按每月一行拆分

要将 SQL Server 中的日期范围按每月一行拆分&#xff0c;可以使用一个表值函数&#xff08;Table-Valued Function&#xff09;来生成日期范围内的月份&#xff0c;并将其与其他数据连接&#xff0c;以创建包含每月一行的结果集。 以下是一个示例&#xff0c;说明如何实现这一…

怎么用外网访问自己的网站?快解析内网端口映射来实现

想要访问服务器上的网站需要直接或间接访问服务器IP地址&#xff0c;但是如果服务器没有公网IP地址&#xff0c;那么就需要借助外网进行访问。当我们需要远程访问内网的Web服务器时&#xff0c;我们需要使用一些技术来实现此目的。这就需要通过使用类似快解析内网端口映射方式进…

使用HTTP爬虫ip中的常见误区与解决方法

在使用HTTP爬虫进行网页抓取时&#xff0c;涉及到IP地址的处理&#xff0c;可能会存在一些常见的误区。以下是一些常见误区及解决方法&#xff1a; 1.使用个人IP进行大规模爬取&#xff1a;如果你使用个人住宅IP进行大规模爬取&#xff0c;可能会被目标网站视为恶意攻击&#x…

解决Pycharm使用Conda激活环境失败的问题

Q:公司电脑终端使用powershell来激活conda环境时报错? 同时手动打开powershell报"profile.ps1” 无法被加载的错误 A: 1,手动打开powershell&#xff0c;设置管理员打开 2,打开powershell 打开 PowerShell 终端&#xff0c;并输入以下命令&#xff1a;Get-ExecutionPo…

Spring AOP以及统一处理

一.Spring AOP 1.什么是Spring AOP AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff1a;面向切面编程&#xff0c;它是一种思想&#xff0c;它是对某一类事情的集中处理。 2.AOP的作用 想象一个场景&#xff0c;我们在做后台系统时&#xff0c;除了登录…

Java基于SpringBoot的漫画网站,附源码,教程

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 今天为大家带来的是 基于SpringBootVue的漫画之家系统&#xff0c;亲测可用&#xff0c;可以作为课程设计&…

搜索关键词标红组件

搜索关键词标红功能 今年做的一个需求&#xff0c;先看效果图。 先讲一下实现方式&#xff0c;前端输入关键词查询&#xff0c;后端返回html模板&#xff0c;前端通过v-html渲染 查到的数据是分页式&#xff0c;v-html的样式需要使用/deep/声明 下面是组件代码 <template…

现货黄金与黄金一样吗?

在众多的黄金投资方式中&#xff0c;现货黄金可以说是集各家所长于一身的一种&#xff0c;它既承载了实物黄金的特性&#xff0c;能够发挥黄金抗通胀、避风险的重要作用&#xff0c;也拥有纸黄金无需交收的优点&#xff0c;更具有黄金期货可以双向交易、带有资金杠杆的高收益特…