cnetmod 基于 C++23 模块和原生协程的跨平台异步网

cnetmod

基于 C++23 模块和原生协程的跨平台异步网络库。

https://github.com/banderzhm/cnetmod

平台支持

平台 I/O 引擎 编译器 状态
Windows IOCP MSVC 2022 17.12+
Linux io_uring + epoll clang-21 + libc++
macOS kqueue clang-21 + libc++

功能特性

核心运行时

  • 协程引擎 : task<T>spawn() 即发即弃、对称传输实现尾调用优化
  • I/O 上下文 : 平台原生事件循环(IOCP / io_uring / epoll / kqueue)+ 线程安全的 post()
  • 多核支持 : server_context 包含专用接受线程 + N 个工作 io_context 线程 + stdexec 线程池
  • stdexec 集成 : schedule() sender、async_scopeblocking_invoke() 用于卸载阻塞调用

网络功能

  • TCP: 异步 accept / connect / read / write,带 RAII socket 封装
  • UDP: 异步 sendto / recvfrom
  • TLS/SSL : 基于 OpenSSL 的 ssl_context / ssl_stream,支持异步握手、SNI、客户端证书
  • 异步 DNS : async_resolve() --- 通过 stdexec 线程池 + getaddrinfo 实现非阻塞 DNS
  • 串口: 跨平台异步串口 I/O

协议支持

  • HTTP/1.1 & HTTP/2: 完整服务器,包含路由器、中间件管道、分块传输、多部分上传;HTTP/2 通过 TLS + ALPN 协商,支持多路复用流
  • WebSocket: 服务端从 HTTP 升级、帧编解码、ping/pong、per-message deflate
  • MQTT v3.1.1 / v5.0: 完整 broker + 异步客户端 --- QoS 0/1/2、保留消息、遗嘱、会话恢复、共享订阅、主题别名、自动重连;同步客户端封装
  • MySQL: 异步客户端,支持预处理语句、连接池、管道、事务管理、ORM(CRUD / 迁移 / 查询构建器 / MyBatis-Plus 风格 XML 映射器 / BaseMapper / 分页 / 软删除 / 乐观锁 / 多租户 / 缓存)
  • Redis: 异步客户端,支持 RESP 协议、连接池
  • Modbus: 完整协议实现 --- TCP/UDP/RTU(串口)客户端和服务端、所有标准功能码、连接池、CRC-16 校验、帧时序控制、数据存储(基于互斥锁和无锁通道)
  • OpenAI: 异步 API 客户端(聊天补全等)

中间件(HTTP)

CORS、JWT 认证、速率限制、gzip 压缩、请求体大小限制、请求 ID、访问日志、指标、超时、优雅关闭、IP 防火墙、缓存、健康检查、文件上传、panic 恢复

同步原语

mutexshared_mutexsemaphorecondition_variable(均支持协程)、channel<T>wait_groupcancel_token

实用工具

  • 定时器 : async_sleep()async_sleep_until()with_timeout() 用于可取消操作
  • 缓冲区: 字节序感知的读写器、缓冲池
  • 日志 : 基于 std::format 的日志器(无外部依赖)
  • 崩溃转储: 平台原生 minidump(Windows)/ 信号处理器(Unix)
  • 异步文件 I/O: 基于 IOCP(Windows)

快速开始

构建要求

CMake 4.0+ 是 C++23 模块支持所必需的。

Windows: Visual Studio 2022 17.12+ 并启用 C++23 模块。

Linux: clang-21 + libc++ + liburing-dev。

bash 复制代码
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh
sudo ./llvm.sh 21 all
sudo apt install libc++-21-dev libc++abi-21-dev liburing-dev

macOS: Homebrew LLVM 21+(系统 clang 不支持 C++23 模块)。

bash 复制代码
brew install llvm ninja cmake
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"  # Apple Silicon
export PATH="/usr/local/opt/llvm/bin:$PATH"      # Intel Mac

克隆和构建

bash 复制代码
# 克隆仓库
git clone https://github.com/banderzhm/cnetmod.git
cd cnetmod

# 初始化子模块(第三方依赖所必需)
git submodule update --init --recursive

# 构建
cmake -B build -DCNETMOD_BUILD_EXAMPLES=ON
cmake --build build

构建系统会自动检测 MSVC 和 libc++ 的标准库模块路径。如果检测失败,手动指定:

