HXNet

C++20协程和基于io_uringの百万并发服务器; 可异步读写, 支持Transfer-Encoding分块编码传输文件; 基于压缩前缀树编写的路由, 支持通配符解析;+ http/https/websocket、socks5代理、Json解析、Json静态反射到结构体, 静态枚举映射, 支持STL容器的print/toString等

APACHE-2.0 License

Stars
3
  • 基于io_uring+协程的http/https服务器, 基于压缩前缀树的路由, 支持http/https解析, WebSocket协议, 支持Transfer-Encoding分块编码传输文件.

  • 客户端提供了简单的协程的request方法(API), 并且支持使用socks5代理. 支持http/https, 以及解析Transfer-Encoding分块编码的响应体

  • Json解析: 支持静态反射注册到结构体, 实现toString生成json字符串和自动生成的构造函数实现jsonString/jsonObj赋值到结构体, 只需要一个宏即可实现!

  • 万能print/toString等工具类;

构建要求

  • Linux 5.1+
  • GCC 编译器 (推荐)
  • C++20

快速开始

[!TIP] 仍然在开发, 非最终产品

  • 编写端点 (提供了简化使用的 API 宏)
#include <HXWeb/HXApiHelper.h> // 使用简化的api

// 用户定义的控制器
class MyWebController {

    // 定义端点函数
    ENDPOINT_BEGIN(API_GET, "/", root) { // 注册get请求, 接收`/`路径请求
        RESPONSE_DATA( // 响应数据
            200, // 状态码
            co_await HX::STL::utils::FileUtils::asyncGetFileContent("index.html"), // (body数据) 异步(协程)读取文件
            "text/html", "UTF-8" // (响应类型), 以及响应编码
        );
        co_return true; // 注意, 端点函数是协程, 得使用 co_return 而不是return (返回值是 bool)
                        // bool 的意思是是否复用连接 (HTTP/1.1 是推荐复用连接的)
    } ENDPOINT_END;

    ENDPOINT_BEGIN(API_GET, "/favicon.ico", faviconIco) {
        RESPONSE_DATA(
            200, 
            co_await HX::STL::utils::FileUtils::asyncGetFileContent("favicon.ico"),
            "image/x-icon" // 响应编码 可以不写
        );
        co_return true;
    } ENDPOINT_END;

    ENDPOINT_BEGIN(API_GET, "/files/**", files) {
        PARSE_MULTI_LEVEL_PARAM(path);
        RESPONSE_STATUS(200).setContentType("text/html", "UTF-8")
                            .setBodyData("<h1> files URL is " + path + "</h1>"); 
        // 支持直接在端点里面响应 (记得co_await)
        // 响应后, 不会再次在 ConnectionHandler 中再次响应!
        co_await io.sendResponse(); // 立即发送响应
        co_return true;
    } ENDPOINT_END;

    ENDPOINT_BEGIN(API_GET, "/home/{id}/{name}", getIdAndNameByHome) {
        START_PARSE_PATH_PARAMS; // 开始解析请求路径参数
        PARSE_PARAM(0, u_int32_t, id, false); // 解析第一个路径参数{id}, 解析为 u_int32_t类型, 命名为 id
                                              // 并且如果解析失败则不复用连接 (false)

        PARSE_PARAM(1, std::string, name);    // 解析第二个路径参数{name} (不写, 则默认复用连接)

        // 解析查询参数为键值对; ?awa=xxx 这种
        GET_PARSE_QUERY_PARAMETERS(queryMap);

        if (queryMap.count("loli")) // 如果解析到 ?loli=xxx
            std::cout << queryMap["loli"] << '\n'; // xxx 的值

        RESPONSE_DATA(
            200, 
            "<h1> Home id 是 " + std::to_string(*id) + ", 而名字是 " 
            + *name + "</h1><h2> 来自 URL: " 
            + io.getRequest().getRequesPath()  + " 的解析</h2>", // 默认`ENDPOINT_BEGIN`会传入 const HX::web::server::IO<>& io, 您可以对其进行更细致的操作
            "text/html", "UTF-8"
        );
        co_return true;
    } ENDPOINT_END;

public: // 控制器成员函数 (请写成`static`方法)
    // todo...
};
  • 绑定控制器到全局路由
#include <HXWeb/HXApiHelper.h> // 宏所在头文件

ROUTER_BIND(MyWebController); // 这个类在上面声明过了
  • 启动服务器, 并且监听 127.0.0.1:28205, 并且设置路由失败时候返回的界面
    • 可选: 可以设置线程数和超时时间 | 每个线程独享一个uring, 但是绑定同一个端口, 由操作系统进行负载均衡
#include <HXWeb/HXApiHelper.h> // 宏所在头文件
#include <HXWeb/server/Server.h>

