协程驱动的高性能异步 HTTP 服务器:基础实现与任务调度机制

发布于:2025-06-27 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、引言:为什么用协程实现 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


网站公告

今日签到

点亮在社区的每一天
去签到