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

【IO多路转接】epoll 高性能网络编程:从底层机制到服务器实战 - 教程


请添加图片描述


半桔:个人主页

  个人专栏: 《IO多路转接》《手撕面试算法》《C++从入门到入土》

世界上有太多孤独的人,害怕先踏出第一步。《绿皮书》

文章目录

  • 前言
  • 一. epoll模型的底层原理
    • 1.1 红黑树
    • 1.2 就绪队列
  • 二. epoll 接口
  • 三. epoll 服务器实现
    • 3.1 网络套接字接口封装
    • 3.2 对epoll接口进行封装
    • 3.3 设计epoll服务器的类
    • 3.4 进行初始化
    • 3.5 对任务进行派发
    • 3.6 服务器主循环函数
  • 四. epoll模型的优势

前言

在 Linux 网络编程领域,IO 多路复用是支撑高并发网络服务的核心技术基石。当服务端需要同时应对成百上千甚至数万级的客户端连接时,如何高效监控、响应这些连接的 IO 事件,直接决定了服务器的性能上限。

传统的selectpoll模型,受限于文件描述符管理效率、事件通知机制等缺陷(如select的文件描述符数量上限、每次轮询的线性遍历开销等),在高并发场景下逐渐力不从心。而epoll作为 Linux 专为解决高并发 IO 痛点设计的多路复用模型,凭借更高效的事件通知、更灵活的文件描述符管理,成为了 Nginx、Redis 等高性能中间件背后的 “性能引擎”。

为了帮助开发者系统掌握epoll,本文将从 “底层逻辑”“工程实现”“技术优势” 三个维度展开解析:

  1. 先深入epoll的底层原理,剖析红黑树、就绪队列的设计智慧与epoll接口的工作机制;
  2. epoll服务器的落地实践,从网络套接字封装、接口二次封装,到服务器类设计、初始化、任务派发与主循环的构建,逐步讲解高并发服务器的实现路径;
  3. 最后总结epoll相对于传统模型的核心优势,让你清晰理解它在高并发场景下的不可替代性。

希望通过本文,你能对epoll的 “原理 - 实现 - 价值” 形成完整认知,为高性能网络编程实践筑牢基础。

一. epoll模型的底层原理

epoll是效率最高的多路转接方案。
在了解epoll的接口之前,我们有必要先学习以下epoll的底层原理,其是如何实现最高效率的。

如下图所示就是一个epoll原理示意图:请添加图片描述

1.1 红黑树

  • epoll模型中,存在一颗红黑树,由操作系统进行管理和维护,内部存储了所有我们要进行等待的文件描述符以及需要进行等待的时间;
  • 当对应的文件中有数据了,就会向CPU发送中断信号,CPU会执行操作系统中的中断向量表中的方法,将内容进来,同时操作系统快速查找对应文件描述符在红黑树的位置,并将其添加到就绪队列中

1.2 就绪队列

操作系统会将所有读写时间就绪的文件描述符添加到就绪队列中,当上层调用epoll的时候,可以直接将该就绪队列拷贝出去就行了


通过上述就绪队列来让上层获取满足条件的文件描述符就可以避免像select,poll一样对整个数组进行访问,来看那些文件就绪。大大提高了效率。

整体步骤就是如下:

  1. 上层创建一个红黑树;
  2. 上层将要进行等待的文件描述符添加到红黑树中;
  3. 操作系统将就绪的文件描述符添加到就绪队列中;
  4. 上层要进行获取时,操作系统直接将就绪队列拷贝出去即可。

下面进行接口介绍,解释上述步骤在进行实现的时候要调用那些接口。

二. epoll 接口

首先就是创建一颗红黑树:

int epoll_create(int size)

  1. 参数:已经被弃用了,没有实际意义,传入一个大于0的数即可;
  2. 返回值:返回一个文件描述符,用来对epoll模型进行管理。

