计算机网络之多路转接epoll

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

计算机网络之多路转接epoll

收录于专栏【计算机网络】
本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨论💌 
  

目录

epoll 初识 

epoll 相关系统调用

epoll_create 

epoll_ctl

epoll_wait 

epoll 工作原理 

epoll 的优先(和 select 的缺点对应) 

epoll 工作方式 

水平触发 Level Triggered 工作模式

边缘触发 Edge Triggered 工作模式 

对比 LT 和 ET 

理解 ET 模式和非阻塞文件描述符

epoll 使用场景 

epoll 的惊群效应 

epoll 惊群效应产生的原因

多线程环境下解决惊群问题方法

多进程下的解决方法 


epoll 初识 

按照 man 手册的说法:是为了处理大批量句柄而作了改进的 poll。 

它是在 2.5.44 内核中被引进的 (epoll(4) is a new API introduced in Linux kernel 2.5.44)

它几乎具备了之前所说的一切优点,被公认为 Linux2.6 下性能最好的多路 I/O 就绪通知方法。

epoll 相关系统调用

epoll 有 3 个相关的系统调用。

epoll_create 

int epoll_create(int size);

创建一个 epoll 句柄。 

自从 Linux2.6.8 之后,size 参数是被忽略的。

用完之后,必须调用 close() 关闭。

epoll_ctl

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

 epoll 的事件注册函数。

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

第一个参数是 epoll_create() 的返回值(epoll 句柄)。

第二个参数表示动作,用三个宏来表示。

第三个参数是需要监听的 fd。

第四个参数是告诉内核需要监听什么事。 

第二个参数的取值:

1. EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;

2. EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件;

3. EPOLL_CTL_DEL:从 epfd 中删除一个 fd; 

struct epill_event 结构如下:

events 可以是以下几个宏的集合:

1. EPOLLIN:表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭)

2. EPOLLOUT:表示对应文件描述符可以写;

3. EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

4. EPOLLERR:表示对应的文件描述符发生错误;

5. EPOLLHUP:表示对应的文件描述符被挂断

6. EPOLLET:将 EPOLL 设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

7. EPOLLONESHOT:只监听一次事件,当监听完这个事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入 EPOLL 队列里

epoll_wait 

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

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

参数 events 是分配好的 epoll_event 结构体数组

epoll 将会把发生的事件赋值到 events 数组中(evens 不可以是空指针,内核只负责把数据赋值到这个 events 数组中,不会去帮助我们在用户态中分配内存)。

maxevents 告之内核这个 events 有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的 size。

参数 timeout 是超时时间(毫秒,0会立即返回,-1是永久阻塞)。

如果函数调用成功,返回对应 I/O 上已经准备好的文件秒描述符数目,如返回 0 表示已超时,返回小于 0 表示函数失败。

epoll 工作原理 

当某一个进程调用 epoll_create 方法时,Linux 内核会创建一个 eventpoll 结构体,这个结构体中有两个成员与 epoll 的使用方式密切相关。

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

1. 每一个 epoll 对象都有一个独立的 eventpoll 结构体,用于存放通过 epoll_ctl 方法向 epoll 对象中添加进来的事件

2. 这些事件都会被挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效识别出来(红黑树的插入时间效率是 logn,其中 n 为树的高度)。

3. 而所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法。

4. 这个回调方法在内核中叫 ep_poll_callback,它会将发生的事件添加到 rdist 双链表中。

5. 在 epoll 中,对于每一个事件,都会建立一个 epitem 结构体。

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

当调用 epoll_wait 检查是否有事件发生时,只需要检查 eventpoll 对象中的 rdist 双链表中是否有 epitem 元素即可。

如果 rdist 不为空,则把发生的事件赋值到用户态,同时将事件数量返回给用户,这个操作的事件复杂度是 O(1).

总结以下,epoll 的使用过程就是三部曲:

1. 调用 epoll_create 创建一个 epoll 句柄、

