当前位置: 首页 > news >正文

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 的大致流程为:

  1. 调用epoll_create创建一个 epoll 模型,获取该模型的句柄(本质也是一个文件描述符)

  2. 调用epoll_ctl,将需要监听的文件描述符添加到 epoll 模型中进行事件检测

  3. 在事件循环中调用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);

参数说明:

  • epfd:epoll 模型的句柄

  • 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);

参数说明:

  • epfd:epoll 模型的句柄

  • 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); }

代码样例——EpollServerDemo

EpollServer.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 N=sizeof(fd_set)*8; const static int defaultfd=-1; const static int gnum=64; public: EpollServer(uint16_t port) :_port(port) ,_socket(std::make_unique<TcpSocket>(port)) ,_epfd(defaultfd) { // 1.创建epoll模型 _epfd=epoll_create(128); if(_epfd<0) { std::cout<<"epoll 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::cout<<"epoll control error, "<<strerror(errno)<<std::endl; exit(-1); } } void HandlerEvents(int num) { std::cout<<"有Num:"<<num<<"个事件就绪了"<<std::endl; } void Loop() { while(true) { 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); } } } private: int _epfd; uint16_t _port; std::unique_ptr<TcpSocket> _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::string&ip, uint16_t port) :_ip(ip) ,_port(port) { memset(&_sockaddr, 0, sizeof(_sockaddr)); _sockaddr.sin_family=AF_INET; _sockaddr.sin_port=htons(port); _sockaddr.sin_addr.s_addr=inet_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)); _port=ntohs(addr->sin_port); _ip=inet_ntoa(addr->sin_addr); } private: struct sockaddr_in _sockaddr; std::string _ip; uint16_t _port; }; class TcpSocket { public: TcpSocket(uint16_t port) :_port(port) { _listensocket=socket(AF_INET, SOCK_STREAM, 0); if(_listensocket<0) { std::cout<<"监听套接字失败"<<std::endl; exit(-1); } // 设置地址复用 int opt=1; setsockopt(_listensocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 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; int n=bind(_listensocket, (struct sockaddr*)&local, sizeof(local)); if(n<0) { std::cout<<"绑定失败"<<std::endl; exit(-1); } // 开启监听 n=::listen(_listensocket, _defaultBacklog); if(n<0) { std::cout<<"TCP监听失败"<<std::endl; exit(-1); } } int Accept(InetAddr*addr) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int sockfd=accept(_listensocket, (struct sockaddr*)&peer, &len); addr->GetInetAddr(&peer); return sockfd; } int GetSockfd() { return _listensocket; } private: uint16_t _port; int _listensocket; // 类内常量,所有类对象共享一份 const static int _defaultBacklog=16; };

Main.cc

#include <iostream> #include "EpollServer.hpp" int main() { std::unique_ptr<EpollServer> server=std::make_unique<EpollServer>(8080); server->Loop(); return 0; }

makefile

test_server:Main.cc g++ -g -o $@ $^ -std=c++14 .PHONY:clean clean: rm -f test_server
http://www.jsqmd.com/news/1402695/

相关文章:

  • 个人微信API如何改变传统微信应用?4个技术优势让开发效率翻倍
  • Cursor 起草 + GPT 终审:省下 60% 成本后,我连夜关了回灌机制
  • 2026年评价高的GEO关键词优化服务商推荐,行业全景分析 - 工业设备
  • DB2存储过程SQLSTATE 22018错误:数据类型转换失败的系统排查与解决方案
  • WPS打不出英文引号?从输入法到系统设置的完整排查指南
  • 从零打造双核智能车:STM32+ESP32-S3硬件设计与固件开发全攻略
  • 深入解析AXI事务属性:缓存、保护与QoS配置实战指南
  • 基于腾讯云ClawPro构建企业级微信AI助手:从架构设计到实战部署
  • 自产自装景观亭厂家的优势分析
  • 缓存剔除算法 (LRU / LFU / ARC / LIRS) 深度剖析
  • 三星SCX-3406W无线打印配置全攻略:从网络连接到多设备打印
  • 自动化的暑假记录
  • 2026铁氟龙高温布十大热门厂家真实横评,选定再拍不交智商税 - 工业设备
  • DM数据库触发器深度解析:从原理到实战的完整指南
  • 论文AI率过高问题及DeepSeek降AI率实战方案
  • 低成本构建AI数据分析系统:DeepSeek V4与Codex集成实战指南
  • 虚拟键值表系统:从输入事件到业务逻辑的解耦实践
  • 从模型幻觉到工程实践:构建生产级大语言模型Prompt的完整指南
  • IBM Storwize V7000后台命令全解析:从基础操作到自动化运维实战
  • 【一个小游戏教你拿捏面向对象】
  • 《骑马与砍杀2》DLL文件修改指南:从反编译到实战,轻松调整游戏核心机制
  • 基于OpenClaw与go-cqhttp构建智能QQ机器人:从大模型集成到实战部署
  • MobaXterm 终极指南:从零掌握 Windows 最强集成终端工具
  • C++模板元编程:让编译器帮你“算出“程序
  • LeetCode刷题的本质:从应试技巧到工程能力的深度转化策略
  • 基于微信小程序的泉州旅游小程序设计与实现毕业设计项目源码
  • 宽压降压新利器|CN8820 100V 异步降压 DC-DC,车载 / 工业 IoT 全能电源方案
  • 指甲与结膜图像贫血检测数据集
  • Android Studio设备连接故障排查:从ADB原理到实战解决Loading Devices问题
  • RAG技术详解:从原理到实战,构建高效检索增强生成系统