bash 复制代码
# Linux/macOS with clang
cmake -B build \
  -DLIBCXX_MODULE_DIRS=/usr/lib/llvm-21/share/libc++/v1 \
  -DLIBCXX_INCLUDE_DIRS=/usr/lib/llvm-21/include/c++/v1

# Windows MSVC
cmake -B build \
  -DLIBCXX_MODULE_DIRS="C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.44.35207/modules"

示例

Echo 服务器(TCP + 协程):

cpp 复制代码
import cnetmod;
using namespace cnetmod;

task<void> handle_client(tcp_socket client) {
    char buf[1024];
    while (true) {
        int n = co_await client.async_recv(buf, sizeof(buf));
        if (n <= 0) break;
        co_await client.async_send(buf, n);
    }
}

task<void> server(io_context& ctx) {
    tcp_acceptor acceptor(ctx, 8080);
    while (true) {
        auto [client, addr] = co_await acceptor.async_accept();
        spawn(ctx, handle_client(std::move(client)));
    }
}

int main() {
    net_init guard;  // Windows 上的 WSAStartup RAII
    io_context ctx;
    spawn(ctx, server(ctx));
    ctx.run();
}

MQTT Broker + 客户端

cpp 复制代码
import cnetmod.protocol.mqtt;

// 启动 broker
mqtt::broker brk(ctx);
brk.set_options({.port = 1883});
brk.listen();
spawn(ctx, brk.run());

// 异步客户端
mqtt::client cli(ctx);
co_await cli.connect({.host = "127.0.0.1", .port = 1883, .version = mqtt::protocol_version::v5});
co_await cli.subscribe("sensor/#", mqtt::qos::at_least_once);
cli.on_message([](const mqtt::publish_message& msg) {
    std::println("topic={} payload={}", msg.topic, msg.payload);
});
co_await cli.publish("sensor/temp", "22.5", mqtt::qos::exactly_once);
co_await cli.disconnect();

Modbus 协议

cpp 复制代码
import cnetmod.protocol.modbus;

// TCP 客户端
modbus::tcp_client client(ctx);
co_await client.connect("192.168.1.100", 502);

auto req = modbus::request_builder()
    .unit_id(1)
    .read_holding_registers(0, 10)
    .build();

auto resp = co_await client.execute(req);

// RTU 服务端(串口)
modbus::memory_data_store store;
modbus::rtu_server_config config;
config.port_name = "COM3";  // Windows 或 "/dev/ttyUSB0" (Linux)
config.baudrate = 9600;
config.unit_id = 1;

modbus::rtu_server server(ctx, store);
co_await server.start(config);

定时器

cpp 复制代码
task<void> delayed_task(io_context& ctx) {
    co_await async_sleep(ctx, 1s);
    std::cout << "1 秒已过\n";
}

Channel(生产者-消费者):

cpp 复制代码
channel<int> ch(10);
spawn(ctx, producer(ch));  // 发送 0..9
spawn(ctx, consumer(ch));  // 接收并打印

task<void> producer(channel<int>& ch) {
    for (int i = 0; i < 10; ++i) co_await ch.send(i);
    ch.close();
}

MySQL ORM(模型映射 + CRUD + 迁移 + 高级特性):

cpp 复制代码
import cnetmod.protocol.mysql;
#include <cnetmod/orm.hpp>

// 定义带高级特性的模型
struct User {
    std::int64_t                id         = 0;
    std::string                 name;
    std::optional<std::string>  email;
    std::int32_t                version    = 0;  // 乐观锁
    std::int32_t                deleted    = 0;  // 软删除
    std::time_t                 created_at = 0;  // 插入时自动填充
    std::time_t                 updated_at = 0;  // 插入/更新时自动填充
};

CNETMOD_MODEL(User, "users",
    CNETMOD_FIELD(id,         "id",         bigint,    PK | AUTO_INC),
    CNETMOD_FIELD(name,       "name",       varchar),
    CNETMOD_FIELD(email,      "email",      varchar,   NULLABLE),
    CNETMOD_FIELD(version,    "version",    int_,      VERSION),
    CNETMOD_FIELD(deleted,    "deleted",    tinyint,   LOGIC_DELETE),
    CNETMOD_FIELD(created_at, "created_at", timestamp, FILL_INSERT),
    CNETMOD_FIELD(updated_at, "updated_at", timestamp, FILL_INSERT_UPDATE)
)