2. 调用 epoll_ctl 将要监控的文件描述符进行注册

3. 调用 epoll_wait,等待文件描述符就绪 

epoll 的优先(和 select 的缺点对应) 

1. 接口使用方便:虽然拆分成了三个函数,但是反而使用起来更方便高效,不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离。

2. 数据拷贝轻量:只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中,这个操作并不频繁(而 select/poll 都是每次循环都要进行拷贝)

3. 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪,这个操作时间复杂度O(1),即文件描述符数目很多,效率也不会收到影响。

epoll 工作方式 

epoll 有两种工作方式,这里举一个妈妈喊你回家吃饭的例子: 

你正在吃鸡, 眼看进入了决赛圈, 你妈饭做好了, 喊你吃饭的时候有两种方式:

1. 如果你妈喊你一次, 你没动, 那么你妈会继续喊你第二次, 第三次...(亲妈,水平触发)

2. 如果你妈喊你一次, 你没动, 你妈就不管你了(后妈, 边缘触发) 

epoll 有两种工作方式 - 水平触发(LT) 和 边缘触发(ET) 

假如有这样一个例子:

我们已经把一个 tcp socket 添加到 epoll 描述符

这个时候 socket 的另一端被写入了 2KB 的数据

调用 epoll_wait,并且它会返回,说明它已经准备好了读取操作

然后调用 read,只读取了 1KB 的数据

继续调用 epoll_wait ..... 

水平触发 Level Triggered 工作模式

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

当 epoll 检测到 socket 上事件就绪的时候,可以不立即进行处理。或者只处理一部分

如上面的例子,由于只读了 1K 数据,缓冲区中还剩 1K 数据,在第二次调用 epoll_wait 时,epoll_wait 仍然会立即返回并通知 socket 读事件就绪。

知道缓冲区上所有数据都被处理完,epoll_wait 才不会立刻返回

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

边缘触发 Edge Triggered 工作模式 

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

当 epoll 检测到 socket 上事件就绪时,必须立刻处理

如上面的例子,虽然只读了 1K 的数据,缓冲区还剩 1K 的数据,在第二次调用 epoll_wait 的时候,epoll_wait 不会再返回了。

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

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

只支持非阻塞的读写。

select 和 poll 其实也是工作在 LT 模式下,epoll 既可以支持 LT,也可以支持 ET。 

对比 LT 和 ET 

LT 是 epoll 的默认行为。

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

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

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

理解 ET 模式和非阻塞文件描述符

使用 ET 模式的 epoll,需要将文件描述符设置为非阻塞,这个不是接口上的要求,而是 “工程实践” 上的要求。

假设这样的场景:服务器接收到一个 10K 请求,会向客户端返回一个应答数据。如果客户端收不到应答,不会发送第二个 10k 请求。

如果服务端写的代码是阻塞式的 read,并且一次只 read 1k 数据的话(read 不能保证一次就把所有的数据都读出来,参考 man 手册的说明,可能被信号打断),剩下的 9k 数据就会待在缓冲区中。

但是问题来了。

1. 服务器只读到了 1k 个数据,要 10k 读完才会给客户返回响应数据

2. 客服端读到服务器的响应,才会发送下一个请求

3. 客户端发送下一个请求,epoll_wait 才会返回,才能去读缓冲区中剩余的数据。 

所以,为了解决这个问题(阻塞 read 不一定能一下把完整的请求读完),于是就可以使用非阻塞轮询的方式来读取缓冲区,保证一定能把完整的请求都读出来。

而如果是 LT 没这个问题,只要缓冲区中的数据没读完,就能够让 epoll_wait 返回文件描述符就绪。

epoll 使用场景 

epoll 的高性能,是有一定的特定场景的。如果场景选择的不适宜,epoll 的性能可能适得其反。

对于多连接,且多连接只有一部分连接比较活跃时,比较适合使用 epoll。 

