从零手写高性能C++ TCP 服务器框架(九) --- EventLoop 实现
从零手写高性能C++ TCP 服务器框架(九) --- EventLoop 实现
一、前置知识
eventfd
eventfd 是 Linux 提供的一种轻量级事件通知机制,创建一个描述符用于实现事件通知。
eventfd 本质是在内核里面的一个计数器,创建 eventfd 就会在内核中创建一个计数器,每当向 eventfd 中写入一个数值 -- 用于表示事件通知机制,可以使用 read 进行数据的读取,读取到的数据就是通知的次数。
假设每次给 eventfd 中写入一个 1,就表示通知一次,连续写了 3 次之后,再去 read 读取出来的数字就是3,读取之后计数为 0。

功能:创建一个 eventfd 对象,实现事件通知机制
参数:
- initval: 计数初值
- flags:
- EFD_CLOEXEC - 带有此标志的描述符会被自动关闭
- EFD_NONBLOCK - 设为非阻塞模式
返回值:会返回一个文件描述符用于操作
eventfd 也是通过 read / write / close 进行操作的
注意:read & write 进行 IO 的时候,数据只能是一个 8 字节
eventfd用处:
EventLoop 的核心循环会在 Poller::Poll() 处等待 I/O 事件,如果没有事件,它会一直阻塞。当其他线程通过 QueueInLoop 向该 EventLoop 的任务队列 添加任务时,必须有一种机制能让 EventLoop 从阻塞中立即返回
测试代码:
#include <sys/eventfd.h>
#include <iostream>
#include <unistd.h>
#include <stdint.h>
using namespace std;
int main()
{
int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (efd < 0)
{
perror("eventfd failed!");
exit(1);
}
uint64_t val = 1;
write(efd, &val, sizeof(val));
write(efd, &val, sizeof(val));
uint64_t res = 0;
read(efd, &res, sizeof(res));
cout << res << endl;
close(efd);
return 0;
}

二、EventLoop
在《从零手写高性能 C++ TCP 服务器框架(七):Channel模块和Poller模块实现》中,我们在编写 服务端的测试代码中,我们说到过 测试代码中里面的一些内容,我们会在后面进行封装。
while (1)
{
std::vector<std::shared_ptr<Channel>> active;
poll.Poll(&active);
for (auto &ch : active)
{
ch->HandleEvent();
}
}

