Muduo 网络框架深度解析:从 EPoll 到 HTTP Server
1. 概述
Muduo 是陈硕开发的一款基于 Reactor 模式的多线程网络库,采用 C++11 编写,核心代码不到 3000 行。本文结合实际 HTTP Server 代码,深入剖析其架构设计。
2. 整体架构设计
2.1 模块层次
+------------------------------------------+
| Application Layer |
| CHttpConn | ApiLogin | ApiRegister |
+------------------------------------------+
| TcpConnection (业务逻辑) |
| OnMessage | OnConnection | send() |
+------------------------------------------+
| TcpServer (连接管理) |
| acceptor | connectionCallback |
+------------------------------------------+
| EventLoop (事件循环) |
| loop() 循环 |
+------------------------------------------+
| Channel (事件分发) |
| fd | events | handleEvent |
+------------------------------------------+
| EpollPoller (Epoll 封装) |
| epoll_wait() | epoll_ctl |
+------------------------------------------+
| Socket (fd 管理) |
+------------------------------------------+
2.2 请求处理流程
Client Server
| |
|---------------- TCP Connect ----------------->|
| |
| mainReactor |
| (acceptor) |
| | |
| v |
| +----------------+ |
| | epoll_accept | |
| +----------------+ |
| | |
| v |
| 分配到某个 subReactor |
| (round-robin) |
| | |
| +----------------+ |
| | channel:fd | |
| | readable | |
| +----------------+ |
| | |
| v |
| 处理 HTTP 请求 |
| JSON 解析 |
| 业务逻辑 |
| 生成响应 |
| | |
| v |
|<--------------- HTTP Response --------------|
| |
|---------------- TCP Close ------------------>|
2.3 关键设计思想
Muduo 的核心设计哲学是区分 IO 线程和业务线程:
- IO 线程:负责 fd 的可读可写事件监听和分发
- 业务线程池:负责处理业务逻辑(计算任务)
这种设计的优势在于:IO 操作和业务计算分离,避免业务逻辑阻塞 IO 线程。
3. 核心组件解析
3.1 EventLoop - 事件循环引擎
EventLoop 是整个框架的心脏,每个 EventLoop 绑定一个线程,持续调用 epoll_wait 监听事件。
class EventLoop {
private:
EpollPoller* poller_; // epoll 封装
std::vector<Channel*> active_channels_; // 就绪的 channel 列表
std::atomic<bool> quit_; // 退出标志
int wakeup_fd_; // 用于唤醒 loop
Channel wakeup_channel_; // wakeup fd 对应的 channel
std::vector<Functor> pending_functors_; // 待执行的回调
public:
EventLoop() {
poller_ = new EpollPoller();
// 创建 eventfd,用于唤醒 epoll_wait
// 当其他线程想向 EventLoop 投递任务时,向 wakeup_fd 写一个字节即可唤醒
wakeup_fd_ = ::eventfd(0, EFD_NONBLOCK);
wakeup_channel_.setFd(wakeup_fd_);
wakeup_channel_.setReadCallback([this]() { handleWakeup(); });
wakeup_channel_.enableReading();
}
// ==========================================
// 核心循环:每次调用 epoll_wait 等待事件
// ==========================================
void loop() {
while (!quit_) {
// 第一步:清空上一轮的就绪列表
active_channels_.clear();
// 第二步:调用 epoll_wait 等待事件
// timeout = 10000ms,超时后即使没有事件也会返回
// 这样可以定期处理 pending_functors_,避免饥饿
int num_events = poller_->epoll_wait(&active_channels_, 10000);
// 第三步:遍历所有就绪的 channel,触发回调
for (int i = 0; i < num_events; ++i) {
active_channels_[i]->handleEvent();
}
// 第四步:执行其他线程投递的任务
// 这步很关键!实现了跨线程任务投递
doPendingFunctors();
}
}
// ==========================================
// 在 IO 线程执行任务(线程安全)
// 如果在当前线程,直接执行
// 如果在其他线程,投递到 pending_functors_
// ==========================================
void runInLoop(const Functor& cb) {
if (isInLoopThread()) {
cb(); // 本线程,直接执行
} else {
{
std::lock_guard<std::mutex> lock(mutex_);
pending_functors_.push_back(cb);
}
wakeup(); // 唤醒 loop 处理任务
}
}
// ==========================================
// 更新 channel 到 epoll
// 由 Channel 调用,当 enableReading/enableWriting 时触发
// ==========================================
void updateChannel(Channel* ch) {
poller_->updateChannel(ch);
}
// 退出 loop
void quit() { quit_ = true; wakeup(); }
private:
// 处理 wakeup fd 的读事件(其他线程投递任务时触发)
void handleWakeup() {
uint64_t one;
::read(wakeup_fd_, &one, sizeof(one)); // 消费掉数据
// 此时 pending_functors_ 已有任务,doPendingFunctors 会执行
}
// 执行 pending_functors_ 中的任务
void doPendingFunctors() {
std::vector<Functor> functors;
{
std::lock_guard<std::mutex> lock(mutex_);
functors.swap(pending_functors_); // 交换出去,减少锁持有时间
}
for (auto& f : functors) {
f(); // 执行任务
}
}
};
关键点:
- EventLoop 绑定一个线程,不能复制(noncopyable)
- epoll_wait 返回就绪的 Channel 列表
- runInLoop 保证线程安全,支持跨线程调用
- pending_functors_ 实现其他线程向 IO 线程投递任务
- wakeup_fd_ 用 eventfd 实现高效的线程唤醒
3.2 EpollPoller - EPoll 封装
Poller 负责管理所有 Channel 的注册和事件监听,对上层透明。
class EpollPoller {
private:
int epoll_fd_; // epoll 实例 fd
std::vector<struct epoll_event> events_; // 事件数组
std::map<int, Channel*> channels_; // fd -> Channel* 映射
public:
// 构造:创建 epoll 实例
// epoll_create 参数在新版本被忽略,但必须 > 0
EpollPoller() {
epoll_fd_ = ::epoll_create(5);
events_.resize(16); // 初始容量 16
}
// ==========================================
// 更新 channel(注册或修改)
// 第一步:判断是新增还是修改
// 第二步:构造 epoll_event(设置关注的事件)
// 第三步:调用 epoll_ctl
// ==========================================
void updateChannel(Channel* ch) {
int fd = ch->fd();
// 判断操作类型:ADD 或 MOD
int op;
if (channels_.count(fd) == 0) {
op = EPOLL_CTL_ADD; // 新增
} else {
op = EPOLL_CTL_MOD; // 修改
}
// 构造 epoll_event
// data.ptr 指向 Channel,事件就绪时可通过它找到对应的 Channel
struct epoll_event event;
event.events = ch->events(); // EPOLLIN | EPOLLOUT | EPOLLET
event.data.ptr = ch;
// 调用 epoll_ctl
::epoll_ctl(epoll_fd_, op, fd, &event);
channels_[fd] = ch;
}
// ==========================================
// 移除 channel
// 直接调用 epoll_ctl DEL,然后从 map 中删除
// ==========================================
void removeChannel(Channel* ch) {
int fd = ch->fd();
::epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr);
channels_.erase(fd);
}
// ==========================================
// 等待事件
// 返回就绪的 Channel 列表
// ==========================================
int epoll_wait(std::vector<Channel*>* active_channels, int timeout_ms) {
int num_events = ::epoll_wait(
epoll_fd_,
events_.data(),
events_.size(),
timeout_ms
);
// 遍历就绪的事件,填充 active_channels
for (int i = 0; i < num_events; ++i) {
// 从 data.ptr 取出 Channel*
Channel* ch = static_cast<Channel*>(events_[i].data.ptr);
// 设置 Channel 的 revents(就绪事件)
ch->set_revents(events_[i].events);
active_channels->push_back(ch);
}
// 如果事件数组满了,自动扩容(避免下轮 epoll_wait 截断)
if (num_events == static_cast<int>(events_.size())) {
events_.resize(events_.size() * 2);
}
return num_events;
}
};
关键点:
- channels_ 维护 fd -> Channel 映射,支持 O(1) 查找
- updateChannel 自动判断是 ADD 还是 MOD
- 事件数组满后自动扩容,避免截断
- data.ptr 指向 Channel,实现事件->对象的转换
3.3 Channel - 事件分发器
Channel 是事件处理的中间层,将 fd 和其关联的回调函数绑定在一起。
class Channel {
public:
typedef std::function<void()> EventCallback;
private:
// 事件标志位
static const int kNoneEvent = 0;
static const int kReadEvent = EPOLLIN | EPOLLPRI; // 可读 + 紧急数据
static const int kWriteEvent = EPOLLOUT; // 可写
EventLoop* loop_; // 所属 EventLoop
const int fd_; // 文件描述符
int events_; // 注册的事件(要关注什么)
int revents_; // 就绪的事件(发生了什么)
bool event_handling_; // 是否正在处理事件
// 回调函数(在 TcpConnection 中设置)
EventCallback read_callback_;
EventCallback write_callback_;
EventCallback close_callback_;
EventCallback error_callback_;
public:
Channel(EventLoop* loop, int fd)
: loop_(loop), fd_(fd), events_(kNoneEvent) {}
// ==========================================
// 设置回调函数
// 这些回调在 handleEvent 中被调用
// ==========================================
void setReadCallback(const EventCallback& cb) { read_callback_ = cb; }
void setWriteCallback(const EventCallback& cb) { write_callback_ = cb; }
void setCloseCallback(const EventCallback& cb) { close_callback_ = cb; }
void setErrorCallback(const EventCallback& cb) { error_callback_ = cb; }
// ==========================================
// 启用事件监听
// enableReading:关注 EPOLLIN,fd 可读时触发 read_callback_
// enableWriting:关注 EPOLLOUT,fd 可写时触发 write_callback_
// 每次修改 events 后,需要调用 updateChannel 更新到 epoll
// ==========================================
void enableReading() {
events_ |= kReadEvent;
loop_->updateChannel(this); // 更新到 epoll
}
void enableWriting() {
events_ |= kWriteEvent;
loop_->updateChannel(this);
}
void disableAll() {
events_ = kNoneEvent;
loop_->updateChannel(this);
}
void disableWriting() {
events_ &= ~kWriteEvent;
loop_->updateChannel(this);
}
// ==========================================
// 事件处理分发
// 根据 revents_ 判断发生了什么事件
// 然后调用对应的回调函数
// ==========================================
void handleEvent() {
event_handling_ = true;
// 第一步:对方关闭(EPOLLHUP)
// EPOLLHUP 表示对方关闭了写端,但可能还在读
// 如果同时没有 EPOLLIN,说明连接彻底断了
if (revents_ & EPOLLHUP && !(revents_ & EPOLLIN)) {
if (close_callback_) close_callback_();
}
// 第二步:可读事件
// EPOLLIN:普通数据可读
// EPOLLPRI:带外数据可读
// EPOLLRDHUP:对方关闭了写端(对端关闭)
if (revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
if (read_callback_) read_callback_();
}
// 第三步:可写事件
if (revents_ & EPOLLOUT) {
if (write_callback_) write_callback_();
}
// 第四步:错误事件
if (revents_ & EPOLLERR) {
if (error_callback_) error_callback_();
}
event_handling_ = false;
}
// getters
int fd() const { return fd_; }
int events() const { return events_; }
void set_revents(int r) { revents_ = r; }
};
关键点:
- Channel 不拥有 fd,只负责事件分发
- enableReading/enableWriting 修改 events_ 后调用 updateChannel
- handleEvent 根据 revents_ 分发到不同回调
- 回调函数在 TcpConnection 中设置
4. HTTP Server 构建流程
4.1 main 函数入口
int main() {
// ==========================================
// 步骤 1:创建主事件循环(mainReactor)
// 主事件循环负责监听 accept 事件
// ==========================================
EventLoop loop;
// ==========================================
// 步骤 2:配置监听地址和端口
// ip = "0.0.0.0" 表示监听所有网卡
// port = 8081
// ==========================================
uint16_t port = 8081;
const char* ip = "0.0.0.0";
InetAddress addr(ip, port);
// ==========================================
// 步骤 3:创建 HTTP 服务器
// num_event_loops = 4 表示开启 4 个 subReactor
// subReactor 负责处理 IO 事件
// ==========================================
int num_event_loops = 4;
HttpServer server(&loop, addr, "HttpServer", num_event_loops);
// ==========================================
// 步骤 4:启动服务器
// 内部会创建 acceptor,开始监听连接
// ==========================================
server.start();
// ==========================================
// 步骤 5:进入事件循环(阻塞)
// mainReactor 开始 loop,持续处理事件
// ==========================================
loop.loop();
return 0;
}
4.2 HttpServer 封装
class HttpServer {
private:
TcpServer server_; // TcpServer
std::map<uint32_t, CHttpConnPtr> http_conn_map_; // 连接映射表
std::atomic<uint32_t> conn_uuid_generator_ = 0; // uuid 生成器
public:
// ==========================================
// 构造函数
// 参数:
// loop:主事件循环指针
// addr:监听地址
// name:服务名称
// num_event_loops:subReactor 数量
// ==========================================
HttpServer(EventLoop* loop, const InetAddress& addr,
const std::string& name, int num_event_loops) {
// 设置新连接回调
// 当客户端 connect() 成功时触发
server_.setConnectionCallback(
[this](const TcpConnectionPtr& conn) { onConnection(conn); }
);
// 设置消息回调
// 当收到客户端数据时触发
server_.setMessageCallback(
[this](const TcpConnectionPtr& conn, Buffer* buf, Timestamp t) {
onMessage(conn, buf, t);
}
);
// 设置写完成回调
// 当数据全部发送完毕时触发
server_.setWriteCompleteCallback(
[this](const TcpConnectionPtr& conn) { onWriteComplete(conn); }
);
// 设置 subReactor 数量
// TcpServer 会创建 num_event_loops 个 EventLoop 线程
server_.setThreadNum(num_event_loops);
}
void start() {
server_.start();
}
private:
// ==========================================
// onConnection:新连接到来或断开
// 参数:conn - TcpConnection 智能指针
// ==========================================
void onConnection(const TcpConnectionPtr& conn) {
if (conn->connected()) {
// ========== 连接建立 ==========
// 第一步:生成唯一标识 uuid
uint32_t uuid = conn_uuid_generator_++;
// 第二步:设置上下文
// std::any 可以存储任意类型
// 这里存储 uint32_t uuid,用于后续识别连接
conn->setContext(uuid);
// 第三步:创建 CHttpConn 并加入映射表
CHttpConnPtr http_conn = std::make_shared<CHttpConn>(conn);
http_conn_map_[uuid] = http_conn;
} else {
// ========== 连接断开 ==========
// 第一步:从上下文获取 uuid
uint32_t uuid = std::any_cast<uint32_t>(conn->getContext());
// 第二步:从映射表移除
// CHttpConn 的 shared_ptr 引用计数减 1
// 当没有其他引用时,CHttpConn 会被析构
http_conn_map_.erase(uuid);
}
}
// ==========================================
// onMessage:收到客户端数据
// 参数:
// conn - TcpConnection 智能指针
// buf - 数据缓冲区
// time - 时间戳
// ==========================================
void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) {
// 第一步:从上下文获取 uuid
uint32_t uuid = std::any_cast<uint32_t>(conn->getContext());
// 第二步:从映射表找到对应的 CHttpConn
CHttpConnPtr& http_conn = http_conn_map_[uuid];
// 第三步:调用 CHttpConn 的 OnRead 处理请求
http_conn->OnRead(buf);
}
// ==========================================
// onWriteComplete:数据发送完成
// ==========================================
void onWriteComplete(const TcpConnectionPtr& conn) {
// 可以记录日志或处理完成回调
// 例如:更新任务状态、触发下一个任务等
}
};
4.3 std::any 上下文传递
Muduo 使用 std::any 作为 TcpConnection 的上下文容器,支持任意类型:
// std::any 是 C++17 的类型擦除容器
// 可以存储任意类型的值,通过 any_cast 提取
// ==========================================
// 设置上下文
// 在 onConnection(连接建立时)调用
// ==========================================
conn->setContext(uint32_t(uuid));
// ==========================================
// 获取上下文
// 在 onMessage(收到消息时)调用
// ==========================================
uint32_t uuid = std::any_cast<uint32_t>(conn->getContext());
// ==========================================
// 安全检查版本
// 如果类型不匹配,std::any_cast 会抛出异常
// ==========================================
if (conn->getContext().has_value()) {
auto uuid = std::any_cast<uint32_t>(conn->getContext());
}
// ==========================================
// 示例:传递多种类型
// ==========================================
conn->setContext(42); // int
conn->setContext(std::string("hello")); // std::string
conn->setContext(CustomStruct{1, 2}); // 自定义结构体
// 获取时需要知道具体类型
auto val = std::any_cast<int>(conn->getContext());
5. 多线程 Reactor 模型
5.1 架构图
+------------------+
| Client 1 |
| Client 2 |
| Client 3 |
+------------------+
|
v
+------------------+
| mainReactor |
| (acceptor) |
| listen_fd=3 |
+------------------+
|
+---------------------+---------------------+
| | |
v v v
+----------------+ +----------------+ +----------------+
| subReactor0 | | subReactor1 | | subReactor2 |
| EventLoop0 | | EventLoop1 | | EventLoop2 |
| epoll fd=4 | | epoll fd=5 | | epoll fd=6 |
| conn_fd=7 | | conn_fd=8 | | conn_fd=9 |
| conn_fd=10 | | conn_fd=11 | | |
+----------------+ +----------------+ +----------------+
| | |
v v v
+----------+ +----------+ +----------+
| Task 1 | | Task 2 | | Task 3 |
| 计算任务 | | 计算任务 | | 计算任务 |
+----------+ +----------+ +----------+
5.2 One Loop Per Thread 模型
Muduo 的 TcpServer 默认采用 One Loop Per Thread 模式:
-
mainReactor:只有 1 个,负责 accept 新连接
-
subReactor:N 个,每个绑定一个 EventLoop 线程,负责 IO 监听
-
连接分发:新连接按 round-robin 分发给各个 subReactor
TcpServer::setThreadNum(4) 的效果:
mainReactor: listen socket, accept 新连接
|
v (round-robin)
+-----+-----+-----+-----+
sub0 sub1 sub2 sub3 (4 个 EventLoop 线程)
| | |
v v v
处理各自的 conn_fd
为什么用 round-robin?
- 保证连接均匀分布到各个 EventLoop
- 每个 EventLoop 线程只处理自己管理的连接
- 避免单个 EventLoop 过度负载
5.3 TcpServer 内部逻辑
class TcpServer {
private:
EventLoop* main_loop_; // 主事件循环
std::vector<EventLoop*> sub_loops_; // 子事件循环列表
int next_loop_index_ = 0; // round-robin 索引
TcpServerAcceptor* acceptor_; // 接受器
public:
// ==========================================
// setThreadNum:设置 subReactor 数量
// 内部会创建对应数量的线程,每个线程一个 EventLoop
// ==========================================
void setThreadNum(int num_threads) {
num_threads_ = num_threads;
// 如果已经启动,需要创建额外的 EventLoop 线程
if (running_) {
// 创建并启动 sub EventLoop 线程
for (int i = 0; i < num_threads_; ++i) {
std::thread t([this]() {
EventLoop* loop = new EventLoop();
sub_loops_.push_back(loop);
loop->loop(); // 启动事件循环
});
t.detach();
}
}
}
// ==========================================
// newConnection:新连接到来时的回调
// 由 acceptor 调用,参数是客户端 socket fd
// ==========================================
void newConnection(int conn_fd, const InetAddress& peer_addr) {
// ========== 步骤 1:选择 subReactor ==========
// round-robin 选择下一个 EventLoop
EventLoop* sub_loop = sub_loops_[next_loop_index_++];
if (next_loop_index_ >= sub_loops_.size()) {
next_loop_index_ = 0;
}
// ========== 步骤 2:在 subReactor 线程中创建 TcpConnection ==========
// 注意:这里用 runInLoop 投递到 subReactor 线程执行
// 因为 TcpConnection 创建涉及 Channel 注册,不能跨线程
sub_loop->runInLoop([this, conn_fd, peer_addr, sub_loop]() {
// 创建 TcpConnection
TcpConnectionPtr conn = std::make_shared<TcpConnection>(
sub_loop, // 绑定到 subReactor
conn_fd, // 客户端 socket fd
local_addr_, // 本地地址
peer_addr // 客户端地址
);
// ========== 步骤 3:设置回调 ==========
conn->setConnectionCallback(connection_callback_);
conn->setMessageCallback(message_callback_);
conn->setWriteCompleteCallback(write_complete_callback_);
// ========== 步骤 4:开启读事件监听 ==========
// connectEstablished 会 enableReading,开始处理 IO
conn->connectEstablished();
});
}
};
5.4 subReactor 处理流程
subReactor EventLoop 的完整流程:
1. subReactor 创建,调用 loop()
|
v
2. epoll_wait() 阻塞等待
|
v
3. 有连接发送数据,epoll_wait 返回
|
v
4. 遍历 active_channels_
|
v
5. Channel.handleEvent() 调用 TcpConnection::OnRead()
|
v
6. TcpConnection::OnRead() 读取缓冲区
|
v
7. 业务逻辑(CHttpConn 处理)
|
v
8. TcpConnection::send() 发送响应
|
v
9. 回到步骤 2,继续 loop
6. Channel 与 EventLoop 绑定机制
6.1 关系图
EventLoop Channel
| |
|--- poller_ (EpollPoller) |
|--- active_channels_ |
| |
| +------+------+
| | fd |
| | events_ |
| | revents_ |
| | callbacks |
| +------+------+
| |
+------- updateChannel() -------->|
| // epoll_ctl(ADD/MOD) |
| |
<-------- handleEvent() ----------+
| // 根据 revents_ 调用回调 |
| |
v |
管理多个 Channel |
(通过 poller) |
6.2 完整事件流(详细步骤)
第一步:应用层创建 TcpConnection
|
v
第二步:TcpConnection 构造时创建 Channel,绑定 socket fd
|
v
第三步:TcpConnection.enableReading() 启用读事件
- 设置 channel_->enableReading()
|
v
第四步:Channel.enableReading() 修改 events_
- events_ |= EPOLLIN
- 调用 loop_->updateChannel(this)
|
v
第五步:EventLoop.updateChannel() 更新到 EpollPoller
- 调用 poller_->updateChannel(ch)
|
v
第六步:EpollPoller.updateChannel() 注册到 epoll
- 调用 epoll_ctl(EPOLL_CTL_ADD, fd, event)
- channels_[fd] = ch
|
v
第七步:Client 发送数据到 socket buffer
|
v
第八步:epoll_wait() 返回就绪的 Channel
- revents_ = EPOLLIN
|
v
第九步:EventLoop.loop() 遍历 active_channels_
- for (ch in active_channels_) ch->handleEvent()
|
v
第十步:Channel.handleEvent() 根据 revents_ 调用回调
- revents_ & EPOLLIN → read_callback_()
|
v
第十一步:TcpConnection.OnRead() 读取缓冲区
- input_buffer_.read(fd)
|
v
第十二步:业务逻辑处理(JSON 解析等)
|
v
第十三步:TcpConnection.send() 发送响应
- output_buffer_.append(msg)
- channel_->enableWriting()
|
v
第十四步:EpollPoller 注册 EPOLLOUT 事件
|
v
第十五步:socket 可写,epoll_wait 返回
|
v
第十六步:Channel.handleEvent() 调用 write_callback_
- TcpConnection::sendInLoop()
|
v
第十七步:数据发送完成,disableWriting
|
v
第十八步:回到 epoll_wait
7. TcpConnection 与 CHttpConn
7.1 职责划分
| 类 | 职责 | 层级 |
|---|---|---|
| TcpConnection | 底层 TCP 连接管理,send/close,读写缓冲区 | 网络层 |
| CHttpConn | HTTP 业务逻辑,请求解析,响应封装 | 应用层 |
TcpConnection CHttpConn
+------------+ +------------+
| socket fd | ---> | 持有指针 |
| 读缓冲区 | | 解析 HTTP |
| 写缓冲区 | | 路由分发 |
| send() | | JSON 处理 |
| shutdown()| | 业务逻辑 |
+------------+ +------------+
7.2 TcpConnection 主要接口
class TcpConnection : public std::enable_shared_from_this<TcpConnection> {
private:
EventLoop* loop_; // 所属 EventLoop
int fd_; // socket fd
std::string name_; // 连接名称
InetAddress local_addr_; // 本地地址
InetAddress peer_addr_; // 客户端地址
Channel* channel_; // 关联的 Channel
Buffer input_buffer_; // 接收缓冲区
Buffer output_buffer_; // 发送缓冲区
public:
// ==========================================
// 发送数据
// 如果在 EventLoop 线程,直接发送
// 如果在其他线程,投递到 EventLoop
// ==========================================
void send(const std::string& msg) {
if (loop_->isInLoopThread()) {
sendInLoop(msg);
} else {
loop_->runInLoop([this, msg]() { sendInLoop(msg); });
}
}
// ==========================================
// 关闭连接
// 通知对方关闭,然后关闭本地 socket
// ==========================================
void shutdown() {
channel_->disableAll();
loop_->runInLoop([this]() { shutdownInLoop(); });
}
private:
void sendInLoop(const std::string& msg) {
// 写入发送缓冲区
ssize_t n = output_buffer_.write(fd_, msg.data(), msg.size());
if (n < 0) {
// 处理错误
} else if (n == msg.size()) {
// 数据全部写入内核缓冲区,禁用写事件
channel_->disableWriting();
} else {
// 部分写入,注册写事件继续发送
channel_->enableWriting();
}
}
void shutdownInLoop() {
// 关闭写端(SHUT_WR)
// 会向对方发送 FIN
::shutdown(fd_, SHUT_WR);
}
};
7.3 CHttpConn 实现
class CHttpConn : public std::enable_shared_from_this<CHttpConn> {
private:
CHttpParserWrapper http_parser_; // HTTP 解析器
TcpConnectionPtr tcp_conn_; // TCP 连接指针
uint32_t uuid_ = 0; // 连接唯一标识
public:
// ==========================================
// 构造函数
// 参数:tcp_conn - TcpConnection 智能指针
// 作用:从 TcpConnection 的上下文获取 uuid
// ==========================================
CHttpConn(TcpConnectionPtr tcp_conn)
: tcp_conn_(tcp_conn) {
// 从上下文中提取 uuid
uuid_ = std::any_cast<uint32_t>(tcp_conn_->getContext());
}
// ==========================================
// OnRead:处理读事件入口
// 参数:buf - 数据缓冲区
// ==========================================
void OnRead(Buffer* buf) {
const char* data = buf->peek(); // 查看数据,不移动指针
int len = buf->readableBytes(); // 获取可读字节数
// ========== 步骤 1:解析 HTTP ==========
http_parser_.ParseHttpContent(data, len);
// ========== 步骤 2:检查是否解析完成 ==========
if (http_parser_.IsReadAll()) {
// 获取解析结果
string url = http_parser_.GetUrlString(); // URL 路径
string body = http_parser_.GetBodyContentString(); // 请求体
// ========== 步骤 3:路由分发 ==========
if (strncmp(url.c_str(), "/api/reg", 8) == 0) {
_HandleRegisterRequest(url, body);
} else if (strncmp(url.c_str(), "/api/login", 10) == 0) {
_HandleLoginRequest(url, body);
} else {
_HandleNotFound();
}
// ========== 步骤 4:消费已解析的数据 ==========
// 将读缓冲区指针前移,释放内存
buf->retrieve(len);
}
}
private:
// ==========================================
// _HandleRegisterRequest:处理注册请求
// ==========================================
int _HandleRegisterRequest(string& url, string& post_data) {
string resp_json;
// 调用注册业务逻辑
ApiRegisterUser(post_data, resp_json);
// 构造 HTTP 响应
char body[4096];
snprintf(body, sizeof(body), HTTP_RESPONSE_JSON,
resp_json.length(), resp_json.c_str());
// 发送响应
tcp_conn_->send(body);
return 0;
}
// ==========================================
// _HandleLoginRequest:处理登录请求
// ==========================================
int _HandleLoginRequest(string& url, string& post_data) {
string resp_json;
// 调用登录业务逻辑
ApiLoginUser(post_data, resp_json);
// 构造 HTTP 响应
char body[4096];
snprintf(body, sizeof(body), HTTP_RESPONSE_JSON,
resp_json.length(), resp_json.c_str());
// 发送响应
tcp_conn_->send(body);
return 0;
}
// ==========================================
// _HandleNotFound:处理 404
// ==========================================
void _HandleNotFound() {
string resp = "{\"code\": 404}";
char body[256];
snprintf(body, sizeof(body), HTTP_RESPONSE_JSON,
resp.length(), resp.c_str());
tcp_conn_->send(body);
}
};
7.4 响应宏定义
// ==========================================
// HTTP 响应格式宏
// 组成:状态行 + 响应头 + 空行 + 响应体
// ==========================================
// JSON 响应格式
// Content-Type: application/json
#define HTTP_RESPONSE_JSON \
"HTTP/1.1 200 OK\r\n" \
"Connection:close\r\n" \
"Content-Length:%d\r\n" \
"Content-Type:application/json;charset=utf-8\r\n\r\n%s"
// HTML 响应格式
// Content-Type: text/html
#define HTTP_RESPONSE_HTML \
"HTTP/1.1 200 OK\r\n" \
"Connection:close\r\n" \
"Content-Length:%d\r\n" \
"Content-Type:text/html;charset=utf-8\r\n\r\n%s"
// 错误响应格式
// 400 Bad Request
#define HTTP_RESPONSE_BAD_REQ \
"HTTP/1.1 400 Bad Request\r\n" \
"Connection:close\r\n" \
"Content-Length:%d\r\n" \
"Content-Type:application/json;charset=utf-8\r\n\r\n%s"
HTTP 响应格式分解:
HTTP/1.1 200 OK ← 状态行
Connection:close ← 响应头(关闭连接)
Content-Length:12 ← 响应头(body 长度)
Content-Type:application/json... ← 响应头(内容类型)
← 空行(\r\n\r\n)
{"code": 0} ← 响应体(JSON)
8. HTTP 请求解析与 JSON 处理
8.1 HTTP Parser 封装
class CHttpParserWrapper {
private:
http_parser parser_; // 原始 http_parser
http_parser_settings settings_; // 回调配置
bool read_all_ = false; // 是否解析完成
uint32_t total_length_ = 0; // 总长度
string url_; // URL
string body_content_; // 请求体
string host_; // Host 头
string user_agent_; // User-Agent 头
public:
CHttpParserWrapper() {
// ========== 步骤 1:初始化 settings ==========
// 设置各种回调函数
settings_.on_url = OnUrl; // URL 解析完成
settings_.on_body = OnBody; // 请求体解析
settings_.on_header_field = OnHeaderField; // Header 字段
settings_.on_header_value = OnHeaderValue; // Header 值
settings_.on_headers_complete = OnHeadersComplete; // Headers 结束
settings_.on_message_complete = OnMessageComplete; // 消息结束
}
// ==========================================
// ParseHttpContent:解析 HTTP 内容
// 参数:
// buf - HTTP 原始数据
// len - 数据长度
// ==========================================
void ParseHttpContent(const char* buf, uint32_t len) {
// 第一步:初始化 parser
http_parser_init(&parser_, HTTP_REQUEST);
parser_.data = this; // 传递 this 指针,供回调使用
// 第二步:执行解析
// 会触发各种回调函数
size_t nparsed = http_parser_execute(
&parser_,
&settings_,
buf,
len
);
// 第三步:检查解析结果
if (nparsed == len && parser_.http_errno == HPE_OK) {
read_all_ = true;
}
}
// getters
bool IsReadAll() { return read_all_; }
string& GetUrlString() { return url_; }
string& GetBodyContentString() { return body_content_; }
};
// ==========================================
// 静态回调:URL 解析完成
// 参数:
// parser - http_parser 对象
// at - URL 字符串起始位置
// length - URL 长度
// obj - 传入的 CHttpParserWrapper 指针
// ==========================================
int CHttpParserWrapper::OnUrl(http_parser* parser,
const char* at, size_t length, void* obj) {
auto* wrapper = static_cast<CHttpParserWrapper*>(obj);
wrapper->SetUrl(at, length); // 追加到 url_ 字符串
return 0; // 返回 0 表示成功
}
// ==========================================
// 静态回调:Body 解析完成
// ==========================================
int CHttpParserWrapper::OnBody(http_parser* parser,
const char* at, size_t length, void* obj) {
auto* wrapper = static_cast<CHttpParserWrapper*>(obj);
wrapper->SetBodyContent(at, length); // 追加到 body_content_
return 0;
}
// ==========================================
// 静态回调:Headers 解析完成
// ==========================================
int CHttpParserWrapper::OnHeadersComplete(http_parser* parser,
void* obj) {
auto* wrapper = static_cast<CHttpParserWrapper*>(obj);
// 可以在这里获取 Content-Length 等信息
return 0;
}
// ==========================================
// 静态回调:整个消息解析完成
// ==========================================
int CHttpParserWrapper::OnMessageComplete(http_parser* parser,
void* obj) {
auto* wrapper = static_cast<CHttpParserWrapper*>(obj);
wrapper->SetReadAll(); // 设置解析完成标志
return 0;
}
8.2 JSON 序列化与反序列化
// JSON 序列化:将对象转换为 JSON 字符串
// JSON 反序列化:将 JSON 字符串解析为对象
// ==========================================
// 反序列化:解析注册信息
// 输入:{"userName":"zhangsan","nickName":"张三",...}
// 输出:user_name, nick_name, pwd, phone, email
// ==========================================
int decodeRegisterJson(const string& str_json,
string& user_name, string& nick_name,
string& pwd, string& phone, string& email) {
// 第一步:创建 JSON 读取器
Json::Value root;
Json::Reader reader;
// 第二步:解析 JSON 字符串
if (!reader.parse(str_json, root)) {
// 解析失败,JSON 格式错误
return -1;
}
// 第三步:提取必填字段
// 如果字段不存在或为 null,isNull() 返回 true
if (root["userName"].isNull()) return -1;
user_name = root["userName"].asString(); // 转为 string
if (root["nickName"].isNull()) return -1;
nick_name = root["nickName"].asString();
if (root["firstPwd"].isNull()) return -1;
pwd = root["firstPwd"].asString();
// 第四步:提取可选字段
if (!root["phone"].isNull()) {
phone = root["phone"].asString();
}
if (!root["email"].isNull()) {
email = root["email"].asString();
}
return 0;
}
// ==========================================
// 序列化:封装响应
// 输入:code = 0 或 1
// 输出:{"code":0} 或 {"code":1}
// ==========================================
int encodeRegisterJson(int code, string& str_json) {
// 第一步:创建根对象
Json::Value root;
root["code"] = code; // 0 = 成功,非 0 = 失败
// 第二步:创建 FastWriter(效率高)
Json::FastWriter writer;
// 第三步:序列化为字符串
str_json = writer.write(root);
return 0;
}
9. 登录注册 API 实现
9.1 注册 API 流程
注册请求流程:
POST /api/reg
Content-Type: application/json
{
"userName": "zhangsan",
"nickName": "张三",
"firstPwd": "123456",
"phone": "13800138000",
"email": "zhangsan@example.com"
}
|
v
+-----------------------------------------------+
| 步骤 1:HTTP Parser 解析 |
| - 解析请求行:POST /api/reg HTTP/1.1 |
| - 解析 Header:Content-Type 等 |
| - 解析 Body:JSON 字符串 |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 2:decodeRegisterJson() |
| - 解析 JSON |
| - 提取 userName, nickName, firstPwd |
| - 提取 phone, email(可选) |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 3:registerUser() |
| - 检查用户名是否已存在(数据库查询) |
| - 检查昵称是否已存在 |
| - 密码加密 |
| - 插入 user 表 |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 4:encodeRegisterJson() |
| - 构造响应 JSON:{"code": 0} |
+-----------------------------------------------+
|
v
HTTP Response 200 OK
{"code": 0}
9.2 注册 API 伪代码
// api_register.h
int ApiRegisterUser(string& post_data, string& resp_json);
// api_register.cc
// ==========================================
// decodeRegisterJson:反序列化注册信息
// ==========================================
int decodeRegisterJson(const string& str_json,
string& user_name, string& nick_name,
string& pwd, string& phone, string& email) {
// 第一步:创建 JSON 读取器
Json::Value root;
Json::Reader reader;
// 第二步:解析 JSON
if (!reader.parse(str_json, root)) {
return -1; // JSON 格式错误
}
// 第三步:提取 userName(必填)
if (root["userName"].isNull()) {
return -1; // 缺少必填字段
}
user_name = root["userName"].asString();
// 第四步:提取 nickName(必填)
if (root["nickName"].isNull()) {
return -1;
}
nick_name = root["nickName"].asString();
// 第五步:提取 firstPwd(必填)
if (root["firstPwd"].isNull()) {
return -1;
}
pwd = root["firstPwd"].asString();
// 第六步:提取 phone(可选)
if (root["phone"].isNull()) {
phone = ""; // 空字符串表示未提供
} else {
phone = root["phone"].asString();
}
// 第七步:提取 email(可选)
if (root["email"].isNull()) {
email = "";
} else {
email = root["email"].asString();
}
return 0;
}
// ==========================================
// encodeRegisterJson:序列化响应
// ==========================================
int encodeRegisterJson(int code, string& str_json) {
Json::Value root;
root["code"] = code;
Json::FastWriter writer;
str_json = writer.write(root);
return 0;
}
// ==========================================
// registerUser:写入数据库(伪实现)
// ==========================================
int registerUser(string& user_name, string& nick_name,
string& pwd, string& phone, string& email) {
// TODO: 连接数据库,执行以下操作
// 步骤 1:检查用户名是否已存在
// SELECT * FROM user WHERE user_name = ?
// 步骤 2:检查昵称是否已存在
// SELECT * FROM user WHERE nick_name = ?
// 步骤 3:对密码进行加密
// pwd = hash(pwd) // 使用 SHA256 等加密算法
// 步骤 4:插入用户记录
// INSERT INTO user (user_name, nick_name, pwd, phone, email)
// VALUES (?, ?, ?, ?, ?)
return 0;
}
// ==========================================
// ApiRegisterUser:注册 API 入口
// ==========================================
int ApiRegisterUser(string& post_data, string& resp_json) {
string user_name, nick_name, pwd, phone, email;
// ========== 步骤 1:检查数据是否为空 ==========
if (post_data.empty()) {
encodeRegisterJson(1, resp_json); // code = 1 表示失败
return -1;
}
// ========== 步骤 2:解析 JSON ==========
if (decodeRegisterJson(post_data, user_name, nick_name, pwd,
phone, email) < 0) {
encodeRegisterJson(1, resp_json);
return -1;
}
// ========== 步骤 3:写入数据库 ==========
int ret = registerUser(user_name, nick_name, pwd, phone, email);
// ========== 步骤 4:返回响应 ==========
encodeRegisterJson(ret, resp_json);
return 0;
}
9.3 登录 API 流程
登录请求流程:
POST /api/login
Content-Type: application/json
{
"user": "zhangsan",
"pwd": "123456"
}
|
v
+-----------------------------------------------+
| 步骤 1:HTTP Parser 解析 |
| - 解析请求行:POST /api/login HTTP/1.1 |
| - 解析 Body:JSON 字符串 |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 2:decodeLoginJson() |
| - 解析 JSON |
| - 提取 user, pwd |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 3:verifyUserPassword() |
| - 根据 user 查询数据库 |
| - 验证密码是否匹配 |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 4:setToken() |
| - 生成唯一 Token(UUID 或 JWT) |
| - 存储到 Redis |
+-----------------------------------------------+
|
v
+-----------------------------------------------+
| 步骤 5:encodeLoginJson() |
| - 构造响应 JSON:{"code": 0, "token": "xxx"} |
+-----------------------------------------------+
|
v
HTTP Response 200 OK
{"code": 0, "token": "abc123..."}
9.4 登录 API 伪代码
// api_login.h
int ApiLoginUser(string& post_data, string& resp_json);
// api_login.cc
// ==========================================
// decodeLoginJson:反序列化登录信息
// ==========================================
int decodeLoginJson(const string& str_json,
string& user_name, string& pwd) {
Json::Value root;
Json::Reader reader;
if (!reader.parse(str_json, root)) {
return -1;
}
if (root["user"].isNull()) {
return -1;
}
user_name = root["user"].asString();
if (root["pwd"].isNull()) {
return -1;
}
pwd = root["pwd"].asString();
return 0;
}
// ==========================================
// encodeLoginJson:序列化登录响应
// ==========================================
int encodeLoginJson(int code, string& token, string& str_json) {
Json::Value root;
root["code"] = code;
// 只有成功时才包含 token
if (code == 0) {
root["token"] = token;
}
Json::FastWriter writer;
str_json = writer.write(root);
return 0;
}
// ==========================================
// verifyUserPassword:验证用户名密码(伪实现)
// ==========================================
int verifyUserPassword(string& user_name, string& pwd) {
// TODO: 连接数据库
// 步骤 1:根据 user_name 查询用户
// SELECT pwd FROM user WHERE user_name = ?
// 步骤 2:验证密码是否匹配
// if (stored_pwd == hash(pwd)) return 0;
return 0;
}
// ==========================================
// setToken:生成并存储 Token(伪实现)
// ==========================================
int setToken(string& user_name, string& token) {
// 步骤 1:生成唯一 token
// 使用 UUID 或 JWT
token = generateUUID();
// 步骤 2:存储到 Redis
// redis.set("token:" + token, user_name)
// redis.expire("token:" + token, 86400) // 24 小时过期
return 0;
}
// ==========================================
// ApiLoginUser:登录 API 入口
// ==========================================
int ApiLoginUser(string& post_data, string& resp_json) {
string user_name, pwd, token;
// ========== 步骤 1:检查数据是否为空 ==========
if (post_data.empty()) {
encodeLoginJson(1, token, resp_json);
return -1;
}
// ========== 步骤 2:解析 JSON ==========
if (decodeLoginJson(post_data, user_name, pwd) < 0) {
encodeLoginJson(1, token, resp_json);
return -1;
}
// ========== 步骤 3:验证用户名密码 ==========
if (verifyUserPassword(user_name, pwd) < 0) {
encodeLoginJson(1, token, resp_json);
return -1;
}
// ========== 步骤 4:生成 Token ==========
if (setToken(user_name, token) < 0) {
encodeLoginJson(1, token, resp_json);
return -1;
}
// ========== 步骤 5:返回成功响应 ==========
encodeLoginJson(0, token, resp_json);
return 0;
}
9.5 URL 路由分发
// CHttpConn::OnRead 中的路由逻辑
void CHttpConn::OnRead(Buffer* buf) {
// ... HTTP 解析 ...
string url = http_parser_.GetUrlString();
string body = http_parser_.GetBodyContentString();
// ==========================================
// URL 路由表
// 使用 strncmp 进行前缀匹配
// ==========================================
// 注册
if (strncmp(url.c_str(), "/api/reg", 8) == 0) {
_HandleRegisterRequest(url, body);
}
// 登录
else if (strncmp(url.c_str(), "/api/login", 10) == 0) {
_HandleLoginRequest(url, body);
}
// 图片上传(待实现)
else if (strncmp(url.c_str(), "/api/upload", 11) == 0) {
_HandleUploadRequest(url, body);
}
// 图片列表(待实现)
else if (strncmp(url.c_str(), "/api/list", 9) == 0) {
_HandleListRequest(url, body);
}
// 图片访问(待实现):/i/abc123.png
else if (strncmp(url.c_str(), "/i/", 3) == 0) {
_HandleImageRequest(url);
}
// 未知路径
else {
_HandleNotFound();
}
}
10. 总结
10.1 核心要点回顾
| 概念 | 说明 |
|---|---|
| EventLoop | 每个线程一个,负责 epoll_wait 循环,runInLoop 支持跨线程任务投递 |
| EpollPoller | 对 epoll 的封装,updateChannel 管理 ADD/MOD/DEL |
| Channel | 封装 fd 和事件回调,enableReading/enableWriting 控制事件,handleEvent 分发回调 |
| TcpServer | 服务端,管理 acceptor,setThreadNum 设置 subReactor 数量,round-robin 分发连接 |
| TcpConnection | 底层 TCP 连接管理,send/close,读写缓冲区,enableReading 开启读事件 |
| CHttpConn | HTTP 业务层,解析请求,路由分发到 API 函数 |
| std::any | 类型擦除容器,用于 TcpConnection 上下文传递 uuid |
| http-parser | node.js 的 HTTP 解析器,静态回调模式 |
| jsoncpp | JSON 序列化/反序列化,FastWriter 高效序列化 |
10.2 请求处理流程总览
Client TCP Connect
|
v
mainReactor accept()
|
v (round-robin)
subReactor[i] 创建 TcpConnection + Channel
|
v
Channel.enableReading() 注册 EPOLLIN 到 epoll
|
v
epoll_wait() 返回就绪事件
|
v
Channel.handleEvent() 调用 TcpConnection.OnRead()
|
v
CHttpConn.OnRead() -> HTTP Parse
|
v
decodeRegisterJson / decodeLoginJson -> JSON 解析
|
v
registerUser / verifyUserPassword -> 业务处理
|
v
encodeRegisterJson / encodeLoginJson -> JSON 封装
|
v
TcpConnection.send() 发送 HTTP Response
|
v
Client TCP Close
10.3 设计思想
- 事件驱动:所有操作都是对事件的响应,epoll_wait 阻塞直到有事件发生
- 线程私有:EventLoop 绑定线程,不跨线程使用,runInLoop 保证线程安全
- 对象池管理:通过 shared_ptr 管理 TcpConnection/CHttpConn 生命周期
- 上下文传递:通过 std::any 在连接生命周期内传递自定义数据(uuid)
- 分层设计:网络层(TcpConnection)和业务层(CHttpConn)分离
- Reactor 模式:One Loop Per Thread,IO 和计算分离
根据零声教育教学写作https://github .com/0voice