例如,典型的一个需要处理上万个客户端的服务器,例如各种互联网 APP 的入口服务器,这样的服务器就很适合 epoll。 

如果只是系统内部,服务器和服务器之间进行通信,只有少数的几个连接,这种情况下用 epoll 就并不合适,具体要根据需要和场景特点来决定使用那种 IO 模型。 

epoll 的惊群效应 

epoll 惊群效应产生的原因

在 Linux 下使用 epoll 编写过 socket 的服务器程序,在多线程环境下可能会遇到 epoll 惊群效应。那么什么是惊群效应呢。其产生的原因是什么呢?

在多线程或者多进程环境下,有些人为了提高程序的稳定性,往往会让多个线程或者多个进程同时在 epoll_wait 监听的 socket 描述符。当一个新的链接请求进来时,操作系统不知道选派哪个进程或者进程处理此事件,则干脆将其中几个线程或者进程给唤醒,而实际上只有其中一个进程或者线程能够成功处理 accept 事件,其他线程都将失败,且 errno 操作码为 EAGAIN。这种现象称为惊群效应,结果是肯定的,惊群效应肯定会带来资源的消耗和性能的影响。

那么如何解决这个问题呢? 

多线程环境下解决惊群问题方法

这种情况,不建议让多个线程同时在 epoll_wait 监听的 socket,而是让其中一个线程 epoll_wait 监听的 socket,当有新的链接请求进来后,由 epoll_wait 的线程调用 accept,建立新的连接,然后交给其他工作线程处理后续的数据读写请求,这样可以避免了由于多线程环境下的 epoll_wait 惊群效应的问题 

多进程下的解决方法 

目前很多开源软件,如lighttpd,nginx等都采用master/workers的模式提高软件的吞吐能力及并发能力,在nginx中甚至还采用了负载均衡的技术,在某个子进程的处理能力达到一定负载之后,由其他负载较轻的子进程负责epoll_wait的调用,那么nginx和Lighttpd是如何避免epoll_wait的惊群效用的。
lighttpd的解决思路是无视惊群效应,仍然采用master/workers模式,每个子进程仍然管自己在监听的socket上调用epoll_wait,当有新的链接请求发生时,操作系统仍然只是唤醒其中部分的子进程来处理该事件,仍然只有一个子进程能够成功处理此事件,那么其他被惊醒的子进程捕获EAGAIN错误,并无视。
nginx的解决思路:在同一时刻,永远都只有一个子进程在监听的socket上epoll_wait,其做法是,创建一个全局的pthread_mutex_t,在子进程进行epoll_wait前,则先获取锁。

ngx_int_t  ngx_trylock_accept_mutex(ngx_cycle_t *cycle)  {  if (ngx_shmtx_trylock(&ngx_accept_mutex)){if (ngx_enable_accept_events(cycle) == NGX_ERROR) {  ngx_shmtx_unlock(&ngx_accept_mutex);  return NGX_ERROR;  }  ngx_accept_mutex_held = 1;  return NGX_OK;  }  if (ngx_accept_mutex_held){  if (ngx_disable_accept_events(cycle) == NGX_ERROR){  return NGX_ERROR;  }  ngx_accept_mutex_held = 0;  }  return NGX_OK;  } 

且只有在ngx_accept_disabled < 0 时,才会去获取全局锁,及只有在子进程的负载能力在一定的范围下才会尝试去获取锁,并进入epoll_wait监听的socket。

void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{if (ngx_accept_disabled > 0){ngx_accept_disabled--;}else{if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR){return;}}
}
ngx_accept_disabled = ngx_cycle->connection_n / 8  - ngx_cycle->free_connection_n;

表示当子进程的连接数达到连接总数的7/8时,是不会尝试去获取全局锁,只会专注于自己的连接事件请求。

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

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

相关文章

多个Echart遍历生成 / 词图云