接下来就是向红黑树中增加文件,删除文件,以及进行修改,这三种使用的都是同一个接口:

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

  1. 参数一:要进行操作的epoll模型,就是在创建epoll模型时返回的文件描述符;
  2. 参数二:op表示要进行的操作,其中包含:EPOLL_CTL_ADD , EPOLL_CTL_MOD , EPOLL_CTL_DEL分别表示对红黑树进行增加节点,修改节点,删除节点;
  3. 参数三fd:表示要进行等待的文件描述符;

struct epoll_event是操作系统内提供的一个结构体:

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events;   // 要进行等待的事件,读/写/异常
epoll_data_t data;   // 用户数据变量,可以存储触发事件的文件描述符相关的数据。
};
  1. 参数四:存储对应的文件描述符的等待事件,以及用户数据变量,可以存储触发事件的文件描述符相关的数据。
  2. 返回值:表示有多少个文件描述符资源已经就绪。

最后一个接口就是让epoll模型返回就绪队列:
int epoll_wait(int epfd , struct epoll_event *evemts , int maxevents , int timeout)

  1. 参数一:文件描述符;
  2. 参数二:表明将就绪队列放在哪里;
  3. 参数三:参数二的长度,外界最多可以获取多少就绪的文件描述符;
  4. 参数四:进行等待的时间,当时间到/有文件就绪就进行返回;
  5. 返回值:表示有多少文件资源已经就绪。

三. epoll 服务器实现

此处我们只进行一个简单的服务器实现,不进行过多设计,只是简单使用一下对应接口。此处我们在进行设计的时候假设:接收到的TCP报文是完整的。

3.1 网络套接字接口封装

关于网络套接字接口的封装,此处就不再展开说明了,有兴趣的可以看我之前关于TCP的文章;此处直接贴实现:

const std::string defaultip_ = "0.0.0.0";
enum SockErr
{
SOCKET_Err,
BIND_Err,
};
class Sock
{
public:
Sock(uint16_t port)
: port_(port),
listensockfd_(-1)
{
}
void Socket()
{
listensockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensockfd_ < 0)
{
Log(Fatal) << "socket fail";
exit(SOCKET_Err);
}
Log(Info) << "socket sucess";
}
void Bind()
{
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port_);
inet_pton(AF_INET, defaultip_.c_str(), &server.sin_addr);
if (bind(listensockfd_, (struct sockaddr *)&server, sizeof(server)) < 0)
{
Log(Fatal) << "bind fail";
exit(BIND_Err);
}
Log(Info) << "bind sucess";
}
void Listen()
{
if (listen(listensockfd_, 10) < 0)
{
Log(Warning) << "listen fail";
}
Log(Info) << "listen sucess";
}
int Accept()
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int fd = accept(listensockfd_ , (sockaddr*)&client , &len);
if(fd < 0)
{
Log(Warning) << "accept fail";
}
return fd;
}
int Accept(std::string& ip , uint16_t& port)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int fd = accept(listensockfd_ , (sockaddr*)&client , &len);
if(fd < 0)
{
Log(Warning) << "accept fail";
}
port = ntohs(client.sin_port);
char bufferip[64];
inet_ntop(AF_INET , &client.sin_addr , bufferip , sizeof(bufferip) - 1);
ip = bufferip;
return fd;
}
int Get_fd()
{
return listensockfd_;
}
~Sock()
{
close(listensockfd_);
}
private:
uint16_t port_;
int listensockfd_;
};

3.2 对epoll接口进行封装

为了我们后续方便使用,我们此处对epoll的相关接口进行简单封装:

