后端网络通信【免费下载链接】libwebsocketscanonical libwebsockets.org networking library项目地址https://gitcode.com/gh_mirrors/li/libwebsockets点击查看免费下载libwebsockets 的 Secure StreamsSS体系为客户端sspc与 SS 代理proxy之间的连接定义了一套传输路径transport path抽象。在默认实现中这条路径直接架设在 WebSocketwsi传输之上而本文要讲解的Transport Mux则是插入在传输路径中间的复用/解复用mux/demux成帧层它让一条底层连接上可以同时承载多个逻辑通道channel每个通道独立承载一条 Secure Streams 会话。minimal-examples-lowlevel/api-tests/api-test-transport-mux正是这一层的裸机自测程序——它不涉及任何真实的传输、sspc 或代理直接把 mux 命令喂给解析器并检查收发包行为。读完本文你将掌握Transport Mux 的完整线协议帧格式LWSSSS_LLM_*命令族、核心 APIlws_transport_mux_create/rx_parse/pending与回调集lws_txp_mux_parse_cbs_t的语义、客户端角色与代理角色在通道开放上的本质差异以及该测试用例逐条验证的通道生命周期行为——这些知识可以直接用于编写自定义传输custom transport或在自有协议中复用 mux 成帧逻辑。一、Transport Mux 在整个体系中的位置要理解这个测试先要理解 mux 层的定位。在 libwebsockets 的传输抽象中每条路径都有in与onwonward两侧分别绑定到体系的不同部分SSPC 客户端侧与SS 代理侧各自暴露一个终端用于承载 SS 序列化SS Serializationsspc-transport-wsi与proxy-transport-wsi是路径的两个默认端点实现把 SSPC 和代理直接接到 wsi 传输上你也可以创建一个lws_transport_mux_t把它插入到两侧的传输路径中间再实现自己的自定义lws_transportops从而支持任意传输。这一点在头文件 lws-secure-streams-serialization.h 中有明确注释。也就是说Transport Mux 是一个可插拔的中间层底层可以是任何传输Unix socket、串口、UART……上层承载任意数量的 SS 通道。在实际产品代码中mux 层以两种形态出厂形态定义位置说明lws_transport_mux_client_opstransport-mux-client.c客户端侧 ops把 mux 通道绑定到lws_sspc_handle走txp_cpath路径lws_transport_mux_proxy_opstransport-mux-proxy.c代理侧 ops把 mux 通道绑定到lws_sss_proxy_conn走txp_ppath路径而本测试则把这两条真实接线全部架空直接用一组记录型回调recording callbacks站在通道所有者的位置上把 mux 层的行为当成黑盒/白盒来验证。二、测试用例的总体设计测试主程序位于 main.c。它的核心思路扮演对端peer把对端会发送的 mux 命令字节流如CHANNEL_REQ、ACK、NACK、DATA直接喂给lws_transport_mux_rx_parse()检查本方应回发的数据调用lws_transport_mux_pending()收集 mux 层想要发送的内容与期望字节逐一比对见expect_tx()代替通道所有者用lws_txp_mux_parse_cbs_t回调记录事件计数通道打开/关闭次数、可写机会次数、收到的 payload 字节数并在特定场景下由回调返回判决值verdict驱动 mux 的关闭行为。测试在main()里维护一个全局状态结构t其字段含义如下对应 main.cstatic struct { int ch_opens; /* ch_opens 回调被调用的次数 */ int ch_opens_failed; /* 其中以失败determination1结束的次数 */ int ch_closes; /* ch_closes 回调被调用的次数 */ int can_write; /* txp_can_write 被调用的次数 */ int can_write_unbound; /* 其中通道未绑定 priv 的次数不应出现 */ int payload; /* payload 回调收到的帧数 */ int payload_verdict; /* 所有者对 DATA 的判决非 0 表示无法继续处理该通道 */ int can_write_verdict; /* 所有者对可写机会的判决非 0 表示要求关闭该通道 */ int bind_peer_channels; /* 类似代理绑定对端请求的通道客户端无物可绑 */ } t;回调集cbs的注册方式main.cstatic const lws_txp_mux_parse_cbs_t cbs { .payload cb_payload, .ch_opens cb_ch_opens, .ch_closes cb_ch_closes, .txp_req_write cb_txp_req_write, .txp_can_write cb_txp_can_write, };其中cb_ch_opens展示了客户端与代理角色的关键分歧main.c当收到对端请求的通道时若t.bind_peer_channels为 0客户端角色直接返回-1表示拒绝若为 1代理角色则把bound_obj绑定为通道的priv并接受。mux 实例的创建使用了lws_transport_mux_create(cx, tinfo, NULL)其中tinfo设置了ping_interval_us 10s、pong_grace_us 2s并通过tinfo.txp_cpath.ops_onw onw_ops挂上一个只会接收写请求、从不真正写出的底层传输 opsonw_ops见 main.c——因为测试只需要驱动 mux 层不需要真正的 I/O。三、构建与运行原 README 明确说明了该测试的构建前提见 README.mdmux API 面向自定义传输自定义传输需要链接静态库因此必须开启LWS_WITH_SECURE_STREAMS_PROXY_API并且使用静态库LWS_WITH_STATIC这是默认值测试以api-test-transport-mux的名字注册到 ctest。这一定义在 CMakeLists.txt 中得到了落实set(requirements 1) require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements) ... add_executable(${SAMP} ${SRCS}) add_test(NAME api-test-transport-mux COMMAND lws-api-test-transport-mux) set_tests_properties(api-test-transport-mux PROPERTIES TIMEOUT 20)从构建系统的依赖推导可以看出LWS_WITH_SECURE_STREAMS_PROXY_API是一个自包含性较强的选项开启它之后CMakeLists-implied-options.txt 会自动连带打开LWS_WITH_LWS_DSH、LWS_WITH_UNIX_SOCK、LWS_WITH_SYS_SMD与LWS_WITH_SECURE_STREAMS同时LWS_WITH_SECURE_STREAMS又要求LWS_WITH_CLIENT处于开启状态见同一文件第 482-487 行。因此该测试的实际依赖链是client secure streams proxy API dsh unix socket smd。运行方式README 原命令$ ctest -R api-test-transport-mux测试进程不会真正建立任何网络连接info.port CONTEXT_PORT_NO_LISTEN它创建上下文与 mux 实例后在一次调用内完成所有场景的验证最后输出Completed: PASS或Completed: FAIL (N)并返回相应退出码见 main.c。由于 ctest 注册时设置了TIMEOUT 20即使逻辑异常也会在 20 秒内被强制终止。四、线协议mux 层的命令帧格式mux 层在底层连接上传输的是一组单字节命令 变长参数。这些命令常量定义在 lws-secure-streams-serialization.h测试中直接使用了它们来构造输入帧命令值格式语义LWSSSS_LLM_CHANNEL_REQ0xf0[REQ][ch_idx]任一方提议打开新通道客户端发起时从 0 向上取第一个空闲索引服务端发起时从 0xff 向下取LWSSSS_LLM_CHANNEL_ACK0xf1[ACK][ch_idx]对先前CHANNEL_REQ的肯定应答LWSSSS_LLM_CHANNEL_NACK0xf2[NACK][ch_idx]对先前CHANNEL_REQ的否定应答若在已开通道上主动到达则等价于 FINLWSSSS_LLM_CHANNEL_CLOSE0xf3[CLOSE][ch_idx]任一方通知对端正在关闭某通道LWSSSS_LLM_CHANNEL_CLOSE_ACK0xf4[CLOSE_ACK][ch_idx]对端确认关闭之后该通道索引可复用LWSSSS_LLM_MUX0xf5[MUX][ch_idx][len_hi][len_lo][payload...]在已打开通道上封装数据帧长 2 字节大端LWSSSS_LLM_PING0xf6[PING][8B unix_us]链路探测载荷为发出时的微秒级 Unix 时间LWSSSS_LLM_PONG0xf7[PONG][8B PING时间][8B 本端时间]回应 PING校验载荷是否与us_ping_out一致LWSSSS_LLM_PONGACK0xf8[PONGACK][8B PING时间]PING 发起方收到 PONG 后立即发出不再回复使双方一次事务内确认连接双向有效LWSSSS_LLM_RESET_TRANSPORT0xf9[RESET_TRANSPORT]任一方判定失去帧同步要求整条传输重置所有通道关闭并进入LWSTM_TRANSPORT_DOWN测试中的输入帧字节串main.c是这些格式的直接体现例如pongack[] { LWSSSS_LLM_PONGACK, 0, 0, 0, 0, 0, 0, 0, 1 }, /* PONGACK 8B 时间戳 */ req_ack_7[] { LWSSSS_LLM_CHANNEL_REQ, 7, LWSSSS_LLM_CHANNEL_ACK, 7 }, /* REQ 后自 ACK */ data_255[] { LWSSSS_LLM_MUX, 255, 0, 1, x }, /* 通道255上一帧1字节 x */五、核心 API 与回调语义mux 层对外暴露的四个关键 API头文件定义见 lws-secure-streams-serialization.hlws_transport_mux_t * lws_transport_mux_create(struct lws_context *cx, lws_transport_info_t *info, void *txp_handle); void lws_transport_mux_destroy(lws_transport_mux_t **tm); int lws_transport_mux_rx_parse(lws_transport_mux_t *tm, const uint8_t *buf, size_t len, const lws_txp_mux_parse_cbs_t *cbs); int lws_transport_mux_pending(lws_transport_mux_t *tm, uint8_t *buf, size_t *len, const lws_txp_mux_parse_cbs_t *cbs);lws_transport_mux_create()在 transport-mux-common.c 中实现分配 mux 结构、保存info与txp_handle把link_state初始化为LWSTM_TRANSPORT_DOWN并立即调度一个 1us 后触发的 sul 定时器发起第一次 PING 探测。lws_transport_mux_destroy()则会先取消 ping 定时器防止悬挂的 sul 指向已释放内存再沿owner链表逐个销毁通道。lws_transport_mux_rx_parse()transport-mux-common.c是一个增量状态机tm-mp_state依次经历LWSTMCPAR_CMD读命令字节、CHIDX_DONE读通道索引、CHIDX/PLENH/PLENL解析 MUX 帧头、PAY消费负载、T64_1/T64_2消费 8 字节时间戳等状态每处理完一帧就通过cbs-txp_req_write(tm)请求发送权把待发送的控制帧挂进pending_tx链表。遇到未知命令则打印告警并直接令整个传输进入LWSTM_TRANSPORT_DOWN、返回 -1。lws_transport_mux_pending()transport-mux-common.c则负责把要发的东西打包进写机会其内部优先级依次为RESET_TRANSPORTPING→PONG→PONGACK→ 待发 FINNACK→ 各通道的控制帧 → 第一个处于LWSTMC_OPERATIONAL且想写的通道。值得注意的两个防护细节入口要求*len 19以保证RESET_TRANSPORT PING 8 字节时间戳这类最大无检查写不会越界transport-mux-common.c返回非 0 表示 mux 层征用了本次写机会并已向 buf 写入内容*len恒被置为应发送的字节数且只有真正有内容时才非 0。回调集lws_txp_mux_parse_cbs_t的五个成员及其语义lws-secure-streams-serialization.h回调触发时机返回值含义payload(tmc, buf, len)通道上到达len字节 DATA非 0 表示通道所有者无法继续处理mux 将关闭该通道ch_opens(tmc, determination)通道被打开determination0或被对端拒绝determination1返回非 0 表示拒绝接受该通道ch_closes(tmc)通道关闭通常返回 0txp_req_write(tm)mux 想要一个写机会无返回值通常转发给底层传输的req_writetxp_can_write(tmc)mux 为某通道提供可写机会非 0 表示该通道必须关闭mux 自行补发 FIN其中txp_can_write的语义在 transport-mux-common.c 的注释里讲得很透彻判决非 0 时mux 关闭的是这一个通道对应到 wsi 传输上等价于关闭那一条连接而不是把整条底层链路拖垮。六、测试场景逐条拆解以下逐一还原测试的九个场景并给出期望行为与源码依据。场景 1PONGACK 建链测试首先向解析器喂入PONGACK帧main.c。mux 收到 PONGACK 后在 transport-mux-common.c 中记录对端时间戳us_unixtime_peer调用lws_transport_set_link(tm, LWSTM_OPERATIONAL)取消当前 ping sul 并重新以ping_interval_us为周期调度。测试随即断言tm-link_state LWSTM_OPERATIONAL并确认空闲链路没有任何要发送的内容expect_tx(tm, idle link, NULL, 0)。说明真实流程中 PONGACK 是发 PING 的一方收到 PONG 后才产生的见 transport-mux-common.c测试直接以 PONGACK 开局是为了把链路瞬间拉起从而聚焦通道行为。场景 2客户端角色——对端自 ACK 的通道被拒绝输入req_ack_7 { CHANNEL_REQ, 7, CHANNEL_ACK, 7 }对端请求通道 7并在本方应答之前自己就 ACK 了。此时客户端角色没有可绑定给该通道的东西bind_peer_channels 0因此ch_opens计数保持 0——对端自己的 ACK 没有把通道打开本方在写机会中回送NACK, 7nack_7回送后无残留数据且从未出现过未绑定通道被给可写机会的情况can_write_unbound 0。这一行为的代码依据在 transport-mux-common.c当收到(N)ACK时若通道状态不是LWSTMC_AWAITING_CREATE_CHANNEL_ACK即本方并没有发出过 REQ则该 ACK 被判定为无效而PENDING_CREATE_CHANNEL_ACK状态下收到 NACK 则视为对端撤回请求直接删除占位通道。最终拒绝动作发生在lws_transport_mux_pending()的LWSTMC_PENDING_CREATE_CHANNEL_ACK分支ch_opens(mc, 0)失败返回 -1或priv为空时发出 NACK 并销毁通道transport-mux-common.c。场景 3对端以 NACK 撤回请求输入req_nack_9 { CHANNEL_REQ, 9, CHANNEL_NACK, 9 }对端请求通道 9 又立即 NACKFIN 语义撤回。测试断言ch_closes计数为 2占位通道创建后又被销毁触发两次关闭回调且没有任何内容需要发送——撤回不需要回包。场景 4代理角色——绑定并 ACK后续 ACK 无效将t.bind_peer_channels 1切到代理角色后输入req_11 { CHANNEL_REQ, 11 }。此时写机会中回送ACK, 11ch_opens增加到 2随后再喂ack_11对端对本方 ACK 的再次确认ch_opens仍为 2、无任何回发——对已开通道的重复 ACK 是无效的。这对应 transport-mux-common.c(N)ACK到达时若_open位已置位通道已完全打开直接告警并忽略只有 NACK 才会被当作 FIN 真正拆除通道。场景 5本方通道——REQ、对端 ACK 打开、DATA 送达所有者测试调用lws_transport_mux_client_ops.event_retry_connect(path, (struct lws_sspc_handle *)own_obj)申请本方通道main.c。该 ops 的实现transport-mux-client.c会检查链路必须处于LWSTM_OPERATIONAL然后通过lws_transport_mux_add_channel()分配通道priv绑定到own_obj置状态为LWSTMC_PENDING_CREATE_CHANNEL并请求发送。随后写机会发出REQ, 255客户端从 0 向上、本用例恰好分配了空闲的 255喂入ACK, 255后ch_opens增至 3、失败数为 0通道正式打开——对应 transport-mux-common.c 的CHANNEL_ACK分支置_open位、状态转LWSTMC_OPERATIONAL、回调ch_opens(mc, 0)喂入data_255后payload计数为 1——DATA 到达了通道所有者。场景 6所有者写机会但无话可说lws_transport_mux_client_ops.req_write(path.priv_onw)触发一次通道写机会。mux 在pending()中把该 OPERATIONAL 通道交给txp_can_write本例返回 0随后不产生任何输出expect_tx(tm, owner writes, NULL, 0)can_write计数为 1。场景 7所有者判决必须关闭通道——FIN 由 mux 补发设置t.can_write_verdict -1后再触发写机会。txp_can_write返回非 0mux 走lws_transport_mux_fin_channel()路径transport-mux-common.c 与 L440-L457置 FIN 位、回调ch_closes、销毁通道并请求写机会把NACK, 255发出去。测试断言本次写机会不发送调用者缓冲区里的任何东西expect_tx(tm, owner verdict, NULL, 0)——写机会被 FIN 征用下一次写机会发出nack_255FIN之后再无残留内容。场景 8对端在 FIN 之前发来的 DATA 无处可达FIN 已发出后再喂data_255对端在见到 FIN 之前发出的数据payload计数保持 1数据被丢弃且 mux 会再次回送NACK, 255作为对未知/已关通道数据的确认性 NACK。这与rx_parse中LWSTMCPAR_PLENL/PAY分支的处理一致DATA 只对完全打开且存活中的通道有意义通道未开或已 FIN 时一律丢弃负载并置 FIN 位transport-mux-common.c。场景 9所有者拒绝 DATA——只关该通道链路保持最后设置t.payload_verdict 1喂入通道 11 上的数据帧data_11。payload回调返回非 0 后mux 在PAY分支重新查找通道并调用lws_transport_mux_fin_channel()transport-mux-common.c。测试断言payload计数为 2回调被调用了ch_closes计数为 4链路状态仍然是LWSTM_OPERATIONAL——单个通道的问题没有拖垮整条传输写机会发出NACK, 11作为该通道的 FIN。这一通道级隔离是 mux 层相对 wsi 传输的重要差异注释明确指出旧实现曾因一个通道的失败把整条链路连同上面所有通道一起关闭现行为已收敛为关一通道、保链路transport-mux-common.c。七、与真实接线的对应关系测试中的记录回调在真实系统中分别对应两套实现读者可以对照阅读客户端侧lws_transport_mux_client_ops使用的cbstransport-mux-client.cpayload→ 调用lws_txp_inside_sspc.event_read()把数据送进 SSPC 序列化解析器若解析器返回无法继续则返回 1 触发关通道ch_opens→ 判断tmc-priv即lws_sspc_handle是否存在不存在对端主动请求的通道返回 -1 拒绝存在则调用event_connect_disposition()通知句柄连接结果txp_can_write→ 转调lws_txp_inside_sspc.event_can_write(h, 2048)。代理侧transport-mux-proxy.cch_opens→ 通过lws_txp_inside_proxy.event_new_conn()为该通道分配lws_sss_proxy_conn并绑定为tmc-priv——这正是代理能绑定对端请求的通道的原因payload→ 转调lws_txp_inside_proxy.proxy_read()解析器挂起时返回 1txp_can_write→ 转调event_proxy_can_write()。通道数据的写出封装也值得注意客户端与代理两侧的_write/proxy_write都在缓冲区LWS_PRE预留区内就地写入LWSSSS_LLM_MUX帧头命令 通道号 2 字节大端长度然后一次性交给底层传输transport-mux-client.c、transport-mux-proxy.c。八、小结api-test-transport-mux用约 300 行代码把 libwebsockets 传输复用层的全部关键行为钉死成了可重复的 ctest 用例。透过它可以清晰看到该层的设计要点帧协议自洽CHANNEL_REQ/ACK/NACK/CLOSE/CLOSE_ACK/MUX/PING/PONG/PONGACK/RESET_TRANSPORT十个命令覆盖了建链探测、通道打开/拒绝/撤回/关闭、数据封装与链路重置全流程状态机严谨rx_parse增量解析 pending按优先级打包任何状态下非法帧都不会让解析器越界或让未绑定通道被错误上抛通道级故障隔离无论所有者是拒绝 DATA 还是判决关闭mux 都只 FIN 掉该通道底层链路继续LWSTM_OPERATIONAL可插拔设计通过lws_txp_mux_parse_cbs_t回调集同一套成帧/状态机可以同时服务 SSPC 客户端绑定lws_sspc_handle与 SS 代理绑定lws_sss_proxy_conn为自定义传输如 README 提到的 custom client transport 场景提供了标准的复用底座。如果你想在自己编写的自定义传输中复用 mux 成帧逻辑建议以本测试为模板先跑通ctest -R api-test-transport-mux再对照 transport-mux-common.c 的状态机与 lws-secure-streams-serialization.h 的帧格式逐步替换回调接线即可把多通道复用能力平滑地嫁接到任意底层链路上。赞分享后端网络通信【免费下载链接】libwebsocketscanonical libwebsockets.org networking library项目地址https://gitcode.com/gh_mirrors/li/libwebsockets点击查看免费下载相关推荐深入解析 libwebsockets lwsac 分配块 API以 api-test-lwsac 自测为例深入解析 libwebsockets lwsac 分配块 API以 api test lwsac 自测为例 lwsacLWS Allocated Chunk人工智能AI Agent多模态语音AI 应用CubeFS blobstore rpc2 transport基于 smux 的多路复用传输层深入解析CubeFS blobstore rpc2 transport基于 smux 的多路复用传输层深入解析 CubeFS云原生分布式存储的 blobstore存储分布式文件系统对象存储云原生gRPC 传输层Transport架构深度解析多路复用抽象与 HTTP/2 实现gRPC 传输层Transport架构深度解析多路复用抽象与 HTTP/2 实现 gRPC 是一个跨语言、跨平台的 RPC 框架其核心能力之一是把上层一后端RPC框架微服务通信创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?