【Linux】多路转接之epoll

优化poll进行拷贝的开销

poll开销过大

  • 将整个 pollfd 数组拷贝到内核态,以便内核检查 fd 是否就绪(从用户态 → 内核态)。
  • 内核检查 fd 状态,并填充 revents。
  • 将 pollfd 数组从内核态拷贝回用户态,让应用程序可以读取 revents 里的就绪状态(从内核态 → 用户态)。

这个拷贝过程涉及 所有 pollfd 结构体,而 pollfd 结构体的大小通常是 8~16字节(取决于架构),如果监听 fd 数量很大(例如 上千个),拷贝数据量会很大,导致 系统调用的开销上升。

优化遍历开销

  • 优化 poll 遍历查询空位置的问题:poll 需要遍历整个 pollfd 数组来找到空闲位置,以便管理 fd,当 fd 数量庞大时,维护成本很高。
  • 优化遍历查询就绪 fd 的问题:poll 在返回时,仍然需要遍历整个 pollfd 数组来寻找就绪的 fd,时间复杂度为 O(n)。

epoll 的优化点(epoll_ctl + epoll_wait 分离)

操作说明
epoll_ctl只在你要添加/修改/删除监听 fd 时才调用一次,相当于“注册监听列表”
epoll_wait每次只等待事件,不关心你监听了哪些 fd,内核直接返回“就绪事件”

认识epoll的接口

poll 和select 都通过一个接口完成“注册 + 等待”,每次调用都要传递和检查全部 fd,效率低;
而epoll 将监听与等待分离,通过epoll_ctl 注册事件,通过epoll_wait 等待事件,减少了不必要的遍历和数据拷贝

epoll_create()

作用

创建一个 epoll 实例,并返回一个 epoll 文件描述符(epfd),该 epfd 用于后续的 epoll_ctl() 和 epoll_wait() 操作。

函数原型

int epoll_create(int size);这个参数已经进行废弃了,但是为了进行向前兼容没有将该参数进行删除。

返回值

  • 成功:返回 epfd(epoll 实例的文件描述符)。
  • 失败:返回 -1,errno 指示错误类型。

epoll_ctl()

作用

管理 epoll 监控的文件描述符,对epoll模型进行操作,包括添加、修改、删除操作。

函数原型

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

参数

  • epfd:由 epoll_create() 生成的 epoll 句柄。
  • op:操作类型,支持以下三种:

        EPOLL_CTL_ADD:添加一个新的 FD 到 epoll 实例。

        EPOLL_CTL_MOD:修改已有的 FD 监听的事件。

        EPOLL_CTL_DEL:从 epoll 实例中删除 FD。

  • fd:需要监听的文件描述符。
  • event:指向 struct epoll_event 结构体的指针,设置监听的事件类型。

返回值

  • 成功:返回 0。
  • 失败:返回 -1,errno 指示错误类型。

epoll_wait()

作用

等待被监听的文件描述符发生事件,并返回就绪的文件描述符。

函数原型

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

参数

  • epfd:epoll_create() 生成的 epoll 句柄。
  • events:用于存储发生事件的文件描述符,用户需提供足够的空间。
  • maxevents:events 数组的大小,建议大于 0。
  • timeout:超时时间(毫秒):

        0:立即返回(非阻塞)。

        -1:永远阻塞,直到有事件发生。        

        >0:等待指定时间。

返回值

  • 成功:返回就绪的文件描述符数量(nfds)。
  • 失败:返回 -1,errno 指示错误类型。

epoll的原理 

epoll服务器实现的框架

