一、引言:為什么用協程實現 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