echart官网 安装 如果版本报错推荐安装以下版本 npm install echarts4.8.0 --savenpm uninstall echarts//这个是卸载命令以下安装成功后是局部引入:多个Echart遍历生成 vue3echart单个页面多个图表循环渲染展示:<template><div class"main"><div …

Windows server 服务器网络安全管理之防火墙出站规则设置

Windows server 服务器网络安全管理之防火墙出站规则设置 创建一条出站规则 这条出站规则针对IE浏览器设置&#xff0c;指定路径 TCP协议和指定端口&#xff08;多个端口的写法要注意&#xff09; 所有IP&#xff0c;所有应用&#xff0c;都采用阻止 给这条规则进行命名…

jmeter 接口性能测试 学习笔记

目录 说明工具准备工具配置jmeter 界面汉化配置汉化步骤汉化结果图 案例1&#xff1a;测试接口接口准备线程组添加线程组配置线程组值线程数&#xff08;Number of Threads&#xff09;Ramp-Up 时间&#xff08;Ramp-Up Period&#xff09;循环次数&#xff08;Loop Count&…

Pytorch | 从零构建ResNet对CIFAR10进行分类

Pytorch | 从零构建ResNet对CIFAR10进行分类 CIFAR10数据集ResNet核心思想网络结构创新点优点应用 ResNet结构代码详解结构代码代码详解BasicBlock 类ResNet 类ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数 训练过程和测试结果代码汇总resnet.pytrain.pytest.py 前…

gpu硬件架构

1.简介 NVIDIA在视觉计算和人工智能&#xff08;AI&#xff09;领域处于领先地位&#xff1b;其旗舰GPU已成为解决包括高性能计算和人工智能在内的各个领域复杂计算挑战所不可或缺的。虽然它们的规格经常被讨论&#xff0c;但很难掌握各种组件的清晰完整的图景。 这些GPU的高性…

Java中的方法重写:深入解析与最佳实践

在Java编程中&#xff0c;方法重写&#xff08;Method Overriding&#xff09;是面向对象编程&#xff08;OOP&#xff09;的核心概念之一。它允许子类提供一个与父类中同名方法的具体实现&#xff0c;从而实现多态性&#xff08;Polymorphism&#xff09;。本文将深入探讨Java…

使用vcpkg安装opencv>=4.9后#include<opencv2/opencv.hpp>#include<opencv2/core.hpp>无效

使用vcpkg安装opencv>4.9后#include<opencv2/opencv.hpp>#include<opencv2/core.hpp>无效\无法查找或打开 至少从2024年开始&#xff0c;发布的vcpkg默认安装的opencv版本都是4.x版。4.8版本及以前&#xff0c;vcpkg编译后的opencv头文件目录是*/vcpkg/x64-win…

基于java web在线商城购物系统源码+论文

一、环境信息 开发语言&#xff1a;JAVA JDK版本&#xff1a;JDK8及以上 数据库&#xff1a;MySql5.6及以上 Maven版本&#xff1a;任意版本 操作系统&#xff1a;Windows、macOS 开发工具&#xff1a;Idea、Eclipse、MyEclipse 开发框架&#xff1a;SpringbootHTMLjQueryMysq…

基于字节大模型的论文翻译(含免费源码)

基于字节大模型的论文翻译 源代码&#xff1a; &#x1f44f; star ✨ https://github.com/boots-coder/LLM-application 展示 项目简介 本项目是一个基于大语言模型&#xff08;Large Language Model, LLM&#xff09;的论文阅读与翻译辅助工具。它通过用户界面&#xff08…

centos7下docker 容器实现redis主从同步

1.下载redis 镜像 docker pull bitnami/redis2. 文件夹授权 此文件夹是 你自己映射到宿主机上的挂载目录 chmod 777 /app/rd13.创建docker网络 docker network create mynet4.运行docker 镜像 安装redis的master -e 是设置环境变量值 docker run -d -p 6379:6379 \ -v /a…

实现 WebSocket 接入文心一言