我们回头看一下《从零手写高性能 C++ TCP 服务器框架(二):名词介绍》中多Reactor多线程示意图,while(1) 循环体里面的内容 工作的任务不就是 IO 事件监控 (poll.Poll(&active);),IO 操作(ch->HandleEvent()),业务处理是 HandleEvent 具体的事项,例如HandleRead、HandleWrite。等。为此,我们就可以把上面的 while(1) 循环体里面的内容进行封装,封装成 EventLoop 模块。
EventLoop 的定义
EventLoop 依赖 Poll(或 Poller)来完成事件的多路复用监控。
监控了一个连接,而这个连接一旦就绪了,就要进行事件处理。但是如果这个描述符,在多个线程中都触发了事件,进行处理,就会存在线程安全问题。因此我们需要将一个连接的事件监控,以及连接事件处理,以及其他操作都放在同一个线程中进行
如何保证一个连接的所有操作都在 eventloop 对应的线程中呢?
解决方法,给每个 eventloop 模块中,添加一个任务队列,
对连接的所有操作,都进行一次封装,将对连接的操作并不直接后执行,而是当做任务添加到任务队列中
EventLoop 是一个与特定线程绑定的无限事件循环,它处理流程:
-
调用 Poller 进行 IO 多路复用监控;
-
将就绪的 Channel 分发并执行其回调函数(IO 操作 + 业务逻辑);
-
管理一个线程安全的任务队列,接收来自其他线程的请求(如添加/修改 Channel、关闭连接等);
-
通过 eventfd 实现跨线程唤醒,保证任务能立即被处理。
简单来说:EventLoop= IO 监控 + 事件分发 + 任务队列 + 线程绑定。
相关成员
- _thread_id:记录构造时所在的线程 ID,用于判断跨线程调用。
- _poller:实际的 IO 多路复用器(封装了 epoll / select)。
- _event_fd 与 _event_channel:用于跨线程唤醒的 eventfd 及其 Channel 包装。
- _tasks 与 _mutex:待执行的任务队列及保护它的互斥锁。
相关接口
class EventLoop {
public:
// 构造函数:初始化 eventfd 并注册到 Poller,绑定当前线程
EventLoop();
// 启动事件循环(阻塞式主循环,直到程序退出)
void Start();
// 线程安全的任务调度入口:若在循环线程内则直接执行,否则放入队列并唤醒
void RunInLoop(const Functor &cb);
// 将任务加入任务池,并唤醒循环线程(跨线程调用)
void QueueInLoop(const Functor &cb);
// 执行当前积累的所有任务(只在循环线程内调用)
void RunAllTask();
// 添加或修改某个 Channel 的事件监控(必须在循环线程调用)
void UpdateEvent(std::shared_ptr<Channel> channel);
// 移除某个 Channel 的事件监控(必须在循环线程调用)
void RemoveEvent(std::shared_ptr<Channel> channel);
private:
// 创建非阻塞、带 CLOEXEC 标志的 eventfd
static int CreateEventFd();
// 判断当前线程是否为事件循环所属线程
bool IsInLoop();
// 向 eventfd 写入数据,唤醒可能阻塞在 Poll() 中的循环线程
void WeakUpEventFd();
// 从 eventfd 读取数据,清除可读状态
void ReadEventfd();
// 成员变量(仅供了解,非接口)
std::thread::id _thread_id;
int _event_fd;
Poller _poller;
std::shared_ptr<Channel> _event_channel;
std::vector<Functor> _tasks;
std::mutex _mutex;
};
EventLoop()
- - 记录当前线程 ID(_thread_id),保证所有 I/O 和任务都在该线程执行。
- - 调用 CreateEventFd() 创建 eventfd(EFD_CLOEXEC | EFD_NONBLOCK)。
- - 为该 eventfd 创建 Channel 对象,设置可读回调为 ReadEventfd,并 EnableRead() 将其注册到 Poller。
- - 后续任何线程调用 WeakUpEventFd() 写入 eventfd,即可唤醒阻塞在 Poller::Poll() 上的循环线程。
void Start()
- - 启动事件循环,进入主循环,直到程序结束。
- - 循环固定三步:
- 事件监控:调用 _poller.Poll(&actives) 等待活动事件,若无事件且无任务则阻塞(可被 eventfd 唤醒)。
- 事件处理:遍历所有就绪 Channel,执行其 HandleEvent()(读/写/错误等回调)。
- 执行任务:调用 RunAllTask() 一次性处理所有积累的跨线程任务。
- - 这是经典的 one loop per thread 模型。
void RunInLoop(const Functor &cb)
- - 线程安全的任务调度入口。
- - 若调用者就是 EventLoop 所在线程(IsInLoop() 为真),则直接执行 cb。
- - 否则调用 QueueInLoop(cb) 将任务放入队列,并唤醒循环线程,保证任务最终在事件循环线程中执行。
void QueueInLoop(const Functor &cb)
- - 将回调函数放入任务池并唤醒循环线程。
- - 加锁后将 cb 追加到 _tasks 向量。
- - 然后调用 WeakUpEventFd(),向 eventfd 写入一个 64 位无符号整数,使 Poller 感知到 eventfd 可读,从而从 Poll() 返回,进而处理任务队列。
- - 这是实现跨线程任务调度的关键接口。
void RunAllTask()
- - 执行当前积累的所有任务。
- - 在锁的保护下,将 _tasks 与一个局部空 vector 进行 swap,快速清空任务池并减少锁持有时间。
- - 遍历局部 vector 中的每个 Functor 并执行。
- - 该方法仅在循环线程中被 Start() 调用,无并发风险。
void UpdateEvent(std::shared_ptr<Channel> channel)
- - 向 Poller 添加或修改某个文件描述符的事件监控。
- - 直接转发给 _poller.UpdateEvent(channel)。
- - 必须在循环线程中调用,否则会导致 epoll 操作的线程安全问题。
void RemoveEvent(std::shared_ptr<Channel> channel)
- - 从 Poller 中移除某个文件描述符的监控。
- - 转发给 _poller.RemoveEvent(channel)。
- - 同样要求在当前循环线程中执行。
static int CreateEventFd()
- - 创建 eventfd 对象,标志位 EFD_CLOEXEC | EFD_NONBLOCK。
- - 保证描述符在 exec 时关闭,读写非阻塞。
- - 失败则打印错误并终止程序。
bool IsInLoop()
- - 比较 _thread_id 与 std::this_thread::get_id(),判断当前线程是否为事件循环所属线程。
- - 用于 RunInLoop 的快速路径判断。
void WeakUpEventFd()
- - 向 eventfd 写入 uint64_t(1)。
- - 若写入失败且 errno == EINTR(被信号打断),则直接返回。
- - 其他错误打印日志并终止。
- - 写入后 eventfd 变为可读,唤醒阻塞在 Poll() 中的循环线程。
void ReadEventfd()
- - 从 eventfd 读取一个 uint64_t,清除其可读状态。
- - 通常读取的值表示自上次读取以来被唤醒的次数。
- - 若错误为 EINTR 或 EAGAIN(非阻塞下无数据可读),则安全返回。
- - 该函数作为 eventfd 的可读回调被注册,在事件处理阶段执行。
代码编写:
#define __EVENTLOOP__
#ifdef __EVENTLOOP__
#include <sys/eventfd.h>
#include <iostream>
#include <unistd.h>
#include <stdint.h>
#include <thread>
#include <functional>
#include <vector>
#include <mutex>
#include "poller.hpp"
#include "log.hpp"
#include "channel.hpp"
namespace xxhh
{
class EventLoop
{
private:
using Functor = std::function<void()>;
static int CreateEventFd()
{
int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (efd < 0)
{
ERR_LOG("eventfd failed!");
abort();
}
return efd;
}
bool IsInLoop()
{
return _thread_id == std::this_thread::get_id();
}
void WeakUpEventFd()
{
// 写操作
uint64_t val = 1;
int ret = write(_event_fd, &val, sizeof(val));
if (ret <= 0)
{
if (errno == EINTR)
{
return;
}
ERR_LOG("Weak eventfd Failed!");
abort();
}
return;
}
void ReadEventfd()
{
// 读操作
uint64_t res = 0;
int ret = read(_event_fd, &res, sizeof(res));
if (ret < 0)
{
// EINTR -- 被信号打断 EAGAIN -- 表示无数据可读
if (errno == EINTR || errno == EAGAIN)
{
return;
}
ERR_LOG("Read EventFd Failed");
}
return;
}
public:
EventLoop() : _thread_id(std::this_thread::get_id()), _event_fd(CreateEventFd()),
_event_channel(std::make_shared<Channel>(this, _event_fd))
{
// 给eventfd添加可读事件回调函数,读取eventfd事件通知次数
// 启动 eventfd 的读事件
// 函数参数可以只写类型不写名字,这表示"我知道有这个参数,但当前函数体内不需要使用它"
_event_channel->SetReadCallBack(
[this](std::shared_ptr<Channel> /*channel*/)
{
ReadEventfd();
});
_event_channel->EnableRead();
}
void RunAllTask()
{
std::vector<Functor> Functors;
{
std::unique_lock<std::mutex> lock(_mutex);
// 把 任务池中的 任务交换一下
_tasks.swap(Functors);
}
for (auto &f : Functors)
{
f();
}
return;
}
// 判断将要执行的任务是否处于当前的线程中,如果则执行,否则则压入队列
void RunInLoop(const Functor &cb)
{
if (IsInLoop())
{
return cb();
}
return QueueInLoop(cb);
}
// 将操作压入到任务池中
void QueueInLoop(const Functor &cb)
{
// 需要加锁
{
std::unique_lock<std::mutex> lock(_mutex);
_tasks.push_back(cb);
}
// 写一个事件
WeakUpEventFd();
}
// 添加 / 修改描述符的事件监控
void UpdateEvent(std::shared_ptr<Channel> channel)
{
return _poller.UpdateEvent(channel);
}
// 移除描述符的监控
void RemoveEvent(std::shared_ptr<Channel> channel)
{
return _poller.RemoveEvent(channel);
}
// 三步走 -- 事件监控,就绪事件处理,执行任务
void Start()
{
while (1)
{
// 1. 事件监控
std::vector<std::shared_ptr<Channel>> actives;
_poller.Poll(&actives);
// 2. 事件处理。
for (auto &channel : actives)
{
channel->HandleEvent();
}
// 3. 执行任务
RunAllTask();
}
}
private:
std::thread::id _thread_id; // 线程 ID
int _event_fd; // eventfd 唤醒IO事件监控有可能导致的阻塞
Poller _poller; // 进行所有描述符的事件监控
std::shared_ptr<Channel> _event_channel;
std::vector<Functor> _tasks; // 任务池
std::mutex _mutex; // 实现任务池操作的线程安全
};
void xxhh::Channel::Remove()
{
auto self = shared_from_this();
return _loop->RemoveEvent(self);
}
void xxhh::Channel::Update()
{
auto self = shared_from_this();
return _loop->UpdateEvent(self);
}
}
#endif
三、测试代码
前置工作
在编写测试代码之前, 需要对之前的 Poller 和 Channel 都要进行修改:
#ifndef __CHANNEL__
#define __CHANNEL__
#include <functional>
#include <cstdint>
#include <memory>
#include <sys/epoll.h>
namespace xxhh
{
class EventLoop;
class Channel : public std::enable_shared_from_this<Channel>
{
public:
using EventCallBack = std::function<void(std::shared_ptr<Channel>)>;
Channel(EventLoop *loop, int fd)
: _sockfd(fd), _loop(loop), _events(0), _revents(0) {}
int Fd() const { return _sockfd; }
uint32_t Events() const { return _events; }
void SetREvents(uint32_t events) { _revents = events; }
void SetReadCallBack(const EventCallBack &cb) { _read_callback = cb; }
void SetWriteCallBack(const EventCallBack &cb) { _write_callback = cb; }
void SetErrorCallBack(const EventCallBack &cb) { _error_callback = cb; }
void SetCloseCallBack(const EventCallBack &cb) { _close_callback = cb; }
void SetEventCallBack(const EventCallBack &cb) { _event_callback = cb; }
bool ReadAble() const { return (_events & EPOLLIN); }
bool WriteAble() const { return (_events & EPOLLOUT); }
void EnableRead()
{
_events |= EPOLLIN;
Update();
}
void EnableWrite()
{
_events |= EPOLLOUT;
Update();
}
void DisableRead()
{
_events &= ~EPOLLIN;
Update();
}
void DisableWrite()
{
_events &= ~EPOLLOUT;
Update();
}
void Remove();
void Update();
void HandleEvent()
{
auto self = shared_from_this(); // 保证在 HandleEvent 全程对象不销毁
if ((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) || (_revents & EPOLLPRI))
{
if (_read_callback)
_read_callback(self);
}
else if (_revents & EPOLLOUT)
{
if (_write_callback)
_write_callback(self);
}
else if (_revents & EPOLLERR)
{
if (_error_callback)
_error_callback(self);
}
else if (_revents & EPOLLHUP)
{
if (_close_callback)
_close_callback(self);
}
if (_event_callback)
_event_callback(self);
}
private:
int _sockfd;
EventLoop *_loop;
uint32_t _events;
uint32_t _revents;
EventCallBack _read_callback;
EventCallBack _write_callback;
EventCallBack _error_callback;
EventCallBack _close_callback;
EventCallBack _event_callback;
};
}
#endif
#ifndef __POLLER__
#define __POLLER__
#include <unordered_map>
#include <sys/epoll.h>
#include <vector>
#include <cstring>
#include <cassert>
#include <memory>
#include "log.hpp"
#include "channel.hpp"
namespace xxhh
{
#define MAX_EPOLLEVENTS 1024
class Poller
{
private:
void Update(std::shared_ptr<Channel> channel, int op)
{
int fd = channel->Fd();
struct epoll_event ev;
ev.events = channel->Events();
ev.data.fd = fd;
int ret = epoll_ctl(_epfd, op, fd, &ev);
if (ret < 0)
{
switch (op)
{
case EPOLL_CTL_ADD:
ERR_LOG("EPOLL_CTL_ADD Failed!");
break;
case EPOLL_CTL_DEL:
ERR_LOG("EPOLL_CTL_DEL Failed!");
break;
case EPOLL_CTL_MOD:
ERR_LOG("EPOLL_CTL_MOD Failed!");
break;
}
}
}
bool IsExitChannel(int fd) const
{
return _channels.find(fd) != _channels.end();
}
public:
Poller()
{
_epfd = epoll_create(MAX_EPOLLEVENTS);
if (_epfd < 0)
{
ERR_LOG("Poller Create Error!");
abort();
}
}
~Poller() { close(_epfd); }
void UpdateEvent(std::shared_ptr<Channel> channel)
{
int fd = channel->Fd();
if (!IsExitChannel(fd))
{
_channels[fd] = channel;
Update(channel, EPOLL_CTL_ADD);
}
else
{
Update(channel, EPOLL_CTL_MOD);
}
}
void RemoveEvent(std::shared_ptr<Channel> channel)
{
int fd = channel->Fd();
auto it = _channels.find(fd);
if (it != _channels.end())
{
_channels.erase(it);
}
Update(channel, EPOLL_CTL_DEL);
}
void Poll(std::vector<std::shared_ptr<Channel>> *active)
{
int nfds = epoll_wait(_epfd, _evs, MAX_EPOLLEVENTS, -1);
if (nfds < 0)
{
if (errno == EINTR)
return;
ERR_LOG("Epoll Wait Error: %s\n", strerror(errno));
abort();
}
for (int i = 0; i < nfds; ++i)
{
int fd = _evs[i].data.fd;
auto it = _channels.find(fd);
assert(it != _channels.end());
it->second->SetREvents(_evs[i].events);
active->push_back(it->second);
}
}
private:
int _epfd;
struct epoll_event _evs[MAX_EPOLLEVENTS];
std::unordered_map<int, std::shared_ptr<Channel>> _channels;
};
}
#endif
服务端测试代码修改:
#include "../source/socket.hpp"
#include "../source/channel.hpp"
#include "../source/poller.hpp"
#include "../source/eventLoop.hpp"
#include <memory>
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <functional>
using namespace xxhh;
void HandleClose(std::shared_ptr<Channel> channel)
{
std::cout << "close: " << channel->Fd() << std::endl;
channel->Remove(); // 仅从 epoll 移除,不 delete
// channel 的 shared_ptr 会自动释放,无需额外操作
}
void HandleRead(std::shared_ptr<Channel> channel)
{
int fd = channel->Fd();
char buf[1024] = {0};
int ret = recv(fd, buf, sizeof(buf) - 1, 0);
if (ret <= 0)
{
HandleClose(channel);
return;
}
std::cout << "recv: " << buf << std::endl;
channel->EnableWrite(); // 启动可写事件
}
void HandleWrite(std::shared_ptr<Channel> channel)
{
int fd = channel->Fd();
const char *msg = "天气好";
int ret = send(fd, msg, strlen(msg), 0);
if (ret < 0)
{
HandleClose(channel);
return;
}
channel->DisableWrite(); // 写完关闭写监控
}
void HandleError(std::shared_ptr<Channel> channel)
{
HandleClose(channel);
}
void HandleEvent(std::shared_ptr<Channel> channel)
{
std::cout << "有了一个事情" << std::endl;
}
// 连接接收器
void Acceptor(EventLoop *loop, std::shared_ptr<Channel> listen_channel)
{
int fd = listen_channel->Fd();
int newFd = accept(fd, nullptr, nullptr);
if (newFd < 0)
return;
// 为新连接创建 Channel,使用 shared_ptr 管理
auto newChannel = std::make_shared<Channel>(loop, newFd);
// 设置回调,注意签名需要匹配 void(std::shared_ptr<Channel>)
// ch 是 newChannel
newChannel->SetReadCallBack(
[](std::shared_ptr<Channel> ch)
{ HandleRead(ch); });
newChannel->SetWriteCallBack(
[](std::shared_ptr<Channel> ch)
{ HandleWrite(ch); });
newChannel->SetCloseCallBack(
[](std::shared_ptr<Channel> ch)
{ HandleClose(ch); });
newChannel->SetErrorCallBack(
[](std::shared_ptr<Channel> ch)
{ HandleError(ch); });
newChannel->SetEventCallBack(
[](std::shared_ptr<Channel> ch)
{ HandleEvent(ch); });
newChannel->EnableRead();
}
int main()
{
EventLoop loop;
Socket tcpSocket;
tcpSocket.CreateServer(8080);
// 监听 Channel 必须用 shared_ptr 管理,否则回调捕获裸指针会悬空
auto listenChannel = std::make_shared<Channel>(&loop, tcpSocket.Fd());
// 设置 Accept 回调,捕获 loop 指针和 listenChannel 的 shared_ptr
listenChannel->SetReadCallBack(
[&loop](std::shared_ptr<Channel> ch)
{
Acceptor(&loop, ch);
});
listenChannel->EnableRead();
loop.Start();
tcpSocket.Close();
return 0;
}
客户端测试代码不变

更多推荐

所有评论(0)