enum EpollErr
{
CREAR_Err,
};
class Epoll
{
public:
Epoll()
{
// 创建epoll模型
_epfd = epoll_create(1);
if (_epfd < 0)
{
Log(Fatal) << "epoll_create fail";
exit(CREAR_Err);
}
Log(Info) << "epoll create sucess ";
}
void Add_fd(int fd, uint32_t event)
{
// 添加文件描述符到红黑树中
struct epoll_event epevt;
epevt.events = event;
epevt.data.fd = fd;
if (epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &epevt) < 0)
{
Log(Warning) << "epoll add error : " << strerror(errno);
}
Log(Info) << "epoll add sucess , fd : " << fd ;
}
void Del_fd(int fd)
{
// 删除要进行等待的文件描述符
if (epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr) < 0)
{
Log(Warning) << "epoll del error : " << strerror(errno);
}
Log(Info) << "epoll del sucess  , fd : " << fd;
}
void Mod_fd(int fd, uint32_t event)
{
// 对文件描述符的事件进行修改
struct epoll_event epevt;
epevt.events = event;
epevt.data.fd = fd;
if (epoll_ctl(_epfd, EPOLL_CTL_MOD, fd, &epevt) < 0)
{
Log(Warning) << "epoll mod error : " << strerror(errno);
}
Log(Info) << "epoll mod sucess , fd : " << fd ;
}
int Wait(struct epoll_event *ep_array, int max_size, int timeout)
{
// 进行等待
return epoll_wait(_epfd, ep_array, max_size, timeout);
}
private:
int _epfd;
};

3.3 设计epoll服务器的类

  1. Sock对象,进行网络通信;
  2. Epoll对象,来使用epoll的接口;
  3. 一个struct epoll_event的数组,在调用epoll_wait接口时进行使用。
class Epollserver
{
static const int default_array_num = 1024;
public:
Epollserver(uint16_t port)
:_epoll_ptr(new Epoll) ,
_sock_ptr(new Sock(port))
{}
private:
std::shared_ptr<Epoll> _epoll_ptr;std::shared_ptr<Sock> _sock_ptr;struct epoll_event _ep_array[default_array_num];};

3.4 进行初始化

  1. 创建套接字
  2. 进行绑定
  3. 设置监听
  4. 将网络套接字,加入到epol1模型中
void Init()
{
// 1. 创建套接字
// 2. 进行绑定
// 3. 设置监听
// 4. 将网络套接字 ,加入到epoll模型中
_sock_ptr->Socket();
_sock_ptr->Bind();
_sock_ptr->Listen();
_epoll_ptr->Add_fd(_sock_ptr->Get_fd() , EPOLLIN);
}

3.5 对任务进行派发

因为存在两种文件描述符,因此我们需要对不同文件的处理分开:

  1. 对于网络套接字,就获取连接;
  2. 对于普通文件描述符,就将数据读取上来,在进行返回。
void Sockfd_Ready()
{
// 网络套接字就绪
// 1. 获取建立文件描述符
// 2. 将文件描述符加入到epoll模型中
int newfd = _sock_ptr->Accept();
_epoll_ptr->Add_fd(newfd, EPOLLIN);
}
void Normalfd_Ready(int fd)
{
// 普通文件描述符就绪
// 1. 将数据读取上来
// 2. 向用户端返回信息
char buffer[1024];
int n = read(fd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::string ret = "server got a message : ";
ret += buffer;
write(fd, ret.c_str(), ret.size());
}
else if (n == 0)
{
// 对方将文件关闭
// 1. 此处我们也就不需要在进行等待了, 从epoll模型中移除
// 2. 关闭文件描述符
_epoll_ptr->Del_fd(fd);
close(fd);
}
else
{
// 出错了
// 1. 打印日志
// 2. 将文件描述符从epoll模型中移除
// 3. 关闭文件描述符
Log(Warning) << "read fail";
_epoll_ptr->Del_fd(fd);
close(fd);
}
}
void Dispatcher(int n)
{
int listensock = _sock_ptr->Get_fd();
for (int i = 0; i < n; i++)
{
int fd = _ep_array[i].data.fd;
if (fd == listensock)
{
Sockfd_Ready();
}
else
{
Normalfd_Ready(fd);
}
}
}

3.6 服务器主循环函数

此时就可以根据上述接口进行编写主循环函数了,主循环函数只需要负责将epoll模型中的就绪队列中的数据拿出来就行了。

