高性能服务器-I/O多路复用(epoll)

news2024/11/25 11:24:55

系列文章目录

第一章 高性能服务器技术栈 (select)
第二章 高性能服务器技术栈 (epool/poll)


文章目录

  • 系列文章目录
  • 前言
  • 一、epoll 接口
  • 二、epoll 原理
  • 三、epoll 触发方式
  • 四、设置阻塞方式
  • 代码 实例
  • 总结
  • 参考


前言

在网络中实现IO多路复用的技术,最常用的就是(select, poll,epoll)三种模型,但是select 受限于底层的实现,随着管理fd数量的增多,造成轮询效率下降。进而出现了epoll模型,epoll 模型底层实现是采用红黑树,不会受限于检测句柄的数量。


一、epoll 接口

epoll 的实现共有三个接口。它们分别如下所示:

接口 epoll_create

epoll_create主要用来创建eventpoll 实例并对其进行初始化,同时返回一个指向epoll实例的文件描述符。

int epoll_create(int size)

参数 size: 现无意义,必须大于0,一般填1,告知内核事件表有多大。
返回值:成功返回对应的 epfd ( eventpoll 结构体) ,失败返回 -1

接口 epoll_ctl:
将一个 fd 添加到 epoll 的红黑树中,并设置 ep_poll_callback,callback 触发时,把对应 fd 加入到rdlist 就绪列表中。 对fd 实现增删改的操作。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
/*
返回值:成功返回0,失败返回-1。

参数:
- 参数1 epfd:epoll对象

- 参数2 op:指定操作类型。
  EPOLL_CTL_ADD 增加 
  EPOLL_CTL_MOD 修改 
  EPOLL_CTL_DEL 删除 
  
- 参数3 fd:要监听的 fd

- 参数4 event: 要监听的事件类型,红黑树键值对kv:fd-event
  event.events:
  
  EPOLLIN		可读
  EPOLLOUT		可写
  EPOLLET		边缘触发,默认⽔平触发LT
*/

具体详解:
epoll_ctl主要是对epitem对象进行操作:

EPOLL_CTL_ADD:先查看含fd的epitem对象是否在红黑树中,如果在则退出,不在则将fd和event加到epitem对象中并将该对象插入红黑树中。
EPOLL_CTL_DEL:先查看含fd的epitem对象是否在红黑树中,如果不在则退出,在则将该epitem对象从红黑树中删除。
EPOLL_CTL_MOD:先查看含fd的epitem对象是否在红黑树中,如果不在则退出,在则更新事件。

epoll_wait
检测 rdlist 列表是否为空,不为空时候则主要是把就绪链表中的事件信息拷贝到events数组中 返回就绪的 fd 的数量

// 收集 epoll 监控的事件中已经发生的事件,如果 epoll 中没有任何⼀个事件发⽣,则最多等待 timeout 毫秒后返回。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
    
返回值:成功返回实际就绪的 fd 数量,失败返回-1

参数:
- 参数1 epfd:epoll 对象。

- 参数2 events:用户态创建的evnet数组,内核拷贝就绪的 fd 到该数组中。
    events[i].events:
    EPOLLIN 		触发读事件
    EPOLLOUT 		触发写事件
    EPOLLERR 		连接发生错误
    EPOLLRDHUP 		连接读端关闭
    EPOLLHUP 		连接双端关闭

- 参数3 maxevents:可以返回的最⼤事件数目,一般设置为event数组的⻓度

- 参数4 timeout:超时时间 ms。-1(一直等待)0(不等待)>0(等待时间)。断开与服务器没有交互的客户端

其它接口 fcntl

fcntl 函数可以将 fd 设置为非阻塞。
//修改(获取)文件描述符属性
int fcntl(int fd, int cmd, ... /* arg */ );
/*
返回值:失败返回-1
参数
- 参数1:需要修改的文件描述符,
- 参数2:修改(获取)文件描述符的操作
*/

//1、获取原有套接字状态的信息
	int status = fcntl(fd, F_GETFL);
//2、将非阻塞的标志与原有的标志信息做或操作
	status |= O_NONBLOCK;
//3、将标志位信息写回到socket中
	fcntl(fd, F_SETFL, status);

二、epoll 原理

