libhv 通道类 Channel / SocketChannel / WebSocketChannel 完全指南:连接、读写、超时、心跳与拆包实战
发布时间:2026/9/28 3:26:26来源:尧图网络
网络后端通信【免费下载链接】libhv 比libevent/libuv/asio更易用的网络库。A c/c network library for developing TCP/UDP/SSL/HTTP/WebSocket/MQTT/Redis client/server.项目地址https://gitcode.com/gh_mirrors/li/libhv点击查看免费下载本文是 libhv 事件循环与网络编程中通道类Channel体系的深度技术指南对应仓库文档 docs/cn/Channel.md 的完整内容。Channel 是 libhv 面向对象的网络编程核心抽象Channel封装底层hio_t的读写、关闭与上下文管理SocketChannel在其上补充 TCP 连接、SSL/TLS、超时、心跳与拆包能力WebSocketChannel则面向 WebSocket 提供文本/二进制/分片帧发送。读完本文你将掌握这三层通道类的全部 API、底层实现原理以及如何结合事件循环写出可落地的 TCP 客户端/服务端与 WebSocket 应用。一、Channel 体系概览从 hio_t 到三态继承链libhv 的核心事件模型由 event/hloop.h 中的hio_tIO 对象承载而 C 侧的 Channel 类则是对hio_t的面向对象封装两者通过hio_context关联见 evpp/Channel.hChannel 构造时将自身指针写回hio_set_context(io, this)底层读/写/关闭回调再通过hio_context(io)取回 Channel 对象实现 C 回调到 C 对象的桥接。整个继承链共三层类职责源码位置Channelhio 句柄、fd、id、错误码、上下文、读写与关闭evpp/Channel.hSocketChannel : ChannelTCP 连接、SSL、超时、心跳、拆包、地址信息evpp/Channel.hWebSocketChannel : SocketChannelWebSocket 帧发送文本/二进制/分片/Ping/Ponghttp/WebSocketChannel.h、http/WebSocketChannel.cpp三者对应的智能指针别名也定义在 evpp/Channel.htypedef std::shared_ptrChannel ChannelPtr; typedef std::shared_ptrSocketChannel SocketChannelPtr;此外 http/WebSocketChannel.h 还定义了WebSocketChannelPtr。实际编程中通道多以shared_ptr传递因此理解各成员函数对“通道是否仍打开”的守卫逻辑isOpened()很重要。二、Channel底层 IO 的面向对象封装2.1 句柄、描述符、唯一 ID 与错误码class Channel { // 返回底层的io结构体指针 hio_t* io() { return io_; } // 返回socket文件描述符 int fd() { return fd_; } // 返回一个唯一标示id uint32_t id() { return id_; } // 返回错误码 int error() { return hio_error(io_); } ... };io()直接暴露底层hio_t*可继续调用 hloop.h 中的任意hio_*APIfd()为 socket 描述符构造时由hio_fd(io)取得id()是hio_t分配的唯一 ID。它在 evpp/Channel.h 的析构逻辑中用于防误用仅当id_ hio_id(io_)时才会清空hio_context避免析构时误伤已复用的 IO 对象error()直接透传hio_error(io_)非阻塞连接失败或读写错误时用它判断失败原因。2.2 上下文管理两种模式通道允许绑定任意用户数据提供裸指针与智能指针两套 API// 获取/设置/新建/删除 上下文 void* context(); void setContext(void* ctx); templateclass T T* newContext(); templateclass T T* getContext(); templateclass T void deleteContext(); // 获取/设置/新建/删除 上下文智能指针 std::shared_ptrvoid contextPtr(); void setContextPtr(const std::shared_ptrvoid ctx); void setContextPtr(std::shared_ptrvoid ctx); templateclass T std::shared_ptrT newContextPtr(); templateclass T std::shared_ptrT getContextPtr(); void deleteContextPtr();裸指针版newContextT()内部ctx_ new T需要自行deleteContextT()释放生命周期由调用者全权负责智能指针版newContextPtrT()通过std::make_sharedT()创建并保存为std::shared_ptrvoid实现见 evpp/Channel.h由引用计数自动管理推荐在生产代码中使用可避免内存泄漏。典型用法可参考 examples/websocket_server_test.cpp连接建立时channel-newContextPtrMyContext()创建连接级会话对象并启动定时器关闭回调里getContextPtrMyContext()取出对象清理定时器。2.3 状态查询isOpened / isClosed// 是否打开状态 bool isOpened(); // 是否关闭状态 bool isClosed();注意isOpened()并非简单布尔它要求io_非空、内部状态小于DISCONNECTED、id_与当前hio_id(io_)一致且底层hio_is_opened(io_)为真evpp/Channel.h。所以它同时校验了“对象有效性”与“底层打开状态”是判断通道是否还能读写的权威依据。Channel内部维护的std::atomicStatus status取值包括OPENED / CONNECTING / CONNECTED / DISCONNECTED / CLOSEDSocketChannel::isConnected()即要求status CONNECTED isOpened()。2.4 读操作持续读、读一次、按模式读// 开始读 int startRead(); // 停止读 int stopRead(); // 读一次 int readOnce(); // 读一个字符串 int readString(); // 读一行 int readLine(); // 读取N个字节 int readBytes(int len);这些方法均转发到 event/hloop.h 的底层宏与函数#define hio_read_start(io) hio_read(io) #define hio_read_stop(io) hio_del(io, HV_READ) #define hio_readline(io) hio_read_until_delim(io, \n) #define hio_readstring(io) hio_read_until_delim(io, \0) #define hio_readbytes(io, len) hio_read_until_length(io, len)startRead()持续监听读事件数据到来即触发onread是最常用模式readOnce()只读一次读完自动停止读监听注释为hio_read_start hread_cb hio_read_stopreadLine()/readString()分别按\n与\0分隔读取底层hio_read_until_delim适合行式文本协议readBytes(len)按固定字节数读取hio_read_until_length适合定长报文。所有读方法都以isOpened()为前置守卫未打开时返回-1。2.5 写操作与写缓存管理// 写 int write(const void* data, int size); int write(Buffer* buf); int write(const std::string str); // 设置最大读缓存 void setMaxReadBufsize(uint32_t size); // 设置最大写缓存 void setMaxWriteBufsize(uint32_t size); // 获取当前写缓存大小 size_t writeBufsize(); // 是否写完成 bool isWriteComplete();三种write重载最终都落到hio_write(io_, data, size)。hio_write是非阻塞、线程安全的底层hio_t内部维护写队列缓存未写完的数据等待可写事件继续发送且由递归互斥锁保护允许被其他线程调用见 event/hloop.h 与 event/hloop.h 的注释——这是 libhv 多线程编程中“跨线程发送数据”合法性的根本保证setMaxReadBufsize/setMaxWriteBufsize限制读/写缓冲上限避免无界增长writeBufsize()返回当前写队列中尚未发完的字节数isWriteComplete()即writeBufsize() 0在onwrite回调里用它可以判断“本次数据是否全部写出”进而决定是否继续发送下一批数据。示例 examples/websocket_server_test.cpp 的定时发送就同时检查了isConnected()与isWriteComplete()。2.6 关闭通道与回调// 关闭 int close(bool async false); // 读回调 std::functionvoid(Buffer*) onread; // 写回调 std::functionvoid(Buffer*) onwrite; // 关闭回调 std::functionvoid() onclose;close(false)同步调用hio_close(io_)close(true)走hio_close_async(io_)由事件循环投递关闭事件适合在非事件循环线程中安全关闭底层hio的读/写/关闭回调在 Channel 构造时注册若尚未注册并分别桥接到onread / onwrite / oncloseevpp/Channel.h。回调中构造的Buffer即typedef HBuf Buffer见 evpp/Buffer.h指向底层读/写缓冲的数据与长度回调结束后即失效如需长期保存请自行拷贝onclose触发时通道状态被置为CLOSED是清理会话资源的标准时机。三、SocketChannel连接、SSL、超时与心跳SocketChannel在Channel之上补齐了面向 TCP 的完整能力其状态机为CONNECTING - CONNECTED - DISCONNECTED/CLOSED。3.1 连接与状态回调class SocketChannel : public Channel { // 连接状态回调 std::functionvoid() onconnect; // 心跳回调 std::functionvoid() heartbeat; // 开始连接 int startConnect(int port, const char* host 127.0.0.1); int startConnect(struct sockaddr* peeraddr); int startConnect(); // 是否已连接 bool isConnected(); ... };三种startConnect重载实现见 evpp/Channel.hstartConnect(port, host)默认127.0.0.1内部经sockaddr_set_ipport组装 sockaddr 后走下面的重载startConnect(struct sockaddr* peeraddr)先hio_set_peeraddr再发起连接startConnect()将状态置为CONNECTING注册on_connect回调并调用hio_connect(io_)。连接成功时底层回调把状态置为CONNECTED并触发用户设置的onconnectevpp/Channel.h。onconnect注释标明“only for TcpClient”即客户端发起连接场景服务端 accept 出的通道无需再连接。3.2 SSL/TLS 能力// 启用SSL/TLS加密通信 int enableSSL(); // 是否是SSL/TLS加密通信 bool isSSL(); // 设置SSL int setSSL(hssl_t ssl); // 设置SSL_CTX int setSslCtx(hssl_ctx_t ssl_ctx); // 新建SSL_CTX int newSslCtx(hssl_ctx_opt_t* opt); // 设置主机名 int setHostname(const std::string hostname);它们分别透传hio_enable_ssl / hio_is_ssl / hio_set_ssl / hio_set_ssl_ctx / hio_new_ssl_ctx / hio_set_hostnameevpp/Channel.h。newSslCtx(hssl_ctx_opt_t*)允许一次性传入证书、密钥与端点类型创建 SSL_CTXsetHostname服务于 SNI/主机名校验。libhv 的 SSL 实现是抽象可插拔的OpenSSL、mbedTLS、GnuTLS、Apple TLS 等见 ssl/hssl.h 与 ssl 目录。服务端启用 WSS 的完整示例在 examples/websocket_server_test.cpp配置crt_file cert/server.crt、key_file cert/server.key、endpoint HSSL_SERVER后server.newSslCtx(param)。3.3 五类超时设置// 设置连接超时 void setConnectTimeout(int timeout_ms); // 设置关闭超时 (说明非阻塞写队列非空时需要等待写完成再关闭) void setCloseTimeout(int timeout_ms); // 设置读超时 (一段时间没有数据到来便自动关闭连接) void setReadTimeout(int timeout_ms); // 设置写超时 (一段时间没有数据发送便自动关闭连接) void setWriteTimeout(int timeout_ms); // 设置keepalive超时 (一段时间没有数据收发便自动关闭连接) void setKeepaliveTimeout(int timeout_ms);五个方法分别对应hio_set_connect_timeout / hio_set_close_timeout / hio_set_read_timeout / hio_set_write_timeout / hio_set_keepalive_timeoutevpp/Channel.h。底层声明在 event/hloop.h其中连接/关闭/keepalive 超时带有默认值宏HIO_DEFAULT_CONNECT_TIMEOUT / HIO_DEFAULT_CLOSE_TIMEOUT / HIO_DEFAULT_KEEPALIVE_TIMEOUT。语义要点超时类型触发条件后果连接超时连接在指定毫秒内未建立连接失败读超时一段时间无数据到达自动关闭连接写超时一段时间无数据可发送自动关闭连接keepalive 超时一段时间无任何数据收发自动关闭连接关闭超时写队列非空时等待写完成超时后强制关闭见原文档注释其中关闭超时特别值得注意非阻塞写队列非空时close()需要等待数据写完再真正关闭setCloseTimeout即约束这一“优雅关闭”的最长等待时间。3.4 心跳与拆包// 设置心跳 (定时发送心跳包) void setHeartbeat(int interval_ms, std::functionvoid() fn); // 设置拆包规则 void setUnpack(unpack_setting_t* setting);心跳setHeartbeat(interval_ms, fn)将用户心跳回调存入heartbeat并调用hio_set_heartbeat(io_, interval_ms, send_heartbeat)底层定时触发时通过send_heartbeat静态回调转调用户的heartbeatevpp/Channel.h、evpp/Channel.h。源码注释特别提醒把SocketChannelPtr按值捕获进心跳闭包要小心循环引用shared_ptr 自持有导致无法释放。hloop.h 中的示例event/hloop.h展示了配合hio_write(io, buf, 6)每 3 秒发送一个 6 字节心跳包的经典写法。拆包unpacksetUnpack让底层在 TCP 流上按规则自动拆分完整数据包后再触发onread。unpack_setting_t定义在 event/hloop.h支持三种模式模式适用场景关键字段UNPACK_BY_FIXED_LENGTH 1定长报文注释明确 Not recommendedfixed_lengthUNPACK_BY_DELIMITER 2文本协议delimiter[]、delimiter_bytes最大 8 字节分隔符UNPACK_BY_LENGTH_FIELD 3二进制协议body_offset、length_field_offset、length_field_bytes、length_adjustment、length_field_coding长度域模式的核心公式源码注释package_len head_len body_len length_adjustmentlength_field默认存 body 长度若协议里长度域同时包含头部长度则把length_adjustment设为-head_len。编码支持ENCODE_BY_VARINT1 MSB 7 bitsMQTT 风格、大小端序ENCODE_BY_LITTEL_ENDIAN/ENCODE_BY_BIG_ENDIAN。unpack_setting_s的 C 构造函数给出推荐默认mode UNPACK_BY_LENGTH_FIELD、body_offset 5、length_field_offset 1、length_field_bytes 4、length_field_coding ENCODE_BY_BIG_ENDIAN、package_max_length 2MDEFAULT_PACKAGE_MAX_LENGTH (1 21)。hloop.h 注释中还给出 FTP\r\n分隔、MQTT1 字节 varint 长度域、gRPC5 字节头 4 字节大端长度域三种真实协议的拆包配置范例event/hloop.hexamples/jsonrpc 与 examples/protorpc 也有完整可运行示例。注意hio_t只保存unpack_setting_t指针其生命周期须由调用者保证同一函数的多个 IO 可共享同一配置。3.5 地址信息// 返回本地地址 std::string localaddr(); // 返回对端地址 std::string peeraddr();两者分别通过hio_localaddr/hio_peeraddr取得 sockaddr 并格式化为字符串SOCKADDR_STRevpp/Channel.h调试日志与访问控制场景常用。四、WebSocketChannel帧级发送封装WebSocketChannel继承自SocketChannelhttp/WebSocketChannel.h构造时需传入会话类型ws_session_type typeWS_CLIENT或WS_SERVER并维护当前帧opcode与内部Buffer sendbuf_、发送互斥锁mutex_。4.1 发送 APIclass WebSocketChannel : public SocketChannel { // 发送文本帧 int send(const std::string msg, enum ws_opcode opcode WS_OPCODE_TEXT, bool fin true); // 发送二进制帧 int send(const char* buf, int len, enum ws_opcode opcode WS_OPCODE_BINARY, bool fin true); // 分片发送 int send(const char* buf, int len, int fragment, enum ws_opcode opcode WS_OPCODE_BINARY); // 关闭 int close(); };文本重载默认WS_OPCODE_TEXT、二进制重载默认WS_OPCODE_BINARYfin控制是否本帧即消息结束单次数据超过 65535 字节0xFFFF时自动转入分片路径http/WebSocketChannel.cpp三参数send(buf, len, fragment, opcode)手动指定分片大小其内部流程见 http/WebSocketChannel.cpp先发首帧finfalse中间帧连续使用WS_OPCODE_CONTINUE最后一片以fintrue收尾全程持锁保证多线程下帧序列不乱。4.2 帧构造与掩码所有发送最终汇聚到sendFramehttp/WebSocketChannel.cppbool has_mask false; char mask[4] {0}; if (type WS_CLIENT) { *(int*)mask rand(); has_mask true; } int frame_size ws_calc_frame_size(len, has_mask); ... ws_build_frame(sendbuf_.base, buf, len, mask, has_mask, opcode, fin); return write(sendbuf_.base, frame_size);关键点客户端WS_CLIENT发送帧必须带 4 字节随机掩码mask服务端WS_SERVER则不加掩码这符合 WebSocket 协议规范RFC 6455客户端到服务端的帧必须掩码。帧通过ws_calc_frame_size计算长度、ws_build_frame组装二者来自 http/websocket_parser.h 对应的实现sendbuf_按ceil2e预分配以复用缓冲、减少内存拷贝。此外 WebSocketChannel 还提供sendPing() / sendPong()按会话类型选择客户端或服务端帧模板见 http/WebSocketChannel.cpp以及按会话类型选择同步/异步关闭的close()return SocketChannel::close(type WS_SERVER)服务端走异步关闭。4.3 与 WebSocketServer 的联动在实际项目中WebSocketChannel通常由WebSocketServer管理回调签名直接使用WebSocketChannelPtr。参考 examples/websocket_server_test.cpp 的完整可运行示例WebSocketService ws; ws.onopen [](const WebSocketChannelPtr channel, const HttpRequestPtr req) { printf(onopen: GET %s\n, req-Path().c_str()); auto ctx channel-newContextPtrMyContext(); // 每秒向客户端推送一次时间 ctx-timerID setInterval(1000, channel { if (channel-isConnected() channel-isWriteComplete()) { char str[DATETIME_FMT_BUFLEN] {0}; datetime_t dt datetime_now(); datetime_fmt(dt, str); channel-send(str); // 默认 WS_OPCODE_TEXT 文本帧 } }); }; ws.onmessage [](const WebSocketChannelPtr channel, const std::string msg) { auto ctx channel-getContextPtrMyContext(); ctx-handleMessage(msg, channel-opcode); }; ws.onclose [](const WebSocketChannelPtr channel) { auto ctx channel-getContextPtrMyContext(); if (ctx-timerID ! INVALID_TIMER_ID) { killTimer(ctx-timerID); ctx-timerID INVALID_TIMER_ID; } };该示例同时演示了newContextPtr/getContextPtr管理连接级会话、定时器与isWriteComplete配合避免发送堆积、onclose中清理资源等上文全部 API 的组合用法。构建运行方式文件头注释make examples后启动bin/websocket_server_test 9999可用bin/websocket_client_test ws://127.0.0.1:9999/或脚本 scripts/websocket_server.py 联测开启 WSS 需./configure --with-openssl重新构建并复用 cert 目录下的证书。五、线程安全与生命周期注意事项源码级小结综合 evpp/Channel.h 与 event/hloop.h 的源码注释使用时需牢记以下要点写操作线程安全hio_write由递归互斥锁保护可跨线程调用close同理线程安全跨线程可走close(true)hio_close_async。读回调内的 Buffer 是瞬时的Buffer只是HBuf的 typedefevpp/Buffer.h指向底层缓冲回调返回即失效需要保留数据务必深拷贝。unpack 配置的生命周期hio_t只保存unpack_setting_t*指针配置对象须存活到通道关闭。心跳闭包防循环引用不要在 lambda 中按值捕获SocketChannelPtr形成 shared_ptr 环必要时用 weak_ptr 或在onclose中解绑。isOpened 是复合判断它同时校验对象有效性、状态机与底层打开标志判断“通道是否可用”优先使用它而不是仅看 fd。析构顺序Channel 析构时若仍打开会先close()并清空hio_context避免在底层回调中被再次引用evpp/Channel.h。这些行为都有 unittest/sizeof_test.cpp 对三个通道类的对象布局做编译期验证可作为进一步研读的入口。掌握了 Channel 三层体系后即可在 libhv 的EventLoop见 evpp/EventLoop.h之上自由组合 TCP/SSL/WebSocket 客户端与服务端程序。赞分享网络后端通信【免费下载链接】libhv 比libevent/libuv/asio更易用的网络库。A c/c network library for developing TCP/UDP/SSL/HTTP/WebSocket/MQTT/Redis client/server.项目地址https://gitcode.com/gh_mirrors/li/libhv点击查看免费下载相关推荐libhv连接池性能复用与超时深度优化指南libhv连接池性能复用与超时深度优化指南 引言连接管理的性能瓶颈 你是否遇到过服务高并发时的性能骤降是否因频繁创建TCP连接导致CPU占用率飙升在现代网络后端通信gnet连接超时控制TCP keepalive与应用层心跳gnet连接超时控制TCP keepalive与应用层心跳 连接超时控制的技术痛点与解决方案 在高并发网络服务中无效连接占用系统资源是影响性能的关键问题。据后端网络通信彻底解决Redis超时难题go-redis连接读写超时配置实战指南彻底解决Redis超时难题go redis连接读写超时配置实战指南 你是否曾因Redis连接超时导致系统雪崩是否在面对redis: connection后端数据库客户端缓存上一篇nunif iw3深度解析从2D视频到VR 3D SBS格式的完整实践指南下一篇Auto.js3分钟上手Android自动化脚本开发的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网