# gincpp **Repository Path**: judeli/gincpp ## Basic Information - **Project Name**: gincpp - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-28 - **Last Updated**: 2026-09-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ginCpp — C++23 原生协程 Web 框架(gin 风格) 一个 **header-only、零第三方依赖、基于 C++23 协程**的高性能 Web 框架。 API 语义对齐 golang/gin:`Engine` / `GET|POST|...` / `Use` / `Group` / `Context` / 中间件洋葱链。 事件循环后端**默认 epoll**,可经编译开关 `-DGINCPP_IO_URING` 切换为 **io_uring**(Linux 5.6+)。 设计哲学与实现细节见 [docs/](docs/)(含 C4 架构图、Mermaid 时序/状态图、六大支柱映射、压测方法论)。 --- ## 特性 - **C++23 原生协程**:`Task` 急切启动 + 嵌套 continuation 链式恢复;无宏、无 Boost.Coroutine。 - **双事件循环后端(编译开关切换)**:默认 `epoll`(`event_loop.hpp`);`-DGINCPP_IO_URING` 编译为 `io_uring` 后端(`event_loop_uring.hpp`,裸系统调用 `io_uring_setup/io_uring_enter` + mmap ring, 零依赖不引 liburing)。两后端公共接口(`watch_readable/watch_writable/ReadableAwaiter/...`)完全一致, **socket/server/tests 零改动**;io_uring 后端对连接/定时器/唤醒统一以 `IORING_OP_POLL_ADD` 常驻监听, `poll(ring_fd)` 阻塞等待 CQ 完成。 - **事件循环调度**:单 `timerfd` 小根堆定时器、`eventfd` 跨线程投递、`IoState.claimed` 就绪/超时竞争消解。 - **多线程 N 事件循环**:连接亲和单线程(免锁、缓存友好);loop0 单点 accept + 轮询分发。 - **异步 IO 线性化**:`co_await sock.read_some_timeout(...)` 同步式代码、无阻塞代价、空闲 60s 超时。 - **零拷贝 IO**:响应经 `writev` 多段 scatter 发送([状态行+头, body] 不再拼大字符串);静态文件用 `sendfile` 由内核直发(大文件实测 1.5x+ 吞吐,见基准)。 - **gin 风格路由**:radix tree,字面量 `>` `:param` `>` `*catchall` 优先级,注册期中间件快照; 注册期冲突检测(参数名冲突 / catchall 后挂子节点 / 重复注册在启动期报错)、 `RedirectTrailingSlash` / `RedirectFixedPath`(301/307)、`HandleMethodNotAllowed`(405)、 `NoRoute` / `NoMethod` 自定义、路径参数 URL 解码、`Routes()` 枚举、静态文件服务。 - **流式 HTTP 解析**:请求行→头→body(Content-Length / chunked),兼容分片与 pipelining。 - **中间件洋葱链**:`co_await c.Next()` 先下游后收尾,`Abort()` 截断;全局 / 分组 / 嵌套分组逐级继承。 - **优雅关闭**:SIGINT/SIGTERM → 停 accept、关监听、回收线程。 - **测试与压测**:80 个单测/集成测试在 **epoll 与 io_uring 双后端各跑一遍全绿**(含半包/粘包/keep-alive 管道专项);路由/解析/JSON/全链路/零拷贝五类内建基准(`make test` / `make bench`)+ **wrk 外部 HTTP 压测**(`make bench-wrk`,4 场景 / 千连接压力)。 --- ## 快速开始 ### 环境 Linux 或 WSL2(默认后端依赖 `epoll/timerfd/eventfd`;io_uring 后端需 Linux 5.6+),编译器 g++ 13+(`-std=c++23`)。 ```bash make # 编译 example + tests + bench(默认 epoll 后端) make test # 运行全部测试(80 个断言全绿,含半包/粘包专项) make run # 启动示例服务器 :8080(默认 4 线程) make bench-wrk # wrk 外部 HTTP 压测(需先 apt install wrk) # --- io_uring 后端(编译开关 GINCPP_IO_URING)--- make uring # 编译 example + tests + bench(io_uring 版,独立二进制 *_uring) make uring-test # 编译并运行 io_uring 版全部测试 ./build/gincpp_example_uring 8080 # 启动 io_uring 版示例服务器 ``` ### 手动启动示例 ```bash ./build/gincpp_example [port=8080] [threads=4] curl http://localhost:8080/hello/world # -> hello, world! curl 'http://localhost:8080/search?q=gin' # -> {"q":"gin",...} curl -X POST --data 'hi' http://localhost:8080/echo curl -i http://localhost:8080/routes # 查看全部已注册路由 ``` ### 全特性示例(example/main.cpp) `example/main.cpp`(默认构建目标)覆盖框架各特性族的可运行示例(自带 `example/public` 静态目录): ```bash make example # 编译(epoll 默认后端) make uring # 编译全部 io_uring 版(含 gincpp_example_uring) ./build/gincpp_example [port=8080] [threads=4] # 从项目根目录启动 bash example/verify.sh # 双后端端到端验收(epoll 39 + uring 9 = 48 项) ``` 覆盖清单:全 HTTP 方法(GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/Any)、路径参数 `:name`、通配符 `*filepath`、query(Query/QueryDefault)、请求头/请求体、JSON/Data/String/NoContent/Status 响应、洋葱链中间件(Logger/RequestID Set-Get 共享)、Abort 截断鉴权、分组 + 组中间件 + 嵌套分组、静态文件(Static/StaticFile/File 零拷贝 sendfile)、自定义 404/405、301/307 重定向行为(TrailingSlash/FixedPath)、路由枚举诊断(`/routes`)。 ### 最小应用 ```cpp #include "gincpp/gincpp.hpp" using namespace gincpp; int main() { Engine app; app.Use([](Context& c) -> Task { // 全局中间件 auto t0 = std::chrono::steady_clock::now(); co_await c.Next(); auto ms = std::chrono::duration( std::chrono::steady_clock::now() - t0).count(); printf("%s -> %d (%.2fms)\n", c.request().path.c_str(), c.response().status, ms); }); app.GET("/", [](Context& c) -> Task { c.String(200, "hello from gincpp\n"); co_return; }); app.GET("/hello/:name", [](Context& c) -> Task { c.String(200, "hello, " + c.Param("name") + "!\n"); co_return; }); app.GET("/query", [](Context& c) -> Task { c.String(200, "a=" + c.Query("a") + " b=" + c.QueryDefault("b", "x") + "\n"); co_return; }); app.POST("/echo", [](Context& c) -> Task { c.String(200, c.GetBody()); co_return; }); app.GET("/json", [](Context& c) -> Task { c.JSON(200, json::object({ {"ok", true}, {"now", 8361294} }).dump()); co_return; }); auto api = app.Group("/api"); // 路由分组 api.GET("/ping", [](Context& c) -> Task { c.JSON(200, R"({"pong":true})"); co_return; }); api.GET("/user/:id", [](Context& c) -> Task { c.JSON(200, json::object({ {"id", c.Param("id")} }).dump()); co_return; }); app.Run(8080, 4); // 4 个事件循环线程,阻塞运行 } ``` --- ## API 速查 ### Engine / 路由注册 ```cpp app.Use(middleware...); // 全局中间件(须先于路由注册,gin 语义,支持变长) app.GET/POST/PUT/DELETE/HEAD/OPTIONS/PATCH(path, handlers...); // 变长 handlers app.Any(path, handlers...); // 注册到全部 7 个方法 app.Handle(method, path, handlers...); app.Group("/prefix", mw...); // 返回 RouterGroup(可带组中间件,链式注册) app.NoRoute(handlers...); // 自定义 404 处理器链 app.NoMethod(handlers...); // 自定义 405 处理器链 app.Static("/static", "/path/to/dir"); // 静态目录服务(内置穿越防护) app.StaticFile("/robots.txt", "/path/file"); // 静态单文件服务 app.Routes(); // 枚举已注册路由 (method, path) // gin 行为开关(默认全部开启) app.RedirectTrailingSlash = true; // /user/ <-> /user 301/307 重定向 app.RedirectFixedPath = true; // 大小写 / 连续斜杠路径修正重定向 app.HandleMethodNotAllowed = true; // 方法不匹配返回 405 app.UnescapePathValues = true; // 路径参数百分号解码 app.Run(port, threads); // 阻塞运行;或 app.Start()/Shutdown() 手动管理 ``` ### RouterGroup(分组) ```cpp auto v1 = app.Group("/v1", auth); // 组中间件 auto admin = v1.Group("/admin", audit); // 嵌套分组继承父组中间件 v1.GET("/ping", h); // 自动带前缀 + 全局 + 组中间件链 v1.Static("/static", dir); // 组内静态服务 ``` ### Context ```cpp c.Param("name"); // 路径参数 c.Query("a"); c.QueryDefault("a", "x"); // query(URL 解码) c.GetBody(); // 请求体 c.Set("k", v); c.Get("k"); // 请求级 KV c.String(status, body); c.JSON(status, body); c.Data(status, body, ctype); c.NoContent(); c.Status(status); c.Header(k, v); c.Next(); c.Abort(); // 中间件链控制 ``` ### 协程 IO(server_impl 内部使用,亦可作为 API) ```cpp co_await sock.read_some_timeout(span, timeout_ms); // -> {n, timed_out, error} co_await sock.write_all(span); // 写完整(EAGAIN 挂起) co_await loop.sleep_for(ms); ``` --- ## 目录结构 ``` include/gincpp/ ├── gincpp.hpp # 总入口 ├── core/ # task.hpp / buffer.hpp / event_loop.hpp ├── net/socket.hpp # Socket 异步 IO ├── http/ # request / response / parser ├── json.hpp # JSON 构建 ├── router.hpp # radix tree 路由 ├── context.hpp # Context + 中间件链 ├── engine.hpp # Engine + RouterGroup └── server.hpp / server_impl.hpp # 多线程服务器 example/main.cpp # 全特性示例(覆盖各特性族,含 example/verify.sh 双后端验收 + public 静态演示文件) tests/ # 单元 + 集成测试 bench/benchmark.cpp # 内建微基准(路由/解析/JSON/全链路) bench/wrk_bench.sh # wrk 外部 HTTP 压测(make bench-wrk) docs/ # 设计文档(01~06) ``` --- ## 文档索引 | 文档 | 内容 | |---|---| | [docs/01-architecture.md](docs/01-architecture.md) | 总体架构、C4 图、六大支柱映射、时序 | | [docs/02-coroutine.md](docs/02-coroutine.md) | Task 协程生命周期、异常、GC、帧内存 | | [docs/03-event-loop.md](docs/03-event-loop.md) | epoll、定时器堆、竞争消解、多线程亲和 | | [docs/04-http-router.md](docs/04-http-router.md) | 流式解析、radix tree、中间件洋葱链 | | [docs/05-engineering.md](docs/05-engineering.md) | 构建/测试/压测方法、内核调优、生产化路线 | | [docs/06-io-uring.md](docs/06-io-uring.md) | io_uring 后端:开关、ring 布局、提交/收割、双后端对比 | | [docs/07-benchmark-epoll-vs-uring.md](docs/07-benchmark-epoll-vs-uring.md) | epoll vs io_uring 基准复核:14 轮统计、结论、原因分析与复测建议 | --- ## 基准参考 **内建微基准**(WSL2 16 核 VM,`./build/gincpp_bench 8 3000 4`): ``` router static match : ~115 ns/op parser GET : ~530 ns/op (~1.3M req/s) router param match : ~270 ns/op parser chunked: ~620 ns/op full-stack (4 loops): ~46 Kreq/s, p50≈0.14ms, p99≈0.49ms (客户端顺序请求模型,非峰值) ``` **epoll vs io_uring 后端对比**(2026-09-04 复核,同机同参 `./build/gincpp_bench{,_uring}`,**14 轮统计**,详见 [docs/07-benchmark-epoll-vs-uring.md](docs/07-benchmark-epoll-vs-uring.md)): | 统计口径 | epoll(默认) | io_uring(`-DGINCPP_IO_URING`) | 比值 (uring/epoll) | |---|---|---|---| | full-stack 吞吐(中位) | 50.8 Kreq/s | 45.5 Kreq/s | 0.89 | | full-stack 吞吐(峰值) | 58.0 Kreq/s | 55.2 Kreq/s | 0.95 | | p50(好轮) | ~0.125 ms | ~0.137 ms | 相当 | > ⚠️ **修正早期记录**:此前 "+39%(34.6→48.2)" 为早期单次测量,**不可复现**。复核显示当前 WSL2 > 环境下 io_uring 与 epoll 吞吐量级相当、略低约 5–11%(虚拟化层 syscall 开销 + 低并发 + 尚未实现 > `IORING_OP_RECV/SEND`)。两后端功能/测试完全等价(80 测试双后端全绿),性能取舍需在真实 Linux 物理机 + 高并发复测。 **零拷贝发送对比**(本次实测,loopback TCP,同文件同连接,`[5]` 项): - 大文件 512KB:`memcpy+send` ≈ 5.2 GB/s vs `sendfile` ≈ 8.2 GB/s → **1.57x** - 小文件 4KB:`memcpy+send` ≈ 1.9 GB/s vs `sendfile` ≈ 2.2 GB/s → **1.16x** - 说明:sendfile 收益在"page cache → socket(网卡)"路径(内核直发、数据不过用户态)随文件增大而显著;AF_UNIX socketpair 上无 DMA 收益(反而更慢),故对比使用 loopback TCP。 **wrk 外部压测**(`make bench-wrk`,示例服务器 4 事件循环,多轮取样典型值): ``` GET / -c100 : ~170K req/s, P50≈0.5ms, P99≈1.7ms GET /hello/world -c100 : ~171K req/s, P50≈0.5ms, P99≈1.6ms GET /json -c100 : ~172K req/s, P50≈0.5ms, P99≈1.7ms GET / -c1000: ~136K req/s, P50≈7ms, P99≈12ms (千连接压力) ``` > WSL2 共享宿主机 CPU,同命令多次吞吐可波动 ±50%;完整方法与场景矩阵见 > [docs/05-engineering.md](docs/05-engineering.md) 第 3.5 节。 ## 许可 MIT。