{"id":15047701,"url":"https://github.com/HengXin666/HXLibs","last_synced_at":"2025-10-31T02:31:24.118Z","repository":{"id":248797980,"uuid":"829807063","full_name":"HengXin666/HXLibs","owner":"HengXin666","description":"C++20协程和基于io_uringの百万并发服务器; 可异步读写, 支持Transfer-Encoding分块编码传输文件; 基于压缩前缀树编写的路由, 支持通配符解析;+ http/https/websocket、socks5代理、Json解析、Json静态反射到结构体, 封装了线程安全的LFUCache和LRUCache,  静态枚举映射, 支持STL容器的print/toString等","archived":false,"fork":false,"pushed_at":"2025-01-12T16:19:50.000Z","size":1227,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-12T17:26:18.686Z","etag":null,"topics":["coroutine","cpp","cpp20","http","https","io-uring","json","lfu-cache","lru-cache","openssl","router","websocket"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HengXin666.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-17T03:29:56.000Z","updated_at":"2025-01-12T16:19:54.000Z","dependencies_parsed_at":"2025-01-12T17:27:02.408Z","dependency_job_id":"c0263640-074f-4704-b659-957bb0865230","html_url":"https://github.com/HengXin666/HXLibs","commit_stats":null,"previous_names":["hengxin666/hxnet","hengxin666/hxlibs"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HengXin666%2FHXLibs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HengXin666%2FHXLibs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HengXin666%2FHXLibs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HengXin666%2FHXLibs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HengXin666","download_url":"https://codeload.github.com/HengXin666/HXLibs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239097845,"owners_count":19581111,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coroutine","cpp","cpp20","http","https","io-uring","json","lfu-cache","lru-cache","openssl","router","websocket"],"created_at":"2024-09-24T21:03:17.136Z","updated_at":"2025-10-31T02:31:24.110Z","avatar_url":"https://github.com/HengXin666.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\" style=\"color:yellow\"\u003eHXLibs\u003c/h1\u003e\n\n## 一、概述\n\nHXLibs 是一个现代 C++ 库. 目前集合了:\n\n- 基于 `io_uring` / `IOCP` **协程** 的 Http / WebSocket 客户端与服务端, 服务器支持**分块编码**/**断点续传**/**超时机制**! 客户端支持 `Socks5` 代理.\n\n    \u003e 本地测试 500MB 文件传输吞吐量高达 20 GB/s; wrk 压测 Http 请求并发高达 200w+ Requests/sec\n\n- WebSocket 服务端接口学习了 FastApi 思想, 使用简单.\n\n- 支持聚合类反射 / 宏反射(支持别名 和 私有成员), 轻松支持 Json 的序列化与反序列化! (反序列化时对字符串的零拷贝)\n\n---\n\n还包含:\n\n- 协程库\n- 网络库\n- 反射库\n    - Json序列化库 (基于反射)\n- 日志库\n\n\u003e 更多介绍 `@todo` ...\n\n## 二、构建要求\n\n- Linux 5.1+ || Windows\n- GCC / Clang || MSVC 编译器\n- C++20\n\nCMake以子项目的方式导入使用:\n\n```cmake\n# 导入 libs 文件夹下的 HXLibs\nadd_subdirectory(libs/HXLibs)\ntarget_link_libraries(YouProject PRIVATE HXLibs)\n```\n\n## 三、快速开始\n\u003e [!TIP]\n\u003e 仍然在开发, 非最终产品\n\n### 3.1 HX::net (网络模块)\n#### 3.1.1 编写服务端\n\n下面是一个简单的服务端示例: ([examples/HttpServer/01_http_server.cpp](examples/HttpServer/01_http_server.cpp))\n\n```cpp\n#include \u003cHXLibs/net/Api.hpp\u003e // 包含了 端点宏 ENDPOINT\n#include \u003cHXLibs/log/Log.hpp\u003e\n\nusing namespace HX;\nusing namespace net;\n\nint main() {\n    HttpServer ser{\"127.0.0.1\", \"28205\"}; // 定义服务器\n    ser.addEndpoint\u003cGET\u003e(\"/user/{id}\", [] ENDPOINT {\n        co_await res.setStatusAndContent(\n            Status::CODE_200,\n            \"\u003ch1\u003eHello\" \n            + req.getPathParam(0) // 获取 {id} 路径参数, 可以 req.getPathParam(0).to\u003cuint64_t\u003e() 快速转为数字/枚举哦~\n            + \"\u003c/h1\u003e\"\n            + utils::DateTimeFormat::formatWithMilli()\n        ).sendRes();\n        co_return;\n    })\n    .addEndpoint\u003cGET\u003e(\"/stop\", [\u0026] ENDPOINT {\n        // 支持手动关闭服务器\n        co_await res.setStatusAndContent(\n            Status::CODE_200, \"\u003ch1\u003estop server!\u003c/h1\u003e\" + utils::DateTimeFormat::formatWithMilli())\n                    .sendRes();\n        req.addHeaders(\"Connection\", \"close\");\n        ser.asyncStop();\n        co_return;\n    })\n    .addEndpoint\u003cGET\u003e(\"/favicon.ico\", [] ENDPOINT {\n        log::hxLog.debug(\"get .ico\");\n        co_return co_await res.useRangeTransferFile( // 传输单个文件\n            req.getRangeRequestView(),\n            \"static/favicon.ico\"\n        );\n    })\n    .addEndpoint\u003cGET, HEAD\u003e(\"/files/**\", [] ENDPOINT {\n        using namespace std::string_literals;\n        using namespace std::string_view_literals;\n        auto path = req.getUniversalWildcardPath();\n        log::hxLog.debug(\"请求:\", path, \"| 断点续传:\", req.getReqType() == \"HEAD\"sv || req.getHeaders().contains(\"range\"));\n        try {\n            // 使用断点续传传输文件 (匹配到 `static/bigFile/**` 路径)\n            co_await res.useRangeTransferFile(\n                req.getRangeRequestView(),\n                \"static/bigFile/\"s + std::string{path.data(), path.size()}\n            );\n            log::hxLog.debug(\"发送完毕!\");\n        } catch (std::exception const\u0026 ec) {\n            // 协程支持异常\n            log::hxLog.error(__FILE__, __LINE__, \":\", ec.what());\n        }\n        co_return ;\n    });\n\n    using namespace utils;\n    ser.syncRun(16, 30_s); // 启动服务器, 并且阻塞\n                           // 其中第一个参数是线程个数, 每个线程独立维护一个 事件循环\n                           // 一个事件循环控制 该线程的所有协程\n                           // 第二个参数是超时时间, 超时会自动断开连接 (从离开端点函数时候开始计算)\n                           // 注意: 其时间类型定义在 include/HXLibs/utils/TimeNTTP.hpp 中, 并非标准库的时间\n}\n```\n\n其中, `ENDPOINT` 只是一种简写, 其被定义为:\n\n```cpp\n/**\n * @brief 定义标准的端点, 请求使用`req`变量, 响应使用`res`变量\n */\n#define ENDPOINT (                           \\\n    [[maybe_unused]] HX::net::Request\u0026 req,  \\\n    [[maybe_unused]] HX::net::Response\u0026 res  \\\n) -\u003e HX::coroutine::Task\u003c\u003e\n```\n\n特别的, 它还支持`面向切面`编程:\n\n```cpp\nenum class ServerStatus : uint32_t {\n    Error = 0,\n    Ok = 1\n};\n\nstruct TimeLog { // 计时器切面: 端点访问时间间隔\n    decltype(std::chrono::steady_clock::now()) t;\n\n    // 进入端点之前\n    auto before(Request\u0026, Response\u0026) {\n        t = std::chrono::steady_clock::now();\n        return ServerStatus::Ok; // 返回值只要可以转换为 bool 类型, 就是合法的\n                                 // 如果返回 bool{false}, 那么就会终止连接 (类似于`拦截器`)\n    }\n\n    // 端点结束之后\n    auto after(Request\u0026 req, Response\u0026) {\n        auto t1 = std::chrono::steady_clock::now();\n        auto dt = t1 - t;\n        int64_t us = std::chrono::duration_cast\u003cstd::chrono::milliseconds\u003e(dt).count();\n        log::hxLog.info(\"已响应: \", req.getPureReqPath(), \"花费: \", us, \" us\");\n        return true;\n    }\n};\n\nHttpServer ser{\"127.0.0.1\", \"28205\"};\nser.addEndpoint\u003cGET\u003e(\"/\", [] ENDPOINT {\n    co_await res.setStatusAndContent(\n        Status::CODE_200, \"\u003ch1\u003e这是 HXLibs::net 服务器\u003c/h1\u003e\" + utils::DateTimeFormat::formatWithMilli())\n                .sendRes();\n    co_return;\n}, TimeLog{}) // \u003c-- 面向切面编程, 可以编写任意个切面, 只需要在此传参即可\n```\n\n#### 3.1.2 编写客户端\n\n下面是一个简单的客户端示例: ([tests/client/01_http_client.cpp](tests/client/01_http_client.cpp))\n\n\u003e [!TIP]\n\u003e 客户端的 http 请求都提供了`协程`和`异步`两种接口, 后者可以通过 `.get()` 阻塞等待获取 `返回值`, 以变为同步接口 (如下示例)\n\n```cpp\n#include \u003cHXLibs/net/client/HttpClient.hpp\u003e\n\n#include \u003cHXLibs/log/Log.hpp\u003e\n\nusing namespace HX;\nusing namespace net;\n\n// 协程接口\ncoroutine::Task\u003c\u003e coMain() {\n    HttpClient cli{};\n    log::hxLog.debug(\"开始请求\");\n    ResponseData res = co_await cli.coGet(\"http://httpbin.org/get\");\n    log::hxLog.info(\"状态码:\", res.status);\n    log::hxLog.info(\"拿到了 头:\", res.headers);\n    log::hxLog.info(\"拿到了 体:\", res.body);\n}\n\nint main() {\n    coMain().start();\n\n    // 同步接口\n    HttpClient cli{};\n    auto res = cli.get(\"http://httpbin.org/get\").get(); // 后面的 .get() 是阻塞获取返回值\n                                                        // 如果不 .get(), 那么它实际上是异步的 (内部有线程执行)\n    log::hxLog.info(\"状态码:\", res.status);\n    log::hxLog.info(\"拿到了 头:\", res.headers);\n    log::hxLog.info(\"拿到了 体:\", res.body);\n\n    // 支持异步 API thenTry(Try\u003cT\u003e)\n    cli.get(\"http://httpbin.org/get\").thenTry([](Try\u003cResponseData\u003e resTry) -\u003e void {\n        if (resTry) {\n            auto res = resTry.move();\n            log::hxLog.info(\"状态码:\", res.status);\n            log::hxLog.info(\"拿到了 头:\", res.headers);\n            log::hxLog.info(\"拿到了 体:\", res.body);\n        } else {\n            log::hxLog.error(\"发生异常:\", resTry.what());\n        }\n        return;\n    }).thenTry([](Try\u003cvoid\u003e /* 进一步的, 可以写为 `Try\u003c\u003e`, 因为默认参数: T = void*/) {\n        // ...\n        // 注意, Try\u003cT\u003e 的 应该是前一个的返回值类型\n    });\n    return 0;\n}\n\nvoid test() {\n    // 使用代理\n    HttpClient cli{HttpClientOptions{{\"socks5://127.0.0.1:2334\"}}};\n    auto res = cli.get(\"http://httpbin.org/get\").get();\n    log::hxLog.info(\"状态码:\", res.status);\n    log::hxLog.info(\"拿到了 头:\", res.headers);\n    log::hxLog.info(\"拿到了 体:\", res.body);\n    return 0;\n}\n```\n\n#### 3.1.3 WebSocket 客户端与服务端\n\n在 **服务端**, 我们提供了类似于 FastApi 的 WebSocket 接口, 使用非常方便!\n\n\u003e 详细内容请看: [tests/server/02_ws_server.cpp](tests/server/02_ws_server.cpp), 里面还提供了 WebSocket **消息群发** 的代码示例.\n\n```cpp\nHttpServer serv{\"127.0.0.1\", \"28205\"};\nserv.addEndpoint\u003cGET\u003e(\"/ws\", [] ENDPOINT {\n    auto ws = co_await WebSocketFactory::accept(req, res); // 升级为 WebSocket\n    struct JsonDataVo {\n        std::string msg;\n        int code;\n    };\n    JsonDataVo const vo{\"Hello 客户端, 我只能通信3次!\", 200};\n    co_await ws.sendJson(vo); // WebSocket 发送 Json 数据 (基于聚合类反射)\n    for (int i = 0; i \u003c 3; ++i) {\n        auto res = co_await ws.recvText(); // 等待读取文本\n                                           // [!] 特别的: 如果客户端发送了 ping, 其内部会自动回应 pong\n                                           //     并且不会阻断 recvText (也就是无感知的)\n                                           //     同理, 如果客户端发送 close, 则内部也会响应 close\n                                           //     关闭 ws 后, 会抛出 {已经安全关闭连接} 的异常\n                                           // [!] 如果读取的数据格式不对, 也会抛异常: 比如期望是 Test, 但是读取到二进制\n        log::hxLog.info(res);\n        co_await ws.sendText(\"Hello! \" + res); // 发送文本\n    }\n    co_await ws.close(); // 主动关闭 WebSocket 连接\n    log::hxLog.info(\"断开ws\");\n    co_return ;\n});\n```\n\n在 **客户端**, 我们提供的是协程回调的 WebSocket, 并且同样提供了`协程`和`异步`两种接口: ([tests/client/02_ws_client.cpp](tests/client/02_ws_client.cpp))\n\n\u003e [!TIP]\n\u003e 为什么是 **协程回调**?\n\u003e\n\u003e 实际上重点是`回调`, 内部会传入一个 `WebSocketClient` 供用户使用, 而将它作为回调是为了安全的控制 WebSocket 的生命周期. 以防止它被用户滥用.\n\n```cpp\ncoroutine::Task\u003c\u003e coMain() {\n    HttpClient cli{};\n    co_await cli.coWsLoop(\"ws://127.0.0.1:28205/ws\",\n        [](WebSocketClient ws) -\u003e coroutine::Task\u003c\u003e {\n            co_await ws.sendText(\"hello\");\n            auto msg = co_await ws.recvText();\n            log::hxLog.info(\"收到: \", msg);\n            co_await ws.close();\n        }\n    );\n}\n\nint main() {\n    // 协程\n    coMain().start();\n\n    // 同步\n    HttpClient cli{};\n    cli.wsLoop(\"ws://127.0.0.1:28205/ws\",\n        [](WebSocketClient ws) -\u003e coroutine::Task\u003c\u003e {\n            co_await ws.sendText(\"hello 我是同步接口的协程回调\");\n            auto msg = co_await ws.recvText();\n            log::hxLog.info(\"收到: \", msg);\n            co_await ws.close();\n        }\n    ).wait();\n    return 0;\n}\n```\n\n### 3.2 HX::coroutine (协程模块)\n#### 3.2.1 Task (默认协程)\n\n- `T`: 返回值类型\n- `P`: 协程控制者 (默认的`Promise\u003cT\u003e`是懒启动, 以及 `co_return` 可以恢复到之前的协程调用者)\n- `Awaiter`: 则是指定 `co_await` 的功能\n\n其中的 `runSync` 方法很危险, **除非你可以保证它可以执行到 co_return 并且在此之前不会返回, 否则不要调用该方法**.\n\n```cpp\ntemplate \u003c\n    typename T = void,\n    typename P = Promise\u003cT\u003e,\n    typename Awaiter = ExitAwaiter\u003cT, P\u003e\n\u003e\nstruct [[nodiscard]] Task {\n    /**\n     * @brief 立即执行协程\n     * @warning 除非你可以保证它可以执行到 co_return 并且在此之前不会返回, 否则不要调用该方法\n     * @return constexpr auto \n     */\n    constexpr auto runSync() const {\n        _handle.resume();\n        if constexpr (requires {\n            _handle.promise().result(); // Promise\u003cT\u003e 存在的\n        }) {\n            if (_handle.done()) [[likely]] {\n                return _handle.promise().result();\n            } else {\n                // 不是期望的! 协程还没有执行完毕\n                throw std::runtime_error{\"The collaborative process has not been completed yet\"};\n            }\n        }\n    }\n};\n```\n\n#### 3.2.2 RootTask (根协程)\n\n它可以让自己从当前协程中分离出来, 变成一个独立的协程 \"根协程\" 被调度.\n\n```cpp\n// 使用示例: include/HXLibs/net/server/ConnectionHandler.hpp\ncoroutine::Task\u003c\u003e start(std::atomic_bool const\u0026 isRun) {\n    auto serverFd = co_await makeServerFd();\n    for (;;) [[likely]] {\n        auto fd = exception::IoUringErrorHandlingTools::check(\n            co_await _eventLoop.makeAioTask().prepAccept(\n                serverFd,\n                nullptr,\n                nullptr,\n                0\n            )\n        );\n        log::hxLog.debug(\"有新的连接:\", fd);\n\n        // 直接从当前协程分离\n        ConnectionHandler::start\u003cTimeout\u003e(fd, isRun, _router, _eventLoop).detach();\n    }\n}\n```\n\n#### 3.2.3 WhenAny (Awaiter)\n\n它可以同时启动所有的协程, 并且返回第一个完成的那个\n\n```cpp\n// 常见使用场景: 超时\nauto res = co_await whenAny(协程_01(), 定时器());\nif (res.index() == 1)\n    log::hxLog.debug(\"超时:\");\n\n// 也可以使用运算符重载\nauto ans = co_await (协程_01() || 定时器());\n```\n\n#### 3.2.4 WhenAll (Awaiter)\n\n它可以同时启动所有的协程, 并且等待所有执行完毕, 最终返回 `std::tuple\u003cTs...\u003e` (其中 void 被表示为 NoVoid 类型)\n\n```cpp\nauto [r1, t2] = co_await whenAll(协程_01(), 协程_02());\n\n// 也可以使用运算符重载\nauto [a, b, c] = co_await (协程_01() \u0026\u0026 协程_02() \u0026\u0026 协程_03());\n```\n\n\u003e [!TIP]\n\u003e 暂时不支持 混合 `\u0026\u0026` 和 `||` 的调用: 如: `co_await ((协程_01() \u0026\u0026 协程_02()) || 协程_03())`\n\n#### 3.2.5 协程调度器\n\n见 [EventLoop.hpp](include/HXLibs/coroutine/loop/EventLoop.hpp) (Linux基于`io_uring`实现, Win基于`IOCP`实现)\n\n以及协程定时器 (基于红黑树实现快速删除 (析构时候直接通过迭代器删除, 无需查找)) [TimerLoop.hpp](include/HXLibs/coroutine/loop/TimerLoop.hpp)\n\n常用API:\n\n```cpp\nstruct EventLoop {\n    // 启动一个协程, 如果内部被挂起, 稍后应该调用 run(), 以从挂起中恢复\n    template \u003cCoroutineObject T\u003e\n    void start(T\u0026 mainTask);\n\n    // 同步等待一个协程完成 (在此之前, 应该保证事件循环为空, 否则会抛出异常)\n    // 如果 协程 mainTask 抛出异常, 则 sync 会捕获并且重新抛出\n    template \u003cCoroutineObject T, typename Res = AwaiterReturnValue\u003cT\u003e\u003e\n    Res sync(T\u0026\u0026 mainTask);\n\n    // 启动事件循环\n    void run();\n};\n```\n\n\u003e [!TIP]\n\u003e 如果你的类有 EventLoop, 那么你可以轻易的在析构的时候通过调用`_eventLoop.sync(this-\u003eclose(...))`来实现所谓 **RAII协程**\n\n### 3.3 HX::reflection (反射模块)\n#### 3.3.1 反射模板\n\n```cpp\n#include \u003cHXLibs/reflection/MemberName.hpp\u003e\n\nstruct Test {\n    int a;\n    std::string name;\n};\n\n// 支持获取字段个数 [编译期]\nconstexpr auto Cnt = reflection::membersCount\u003cTest\u003e();\nCHECK(Cnt == 2);\nstatic_assert(Cnt == 2, \"\");\n\n// 支持从索引访问字段名 [编译期]\nconstexpr auto Name = reflection::getMembersNames\u003cTest\u003e();\nCHECK(Name[0] == \"name01\");\nCHECK(Name[1] == \"name02\");\nstatic_assert(Name[0] == \"name01\", \"\");\nstatic_assert(Name[1] == \"name02\", \"\");\n\n// 支持获取字段名到索引的哈希 [编译期]\nconstexpr auto map = reflection::getMembersNamesMap\u003cTest\u003e();\nstatic_assert(map.at(\"name01\") == 0, \"\");\nstatic_assert(map.at(\"name02\") == 1, \"\");\nstatic_assert(map.find(\"_name\") == map.end(), \"\");\n\n// 获取到的哈希表是编译期常量, 可以进行编译期映射 [编译期]\n[[maybe_unused]] constexpr auto _ = std::index_sequence\u003c\n    map.at(\"name01\"),\n    map.at(\"name02\")\n\u003e{};\n\n// (部分)编译期 for 循环\nTest obj;\nreflection::forEach(obj, [\u0026] \u003cstd::size_t I\u003e (\n    std::index_sequence\u003cI\u003e, // 字段索引 I (编译器字面量)\n    auto name,              // 字段名称 std::string_view\n    auto\u0026 val               // 字段引用 (如果传入的是 Test const\u0026, 那么对应的 val 也是 auto const\u0026)\n) {\n    if constexpr (I == 0)\n        val = 0;\n    if constexpr (I == 1)\n        val = \"index = 1\";\n});\n```\n\n#### 3.3.2 使用宏进行反射\n\n在 `HXLibs/reflection/ReflectionMacro.hpp` 中, 我们支持用户使用宏来生成反射代码, 它可以反射私有字段:\n\n1. 类内反射, 支持私有成员变量\n\n```cpp\n#include \u003cHXLibs/reflection/ReflectionMacro.hpp\u003e // 头文件\n\nstruct HXTest {\nprivate:\n    int a{};\n    std::string b{};\n    double c{};\npublic:\n    HX_REFL(a, b) // 指定 反射 a, b, 而可以不反射 c\n};\n\n#include \u003cHXLibs/reflection/json/JsonRead.hpp\u003e\n#include \u003cHXLibs/reflection/json/JsonWrite.hpp\u003e\n\nTEST_CASE(\"宏反射内私有成员\") {\n    using namespace HX;\n    [[maybe_unused]] constexpr auto N = reflection::membersCountVal\u003cHXTest\u003e;\n    constexpr auto name = reflection::getMembersNames\u003cHXTest\u003e();\n    static_assert(name[0] == \"a\", \"\"); // 依旧是编译期反射\n    \n    HXTest t{};\n    [[maybe_unused]] auto tr = reflection::internal::getObjTie(t);\n    [[maybe_unused]] auto res = HXTest::visit(t);\n    [[maybe_unused]] auto cnt = reflection::HasReflectionCount\u003cHXTest const\u0026\u003e;\n\n    // 同样支持 forEach\n    reflection::forEach(t, [] \u003cstd::size_t Idx\u003e (std::index_sequence\u003cIdx\u003e, auto name, auto\u0026 v) {\n        if constexpr (Idx == 0) {\n            v = 2233;\n        } else if constexpr (Idx == 1) {\n            v = \"666\";\n        }\n        log::hxLog.info(Idx, name, v);\n    });\n\n    // 只要注册了, 就可以随意使用 json / log 以反射的实现 (前提是该字段类型是支持的)\n    HXTest newT;\n    std::string s;\n    reflection::toJson(t, s);\n    reflection::fromJson(newT, s);\n    log::hxLog.info(newT);\n}\n```\n\n2. 特别的, 支持起 **别名**:\n\n```cpp\nstruct HXTest_2 {\nprivate:\n    [[maybe_unused]] int a{};\n    [[maybe_unused]] std::string b{};\n    [[maybe_unused]] double c{};\npublic:\n    // 支持起别名的反射宏\n    HX_REFL_AS(\n        // 格式: 原变量名, 别名\n        a, int_a, \n        c, double_c\n    )   // 注意: 格式一定要统一, 必须为偶数个参数, 顺序对应~\n};\n\nTEST_CASE(\"宏反射内私有成员 支持别名\") {\n    using namespace HX;\n    [[maybe_unused]] constexpr auto N = reflection::membersCountVal\u003cHXTest_2\u003e;\n    constexpr auto name = reflection::getMembersNames\u003cHXTest_2\u003e();\n    [[maybe_unused]] HXTest_2 t{};\n    // 此处是别名!\n    static_assert(name[0] == \"int_a\", \"\");\n    [[maybe_unused]] auto tr = reflection::internal::getObjTie(t);\n\n    HXTest_2 newT;\n    std::string s;\n    log::hxLog.info(t);\n    reflection::forEach(t, [] \u003cstd::size_t Idx\u003e (std::index_sequence\u003cIdx\u003e, auto name, auto\u0026 v) {\n        if constexpr (Idx == 0) {\n            v = 666;\n        } else if constexpr (Idx == 1) {\n            v = 0.721;\n        }\n        log::hxLog.info(Idx, name, v);\n    });\n    // 依旧支持序列化和反序列化、log等等, 均以 别名 使用\n    reflection::toJson(t, s);\n    reflection::fromJson(newT, s);\n    log::hxLog.info(newT);\n}\n```\n\n3. **类外反射**, 在宏加上 `_G` (global), 即为声明为`全局函数`的反射版本, 但是仅能反射 `public` 字段. 不如类内声明\n\n```cpp\nnamespace UserNs { // 用户命名空间\n\nclass MyClass {\npublic:\n    int a;\n    std::string b;\n    std::vector\u003cMyClass\u003e c;\n};\n\n// 定义全局反射宏, 可以在用户的命名空间内定义\nHX_REFL_G(MyClass, a, b, c)\n\n}\n\nTEST_CASE(\"全局注册的反射\") {\n    static_assert(\n        reflection::HasInsideReflection\u003cUserNs::MyClass\u003e == false\n        \u0026\u0026 reflection::HasOutReflection\u003cUserNs::MyClass\u003e == true\n        \u0026\u0026 reflection::IsReflective\u003cUserNs::MyClass\u003e, \"\");\n    UserNs::MyClass myCLass;\n    // 参数个数\n    static_assert(reflection::membersCountVal\u003cdecltype(myCLass)\u003e == 3, \"\");\n    // 反射名称\n    constexpr auto name = reflection::getMembersNames\u003cUserNs::MyClass\u003e();\n    static_assert(name[0] == \"a\", \"\");\n    static_assert(name[1] == \"b\", \"\");\n    static_assert(name[2] == \"c\", \"\");\n    reflection::forEach(myCLass, [] \u003cstd::size_t Idx\u003e (std::index_sequence\u003cIdx\u003e, auto name, auto\u0026 v) {\n        log::hxLog.debug(Idx, name, \"-\u003e\", v);\n        if constexpr (Idx == 0) {\n            v = 1;\n        } else if constexpr (Idx == 1) {\n            v = \"b\";\n        } else if constexpr (Idx == 2) {\n            v = {{1, \"2\", {}}};\n        }\n    });\n    log::hxLog.debug(myCLass); // 依据支持打印等等操作\n    UserNs::MyClass newT;\n    std::string s;\n    reflection::toJson(myCLass, s);\n    reflection::fromJson(newT, s);\n    CHECK(myCLass.a == newT.a);\n    CHECK(myCLass.b == newT.b);\n    CHECK(myCLass.c.size() == newT.c.size());\n    log::hxLog.info(newT);\n}\n```\n\n4. 同理, 在宏的后面加上 `_AS`, 即是全局函数版本的起别名反射:\n\n```cpp\nnamespace UserNs { // 用户命名空间\n\nclass MyClassAs {\npublic:\n    int a;\n    std::string b;\n    std::vector\u003cMyClassAs\u003e c;\n};\n\n// 定义全局反射宏, 可以在用户的命名空间内定义\nHX_REFL_G_AS(MyClassAs, \n             a, new_a, \n             b, new_b, \n             c, new_c\n            )\n\n}\n\nTEST_CASE(\"全局注册的反射(别名)\") {\n    static_assert(\n        reflection::HasInsideReflection\u003cUserNs::MyClassAs\u003e \n        != reflection::HasOutReflection\u003cUserNs::MyClassAs\u003e\n        \u0026\u0026 reflection::IsReflective\u003cUserNs::MyClassAs\u003e, \"\");\n    UserNs::MyClassAs myCLass;\n    // 参数个数\n    static_assert(reflection::membersCountVal\u003cdecltype(myCLass)\u003e == 3, \"\");\n    // 反射名称\n    constexpr auto name = reflection::getMembersNames\u003cUserNs::MyClassAs\u003e();\n    static_assert(name[0] == \"new_a\", \"\");\n    static_assert(name[1] == \"new_b\", \"\");\n    static_assert(name[2] == \"new_c\", \"\");\n    reflection::forEach(myCLass, [] \u003cstd::size_t Idx\u003e (std::index_sequence\u003cIdx\u003e, auto name, auto\u0026 v) {\n        log::hxLog.debug(Idx, name, \"-\u003e\", v);\n        if constexpr (Idx == 0) {\n            v = 1;\n        } else if constexpr (Idx == 1) {\n            v = \"b\";\n        } else if constexpr (Idx == 2) {\n            v = {{1, \"2\", {}}};\n        }\n    });\n    log::hxLog.debug(myCLass); // 依据支持打印等等操作\n    UserNs::MyClassAs newT;\n    std::string s;\n    reflection::toJson(myCLass, s);\n    reflection::fromJson(newT, s);\n    CHECK(myCLass.a == newT.a);\n    CHECK(myCLass.b == newT.b);\n    CHECK(myCLass.c.size() == newT.c.size());\n    log::hxLog.info(newT);\n}\n```\n\n#### 3.3.3 基于反射的Json序列化与反序列化\n\n```cpp\n#include \u003cHXLibs/reflection/json/JsonRead.hpp\u003e\n#include \u003cHXLibs/reflection/json/JsonWrite.hpp\u003e\n#include \u003cHXLibs/log/Log.hpp\u003e\n\nusing namespace HX;\n\nTEST_CASE(\"json 序列化\") {\n    struct Cat {\n        std::string name;\n        int num;\n        std::vector\u003cCat\u003e sub;\n    };\n    Cat cat = {\n        \"咪咪\",\n        2233,\n        {{\n            \"明卡\",\n            233,\n            {{\n                \"大猫\",\n                666,\n                {}\n            }}\n        }, {\n            \"喵喵\",\n            114514,\n            {{}}\n        }}\n    };\n    std::string catStr;\n    reflection::toJson(cat, catStr);\n    log::hxLog.info(\"元素:\", cat);\n    log::hxLog.info(\"序列化字符串:\", catStr);\n    CHECK(catStr == R\"({\"name\":\"咪咪\",\"num\":2233,\"sub\":[{\"name\":\"明卡\",\"num\":233,\"sub\":[{\"name\":\"大猫\",\"num\":666,\"sub\":[]}]},{\"name\":\"喵喵\",\"num\":114514,\"sub\":[{\"name\":\"\",\"num\":0,\"sub\":[]}]}]})\");\n    catStr.clear();\n}\n\nTEST_CASE(\"json 反序列化\") {\n    struct A {\n        bool a;\n        std::vector\u003cstd::vector\u003cstd::string\u003e\u003e b;\n        std::unordered_map\u003cstd::string, std::string\u003e c;\n        std::optional\u003cstd::string\u003e d;\n        std::shared_ptr\u003cstd::string\u003e e;\n        double f;\n    } t{};\n\n    reflection::fromJson(t, R\"({\n        \"f\": -3.1415926E+10,\n        \"d\": null,\n        \"a\": 0,\n        \"c\": {\n            \"\": \"\",\n            \"中文Key\": \"中文Value\",\n            \"特殊字符!@#$%^\u0026*()_+\": \"特殊Value\",\n            \"换行\": \"第一行\\n第二行\",\n            \"制表符\": \"\\tTabTest\"\n        },\n        \"b\": [\n            [],\n            [\"单独一个\"],\n            [\"多行\\n字符串\", \"特殊字符\\\"\\\\\\'\", \"空字符串\", \"\\u4E2D\\u6587\"]\n        ],\n        \"e\": \"HelloPtr\"\n    })\");\n\n    // 断言检查\n    REQUIRE(t.a == false);\n    REQUIRE(t.f == -3.1415926E+10);\n    REQUIRE(!t.d.has_value());\n    REQUIRE(t.e != nullptr);\n    REQUIRE(*t.e == \"HelloPtr\");\n\n    REQUIRE(t.c.at(\"\") == \"\");\n    REQUIRE(t.c.at(\"中文Key\") == \"中文Value\");\n    REQUIRE(t.c.at(\"特殊字符!@#$%^\u0026*()_+\") == \"特殊Value\");\n    REQUIRE(t.c.at(\"换行\") == \"第一行\\n第二行\");\n    REQUIRE(t.c.at(\"制表符\") == \"\\tTabTest\");\n\n    REQUIRE(t.b.size() == 3);\n    REQUIRE(t.b[0].empty());\n    REQUIRE(t.b[1].size() == 1);\n    REQUIRE(t.b[1][0] == \"单独一个\");\n    REQUIRE(t.b[2].size() == 4);\n    REQUIRE(t.b[2][0] == \"多行\\n字符串\");\n    REQUIRE(t.b[2][1] == \"特殊字符\\\"\\\\\\'\");\n    REQUIRE(t.b[2][2] != \"\");\n    REQUIRE(t.b[2][3] == \"中文\");\n}\n```\n\n### 3.3.4 (编译期) 枚举反射\n\n\u003e [!TIP]\n\u003e 目前仅支持反射 `[-128, 127]` 的枚举值.\n\u003e\n\u003e 日后根据需要, 本库会迭代升级...\n\n```cpp\n#include \u003cHXLibs/reflection/EnumName.hpp\u003e\nusing namespace HX;\n\nenum MyEnum {\n    Z = -100,\n    A = 0,\n    B = 10,\n    C = 100\n};\n\n// 获取对应值的字符串\nstatic_assert(reflection::toEnumName(static_cast\u003cMyEnum\u003e(0)) == \"A\", \"\");\n\n// 从字符串反射到值\nstatic_assert(reflection::toEnum\u003cMyEnumClass\u003e(\"Z\") == MyEnumClass::Z, \"\");\n\n// 支持获取枚举个数\nstatic_assert(reflection::getValidEnumValueCnt\u003cMyEnum\u003e() == 4, \"\");\n\n// @todo 支持获取枚举哈希表 (内部存在, 但是目前没有打算暴露)\n```\n\n### 3.3.5 (编译期) 反射 `类、结构体、共用体、枚举类型名称`\n\n少部分情况下, 我们可能希望从 **自定义类型** 反射到 类型的`字符串`编译期常量, 则可以使用本方法.\n\n\u003e [!TIP]\n\u003e 反射 Lambda 类型 是 UB (未定义行为)\n\u003e\n\u003e 同理, 使用者 **不应该** 反射 带指针、引用、cv限定的类型.\n\n```cpp\n#include \u003cHXLibs/reflection/TypeName.hpp\u003e\n#include \u003cHXLibs/log/Log.hpp\u003e\n\n#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN\n#include \u003cdoctest.h\u003e\n\nusing namespace HX;\n\nnamespace test {\n\nstruct Student {\n    struct ClassRoom {\n        int id;\n    };\n    std::string name;\n};\n\ntemplate \u003ctypename T, std::size_t N\u003e\nstruct Array {\n    T arr[N];\n};\n\nusing hxArray = Array\u003cdouble, 0721\u003e;\n\nusing hxVector = Array\u003cArray\u003cint, 2\u003e, 3\u003e;\n\n}\n\ntemplate \u003ctypename... Args\u003e\nstruct Tmp {};\n\nTEST_CASE(\"测试类型反射: struct\") {\n    struct Student {\n        struct ClassRoom {\n            int id;\n        };\n        std::string name;\n    };\n    static_assert(reflection::getTypeName\u003cStudent\u003e() == \"Student\", \"\");\n    // 只能获取到 类名, 不包含任何的 :: 嵌套\n    static_assert(reflection::getTypeName\u003cStudent::ClassRoom\u003e() == \"ClassRoom\", \"\");\n    static_assert(reflection::getTypeName\u003cstruct Abcd\u003e() == \"Abcd\", \"\");\n\n    // 支持命名空间\n    static_assert(reflection::getTypeName\u003ctest::Student\u003e() == \"Student\", \"\");\n    static_assert(reflection::getTypeName\u003ctest::Student::ClassRoom\u003e() == \"ClassRoom\", \"\");\n\n    // 支持模板\n    static_assert(reflection::getTypeName\u003cTmp\u003cint, double, Tmp\u003cTmp\u003c\u003e\u003e\u003e\u003e() == \"Tmp\", \"\");\n    static_assert(reflection::getTypeName\u003ctest::Array\u003cint, 3\u003e\u003e() == \"Array\", \"\");\n    static_assert(reflection::getTypeName\u003ctest::Array\u003cstruct ONaNi, 114514\u003e\u003e() == \"Array\", \"\");\n\n    // 如果是别名, 只能获取到 最初始的 名称\n    static_assert(reflection::getTypeName\u003ctest::hxArray\u003e() == \"Array\", \"\");\n    static_assert(reflection::getTypeName\u003ctest::hxVector\u003e() == \"Array\", \"\");\n}\n```\n\n### 3.4 HX::log (日志模块)\n\n\u003e 目前实现比较简单, 只是提供了颜色; 日后会支持协程、分线程的异步模式..\n\u003e\n\u003e 输出自带格式化, 特别是对于键值对和聚合类, 默认带空格 (类似于 `QDebug()`)\n\n@todo\n\n## 四、相关依赖\n\n|依赖库|说明|备注|\n|---|---|---|\n|liburing|io_uring的封装|https://github.com/axboe/liburing|\n|OpenSSL 3.3.1+|用于https的证书/握手|https://github.com/openssl/openssl|\n\n## 五、性能测试 (服务端)\n\u003e [!TIP]\n\u003e - Arth Linux\n\u003e - 13th Gen Intel(R) Core(TM) i9-13980HX\n\u003e - RAM: 64GB\n\u003e - cmake -\u003e Release (选项 `--config Release`)\n\u003e - 测试代码: [benchmarks/01_server.cpp](examples/benchmarks/01_server.cpp)\n\u003e - 编译器: Clang 19.1.7 x86_64-pc-linux-gnu\n\n- [断点续传的测试](./documents/UseRangeTransferFile.md)\n\n- http\n\n```sh\n# 全程笔记本 CPU 最高温 85度左右, 最高核心频率都在 3.5GHz\n\n# 纯内存回复 Hello World!\n╰─ wrk -d10s -t32 -c1000 http://127.0.0.1:28205/\nRunning 10s test @ http://127.0.0.1:28205/\n  32 threads and 1000 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     2.84ms   10.60ms 250.54ms   94.92%\n    Req/Sec    73.90k    59.46k  203.26k    63.13%\n  22970587 requests in 10.10s, 2.91GB read\n  Socket errors: connect 3, read 0, write 0, timeout 0\nRequests/sec: 2274737.83\nTransfer/sec:    295.03MB\n\n# 涉及I/O读写小文件 (558KB)\n╰─ wrk -d10s -t32 -c1000 http://127.0.0.1:28205/html/1\nRunning 10s test @ http://127.0.0.1:28205/html/1\n  32 threads and 1000 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    43.97ms   50.57ms 502.64ms   88.57%\n    Req/Sec   599.57    282.34     1.47k    64.59%\n  189702 requests in 10.10s, 100.39GB read\n  Socket errors: connect 3, read 189702, write 0, timeout 0\nRequests/sec:  18788.05\nTransfer/sec:      9.94GB\n\n# 涉及I/O读写大文件 (574.6 MB) 断点续传, 但是 wrk 并不会测试; 故退化为普通异步文件读写\n╰─ wrk -d10s -t32 -c100 http://127.0.0.1:28205/mp4\nRunning 10s test @ http://127.0.0.1:28205/mp4\n  32 threads and 100 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   947.45ms  451.35ms   1.98s    65.85%\n    Req/Sec     2.27      2.96    20.00     90.68%\n  376 requests in 10.09s, 225.80GB read\n  Socket errors: connect 0, read 376, write 0, timeout 171 # 可能: 有些请求未在 wrk 时限内完成 → 属于\"还在下载中\"\nRequests/sec:     37.26   # 此为完成的请求个数, 不能通过此看效果, 因为显然有一些请求还在下载\nTransfer/sec:     22.37GB\n```\n\n## 六、特别感谢\n### 6.1 Stargazers\n[![Stargazers repo roster for @HengXin666/HXLibs](https://reporoster.com/stars/dark/HengXin666/HXLibs)](https://github.com/HengXin666/HXLibs/stargazers)\n\n### 6.2 Forkers\n[![Forkers repo roster for @HengXin666/HXLibs](https://reporoster.com/forks/dark/HengXin666/HXLibs)](https://github.com/HengXin666/HXLibs/network/members)\n\n## 七、杂项\n### 7.1 代码规范\n\u003e --\u003e [C++ 编码规范](documents/CodingStandards/CppStyle.md)\n\n### 7.2 开发计划\n\u003e --\u003e [开发计划](documents/DevelopmentPlan.md)\n\n### 7.3 开发日志\n\u003e --\u003e [开发日志](documents/DevelopmentLog.md)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHengXin666%2FHXLibs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHengXin666%2FHXLibs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHengXin666%2FHXLibs/lists"}