task<void> demo(mysql::client& cli) {
    orm::db_session db(cli);

    // DDL --- create / drop / sync_schema(自动迁移)
    co_await db.create_table<User>();
    co_await orm::sync_schema<User>(cli);  // 检测差异并应用 ALTER TABLE

    // INSERT(自增 ID 自动填充)
    User u; u.name = "Alice"; u.email = "a@b.com";
    co_await db.insert(u);           // u.id 现在已设置
    co_await db.insert_many(batch);  // 批量插入

    // SELECT --- find_all / find_by_id / 查询构建器
    auto all = co_await db.find_all<User>();
    auto one = co_await db.find_by_id<User>(param_value::from_int(1));
    auto top = co_await db.find(
        orm::select<User>()
            .where("`name` = {}", {param_value::from_string("Alice")})
            .order_by("`id` DESC")
            .limit(10).offset(0)
    );

    // UPDATE --- 按主键
    u.name = "Bob";
    co_await db.update(u);

    // DELETE --- 按模型 / 按 ID / 条件删除
    co_await db.remove(u);
    co_await db.remove_by_id<User>(param_value::from_int(1));
    co_await db.remove(orm::delete_of<User>()
        .where("`name` = {}", {param_value::from_string("test")}));
}

// BaseMapper --- 通用 CRUD 接口
task<void> base_mapper_demo(mysql::client& cli) {
    base_mapper<User> mapper(cli);
    
    // QueryWrapper --- 流式查询构建器
    query_wrapper<User> wrapper;
    wrapper.eq("status", 1)
           .like("name", "%Alice%")
           .gt("age", 18)
           .order_by_desc("created_at")
           .limit(10);
    
    auto users = co_await mapper.select_list(wrapper);
    
    // 分页
    auto page = co_await mapper.select_page(1, 10, wrapper);
    std::println("第 {}/{} 页,总数: {}", 
        page.current_page, page.total_pages, page.total);
}

// MyBatis-Plus 风格 XML 映射器 --- 动态 SQL
task<void> xml_mapper_demo(mysql::client& cli) {
    mapper_registry registry;
    registry.load_file("mappers/user_mapper.xml");
    
    mapper_session session(cli, registry);
    
    // 执行带动态条件的查询
    auto result = co_await session.query<User>("UserMapper.findByCondition",
        param_context::from_map({
            {"name", param_value::from_string("Alice")},
            {"status", param_value::from_int(1)}
        }));
}

ID 策略 --- 内置 UUID v4 和 Snowflake:

cpp 复制代码
struct Tag {
    orm::uuid    id;
    std::string  name;
};
CNETMOD_MODEL(Tag, "tags",
    CNETMOD_FIELD(id,   "id",   char_, UUID_PK_FLAGS, UUID_PK_STRATEGY),
    CNETMOD_FIELD(name, "name", varchar)
)

struct Event {
    std::int64_t  id = 0;
    std::string   title;
};
CNETMOD_MODEL(Event, "events",
    CNETMOD_FIELD(id,    "id",    bigint, SNOWFLAKE_PK_FLAGS, SNOWFLAKE_PK_STRATEGY),
    CNETMOD_FIELD(title, "title", varchar)
)

// Snowflake 需要生成器
orm::snowflake_generator sf(/*machine_id=*/1);
orm::db_session db(cli, sf);
co_await db.insert(event);  // event.id 自动生成

查看 examples/ 获取完整示例,包括 http_demohttp2_demows_demomqtt_demomysql_crudmysql_ormredis_clientmodbus_demomulticore_httpssl_echo_server 等。

架构

模块结构 : 纯 C++23 模块接口(.cppm),无头文件。平台特定实现在 .cpp 文件中通过 CMake 选择。

复制代码
cnetmod.core          --- socket、buffer、address、error、log、dns、ssl、serial_port
cnetmod.coro          --- task、spawn、channel、mutex、semaphore、timer、cancel
cnetmod.io            --- io_context + 平台后端(iocp、io_uring、epoll、kqueue)
cnetmod.executor      --- async_op、server_context、scheduler、stdexec 桥接
cnetmod.protocol.tcp  --- TCP acceptor/connector
cnetmod.protocol.udp  --- UDP 异步 I/O
cnetmod.protocol.http --- HTTP/1.1 + HTTP/2 服务器、路由器、中间件管道、ALPN 协商
cnetmod.protocol.websocket --- WebSocket 服务器
cnetmod.protocol.mqtt --- MQTT broker + 客户端(v3.1.1 / v5.0)
cnetmod.protocol.mysql --- MySQL 异步客户端 + ORM
cnetmod.protocol.redis --- Redis 异步客户端
cnetmod.protocol.modbus --- Modbus TCP/UDP/RTU 客户端 + 服务端
cnetmod.protocol.openai --- OpenAI API 客户端
cnetmod.middleware.*  --- HTTP 中间件组件