目录 什么是 WebSocket&#xff1f; 为什么需要 WebSocket&#xff1f; HTTP 的局限性 WebSocket 的优势 总结&#xff1a;HTTP 和 WebSocket 的区别 WebSocket 的劣势 WebSocket 常见应用场景 WebSocket 握手过程 WebSocket 事件处理和生命周期 WebSocket 心跳机制 …

机动车油耗计算API集成指南

机动车油耗计算API集成指南 引言 在当今社会&#xff0c;随着机动车数量的持续增长和环保意识的不断增强&#xff0c;如何有效管理和降低车辆油耗成为了车主、车队管理者以及交通政策制定者共同关注的问题。为了帮助这些群体更好地理解和优化燃油消耗情况&#xff0c;本接口能…

前端yarn工具打包时网络连接问题排查与解决

最近线上前端打包时提示 “There appears to be trouble with your network connection”&#xff0c;以此文档记录下排查过程。 前端打包方式 docker启动临时容器打包&#xff0c;命令如下 docker run --rm -w /app -v pwd:/app alpine-node-common:v16.20-pro sh -c "…

IIC I2C子协议 SMBus协议 通信协议原理 时序 SMBus深度剖析

引言&#xff1a;系统管理总线&#xff08;SMBus&#xff09;是一种双线接口&#xff0c;通过该接口&#xff0c;各种系统组件芯片和设备可以相互以及与系统其他部分通信。它基于IC总线的操作原理。附录B提供了一些SMBus特性与IC总线不同的方式的描述。 SMBus为系统和电源管理相…

【Lua热更新】上篇

Lua 热更新 - 上篇 下篇链接&#xff1a;【Lua热更新】下篇 文章目录 Lua 热更新 - 上篇一、AssetBundle1.理论2. AB包资源加载 二、Lua 语法1. 简单数据类型2.字符串操作3.运算符4.条件分支语句5.循环语句6.函数7. table数组8.迭代器遍历9.复杂数据类型 - 表9.1字典9.2类9.3…

React图标库: 使用React Icons实现定制化图标效果

React图标库: 使用React Icons实现定制化图标效果 图标库介绍 是一个专门为React应用设计的图标库&#xff0c;它包含了丰富的图标集合&#xff0c;覆盖了常用的图标类型&#xff0c;如FontAwesome、Material Design等。React Icons可以让开发者在React应用中轻松地添加、定制各…

如何使用 WebAssembly 扩展后端应用

1. WebAssembly 简介 随着互联网的发展&#xff0c;越来越多的应用借助 Javascript 转到了 Web 端&#xff0c;但人们也发现&#xff0c;随着移动互联网的兴起&#xff0c;需要把大量的应用迁移到手机端&#xff0c;随着手端的应用逻辑越来越复杂&#xff0c;Javascript 的解析…

Fastdfs V6.12.1集群部署(arm/x86均可用)

文章目录 一、 Fastdfs 介绍二、部署 信息三、步骤tracker/storage 机器的 compose 内容storage 机器的 composetracker 与 storage 启动目录层级与配置文件测试测试集群扩容与缩减注意事项 一、 Fastdfs 介绍 FastDFS 是一款高性能的分布式文件系统&#xff0c;特别适合用于存…

maven-resources-production:ratel-fast: java.lang.IndexOutOfBoundsException

Maven生产环境中遇到java.lang.IndexOutOfBoundsException的问题&#xff0c;尝试了重启电脑、重启IDEA等常规方法无效&#xff0c;最终通过直接重建工程解决了问题。 Rebuild Project 再启动OK

1. JasperSoft介绍与安装

Jaspersoft介绍 Jaspersoft是一款开源的&#xff0c;强大灵活并且使用广泛的报表软件。能够展示丰富的页面内容&#xff0c;并将之转换成PDF、HTML或者XML格式&#xff0c;该库完全由Java写出&#xff0c;可以用于在各种Java应用程序&#xff0c;非常适合Java开发者用来做报表生…