- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
导读
本文深入剖析 libwebsockets(lws)中 Secure Streams 与底层网络协议(h1、h2、ws、mqtt、raw)之间的"胶水层"——即lib/secure-streams/protocols/目录下的协议绑定实现。Secure Streams 的核心设计理念是"严格分离业务载荷与连接元数据":用户代码只收发载荷、接收连接状态通知,而连接地址、TLS 信任链乃至底层线缆协议都由 JSON 策略数据库决定。本文围绕协议绑定层回答三个核心问题:标准 lws 协议回调如何被桥接为 Secure Streams 事件、不同协议差异如何被connect_munge屏蔽、以及库私有的ss_pcols导出结构如何在 vhost 注册与新建流时被检索匹配。读完本文,你将能够理解新增一种 Secure Streams 底层协议所需的全部接线点,并能从源码层面定位 h1/ws/mqtt/raw 各绑定的实际工作路径。
协议绑定层概览:一个目录、四种角色
在 libwebsockets 中,Secure Streams 并不自己发明新协议,而是把已有的 h1(HTTP/1.x)、h2(HTTP/2)、ws(WebSocket)、mqtt、raw(裸 TCP)协议"包装"进统一的状态机与 API 抽象。这份工作全部集中在一个目录中:
third_party/libwebsockets/lib/secure-streams/protocols/ ├── README.md # 协议绑定机制说明(本文主题文档) ├── ss-h1.c # HTTP/1 绑定 ├── ss-h2.c # HTTP/2 绑定 ├── ss-mqtt.c # MQTT 3.1.1 绑定 ├── ss-raw.c # 裸 TCP 绑定 └── ss-ws.c # WebSocket 绑定每个绑定文件承担四种职责:
- 提供标准
struct lws_protocols回调(如secstream_h1),把 lws 协议栈产生的事件和流量转换为 Secure Streams API 调用与 Secure Streams 状态事件; - 提供协议相关的
connect_munge助手,用于把struct lws_client_connect_info修正为与所选协议语义匹配的形式; - 导出库私有的
struct ss_pcols描述结构,告诉 Secure Streams 引擎"这个协议叫什么名字、使用哪个回调、如何修正连接信息"; - 在运行时把底层协议事件翻译成 Secure Streams 状态机事件(CONNECTED、DISCONNECTED、QOS_ACK_REMOTE 等)。
核心角色一:lws_protocols 回调(事件与流量的翻译器)
回调的本质
文档指出,这是"标准的 lwsstruct lws_protocols回调",负责处理所支持协议上的事件与流量,然后把各种事件和流量转换为使用 Secure Streams API 的调用以及 Secure Streams 事件。
以 h1 绑定为例,ss-h1.c 中的secstream_h1()是注册在protocol_secstream_h1中的回调函数,其签名就是标准 lws 协议回调:
int secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);回调内部通过reason分派处理各类 lws 事件,并在每个分支里调用 Secure Streams 引擎的辅助函数,典型映射关系如下(可在 ss-h1.c 等处核实):
| lws 事件(reason) | 转换为的 Secure Streams 行为 |
|---|---|
LWS_CALLBACK_CLIENT_CONNECTION_ERROR | 触发LWSSSCS_UNREACHABLE或LWSSSCS_DISCONNECTED事件,随后进入退避(backoff)重试逻辑 |
LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP | 校验响应码(默认 200–299,或用策略中的http_expect覆盖),抽取响应头元数据,触发LWSSSCS_CONNECTED |
LWS_CALLBACK_COMPLETED_CLIENT_HTTP | 触发LWSSSCS_QOS_ACK_REMOTE(响应码正常)或LWSSSCS_QOS_NACK_REMOTE(异常) |
LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ | 把收到的 HTTP body 数据块交给用户 rx 回调(带LWSSS_FLAG_SOM/LWSSS_FLAG_EOM分帧标记) |
LWS_CALLBACK_CLIENT_HTTP_WRITEABLE | 向用户 tx 回调请求写缓冲,随后lws_write()发出载荷 |
一个关键细节:ws 回调与 h1 回调的差异
ws 绑定 ss-ws.c 的secstream_ws()遵循同样的模式,但事件集不同:使用LWS_CALLBACK_CLIENT_ESTABLISHED/LWS_CALLBACK_ESTABLISHED触发LWSSSCS_CONNECTED,用LWS_CALLBACK_RECEIVE/LWS_CALLBACK_CLIENT_RECEIVE把帧载荷交给 rx 回调,并根据lws_is_first_fragment()/lws_is_final_fragment()设置 SOM/EOM 标志;写侧则依据策略中的ws_binary选择LWS_WRITE_BINARY或LWS_WRITE_TEXT。
raw 绑定 ss-raw.c 更简化:LWS_CALLBACK_RAW_RX直接把收到的字节流原样交给 rx 回调(不携带任何分帧标志),LWS_CALLBACK_RAW_WRITEABLE中 tx 回调提供的字节被直接写出。这印证了主 README 中的说明——raw 没有协议分帧,SOM/EOM 标志被忽略。
回调返回值的约定
协议回调并非简单地返回 0/-1,而是把 Secure Streams 状态机返回值转换为 lws 行为。文档列出的核心返回约定:
| 常量 | 作用范围 | 含义 |
|---|---|---|
LWSSSSRET_TX_DONT_SEND | tx | 放弃本次发送机会 |
LWSSSSRET_OK | state、rx、tx | 无错误,继续 |
LWSSSSRET_DISCONNECT_ME | state、rx | 主动断开与对端的连接 |
LWSSSSRET_DESTROY_ME | state、rx | 调用方应销毁该流 |
LWSSSSRET_SS_HANDLE_DESTROYED | state | 回调中某辅助函数已经销毁了流句柄 |
在 rx/tx 的具体约定上:tx 回调返回LWSSSSRET_OK表示发送*len字节,返回LWSSSSRET_TX_DONT_SEND表示不发,返回LWSSSSRET_DISCONNECT_ME关闭当前连接,返回LWSSSSRET_DESTROY_ME销毁流;rx 回调返回 >=0 表示接受,返回 <0 表示关闭当前连接。ss-h1.c中所有分支都经由_lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h)包装返回值,以便在回调内安全处理"销毁当前流"这类副作用。
核心角色二:connect_munge 助手(协议差异的屏蔽器)
为什么需要 munge
不同协议在客户端连接函数的参数语义上各不相同:HTTP 需要 URL 路径与方法、WebSocket 需要子协议名、MQTT 需要主题与 QoS。文档明确说明:这个协议特定的助手被调用来"munge"(修正)connect_info结构,使其匹配所选协议的细节。而ss->policy->aux字符串正是承载这些协议特定信息的载体——例如 URL 路径或 websocket 子协议名。
h1 的 munge 实现
ss-h1.c 的secstream_connect_munge_h1()展示了典型做法:
static int secstream_connect_munge_h1(lws_ss_handle_t *h, char *buf, size_t len, struct lws_client_connect_info *i, union lws_ss_contemp *ct) { const char *pbasis = h->policy->u.http.url; lws_strexp_t exp; ... /* i.path on entry is used to override the policy urlpath if not "" */ if (i->path[0]) pbasis = i->path; ... /* protocol aux is the path part */ i->path = buf; /* skip the unnessary '/' */ if (*pbasis == '/') pbasis = pbasis + 1; buf[0] = '/'; ... lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1); if (lws_strexp_expand(&exp, pbasis, strlen(pbasis), &used_in, &used_out) != LSTRX_DONE) return 1; ... return 0; }关键点:
- 路径覆盖:
i->path非空时优先覆盖策略中的 URL 路径; - 元数据字符串展开:通过
lws_strexp_expand+lws_ss_exp_cb_metadata回调实现${metadataname}的运行时替换(这正是策略中http_url支持/mypath?whatever=${metadataname}的底层机制); - 标志位透传:
LWSSSPOLF_HTTP_MULTIPART映射为LCCSCF_HTTP_MULTIPART_MIME、LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED映射为LCCSCF_HTTP_X_WWW_FORM_URLENCODED、LWSSSPOLF_HTTP_CACHE_COOKIES映射为LCCSCF_CACHE_COOKIES,从而把这些策略选项落实到真实连接行为上。
ws 的 munge 实现
ss-ws.c 的secstream_connect_munge_ws()几乎同构,但额外把策略中的ws_subprotocol写入i->protocol:
i->protocol = h->policy->u.http.u.ws.subprotocol; lwsl_ss_info(h, "url %s, ws subprotocol %s", buf, i->protocol);这正体现了文档所言:"aux 字符串承载协议特定信息,例如 URL 路径或 websockets 子协议名"。
mqtt 与 raw 的 munge
mqtt 绑定同样实现了主题的字符串展开(secstream_mqtt_subscribe中先用lws_strexp_expand计算展开后长度、再分配缓冲二次展开,见 ss-mqtt.c),并把策略中的 QoS、订阅信息组装进 MQTT 连接。raw 的 munge 最为简单,ss-raw.c 仅仅设置方法名为"RAW":
static int secstream_connect_munge_raw(lws_ss_handle_t *h, char *buf, size_t len, struct lws_client_connect_info *i, union lws_ss_contemp *ct) { i->method = "RAW"; return 0; }核心角色三:库私有的 ss_pcols 导出结构
结构定义
文档指出每个协议绑定向 lws 的其他部分导出两样东西(均不导出给用户代码):一个struct lws_protocols(含回调指针),以及一个描述 Secure Streams 应如何使用该协议的struct ss_pcols(含相关的 connect_munge 助手指针)。ss_pcols的完整定义在库私有头文件 private-lib-secure-streams.h:
struct ss_pcols { const char *name; const char *alpn; const struct lws_protocols *protocol; secstream_protocol_connect_munge_t munge; secstream_protocol_add_txcr_t tx_cr_add; secstream_protocol_get_txcr_t tx_cr_est; };字段含义:
name:策略中protocol字段使用的协议名(如"h1"、"ws"、"MQTT"、"raw");alpn:TLS 握手时使用的 ALPN 协议标识(h1 为"http/1.1",h2 为"h2",mqtt 为"x-amzn-mqtt-ca");protocol:指向导出的struct lws_protocols(内含回调);munge:协议特定的 connect_munge 助手指针;tx_cr_add/tx_cr_est:可选的发送信用(tx credit)管理回调,供 h2 这类有流控语义的协议使用。
各协议绑定的导出实例
从源码中可以确认五个绑定实例(见各绑定文件末尾的导出声明):
| 绑定文件 | ss_pcols 实例 | name | alpn | 协议回调 |
|---|---|---|---|---|
| ss-h1.c | ss_pcol_h1 | "h1" | "http/1.1" | protocol_secstream_h1 |
| ss-h2.c | ss_pcol_h2 | "h2" | "h2" | protocol_secstream_h2 |
| ss-ws.c | ss_pcol_ws | "ws" | "http/1.1" | protocol_secstream_ws |
| ss-mqtt.c | ss_pcol_mqtt | "MQTT" | "x-amzn-mqtt-ca" | protocol_secstream_mqtt |
| ss-raw.c | ss_pcol_raw | "raw" | "" | protocol_secstream_raw |
所有实例与协议回调的原型声明集中在 private-lib-secure-streams.h。
运行时的两次接线:vhost 注册与新建流检索
第一次接线:协议回调加入 vhost
文档指明:在 vhost.c 中,启用的协议被加入 vhost 协议列表以便使用。源码证实这一点——available_secstream_protocols[]数组按编译选项罗列了各协议回调:
#if defined(LWS_WITH_SECURE_STREAMS) const struct lws_protocols *available_secstream_protocols[] = { #if defined(LWS_ROLE_H1) &protocol_secstream_h1, #endif #if defined(LWS_ROLE_H2) &protocol_secstream_h2, #endif #if defined(LWS_ROLE_WS) &protocol_secstream_ws, #endif #if defined(LWS_ROLE_MQTT) &protocol_secstream_mqtt, #endif &protocol_secstream_raw, NULL }; #endif注意LWS_ROLE_H1/LWS_ROLE_H2/LWS_ROLE_WS/LWS_ROLE_MQTT这些编译开关与策略中支持的协议一一对应,raw 则无条件启用。这意味着"策略支持哪些协议"本质上由构建时启用的 role 决定。
第二次接线:新建流时按名字匹配
文档说明:在 secure-streams.c 中,启用的struct ss_pcols被列出,并在用户创建新 Secure Stream 时被检查匹配。源码证实:secure-streams.c内部维护了ss_pcols[]数组,按下标(即h->policy->protocol枚举值)索引:
static const struct ss_pcols *ss_pcols[] = { #if defined(LWS_ROLE_H1) &ss_pcol_h1, /* LWSSSP_H1 */ #else NULL, #endif #if defined(LWS_ROLE_H2) ...在客户端发起连接的核心函数_lws_ss_client_connect()(secure-streams.c)中,检索逻辑清晰可见:
ssp = ss_pcols[(int)h->policy->protocol]; if (!ssp) { lwsl_err("%s: unsupported protocol\n", __func__); return LWSSSSRET_TX_DONT_SEND; } i.alpn = ssp->alpn; ... i.method = h->policy->u.http.method; i.protocol = ssp->protocol->name; /* lws protocol name */ i.local_protocol_name = i.protocol;随后该函数调用ssp->munge(h, buf, len, &i, &ct)对struct lws_client_connect_info做协议特定的修正,最终把连接请求交给 lws 客户端连接机制。由此形成了完整的调用链:策略中的protocol字段 →ss_pcols[]索引查找 → 取 ALPN 与协议名 → 调用 munge 修正连接参数 → 建立底层连接 → 协议回调把事件翻译回 Secure Streams 状态机。
从源码看三种协议的数据通路差异
h1:事务型数据通路
h1 绑定是"事务型"(transactional)的典型代表:一次 HTTP 请求对应一次逻辑响应。在LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP中,绑定会根据响应码决定是否触发LWSSSCS_CONNECTED与 QoS 确认:
- 默认良好响应码区间是 200–299;
- 若策略配置了
http_expect(如捕获门户检测中的 204),则改为"恰好等于该值才视为成功"; - 若配置了
http_resp_map,则通过lws_ss_http_resp_to_state()(ss-h1.c)把服务器自定义响应码映射为离散的 Secure Streams 状态回调; - 503/429 会被识别为"可隐藏的失败"并进入退避逻辑,且优先采用响应头中的
Retry-After(lws_http_check_retry_after)。
rx 数据通路方面,LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ分支会在子序列首包设置LWSSS_FLAG_SOM,在LWS_CALLBACK_COMPLETED_CLIENT_HTTP用LWSSS_FLAG_EOM收尾,从而把 chunked 传输还原为逻辑消息边界。
ws:面向消息的数据通路
ws 绑定利用lws_is_first_fragment()/lws_is_final_fragment()直接映射 SOM/EOM,每个 WS 消息天然对应一个带边界的 SS 消息。写方向则根据ws_binary决定文本/二进制帧类型。此外 ws 服务端场景下,协议升级(HTTP→WebSocket)会在 server-ws.c 和 http2.c 中把wsi->a.protocol切换为protocol_secstream_ws,并触发LWSSSCS_SERVER_UPGRADE状态通知用户代码。
raw:无分帧的字节流通路
raw 绑定最为朴素:rx 直接透传(不带标志),tx 直接写出(忽略标志),正如文档所述——由于 TCP 可能被任意中间设备任意分片,raw 流只能被视为可在任意字节处切分的"有序字节流",SOM/EOM 因此无意义。
与 TEN-framework 的关联:为何关注这份第三方绑定
本仓库 TEN-framework 将 libwebsockets 作为第三方依赖引入(位于 third_party/libwebsockets),其用途正是承载语音智能体场景中的网络传输能力——例如 playground、websocket-example、voice-assistant 系列示例所依赖的 WebSocket/HTTP 通道。Secure Streams 这套协议绑定层之所以重要,在于它让上层应用可以用统一的lws_ss_API 编写网络逻辑,而无需关心底层到底是 h1、ws 还是 mqtt,连接细节全部收敛到 JSON 策略数据库。TEN-framework 若要基于 lws 实现多协议接入(如同时支持 WebSocket 与 HTTP 的网关),这套绑定机制就是天然的协议适配底座。
需要强调的是,本目录只是 lws 的协议绑定层;Secure Streams 的整体状态机、JSON 策略数据库字段(retry/backoff/conceal/jitterpc、certs/trust_stores、auth、各s流类型定义及其 http/ws/mqtt 细分参数)、客户端与服务器端生命周期、序列化与代理模式等完整设计,请参阅同一目录下的 Secure Streams 主文档,其中包含完整的策略 JSON 示例、状态生命周期图与回调返回值约定表,可与本文的绑定层细节相互印证。
小结
Secure Streams 的协议绑定层是一个精巧的"适配器集合":
- lws_protocols 回调负责把 lws 协议事件翻译成 Secure Streams 事件与 API 调用,是双向流量转换的枢纽;
- connect_munge 助手负责屏蔽各协议在连接参数上的语义差异,并借助
lws_strexp实现策略字符串(URL 路径、MQTT 主题)的元数据运行时展开; - ss_pcols 导出结构则是连接回调与 munge 助手的"注册表条目",在 vhost.c 完成 vhost 接线,在 secure-streams.c 完成按协议名检索,二者共同支撑起"策略驱动、协议无关"的连接建立流程。
理解这三个角色及其在源码中的位置,是定制新协议绑定、排查连接行为、或评估在 TEN-framework 中引入新传输协议时的第一块敲门砖。读者可沿着 ss-h1.c、ss-ws.c、ss-mqtt.c、ss-raw.c 四个文件逐一对照阅读,即可获得完整的实现全貌。
- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
相关推荐
TEN-framework 中 libwebsockets Secure Streams 压力测试示例剖析:minimal-secure-streams-stress 并发与预算机制详解
TEN framework 中 libwebsockets Secure Streams 压力测试示例剖析:minimal secure streams str
人工智能AI Agent多模态语音AI 应用用 libwebsockets Secure Streams 实现大文件下载与流量控制:minimal-secure-streams-blob 实战解析
用 libwebsockets Secure Streams 实现大文件下载与流量控制:minimal secure streams blob 实战解析 在 T
人工智能AI Agent多模态语音AI 应用libwebsockets Secure Streams 裸 TCP 服务器实战:minimal-secure-streams-server-raw 完全解析
libwebsockets Secure Streams 裸 TCP 服务器实战:minimal secure streams server raw 完全解析
人工智能AI Agent多模态语音AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考