Epoll模型

发布时间:2026/10/7 19:41:11

Epoll模型 Epoll模型一、底层核心原理每一个epoll模型内部都维护了一棵红黑树当调用epoll_ctl并执行EPOLL_CTL_ADD操作时会将新增的文件描述符fd及关联数据构造成一个新结点存入这棵红黑树中。此外epoll模型中还维护了一个就绪队列当文件描述符上有事件就绪时底层会触发中断操作系统会将该文件描述符对应的红黑树结点放入就绪队列中。当执行epoll_wait时就能直接拿到所有已经就绪的事件对应的fd由程序员自行处理。二、为什么用红黑树存储 fd 数据因为epoll模型可监测的 fd 数量没有硬上限当存在大量 fd 时使用红黑树既可以对 fd 进行高效管理也能实现快速查找与增删操作。三、事件结点如何加入就绪队列红黑树的每一个结点中存储的不只有 fd还包含其他指针字段。当某个结点有事件就绪时不需要重新拷贝整个结点再添加到就绪队列只需要通过结点内的就绪队列指针将结点链入就绪队列即可头插或尾插方式全程无需拷贝结点数据。四、epoll IO 多路转接的整体使用逻辑epoll 是 IO 多路转接模型的一种实现相比 select/poll 效率更高。使用 epoll 的大致流程为调用epoll_create创建一个 epoll 模型获取该模型的句柄本质也是一个文件描述符调用epoll_ctl将需要监听的文件描述符添加到 epoll 模型中进行事件检测在事件循环中调用epoll_wait接口等待就绪的文件描述符五、核心接口详细介绍1.epoll_create功能创建一个 epoll 模型实例返回该实例的句柄文件描述符。说明该函数的参数在新版本内核中已被弃用传入一个大于 0 的数字即可。函数原型#include sys/epoll.h int epoll_create(int size);使用样例// 1.创建epoll模型 _epfd epoll_create(128); if(_epfd 0) { std::cout epoll create error, strerror(errno) std::endl; exit(-1); }2.epoll_ctl功能通过 epoll 句柄对被监听的文件描述符执行增、删、改操作。成功返回 0失败返回 - 1。函数原型#include sys/epoll.h int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);参数说明epfdepoll 模型的句柄op操作类型EPOLL_CTL_ADD将 fd 添加到 epoll 模型中进行监听EPOLL_CTL_MOD修改 fd 对应的监听事件EPOLL_CTL_DEL将 fd 从 epoll 模型中移除fd目标文件描述符event需要监听的事件结构体其中epoll_event结构体定义如下events字段用于设置需要监听的事件常用的有EPOLLIN可读事件、EPOLLOUT可写事件data字段用户自定义数据一般将 fd 存入该字段的fd成员中struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t;使用样例// 2.将listen套接字加入到epoll模型中 // 先构建一个事件结构体 struct epoll_event ev; ev.events | EPOLLIN; ev.data.fd _socket-GetSockfd(); int n epoll_ctl(_epfd, EPOLL_CTL_ADD, _socket-GetSockfd(), ev); // 成功返回0 if(n ! 0) { std::cout epoll control error, strerror(errno) std::endl; exit(-1); }3.epoll_wait功能检查 epoll 模型中是否有文件描述符的事件就绪。如果有就绪事件会将这些就绪的 epoll 事件放到events缓冲区中成功时返回就绪事件的个数超时返回 0出错返回 - 1。函数原型#include sys/epoll.h int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);参数说明epfdepoll 模型的句柄events存放就绪事件的输出缓冲区maxevents缓冲区最多能容纳的事件个数timeout超时时间单位是毫秒使用样例// 定义常量表示就绪事件缓冲区的最大容量 const static int gnum 64; // 定义存放就绪事件的缓冲区 struct epoll_event _events[gnum]; // 调用epoll_wait进行等待超过1秒就会返回继续向下执行 int n epoll_wait(_epfd, _events, gnum, 1000); if(n 0) { // 没有事件就绪超时返回 std::cout time out... std::endl; } else if(n 0) { // 有n个事件就绪开始处理事件 HandlerEvents(n); } else { if(errno EINTR) continue; std::cout epoll wait fail, strerror(errno) std::endl; exit(-1); }代码样例——EpollServerDemoEpollServer.hpp#pragma once #include iostream #include memory #include TcpSocket.hpp #include unordered_map #include unistd.h #include sys/epoll.h class EpollServer { private: const static int Nsizeof(fd_set)*8; const static int defaultfd-1; const static int gnum64; public: EpollServer(uint16_t port) :_port(port) ,_socket(std::make_uniqueTcpSocket(port)) ,_epfd(defaultfd) { // 1.创建epoll模型 _epfdepoll_create(128); if(_epfd0) { std::coutepoll create error, strerror(errno)std::endl; exit(-1); } // 2.将listen套接字加入到epoll模型中 // 先构建一个事件结构体 struct epoll_event ev; ev.events|EPOLLIN; ev.data.fd_socket-GetSockfd(); int n epoll_ctl(_epfd, EPOLL_CTL_ADD, _socket-GetSockfd(), ev); // 成功返回0 if(n!0) { std::coutepoll control error, strerror(errno)std::endl; exit(-1); } } void HandlerEvents(int num) { std::cout有Numnum个事件就绪了std::endl; } void Loop() { while(true) { int nepoll_wait(_epfd, _events, gnum, 1000); if(n0) { // 事件没有就绪 std::couttime out...std::endl; } else if(n0) { // 有n个事件就绪了 HandlerEvents(n); } else { if(errnoEINTR) continue; std::coutepoll wait fail, strerror(errno)std::endl; exit(-1); } } } private: int _epfd; uint16_t _port; std::unique_ptrTcpSocket _socket; struct epoll_event _events[gnum]; };TcpSocket.hpp#pragma once #include iostream #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include cstring class InetAddr { public: InetAddr() {} InetAddr(const std::stringip, uint16_t port) :_ip(ip) ,_port(port) { memset(_sockaddr, 0, sizeof(_sockaddr)); _sockaddr.sin_familyAF_INET; _sockaddr.sin_porthtons(port); _sockaddr.sin_addr.s_addrinet_addr(_ip.c_str()); } std::string Ip() { return _ip; } uint16_t Port() { return _port; } // 将addr里面的数据拷贝给自己 void GetInetAddr(sockaddr_in*addr) { memcpy(_sockaddr, addr, sizeof(*addr)); _portntohs(addr-sin_port); _ipinet_ntoa(addr-sin_addr); } private: struct sockaddr_in _sockaddr; std::string _ip; uint16_t _port; }; class TcpSocket { public: TcpSocket(uint16_t port) :_port(port) { _listensocketsocket(AF_INET, SOCK_STREAM, 0); if(_listensocket0) { std::cout监听套接字失败std::endl; exit(-1); } // 设置地址复用 int opt1; setsockopt(_listensocket, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); // bind struct sockaddr_in local; memset(local, 0, sizeof(local)); local.sin_familyAF_INET; local.sin_porthtons(port); local.sin_addr.s_addrINADDR_ANY; int nbind(_listensocket, (struct sockaddr*)local, sizeof(local)); if(n0) { std::cout绑定失败std::endl; exit(-1); } // 开启监听 n::listen(_listensocket, _defaultBacklog); if(n0) { std::coutTCP监听失败std::endl; exit(-1); } } int Accept(InetAddr*addr) { struct sockaddr_in peer; socklen_t lensizeof(peer); int sockfdaccept(_listensocket, (struct sockaddr*)peer, len); addr-GetInetAddr(peer); return sockfd; } int GetSockfd() { return _listensocket; } private: uint16_t _port; int _listensocket; // 类内常量所有类对象共享一份 const static int _defaultBacklog16; };Main.cc#include iostream #include EpollServer.hpp int main() { std::unique_ptrEpollServer serverstd::make_uniqueEpollServer(8080); server-Loop(); return 0; }makefiletest_server:Main.cc g -g -o $ $^ -stdc14 .PHONY:clean clean: rm -f test_server
延伸阅读

更多相关文章

2026/10/6 15:15:36

Ansible Playbook使用案例

Ansible Loop 循环Ansible 的 loop 循环功能允许您对一组数据项重复执行同一任务,从而简化批量操作。本节将通过创建用户账户的实例,演示 loop 的基本用法及其进阶应用。1. 1 基础循环:创建用户以下 Playbook 使用 loop 在目标主机组 db 上创…

2026/10/6 16:41:26

从OpenClaw到Hermes Agent:AI Agent开发框架的开发者体验演进

1. 项目概述:一场开发者工具的“静默迁徙”最近在AI Agent开发圈里,我观察到一个挺有意思的现象:身边不少老伙计,包括一些之前对OpenClaw推崇备至的朋友,都开始默默地转向了另一个工具——Hermes Agent。这不像是一场轰…

2026/10/6 16:40:41

LeetCode刷题的本质:从应试技巧到工程能力的深度转化策略

1. 这篇文章真正要解决的问题如果你是一名正在准备技术面试的开发者,或者是一名计算机专业的学生,那么“刷LeetCode”这个词对你来说一定不陌生。它几乎是通往大厂Offer的必经之路,是无数人投入数百小时甚至上千小时去重复练习的“标准动作”…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/7 8:18:33

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