调度器/执行器 : io_context 提供 post(coroutine_handle<>) 用于线程安全的任务提交。平台特定的 wake() 实现:

  • Windows: PostQueuedCompletionStatus + 哨兵键
  • Linux: 非阻塞管道 + io_uring 读取
  • macOS/epoll: eventfd/pipe 排空触发

协程原语 : task<T> 使用对称传输实现尾调用优化。spawn() 通过 detached_task 将急切协程桥接到调度器。

异步操作 : 基于 RAII 的 async_op 基类,带平台特定的 overlap/submission 跟踪。完成回调通过 post() 恢复等待的协程。

已知问题

MSVC 错误 C1605: 对象文件大小超过 4 GB 限制

在 Debug 模式下使用 MSVC 构建时,编译器可能报错:

fatal error C1605: 编译器限制: 对象文件大小不能超过 4 GB

这是 MSVC 的已知 bug 。Microsoft 已在开发者社区中承认此问题:C1605: C++23 Modules Exceed 4 GB Object File Limit

问题的原因是编译器在处理 C++23 模块 + 大量模板实例化(特别是 std 模块 + std::format + 协议编解码器)时错误地生成了超大对象文件。MSVC 的 COFF 对象格式有 4 GB 硬限制。

推荐的解决方案:

  1. 使用 Release/RelWithDebInfo 构建 --- 优化构建产生的对象文件要小得多,可以避免此编译器 bug。
  2. 在 WSL/Linux 上使用 clang 构建 --- clang + libc++ 使用 ELF 对象,没有 4 GB 限制。这是推荐的开发工作流。

注意 :这是编译器的 bug,不是 C++23 模块或本库的限制。我们正在等待 Microsoft 修复此问题。详细分析请参见 <MSVC_C1605_Issue.md>。

clang 模块依赖可见性

在模块之间添加新的 import 依赖时,clang 可能报错:

declaration of 'X' must be imported from module 'Y' before it is required

确保显式导入导出所需符号的模块。与头文件不同,模块具有严格的可见性 --- 传递导入不会自动可见。

设计理念

为什么使用模块? 零成本的无头文件构建模型。减少编译时间,更清晰的 API 表面。与 C++23 标准库方向一致。

为什么使用协程? 零开销的 async/await,无回调地狱。无栈协程编译为状态机,性能最优。

为什么使用 io_uring/IOCP/kqueue? 平台原生异步 I/O 提供最佳性能。io_uring 避免系统调用开销。IOCP 是 Windows 服务器的久经考验的选择。kqueue 是 macOS 的唯一选项。

为什么使用 stdexec? sender/receiver 的事实标准。支持与其他异步库组合。通过 async_scope 实现结构化并发。用于阻塞操作卸载(blocking_invoke)。

项目状态

cnetmod 是一个展示 C++23 模块和协程强大能力的现代网络库。它提供了 HTTP/1.1 & HTTP/2、MQTT、MySQL、WebSocket、Modbus 等协议的生产级实现,全部基于零开销的 async/await 构建。

该库证明了 C++23 模块已经可以用于实际项目,并在 Linux、macOS 和 Windows 上提供完整的跨平台支持。

许可证

MIT 许可证。详见 LICENSE 文件。

相关推荐
cheungxiongwei.com1 个月前
使用 C++23 实现 Prompt DSL 的 Header-Only 解析器:从语法设计到工程落地
prompt·c++23
fpcc2 个月前
C++23中的模块应用说明之五综合应用和重点分
c++·c++23
fpcc2 个月前
C++23中的模块应用说明之三深入分析和混合编程
c++·c++23
fpcc2 个月前
C++23中的模块应用说明之二整体说明和导出控制
c++·c++23
特立独行的猫a2 个月前
C++23 std::expected 详解:告别传统错误码和异常,构建现代健壮代码
开发语言·c++23·expected·错误码处理
ALex_zry3 个月前
C++20和C++23 在内存管理、并发控制和类型安全相关优化方式的详细技术分析
安全·c++20·c++23
fpcc3 个月前
C++23中的模块应用说明之一基础分析
c++23
Qt程序员3 个月前
从 C++11 到 C++23:枚举的原理升级与实践
c++·c++23
fpcc3 个月前
C++23中的自定义模块开发
c++·c++23