一、引言:为什么用协程实现 HTTP 服务器?
传统 HTTP 服务器的编程模型大致分为:
-
多线程阻塞型:每连接一线程,简洁但扩展性差
-
事件驱动模型(如 epoll + 状态机):高性能但逻辑复杂
-
回调式异步(如 Boost::Asio):性能好但写法晦涩
C++20 引入 原生协程(coroutine) 后,我们可以用“同步风格”编写异步逻辑,兼顾性能与可读性。
本文将带你从零实现一个基于协程 + epoll 的轻量 HTTP 服务器,支持多连接并发、非阻塞 I/O 以及协程调度。
二、项目目标与能力规划
项目目标
实现一个简易 HTTP 服务:
-
使用
epoll
管理连接 -
每个连接由一个协程处理
-
实现 HTTP 请求解析 + 回应
-
支持并发数千连接的负载能力
三、项目结构概览
swift
复制编辑
/http_coro_server/ │ ├── main.cpp // 主程序,启动监听与事件循环 ├── coroutine_task.hpp // Task 协程封装 ├── epoll_loop.hpp // epoll 封装与事件调度 ├── connection.hpp // 每个连接处理逻辑(协程) └── utils.hpp // socket/非阻塞工具函数
四、基础组件 1:协程返回类型 Task
cpp
复制编辑
// coroutine_task.hpp template<typename T = void> struct Task { struct promise_type { std::optional<T> value; Task get_return_object() { return Task{std::coroutine_handle<promise_type>::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_value(T v) { value = v; } void unhandled_exception() { std::exit(1); } }; std::coroutine_handle<promise_type> handle; Task(std::coroutine_handle<promise_type> h) : handle(h) {} ~Task() { if (handle) handle.destroy(); } void start() { handle.resume(); } };
五、基础组件 2:设置非阻塞 socket 工具
cpp
复制编辑
// utils.hpp int set_non_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int create_server_socket(int port) { int fd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in addr{AF_INET, htons(port), INADDR_ANY}; bind(fd, (sockaddr*)&addr, sizeof(addr)); listen(fd, SOMAXCONN); set_non_blocking(fd); return fd; }
六、基础组件 3:epoll 封装与事件管理
cpp
复制编辑
// epoll_loop.hpp class EpollLoop { int epoll_fd; std::unordered_map<int, std::coroutine_handle<>> waiters; public: EpollLoop() { epoll_fd = epoll_create1(0); } void add(int fd, uint32_t events, std::coroutine_handle<> h) { epoll_event ev{events, {.fd = fd}}; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); waiters[fd] = h; } void resume_events() { epoll_event events[64]; int n = epoll_wait(epoll_fd, events, 64, 10); for (int i = 0; i < n; ++i) { int fd = events[i].data.fd; if (waiters.count(fd)) { auto h = waiters[fd]; waiters.erase(fd); h.resume(); } } } void remove(int fd) { epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr); waiters.erase(fd); } };
七、Awaitable 类型封装:ReadAwaiter / WriteAwaiter
cpp
复制编辑
struct ReadAwaiter { int fd; EpollLoop& loop; bool await_ready() { return false; } void await_suspend(std::coroutine_handle<> h) { loop.add(fd, EPOLLIN | EPOLLET, h); } void await_resume() {} }; struct WriteAwaiter { int fd; EpollLoop& loop; bool await_ready() { return false; } void await_suspend(std::coroutine_handle<> h) { loop.add(fd, EPOLLOUT | EPOLLET, h); } void await_resume() {} };
八、协程:每个连接的处理逻辑
cpp
复制编辑
// connection.hpp Task<> handle_connection(int client_fd, EpollLoop& loop) { char buf[1024]; std::string request; while (true) { co_await ReadAwaiter{client_fd, loop}; int n = read(client_fd, buf, sizeof(buf)); if (n <= 0) break; request.append(buf, n); if (request.find("\r\n\r\n") != std::string::npos) break; } std::string response = "HTTP/1.1 200 OK\r\n" "Content-Length: 13\r\n" "Content-Type: text/plain\r\n\r\n" "Hello, world!"; co_await WriteAwaiter{client_fd, loop}; send(client_fd, response.c_str(), response.size(), 0); close(client_fd); }
九、main 函数:接受连接 + 事件循环
cpp
复制编辑
// main.cpp #include "coroutine_task.hpp" #include "utils.hpp" #include "epoll_loop.hpp" #include "connection.hpp" int main() { int server_fd = create_server_socket(8080); EpollLoop loop; while (true) { sockaddr_in client_addr; socklen_t len = sizeof(client_addr); int client_fd = accept(server_fd, (sockaddr*)&client_addr, &len); if (client_fd >= 0) { set_non_blocking(client_fd); handle_connection(client_fd, loop).start(); } loop.resume_events(); // 激活等待事件的协程 } close(server_fd); }
十、性能测试建议
可以使用 Apache Benchmark 或 wrk 进行测试:
bash
复制编辑
ab -n 10000 -c 100 http://127.0.0.1:8080/
或:
bash
复制编辑
wrk -t4 -c100 -d10s http://localhost:8080/
十一、可拓展方向
功能 | 描述 |
---|---|
路由系统 | 根据 path 分发协程函数处理 |
支持 POST / JSON 请求 | 使用简单 parser 解析请求体 |
Keep-Alive / 连接复用支持 | 复用 client_fd 管理多个请求 |
异步文件读取 | 结合协程读取 HTML/文件资源 |
TLS/SSL 支持 | 整合 OpenSSL,使用协程方式发送加密响应 |
十二、项目优势与可落地性
-
C++20 原生协程,无需 Boost、libuv 等繁重依赖
-
epoll + 协程方式性能优于线程池架构
-
易于理解与拓展,可逐步演化为微型 Web 框架
-
支持高并发请求处理,适合 IoT、嵌入式、微服务网关等场景
十三、总结
我们实现了一个完整的协程驱动 HTTP Server:
-
基于 C++20 coroutine + epoll 构建事件驱动框架
-
每个连接一个协程,逻辑清晰,处理自然
-
使用 Awaitable 类型管理 I/O 阻塞
-
实现非阻塞 accept、read、write