int main() {
    chdir("../static");
    setlocale(LC_ALL, "zh_CN.UTF-8");
    ROUTER_BIND(WSChatController);
    ERROR_ENDPOINT_BEGIN { // 自定义: 设置路由失败时候返回的界面
        RESPONSE_DATA(
            404,
            "<!DOCTYPE html><html><head><meta charset=UTF-8><title>404 Not Found</title><style>body{font-family:Arial,sans-serif;text-align:center;padding:50px;background-color:#f4f4f4}h1{font-size:100px;margin:0;color:#333}p{font-size:24px;color:red}</style><body><h1>404</h1><p>Not Found</p><hr/><p>HXNet</p>",
            "text/html", "UTF-8"
        );
        co_return false;
    } ERROR_ENDPOINT_END;

    // 启动Http服务 [阻塞于此]
    HX::web::server::Server::startHttp("127.0.0.1", "28205", 16 /*可选 线程数(互不相关)*/, 10s /*可选 超时时间*/);

    // 或者, 启动Https服务 [阻塞于此], 需要提供证书和密钥
    HX::web::server::Server::startHttps("127.0.0.1", "28205", "certs/cert.pem", "certs/key.pem");
    return 0;
}

相关依赖

依赖库 说明 备注
liburing io_uring的封装 https://github.com/axboe/liburing
hashlib 用于WebSocket构造SHA-1信息摘要; 以及进行Base64编码 https://create.stephan-brumme.com/hash-library/
OpenSSL 3.3.1+ 用于https的证书/握手 https://github.com/openssl/openssl

性能测试

[!TIP]

  • Arth Linux
  • 13th Gen Intel(R) Core(TM) i9-13980HX
  • RAM: 64GB
  • cmake -> Release
  • http
# build: add_definitions(-DCOMPILE_WEB_SOCKET_SERVER_MAIN)  # websocket服务端

# http 没有文件读写的情况下:
 wrk -t32 -c1100 http://127.0.0.1:28205/home/123/123
Running 10s test @ http://127.0.0.1:28205/home/123/123
  32 threads and 1100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.48ms    2.52ms  36.60ms   86.18%
    Req/Sec    77.98k    16.30k  241.91k    74.07%
  23512408 requests in 10.10s, 4.51GB read
  Socket errors: connect 99, read 0, write 0, timeout 0
Requests/sec: 2327117.67
Transfer/sec:    457.18MB

# http 读写 static/WebSocketIndex.html
 wrk -t32 -c1100 http://127.0.0.1:28205/            
Running 10s test @ http://127.0.0.1:28205/
  32 threads and 1100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.18ms    1.63ms  23.96ms   86.39%
    Req/Sec    45.53k    13.36k  263.46k    89.82%
  14176629 requests in 10.09s, 43.81GB read
  Socket errors: connect 99, read 0, write 0, timeout 0
Requests/sec: 1405535.24
Transfer/sec:      4.34GB
  • https
# build: add_definitions(-DHTTPS_FILE_SERVER_MAIN)  # https简单的文件服务器
# 无文件读写, 仅仅https连接和通过会话密钥加解密通信
 wrk -d10s -t32 -c1000 --timeout 5s https://127.0.0.1:28205
Running 10s test @ https://127.0.0.1:28205
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.04ms    3.21ms  70.65ms   86.96%
    Req/Sec    35.71k     6.56k   77.68k    70.85%
  11447330 requests in 10.10s, 1.97GB read
Requests/sec: 1133384.87
Transfer/sec:    199.96MB

# (100s) 小文件读写 (static/test/github.html) 563.9kb 采用分块编码, 但是需要https加密
 wrk -d100s -t32 -c1000 --timeout 5s https://127.0.0.1:28205/files/a
Running 2m test @ https://127.0.0.1:28205/files/a
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    62.20ms   35.25ms 529.39ms   72.92%
    Req/Sec     1.01k   358.38     3.09k    65.19%
  3203315 requests in 1.67m, 843.28GB read
Requests/sec:  32001.06
Transfer/sec:      8.42GB

# (10s) 小文件读写 (static/test/github.html) 563.9kb 采用分块编码, 但是需要https加密
# 并且修改了 include/HXSTL/utils/FileUtils.h 定义的:
    /// @brief 读取文件buf数组的缓冲区大小
    static constexpr std::size_t kBufMaxSize = 4096U * 2; # 进行了 * 2
# 注: 这个是后来加上的测试, 也就是说其他的测试都是在`kBufMaxSize = 4096U`时候测试的, 因此这个只能和上面的形成对比
 wrk -d10s -t32 -c1000 https://127.0.0.1:28205/files/a
Running 10s test @ https://127.0.0.1:28205/files/a
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    39.14ms   23.01ms 155.74ms   67.09%
    Req/Sec     1.60k   374.46     3.74k    69.51%
  516854 requests in 10.10s, 136.26GB read
Requests/sec:  51182.79
Transfer/sec:     13.49GB

# 对比 小文件读写 (static/test/github.html) 563.9kb `没有`采用分块编码, 但是需要https加密
 wrk -d10s -t32 -c1000 https://127.0.0.1:28205/test
Running 10s test @ https://127.0.0.1:28205/test
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   105.38ms   41.80ms 427.97ms   69.93%
    Req/Sec   294.52     43.68   425.00     74.12%
  94528 requests in 10.10s, 49.77GB read
Requests/sec:   9360.72
Transfer/sec:      4.93GB

代码规范

--> C++ 编码规范

开发计划

--> 开发计划

开发日志

--> 开发日志