在这里插入图片描述
注:调用 epoll_create 会创建一个 epoll 对象。调用 epoll_ctl 添加到 epoll 中的事件都会与网卡驱动程序建立回调关系,相应事件触发时会调用回调函数(ep_poll_callback),将触发的事件拷贝到 rdlist 双向链表中。调用epoll_wait 将会把 rdlist 中就绪事件拷贝到用户态中;

调用 epoll_create 函数:返回一个epfd,同时内核会创建 eventpoll 结构体,该结构体的成员由红黑树和双向链表组成。红黑树:保存需要监听的描述符。双向链表:保存就绪的文件描述符。

调用 epoll_ctl 函数: 对红黑树进行增添,修改、删除。
添加 fd 到红黑树上,从用户空间拷贝到内核空间。一次注册 fd,永久生效。内核会给每一个节点注册一个回调函数,当该 fd 就绪时,会主动调用自己的回调函数,将其加入到双向链表当中。

调用 epoll_wait 函数: 内核监控双向链表,如果双向链表里面有数据,从内核空间拷贝数据到用户空间;如果没有数据,就阻塞。


三、epoll 触发方式

当事件就绪的时候,调用 epoll_wait(select、poll)可以得到通知。事件通知的模式有两种:水平触发(LT)和边缘触发(ET); 在处理非大量数据时候性能相差不大,但是代码逻辑处理有区别。

水平触发 LT: 当内核读缓冲区非空,写缓冲区不满,则一直触发,直至数据处理完成。
边缘触发 ET: 当 IO 状态发生改变,触发一次。每次触发需要一次性把事件处理完。

LT 和 ET 的特性,决定了对于数据的读操作不同

LT + 一次性读,阻塞
ET + 循环读, 非阻塞 循环recv

代码实现

// lt + 一次性读,小数据
ret = read(fd, buf, sizeof(buf));

// et + 循环读,大数据
while(true) {
    ret = read(fd, buf, sizeof(buf);
    // 此时,说明读缓冲区已经空了
    if (ret == EAGAIN || ret == EWOULDBLOCK) break;
}

ET特点:

  1. ET 模式避免了 LT 模式可能出现的惊群现象(如:一个 listenfd 被多个 epoll 监听,一个调用accept接受连接,其他 accept 阻塞);
  2. ET 模式减少了 EPOLL 事件被重复触发的次数,效率高。

ET 的使用

ET 的使用: ET+ 非阻塞 IO + 循环读

循环读:若数据不能一次性处理完,只能等到下次数据到达网卡后才触发读事件。

四、设置阻塞方式

将 recv 函数设置为非阻塞的两种方式:
1.recv 函数的属性设置为MSG_DONWAIT;

ret = recv(newFd, buf, sizeof(buf)-1, MSG_DONTWAIT);
  1. fcntl 函数将文件描述符设置为非阻塞性的。

代码 实例

int conn_fd = 0;

// fd --> epoll  在epoll 底层创建的时候也是通过fd 操作的。
int epfd = epoll_create(1);//参数无意义只要大于0 即可;老版本参数是用来确定一次行就绪数量

struct epoll_event ev, events[EVENTS_LENGTH];
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = listen_fd

epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev)  // 先把listen_fd 注册到事件中; 非阻塞,不可能在这个地方存在挂起

printf("fd : %d\n",epfd);