socket套接编程的封装

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "err.hpp"class Sock
{const static int backlog = 32;public:static int Socket(){// 1. 创建socket文件套接字对象int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){logMessage(FATAL, "create socket error");exit(SOCKET_ERR);}logMessage(NORMAL, "create socket success: %d", sock);int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));return sock;}static void Bind(int sock, int port){// 2. bind绑定自己的网络信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind socket error");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success");}static void Listen(int sock){// 3. 设置socket 为监听状态if (listen(sock, backlog) < 0) // 第二个参数backlog后面在填这个坑{logMessage(FATAL, "listen socket error");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success");}static int Accept(int listensock, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(listensock, (struct sockaddr *)&peer, &len);if (sock < 0)logMessage(ERROR, "accept error, next");else{logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?*clientip = inet_ntoa(peer.sin_addr);*clientport = ntohs(peer.sin_port);}return sock;}
};

epoll模型的封装

epollServer.hpp

#include <iostream>
#include<unistd.h>
#include<sys/epoll.h>
#include<functional>
#include<string>
#include"sock.hpp"
#include"log.hpp"
using namespace std;using func_t = function<string(const string&)>;
#define SIZE 1024const static int defultvalue=-1;
const static int defultnum=64;
class EpollServer
{
public:EpollServer(int port,func_t func,int num=defultnum):_listen_sockfd(defultvalue),_port(port),_num(num),_revs(nullptr),_func(func){}void serverInit(){//1、创建套接字_listen_sockfd=Sock::Socket();//2、进行bind绑定Sock::Bind(_listen_sockfd,_port);//3、设置监听状态Sock::Listen(_listen_sockfd);//4、创建epoll模型//4、1 创建epoll模型实例_epfd=epoll_create(1);if(_epfd<0){logMessage(FATAL,"创建epoll实例失败 :%s",strerror(errno));exit(EPOLL_CREATE_ERROR);}//4、2 管理epoll进行监控的文件描述符struct epoll_event events;events.events=EPOLLIN;events.data.fd=_listen_sockfd;epoll_ctl(_epfd,EPOLL_CTL_ADD,_listen_sockfd,&events);//5、进行开辟就是事件的空间_revs=new struct epoll_event[_num];logMessage(NORMAL,"进行epoll服务器初始化成功");}void serverStart(){int timeout=-1;for(;;){// 进行等待管理的文件描述符发生事件int n = epoll_wait(_epfd,_revs,_num,timeout);switch (n){case -1:logMessage(ERROR,"epoll_wait等待文件描述符发生事件失败");break;case 0:logMessage(NORMAL,"outtime....");break;default://一定有事件进行就绪HandlerEven(n); //将有多少个就绪的事件进行传入break;}}}void HandlerEven(int readyNum){for(int i=0;i<readyNum;i++){int sockfd=_revs[i].data.fd;uint32_t events=_revs[i].events;//处理监听套接字---listen套接字就绪if(sockfd==_listen_sockfd && events&EPOLLIN){string clienip;uint16_t port;int fd=Sock::Accept(sockfd,&clienip,&port);if(fd<0){logMessage(FATAL,"accept 获取链接失败");continue;;}//建立连接成功,我们可以直接进行读取吗???????  不可以!!!!//交给epollstruct epoll_event ev;ev.events=EPOLLIN;ev.data.fd=fd;epoll_ctl(_epfd,EPOLL_CTL_ADD,fd,&ev);}//处理普通套接字---普通套接字就绪else if(events&EPOLLIN){//进行读取客户端的消息char buffer[SIZE];ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);if(n>0){buffer[n]=0;logMessage(NORMAL,"client#  %s",buffer);//通过回调方法进行处理客户端的消息string resp=_func(buffer);//将处理过后的消息进行返回send(sockfd,resp.c_str(),resp.size(),0);}else if(n==0){epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);close(sockfd);logMessage(NORMAL,"客户端退出");}else{//细节:epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);close(sockfd);logMessage(ERROR,"进行读取客户端消息失败");}}else{}}}~EpollServer(){if(_listen_sockfd!=defultvalue){close(_listen_sockfd);}if(_epfd!=defultvalue){close(_epfd);}if(_revs){delete[] _revs;}}
private:int _listen_sockfd;int _port;int _epfd;struct epoll_event* _revs;int _num;func_t _func;
};

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

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

相关文章

下载一个JeecgBoot-master项目 导入idea需要什么操作启动项目

官网&#xff1a;开发环境搭建 | JEECG 文档中心 一般做开发的电脑里都是有的&#xff0c;没有的只能下载了 前端安装 node官网:https://nodejs.org/zh-cnpnpm安装:通过命令 后端安装: jdk17 :https://www.oracle.com/cn/java/technologies/downloads/#java17maven :https://m…

解决 InputStream 只能读取一次问题

是的&#xff0c;InputStream 的一个重要特性是它通常只能被读取一次。这是因为&#xff1a;输入流通常是单向的、顺序访问的数据源很多流&#xff08;如网络流、文件流&#xff09;读取后指针就移动了&#xff0c;无法回退有些流&#xff08;如Socket流&#xff09;甚至读取后…

数据分析面试题

技都测试 1、请列举5个 Excel 中常用的函数及写法。[ if ] IF(A1>60, "及格", "不及格") —— 若 A1 单元格数值≥60&#xff0c;返回 “及格”&#xff0c;否则返回 “不及格”。IF(B2>100, B2*0.8, B2) —— 若 B2 数值 > 100&#xff0c…

【07】VisionMaster入门到精通——Blob分折

文章目录0 视屏讲解与演示1 案例演示2 参数详解1 运行参数0 视屏讲解与演示 1 案例演示 周长使能查找U型槽 短轴使能查找U型槽 面积筛选区域 当条件不符合是&#xff0c;该模块显示红色&#xff0c;状态为NG 显示二值图像 显示Blob图像 2 参数详解 Blob分折&#xff0c;…

解释 MySQL 中的 EXPLAIN 命令的作用和使用场景

解释 MySQL 中的 EXPLAIN 命令的作用和使用场景 总结性回答 EXPLAIN 是 MySQL 中用于分析 SQL 查询执行计划的命令&#xff0c;它能展示 MySQL 如何执行一个查询&#xff0c;包括使用的索引、表连接顺序、扫描行数等关键信息。主要用于查询性能优化&#xff0c;帮助开发者识别潜…

.env 文件

.env 文件其实就是一个纯文本文件&#xff0c;用来写“环境变量”键值对&#xff0c;格式非常简单 &#x1f447;✅ .env 文件写法格式&#xff1a;每一行就是一个变量名 值&#xff0c;不要加引号&#xff0c;不要加空格DEEPSEEK_API_KEYsk-xxxxxxxxxxxxxxxxxxxx完整例子&…

机器学习——K 折交叉验证(K-Fold Cross Validation),案例:逻辑回归 交叉寻找最佳惩罚因子C

什么是交叉验证&#xff1f; 交叉验证是一种将原始数据集划分为若干个子集&#xff0c;反复训练和验证模型的策略。 交叉验证&#xff08;Cross-Validation&#xff09;适用于你在模型调参&#xff08;如逻辑回归中的 C&#xff09; 最常用的&#xff1a;K 折交叉验证&#…

蓝桥杯----串口

&#xff08;五&#xff09;、串口1、串口通信简介制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发&#xff0c;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统&#xff0c;串口USART有两根通信线Tx、Rx&#xff0c;可同时双向通信&#xff0c;称之为…

错误: 找不到或无法加载主类 原因: java.lang.ClassNotFoundException

背景&#xff1a; 代码没有更改&#xff0c;主类位置也没有移动&#xff0c;运行时突然报找不到或无法加载主类的错误 错误: 找不到或无法加载主类 原因: java.lang.ClassNotFoundException编译器上方显示 Java file is located outside of the module source root so it wont …

Lock 接口及实现类详解:从 ReentrantLock 到并发场景实践

在 Java 并发编程中&#xff0c;除了synchronized关键字&#xff0c;java.util.concurrent.locks.Lock接口及其实现类是另一种重要的同步机制。自 JDK 5 引入以来&#xff0c;Lock接口凭借灵活的 API 设计、可中断的锁获取、公平性控制等特性&#xff0c;成为复杂并发场景的首选…

「iOS」————SideTable

iOS学习前言sideTableSlideTablesSideTableBufSideTable前言 我们在上一篇中&#xff0c;简单的介绍了weak的实现原理。其中弱引用表就是存储在SideTable中的&#xff0c;这里我们来学习了解一下SideTable sideTable sideTable主要用于存储和管理对象的额外信息&#xff0c;…

【PHP】CURL请求第三方API接口

当我们需要调用第三方接口时&#xff0c;就需要使用CURL&#xff0c;通过CURL操作去请求第三方API接口&#xff0c;有的是通过POST方式&#xff0c;有的是通过GET方式&#xff0c;下面介绍一个通用的使用CURL调用API接口的方法。一、CURL操作共两个方法&#xff0c;分别是CURL操…

对于考研数学的理解

文章目录高等数学总结补充说明1. 微分方程与无穷级数的特殊性2. 隐藏的逻辑链条3. 向量代数的桥梁作用核心框架为什么这样设计&#xff1f;结论线性代数核心逻辑框架各讲之间的本质联系1. 行列式 → 矩阵2. 矩阵 → 向量组3. 矩阵 向量组 → 线性方程组4. 矩阵 → 特征值与特征…

基于 Hadoop 生态圈的数据仓库实践 —— OLAP 与数据可视化(四)

目录 四、数据可视化与 Hue 简介 1. 数据可视化简介 &#xff08;1&#xff09;数据可视化的重要性 &#xff08;2&#xff09;数据可视化的用途 &#xff08;3&#xff09;实施数据可视化需要考虑的问题 &#xff08;4&#xff09;几种主要的数据可视化工具 2. Hue 简介…

HarmonyOS 开发:基于 ArkUI 实现复杂表单验证的最佳实践

摘要 在现代应用开发中&#xff0c;表单是最常见的交互方式之一。不管是用户注册、信息录入&#xff0c;还是登录验证&#xff0c;表单的可靠性直接影响用户体验。而在鸿蒙 ArkUI 开发中&#xff0c;虽然表单结构清晰&#xff0c;但要实现 复杂验证&#xff08;比如&#xff1a…

高效游戏状态管理:使用双模式位运算与数学运算

在游戏开发中&#xff0c;状态管理是一个核心问题。无论是任务系统、成就系统还是玩家进度跟踪&#xff0c;我们都需要高效地存储和查询大量状态。本文将深入分析一个创新的游戏状态管理工具类 GameStateUtil&#xff0c;它巧妙结合了位运算和数学运算两种模式&#xff0c;在存…

linux-process-control

Linux进程控制 1. 进程终止 1.1. 进程终止的本质是回收资源 1.1 释放资源 内存资源&#xff1a; 释放进程的地址空间&#xff08;mm_struct&#xff09;&#xff0c;包括代码段、数据段、堆、栈等&#xff0c;通过写时复制&#xff08;CoW&#xff09;共享的页会减少引用计数&a…

Autoswagger:揭露隐藏 API 授权缺陷的开源工具

Autoswagger 是一款免费的开源工具&#xff0c;用于扫描 OpenAPI 文档中列出的 API&#xff0c;查找授权漏洞。 即使在拥有成熟安全团队的大型企业中&#xff0c;这类漏洞仍然很常见&#xff0c;而且尤其危险&#xff0c;因为即使技术水平不高的人也能利用它们。 Autoswagger…

Golang 语言 Channel 的使用方式

一、无缓存 channel无缓冲channel 可用于两个goroutine 之间 传递信号&#xff0c;比如以下示例&#xff1a;顺序打印1 至 100 的奇数和偶数&#xff1a;import ("fmt""time" )func main() {block : make(chan struct{})go odd(block)go even(block)time.S…

Element Plus常见基础组件(一)

基础组件 Button 按钮 一、基础用法 <el-button>默认按钮</el-button> <el-button type"primary">主要按钮</el-button>二、按钮类型 (type) 类型说明示例代码default默认按钮<el-button>默认</el-button>primary主要按钮&a…