void Run()
{
while (1)
{
int n = _epoll_ptr->Wait(_ep_array, default_array_num, -1);
if (n > 0)
{
Dispatcher(n);
}
else if( n == 0)
{
Log(Info) << "no file is ready";
}
else
{
Log(Warning) << "epoll wait fail";
}
}
}

四. epoll模型的优势

  1. 检测是否存在就绪的文件时间复杂度为O(1),当然获取的还是要进行拷贝;
  2. epoll模型会帮我们维护要进行检测的文件描述符,不需要我们再设计函数自己维护;
  3. epoll模型返回的就是就绪的文件,不需要再进行判断是否满足条件了;
  4. 要进行检测的文件数量没有限制。
http://www.jsqmd.com/news/58373/

相关文章:

  • “骑跑中国” 重庆站开赛,600 组家庭解锁全民健身新赛道
  • 仓库货架公司推荐,钢制货架/冷库货架/托盘货架/组合式货架/精益管料架/金属货架/仓库货架产品有哪些
  • 2025年高端家具TOP10权威榜单揭晓:真实排名颠覆想象
  • 技术强管理规范的源头厂家甄选指南,助力企业降本提效
  • 嘉峪关青少年飞盘赛开赛,100 余名小将默契比拼
  • 2025年耐高温硅胶线批发厂家权威推荐榜单:硅胶线‌/PVC电子线‌/硅胶数据线‌源头厂家精选
  • 华润饮料中超第 30 轮激战,北京国安 7-0 大胜云南玉昆
  • 湖北男足点球大战险胜广东,首夺全运会男足冠军
  • 第五届北京 BMX 小轮车公开赛收官,青少年展现极限活力
  • Studio 3T 2025.22 发布 - MongoDB 的终极 GUI、IDE 和 客户端
  • 利用梯度下降求一个凸函数的最小值
  • 2025年12月深圳AI搜索优化排名公司推荐:技术领航与性价比之选
  • 防脱洗发水哪个好用?实测这几款防脱洗发水,针对不同脱发类型有效防脱
  • 2025国内企业如何选择国际短信平台?国际物流通知短信平台,全球覆盖、成本控制与高并发能力十强全解析
  • 荷球青少年联赛开赛,河南 50 支队伍同场竞技
  • 商标转让平台有哪些?买商标最值得推荐的商标交易平台大盘点!
  • 2025年狗狗止痒短期方案十大品牌排行榜,快速止痒应急办法精
  • 万乐天摘得全运会女子 50 米仰泳金牌,汪雪儿获银
  • 江苏女足压哨绝杀,斩获全运会女足冠军
  • 王楚钦 4-2 林高远,晋级全运会乒乓球男单四强
  • 《独立开发者精选工具》第 023 期
  • [转]Java使用Milo实现OPC UA客户端及服务端,操作uaexpert工具测试(史上最详细讲解)
  • 允许mysql数据其他pc访问
  • 2025年实木椅源头厂家权威推荐榜单:实木家具‌/书柜‌/电视柜‌源头厂家精选
  • 上海、天津、南京、北京、杭州家具定制公司TOP4推荐,上海拉迷领先!
  • 2025年木门十大品牌权威解析 | 国内一线品牌综合实力排行榜
  • 2025 年聚四氟乙烯厂家最新推荐榜,技术实力与市场口碑深度解析,筛选靠谱优质企业聚四氟乙烯膜/聚四氟乙烯板/聚四氟乙烯一体槽/聚四氟乙烯加工件公司推荐
  • 2025年建筑设备一体化监控系统工厂权威推荐榜单:建筑设备监控系统‌/建筑设备管理系统‌/万盟智控‌源头工厂精选
  • [转]Java实现OPC UA客户端
  • 2025 年蛋液厂家最新推荐榜,聚焦企业技术实力与市场口碑深度解析冷冻全蛋液/炸蛋专用蛋液/蛋饺专用蛋液/餐饮专用蛋液/巴氏杀菌全蛋液/蛋白液/蛋黄液公司推荐