while(1){  //7*24   所有的服务器都有这个循环,做成永真的循环,while(!finshed) / for(;;)
    int nready = epoll_wait(epfd,events, EVENTS_LENGTH,1000); // 是阻塞
    printf("nready :%d",nready);
    int i = 0;
    for (i = 0; i < nready;i++){
        int clientfd = events[i].data.fd ;
        if (listen_fd == clientfd){  // accept处理

            while( 1) {
                struct sockaddr_in client_addr;
                socklen_t length = sizeof(client_addr);

                fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL, 0)|O_NONBLOCK);
                conn_fd = accept(listen_fd,(struct sockaddr *)&client_addr, &length);
                if (0 > conn_fd ) {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
                }

                if (conn_fd == -1){
                    break;
                }

                printf("accept  : %d\n",conn_fd);
                ev.events = EPOLLIN;
                ev.data.fd = conn_fd;
                epoll_ctl(epfd,EPOLL_CTL_ADD, conn_fd, &ev);
            }
        }else if(events[i].events & EPOLLIN)  {   // clientfd

           unsigned char buff[BUFF_LENGTH] = {0};

            int n = recv(clientfd, rbuff, BUFF_LENGTH,0);
            if (n > 0) {
                //rbuffer[n] = '\0';
            
                printf("recv: %s, n: %d\n", rbuffer, n);
            
                memcpy(wbuffer, rbuffer, BUFFER_LENGTH);
            
                ev.events = EPOLLOUT;
                ev.data.fd = clientfd;
            
                epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
                
            } 

        }else if (events[i].events & EPOLLOUT) {
        	int sent = send(clientfd, wbuffer, BUFFER_LENGTH, 0); //
			printf("sent: %d\n", sent);

			ev.events = EPOLLIN;
			ev.data.fd = clientfd;

			epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);

        }
    }
}

总结

参考

IO多路复用
epoll原理学习笔记

荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:

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

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

相关文章

linux(线程概念)

目录&#xff1a; 1.概念 2.linux线程与接口的关系 3.代码验证&#xff08;线程是进程模拟的&#xff09; ------------------------------------------------------------------------------------------------------------------------------- 1.概念 一般教材&#xff1a;…

STM32驱动Realtek RTL8189ES WiFi模块读取MAC地址并下载固件

单片机&#xff1a;STM32F103RE 接口&#xff1a;SDIO Keil 5工程下载链接&#xff1a;https://pan.baidu.com/s/1yIgUJUZcwWOL7xnwA9Rw2Q?pwdftxd Wi-Fi模块电源引脚的连接方法&#xff1a; 【RTL8189ES读取片内MAC地址的代码】 /* 显示WiFi模块参数信息 */ void WiFi_ShowI…

【力扣刷题 | 第八天】

前言&#xff1a; 本章将利用栈与队列来尝试解决实际问题。 20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&…

chatgpt赋能python:Python批量删除:简化SEO优化的过程

Python批量删除&#xff1a;简化SEO优化的过程 SEO&#xff08;Search Engine Optimization&#xff09;优化是网站运营中不可忽视的一项任务。其中涉及到对内容和网站结构的优化&#xff0c;而这些工作也离不开对数据的处理。其中一个常见问题是需要删除一批旧的或者无用的页…

《统计学习方法》——条件随机场(中)

引言 这是统计学习方法第十一章条件随机场的阅读笔记&#xff0c;包含所有公式的详细推导。 条件随机场(conditional random field,CRF)是给定一组输入随机变量条件下另一组输出随机变量的条件概率分布模型&#xff0c;其特点是假设输出随机变量构成马尔可夫随机场。 建议先阅…

chatgpt赋能python:Python怎么持续输入?

Python怎么持续输入&#xff1f; Python是一个高级编程语言&#xff0c;它的简单易用性让它成为程序员们心仪的语言之一。在Python编程中&#xff0c;输入是一个非常重要的环节。 在这篇文章中&#xff0c;我们将讨论Python如何进行持续输入。我们将介绍几种不同的方法&#…

计算机图形学与opengl C++版 学习笔记 第10章 增强表面细节

假设我们想要对不规则表面的物体进行建模&#xff0c;例如橘子凹凸的表皮、葡萄干褶皱的表面或月球的陨石坑表面。我们该怎么做&#xff1f;到目前为止&#xff0c;我们已经学会了两种可能的方法&#xff1a; &#xff08;a&#xff09;我们可以对整个不规则表面进行建模&…

【期末总复习】数字图像处理知识要点

【A卷】 【选择】 1、计算机器显示彩色图像的格式 2、灰度反转后&#xff08;一副图像灰度级&#xff09;的灰度值是几 3、灰度图像浅色背景下加圆环会导致什么后果 4、图像平滑的模板有哪些 5、γ矫正指的是什么数学变换 6、一阶锐化空间滤波器有哪些 7、图像复原、图像增强…

运维(SRE)成长之路-第1天 搭建虚拟机(图示)

