Swoole 源码分析之 epoll 多路复用模块
首发原文链接:Swoole 源码分析之 Http Server 模块
大家好,我是码农先森。
引言
在传统的IO模型中,每个IO操作都需要创建一个单独的线程或进程来处理,这样的操作会导致系统资源的大量消耗和管理开销。
而IO多路复用技术通过使用少量的线程或进程同时监视多个IO事件,能够更高效地处理大量的IO操作,从而提高系统的性能和资源利用率。
在IO多路复用的技术中尤其突出的是 epoll 技术,它是解决 C10K 问题的利器。
Swoole 中的多路复用
多路复用技术可以说是贯穿了整个 Swoole,同时也是 Swoole 为什么是高性能通信框架的根本原因。
Swoole 最重要的协程模块就是利用的 IO 多路复用事件循环技术,这也是与 Go 语言中协程不同的本质原因。
下面我们来一起看下 Swoole 中是如何实现 epoll 多路复用技术的。

这是创建 eoll 实例的方法,其中的 Reactor 是一个线程对象。
// 创建一个 epoll 实例,为其分配事件数组,并设置相关的 reactor 对象属性
// swoole-src/src/reactor/epoll.cc:71
ReactorEpoll::ReactorEpoll(Reactor *_reactor, int max_events) : ReactorImpl(_reactor) {
// 创建一个 epoll 实例
epfd_ = epoll_create(512);
// 检查 epoll 创建是否成功
if (!ready()) {
swoole_sys_warning("epoll_create failed");
return;
}
// epoll_event 结构体数组分配内存
// 用于存储注册到 epoll 实例上的事件
events_ = new struct epoll_event[max_events];
// 设置最大事件数量
reactor_->max_event_num = max_events;
// native_handle 设置为 epoll 实例
reactor_->native_handle = epfd_;
}
这个方法是向 epoll 事件循环中添加一个客户端的连接对象,用于监听。
// 向 epoll 事件循环中添加一个 socket,并为其设置特定的事件监听
// swoole-src/src/reactor/epoll.cc:94
int ReactorEpoll::add(Socket *socket, int events) {
// 定义 epoll_event 结构体实例 e
struct epoll_event e;
// 设置事件类型
e.events = get_events(events);
// 设置 socket 指针,在 epoll 触发事件时,可以找到对应的 socket
e.data.ptr = socket;
// 添加事件到 epoll
if (epoll_ctl(epfd_, EPOLL_CTL_ADD, socket->fd, &e) < 0) {
swoole_sys_warning(
"failed to add events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events);
return SW_ERR;
}
// 在 Reactor 中添加 socket
// 为了在 Reactor 内部进行管理和跟踪
reactor_->_add(socket, events);
swoole_trace_log(
SW_TRACE_EVENT, "add events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events);
return SW_OK;
}
这个方法是从 epoll 事件循环中移除一个客户端连接对象。
// 从 epoll 事件循环中删除一个 socket
// swoole-src/src/reactor/epoll.cc:113
int ReactorEpoll::del(Socket *_socket) {
// 检查 socket 是否已被移除
if (_socket->removed) {
swoole_error_log(SW_LOG_WARNING,
SW_ERROR_EVENT_SOCKET_REMOVED,
"failed to delete events[fd=%d, fd_type=%d], it has already been removed",
_socket->fd, _socket->fd_type);
return SW_ERR;
}
// 使用 epoll_ctl 函数从 epoll 的文件描述符 epfd_ 中删除 socket
if (epoll_ctl(epfd_, EPOLL_CTL_DEL, _socket->fd, nullptr) < 0) {
after_removal_failure(_socket);
if (errno != EBADF && errno != ENOENT) {
return SW_ERR;
}
}
swoole_trace_log(SW_TRACE_REACTOR, "remove event[reactor_id=%d|fd=%d]", reactor_->id, _socket->fd);
// 从 Reactor 中删除该 socket
reactor_->_del(_socket);
return SW_OK;
}
这个方法是用于修改一个已经在 epoll 事件循环中的客户端连接对象。
// 修改一个已经存在于 epoll 事件循环中的 socket 的事件监听类型
// swoole-src/src/reactor/epoll.cc:134
int ReactorEpoll::set(Socket *socket, int events) {
// 定义 epoll_event 结构体实例 e
struct epoll_event e;
// 设置事件类型
e.events = get_events(events);
// 设置 socket 指针,在 epoll 触发事件时,可以找到对应的 socket
e.data.ptr = socket;
// 使用 epoll_ctl 函数修改 epoll 文件描述符 epfd_ 中对应 socket 的事件
int ret = epoll_ctl(epfd_, EPOLL_CTL_MOD, socket->fd, &e);
if (ret < 0) {
swoole_sys_warning(
"failed to set events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events);
return SW_ERR;
}
swoole_trace_log(SW_TRACE_EVENT, "set event[reactor_id=%d, fd=%d, events=%d]", reactor_->id, socket->fd, events);
// 在 Reactor 内部进行相应的设置
reactor_->_set(socket, events);
return SW_OK;
}
这个方法是 epoll 事件循环环节中最重要的一点,开始等待 Socket IO事件的触发,并且调用对应的处理函数。
// swoole-src/src/reactor/epoll.cc:153
int ReactorEpoll::wait(struct timeval *timeo) {
// 声明事件对象 event、Reactor 处理对象 handler
Event event;
ReactorHandler handler;
int i, n, ret;
// reactor 对象 ID 和 最大事件数量
int reactor_id = reactor_->id;
int max_event_num = reactor_->max_event_num;
// 用于设置超时时间,如果 timeout_msec 为 0,则根据传入的 timeo 参数设置超时时间
if (reactor_->timeout_msec == 0) {
if (timeo == nullptr) {
reactor_->timeout_msec = -1;
} else {
reactor_->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000;
}
}
// 在进入事件循环之前调用 before_wait 方法,表示准备开始等待事件
reactor_->before_wait();
while (reactor_->running) {
// 如果定义了 onBegin 回调函数,则调用它来执行相应的操作
if (reactor_->onBegin != nullptr) {
reactor_->onBegin(reactor_);
}
// 调用 epoll_wait 函数获取就绪事件的数量
n = epoll_wait(epfd_, events_, max_event_num, reactor_->get_timeout_msec());
if (n < 0) {
// 如果出现错误且不捕获错误,则打印错误信息并返回错误码
if (!reactor_->catch_error()) {
swoole_sys_warning("[Reactor#%d] epoll_wait failed", reactor_id);
return SW_ERR;
} else {
goto _continue;
}
} else if (n == 0) {
// 如果返回的就绪事件数为 0,则执行结束回调函数并继续下一轮循环。
reactor_->execute_end_callbacks(true);
SW_REACTOR_CONTINUE;
}
for (i = 0; i < n; i++) {
// 在处理每个就绪事件时,将事件相关信息保存在event对象中
event.reactor_id = reactor_id;
event.socket = (Socket *) events_[i].data.ptr;
event.type = event.socket->fd_type;
event.fd = event.socket->fd;
// 如果事件类型是 EPOLLRDHUP、EPOLLERR 或 EPOLLHUP 之一,则设置 event_hup 标志为 1。
if (events_[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) {
event.socket->event_hup = 1;
}
// 检查是否存在可读事件且套接字未被移除
// read 如果是可读事件(EPOLLIN),则调用相应的读事件处理器
if ((events_[i].events & EPOLLIN) && !event.socket->removed) {
handler = reactor_->get_handler(SW_EVENT_READ, event.type);
ret = handler(reactor_, &event);
if (ret < 0) {
swoole_sys_warning("EPOLLIN handle failed. fd=%d", event.fd);
}
}
// 检查是否存在可写事件且套接字未被移除
// write 如果是可写事件(EPOLLOUT),则调用相应的写事件处理器。
if ((events_[i].events & EPOLLOUT) && !event.socket->removed) {
handler = reactor_->get_handler(SW_EVENT_WRITE, event.type);
ret = handler(reactor_, &event);
if (ret < 0) {
swoole_sys_warning("EPOLLOUT handle failed. fd=%d", event.fd);
}
}
// error 如果是错误事件(EPOLLRDHUP、EPOLLERR、EPOLLHUP),则调用相应的错误事件处理器。
if ((events_[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) && !event.socket->removed) {
// ignore ERR and HUP, because event is already processed at IN and OUT handler.
if ((events_[i].events & EPOLLIN) || (events_[i].events & EPOLLOUT)) {
continue;
}
handler = reactor_->get_error_handler(event.type);
ret = handler(reactor_, &event);
if (ret < 0) {
swoole_sys_warning("EPOLLERR handle failed. fd=%d", event.fd);
}
}
// 在处理完事件后,检查是否需要执行一次性事件的删除操作
if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) {
reactor_->_del(event.socket);
}
}
_continue:
// 在事件循环中执行回调函数并继续下一轮循环
reactor_->execute_end_callbacks(false);
SW_REACTOR_CONTINUE;
}
return 0;
}
总结
- epoll 在内部使用了红黑树的数据结构,红黑树是一个高效的数据结构。
- epoll 是解决 C10K 问题的利器,不仅是在 Swoole 中被应用,在很多的高性能服务中也有应用,例如:Nginx 服务等。
- Swoole 被称为高性能通信框架的关键原因,就是采用了 epoll 多路复用技术。
Swoole 源码分析之 epoll 多路复用模块的更多相关文章
- jQuery1.9.1源码分析--数据缓存Data模块
jQuery1.9.1源码分析--数据缓存Data模块 阅读目录 jQuery API中Data的基本使用方法介绍 jQuery.acceptData(elem)源码分析 jQuery.data(el ...
- jQuery 源码分析(十) 数据缓存模块 data详解
jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...
- Hadoop2源码分析-HDFS核心模块分析
1.概述 这篇博客接着<Hadoop2源码分析-RPC机制初识>来讲述,前面我们对MapReduce.序列化.RPC进行了分析和探索,对Hadoop V2的这些模块都有了大致的了解,通过对 ...
- Tornado源码分析 --- 静态文件处理模块
每个web框架都会有对静态文件的处理支持,下面对于Tornado的静态文件的处理模块的源码进行分析,以加强自己对静态文件处理的理解. 先从Tornado的主要模块 web.py 入手,可以看到在App ...
- Python 源码分析:queue 队列模块
起步 queue 模块提供适用于多线程编程的先进先出(FIFO)数据结构.因为它是线程安全的,所以多个线程很轻松地使用同一个实例. 源码分析 先从初始化的函数来看: 从这初始化函数能得到哪些信息呢?首 ...
- jQuery 源码分析(十六) 事件系统模块 底层方法 详解
jQuery事件系统并没有将事件监听函数直接绑定到DOM元素上,而是基于数据缓存模块来管理监听函数的,事件模块代码有点多,我把它分为了三个部分:分底层方法.实例方法和便捷方法.ready事件来讲,好理 ...
- jQuery 源码分析(十三) 数据操作模块 DOM属性 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DO ...
- jQuery源码分析(九) 异步队列模块 Deferred 详解
deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...
- WebRTC源码分析四:视频模块结构
转自:http://blog.csdn.net/neustar1/article/details/19492113 本文在上篇的基础上介绍WebRTC视频部分的模块结构,以进一步了解其实现框架,只有了 ...
- spring源码分析之spring-web web模块分析
0 概述 spring-web的web模块是更高一层的抽象,它封装了快速开发spring-web需要的基础组件.其结构如下: 1. 初始化Initializer部分 1.1 Servlet3.0 的 ...
随机推荐
- Django Admin:自动选择当前用户
--转载自:林肯李 该文章写的很好,特转载留待后期备用 背景 在开发 CMS 时,经常需要标记谁创建了记录.Django 为我们提供了一个很好的管理界面.但是当我们只使用默认值时,用户需要自己选择他们 ...
- 高并发场景QPS等专业指标揭秘大全与调优实战
高并发场景QPS等专业指标揭秘大全与调优实战 最近经常有小伙伴问及高并发场景下QPS的一些问题,特意结合项目经验和网上技术贴做了一些整理和归纳,供大家参考交流. 一.一直再说高并发,多少QPS才算高并 ...
- CentOS-6.5快速搭建HTTP服务器和仅供授权用户登陆的FTP服务器
CentOS-6.5快速搭建HTTP服务器和仅供授权用户登陆的FTP服务器 (2014-01-09 21:29:31) 转载▼ 标签: linux centos 服务器 http vsftp 分类:& ...
- sql 语句系列(闰年)[八百章之第十九章]
前言 判断闰年还是挺有用的. mysql select DAY(LAST_DAY(DATE_ADD(CURRENT_DATE,INTERVAL -DAYOFYEAR(CURRENT_DATE)+1+3 ...
- redis和memcached的区别和使用场景
Redis 和 Memcached 都是基于内存的数据存储系统.Memcached是高性能分布式内存缓存服务,其本质上就是一个内存key-value数据库.Redis是一个开源的key-value存储 ...
- 《c#高级编程》第2章C#2.0中的更改(二)——匿名类型
一.概念 C#中的匿名类型是一种特殊类型,可以在运行时动态创建一个对象,该对象可以包含多个属性,这些属性的名称和类型可以在创建时指定.相对于定义具体的类,匿名类型更加灵活和简洁. C#的匿名类型通常用 ...
- easyx的使用 鼠标交互(3.1)
本文学习于B站,进行借鉴学习记录: 视频链接:鼠标操作(新版)_哔哩哔哩_bilibili 初始化调用文件头不再使用#include<graphics.h>,选择调用#include< ...
- 力扣507(java)-完美数(简单)
题目: 对于一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 「完美数」. 给定一个 整数 n, 如果是完美数,返回 true:否则返回 false. 示例 1: 输入:num ...
- Apsara Stack 技术百科 | 联结良性生态,筑千行百业的数字基石
简介:作为现今IT领域最重要的课题:基础设施云化,离不开与伙伴的携手合作,如何让云上解决方案能充分释放价值的同时形成一个相互依存的自循环生态系统,混合云君来跟你聊聊! 生态系统这个词在维基百科上 ...
- 一文了解阿里一站式图计算平台GraphScope
简介: 随着大数据的爆发,图数据的应用规模不断增长,现有的图计算系统仍然存在一定的局限.阿里巴巴拥有全球最大的商品知识图谱,在丰富的图场景和真实应用的驱动下,阿里巴巴达摩院智能计算实验室研发并开源了全 ...