1.Linux安装前准备 虚拟机&#xff1a;用软件&#xff08;如&#xff1a;vmware,virtualbox等&#xff09;模拟硬件,方便实验的灵活配置 虚拟化软件&#xff0c;建议使用 Vmware Workstation 虚拟硬件配置 CPU&#xff1a;2核或更多 内存&#xff1a;1G以上&#xff0c;推荐2…

chatgpt赋能python:Python中如何找出最小的2个值

Python中如何找出最小的2个值 Python作为一种高效可靠的编程语言&#xff0c;拥有出色的处理数据和算法的能力。在数据处理中&#xff0c;常常需要对数据进行排序并找出最小&#xff08;或最大&#xff09;的数值。本文将着重介绍如何在Python中找出最小的2个值。 方法1&…

编程比赛 (ACM竞赛)常用术语

比赛相关 AK&#xff1a;All-Killed 通常指在信息学竞赛中获得满分&#xff08;即 AC 了所有题目&#xff09;。 由于在中高级信息学竞赛中&#xff08;如 IOI&#xff0c;NOI&#xff0c;NOIP&#xff0c;ACM&#xff09;&#xff0c;想要获得满分普遍较难&#xff0c;甚至根…

java三大特性之【继承】

概念 继承就是将多个类的共性抽取出来&#xff0c;提出继承的概念。就是在进行共性抽取&#xff0c;实现代码复用。 提高代码的复用性&#xff0c;方便进行子类&#xff0c;子功能的扩展&#xff0c;将所有共性的内容放在父类&#xff08;基类&#xff09;&#xff0c;子类只…

裁剪lua5.3.4

在 Lua 5.3.4 中&#xff0c;有一些宏定义可以缩小 Lua 的资源占用&#xff0c;包括&#xff1a; LUA_COMPAT_5_2&#xff1a;该宏定义可以将 Lua 5.3 的行为与 Lua 5.2 兼容&#xff0c;缩小 Lua 的资源占用。例如&#xff0c;该宏定义可以禁用一些 Lua 5.3 中新增的语法特性&…

在线协同办公小程序开发搭建开发环境

目录 介绍 开发环境说明 虚拟机 原因 VirtualBox虚拟机 VMware虚拟机v15 安装MySQL数据库 安装步骤 导入EMOS系统数据库 安装MongoDB数据库 启动Navicat&#xff0c;选择创建MongoDB连接 创建用户 搭建Redis数据库 配置Maven 安装IDEA插件 Lombok插件 …

基于Java+SpringBoot+Vue实现前后端分离美术馆管理系统

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

【哈佛积极心理学笔记】第15讲 完美主义

第15讲 完美主义 A person committed to excellence can still be as ambitious or more ambitious than a perfectionist. The difference between the two is just their approach toward the journey. There is no straight line toward success. Perfection vs Excellenc…

CMake入门(1)

背景 目前很多大型框架都是使用cmake去构建&#xff0c;如果看不懂cmake, 在实际修改框架&#xff0c;添加新的模块时候就会受制于人&#xff0c;为此需要了解cmake的相关基础支持&#xff0c;避免被某些装逼大佬卡脖子&#xff0c;同时也进一步提高自己的业务水平。 变量 c…

Hello算法学习笔记之数组与链表

一、数组 线性数据结构&#xff0c;其将相同类型元素&#xff08;链表就不一定要存储相同类型的元素&#xff09;存储在连续的内存空间中。。数组有Index 数组的优点&#xff1a;访问很高效&#xff0c;O&#xff08;1&#xff09; PS:元素长度是单个元素占用的内存空间&…

CSS基础学习--13 Display(显示) 与 Visibility(可见性)

一、定义 display属性设置一个元素应如何显示 visibility属性指定一个元素应可见还是隐藏 二、隐藏元素 - display:none或visibility:hidden 隐藏一个元素可以通过把display属性设置为"none"&#xff0c;或把visibility属性设置为"hidden"。但是请注意&am…

Aprioi关联算法

国际权威的学术会议IEEE International Conference on Data Mining (ICDM) 评选出了数据挖掘领域的十大经典算法&#xff0c;他们分别是&#xff1a;C4.5、kMeans、SVM、Apriori、EM、PageRank、AdaBoost、KNN、Naive Bayes以及CART。今天就让我们共同探讨一下十大算法之一Apri…