- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
导读
lws_system是 libwebsockets 为设备端(尤其是嵌入式与物联网场景)提供的一套系统集成抽象层:它把"重启设备、设置系统时钟、从外来线程向事件循环投递回调、读写设备身份类数据、追踪系统就绪状态"这类平台相关能力,收敛为一张可在lws_context创建时注入的lws_system_ops_t函数表。用户代码只需通过lws_system_get_ops(context)拿到这张表即可调用,从而把系统依赖隔离在单一实现点,让同一份业务代码可以直接运行在完全不同的硬件平台上。读完本文,你将掌握lws_system_ops_t的完整用法、外来线程attach回调机制的实现原理、Blob 存取 API,以及系统状态机(LWS_SYSTATE_*)与通知器的注册方式。本文以 README.lws_system.md 为主体,并结合仓库内的头文件与实现源码进行纵深讲解。
说明:TEN-framework 将 libwebsockets 作为第三方依赖托管于 third_party/libwebsockets,本文讨论的
lws_system即该依赖库中面向平台集成的公开子系统,文中所有源码路径均指向当前仓库内实际文件。
一、System integration api:平台依赖的一次性收敛
1.1 设计思想:把系统依赖从业务代码中剥离
lws_system的核心思路非常简洁:在lws_context创建(Context Creation)时,调用方可以设置一个system_ops结构体,结构体内写入若干面向系统集成的函数回调。此后,用户代码不再直接调用任何平台 API,而是通过lws_system_get_ops(context)从 context 中取回这个 ops 指针再发起调用。
这样做的直接收益是:系统依赖不会散布在用户代码各处,当项目需要从一块板卡迁移到另一块完全不同的平台时,只需要替换创建 context 时注入的那一份lws_system_ops_t实现即可,上层代码零改动。该文件头部的说明也印证了这一点:"This provides a clean way to interface lws user code to be able to work unchanged on different systems for fetching common system information, and performing common system operations like reboot."
在源码层面,lws_system_get_ops()的实现不过是一个访问器,直接从 context 结构返回context->system_ops,见 lib/system/system.c。正因为system_ops是可选注入的,其配套的宏与包装逻辑对"成员为 NULL 或根本没有设置 ops 结构"的情况做了兜底处理,保证未配置时调用依然安全。
1.2 lws_system_ops_t:函数表与调用约定
lws_system_ops_t在 lws-system.h 中定义。原文档给出的最小形态包含三个成员:
typedef struct lws_system_ops { int (*reboot)(void); int (*set_clock)(lws_usec_t us); int (*attach)(struct lws_context *context, int tsi, lws_attach_cb_t cb, lws_system_states_t state, void *opaque, struct lws_attach_item **get); } lws_system_ops_t;| 成员 | 含义 |
|---|---|
(*reboot)() | 重启整个系统(设备) |
(*set_clock)() | 将系统时钟设置为 us 精度的 Unix 时间(秒) |
(*attach)() | 从其他线程上下文请求一次事件循环回调 |
1.3 头文件中更完整的 ops 全貌
对照仓库内 lws-system.h,实际可用的lws_system_ops_t还包含以下可选成员(原文档未展开,这里按源码补充):
int (*captive_portal_detect_request)(struct lws_context *context):发起一次"是否被强制门户(Captive Portal)拦截"的异步检测,检测完成后通过lws_captive_portal_detect_result()回报结果。该功能与系统状态机中的LWS_SYSTATE_CPD_PRE_TIME/LWS_SYSTATE_CPD_POST_TIME状态配合使用。int (*metric_report)(lws_metric_pub_t *mdata):指标上报回调,返回 0 表示保留指标对象,非 0 表示重置。int (*jit_trust_query)(struct lws_context *cx, const uint8_t *skid, size_t skid_len, void *got_opaque):自定义信任库查询。如果系统信任某个 SKID 匹配的根证书,应取到对应根 CA 的 DER 数据并调用lws_tls_jit_trust_got_cert_cb(...)后返回(堆上的 DER 需在返回前销毁)。uint32_t wake_latency_us:设备从挂起(suspend)状态唤醒所需的时间,单位微秒。
这些成员进一步印证了lws_system的定位:凡是"换一块板子就要改一遍"的平台能力,都被收进这张表里,由平台移植层统一实现。
1.4 reboot 与 set_clock
reboot:直接重启设备。它是嵌入式与物联网场景中最典型的平台操作,由平台层实现具体的复位流程。set_clock:把系统时钟设置为微秒精度的 Unix 时间(以秒为单位)。在 libwebsockets 的体系里,时钟有效性直接影响 TLS 证书校验——系统时间不可信时 TLS 握手无法可靠工作(详见下文LWS_SYSTATE_TIME_VALID的说明),因此它通常与 NTP 客户端(ntpclient,见 lib/system/ntpclient/ntpclient.c)配合,在拿到网络时间后回调该函数。
二、外来线程 attach 机制:让回调安全地"跳"进事件循环
2.1 问题场景
在真实设备上,Wi-Fi 驱动、传感器采集、协议栈等往往运行在各自独立的线程中。这些外来线程如果直接操作 lws 的网络对象,会产生竞态。lws_system的attach机制解决的就是这个问题:允许任意线程通过平台级锁保护,向 lws 事件循环线程投递一个"稍后在事件循环线程和栈上下文里执行"的回调请求。
典型用法是:外来线程在自己的回调里完成事件循环活动的初始化,随后退出,剩下的活动全部交给 lws 事件循环线程的栈上下文持续驱动。
2.2 架构:平台锁 + 无锁核心__lws_system_attach()
原文档明确指出 attach 架构分两层:
- 平台特定包装层(
.attach成员实现):负责在调用前后施加平台相关的锁,保证多个线程同时调用.attach()不会互相冲突。 - 无锁核心层(
__lws_system_attach()):真正完成"排队回调请求"这一核心工作的公共 API。它被设计为非线程安全,仅允许在上述平台锁保护下被调用。
__lws_system_attach()的签名如下(见 lws-system.h):
int __lws_system_attach(struct lws_context *context, int tsi, lws_attach_cb_t cb, lws_system_states_t state, void *opaque, struct lws_attach_item **get);从 system.c 的实现可以看到它的具体行为:
- 当
get == NULL时:分配一个lws_attach_item,把cb、opaque、state填入,用双向链表lws_dll2_add_head()挂到对应lws_context_per_thread(pt)的attach_owner列表头部,随后调用lws_cancel_service(context)唤醒事件循环以便尽快处理;返回 0 表示成功。 - 当
get != NULL时:遍历pt->attach_owner,找出第一个"系统状态已满足要求"(context->mgr_system.state >= item->state)的项,将其从链表中摘除并把指针写入*get,由调用方负责lws_free()释放。
2.3 用户侧调用方式与回调签名
用户代码只需一行即可投递请求:
lws_system_get_ops(context)->attach(context, tsi, cb, state, opaque, NULL);各参数含义:
context:lws_context指针;tsi:线程服务索引(thread service index),通常传 0;cb:用户回调,形式固定为void (*lws_attach_cb_t)(struct lws_context *context, int tsi, void *opaque)(见 lws-system.h);state:执行回调前系统必须达到的状态门槛,通常传LWS_SYSTATE_OPERATIONAL(即"网络、NTP、认证都已完成后再回调我");opaque:随回调传回的用户指针。
回调的执行发生在事件循环线程,由lws_system_do_attach()驱动——它循环检查pt->attach_owner,通过.attach操作(在平台锁保护下)摘取满足条件的项,然后直接item->cb(pt->context, pt->tid, item->opaque)调用并释放该项,见 system.c。
2.4 一个关键注意事项:opaque 必须指向堆
投递请求后,发起线程及其栈上下文随时可能被释放。回调实际执行时,投递时的那份栈早已不存在,因此:
- 若
opaque确实需要被使用,它通常应指向堆上的对象; cb内部通常会创建定时事件(scheduled events)并建立 lws 网络相关活动,从而在事件循环线程的栈上下文中持续运行。
这一点在原文档中被特别强调,是实现多线程 attach 时最容易踩的坑。
2.5 相关系统 helper 约定
当任意系统 helper(async DNS、ntpclient、DHCP client 等,见 lib/system/README.md)被编译启用时,lws 会在创建 context 时额外建立一个名为"system"的 vhost,系统功能创建的 wsi 都绑定在该 vhost 上,在 context 对象中以.vhost_system成员暴露。这意味着系统级网络活动与用户业务 vhost 相互隔离,便于统一管理。
三、lws_system Blobs:任意二进制对象的存取抽象
3.1 什么是 Blob
"Blob" 指具有总长度的任意二进制对象。lws_system允许以两种方式写入:
- direct(直接)模式:直接指向一段内存(
ptr+len),不产生任何堆分配; - heap(堆)模式:将一个或多个任意长度的块追加到一个链式堆对象上(底层是 buflist),可以增量构建,不必一次性声明整个 blob。
读取时,同一套 API 支持把全部或部分 blob 拷入用户缓冲区。
3.2 Blob 类型
lws_system_get_blob(context, type, idx)通过类型 + 索引定位 blob。原文档列出的类型如下:
| 类型 | 含义 |
|---|---|
LWS_SYSBLOB_TYPE_AUTH | 认证相关 blob 1,通常是注册令牌(registration token) |
LWS_SYSBLOB_TYPE_AUTH + 1 | 认证相关 blob 2,通常是访问令牌(auth token) |
LWS_SYSBLOB_TYPE_CLIENT_CERT_DER | 客户端证书公钥部分(DER) |
LWS_SYSBLOB_TYPE_CLIENT_KEY_DER | 客户端证书私钥部分(DER) |
LWS_SYSBLOB_TYPE_DEVICE_SERIAL | 任意设备序列号 |
LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION | 任意固件版本号 |
LWS_SYSBLOB_TYPE_DEVICE_TYPE | 任意设备类型标识符 |
LWS_SYSBLOB_TYPE_NTP_SERVER | NTP 服务器地址字符串(默认pool.ntp.org) |
对照 lws-system.h 中的枚举定义,仓库内实际还包含更多类型:LWS_SYSBLOB_TYPE_MQTT_CLIENT_ID、LWS_SYSBLOB_TYPE_MQTT_USERNAME、LWS_SYSBLOB_TYPE_MQTT_PASSWORD(用于 MQTT 连接的身份凭据),以及在编译LWS_WITH_SECURE_STREAMS_AUTH_SIGV4时扩展的 4 组额外认证 blob(LWS_SYSBLOB_TYPE_EXT_AUTH1..4,每组各占 2 个槽位)。枚举末尾的LWS_SYSBLOB_TYPE_COUNT恒为最后一个元素,用于统计总数。
3.3 Blob 句柄获取
lws_system_blob_t * lws_system_get_blob(struct lws_context *context, lws_system_blob_item_t type, int idx);返回代表指定类型(上表所列)blob 的对象。注意其实现(见 system.c):idx必须落在context->system_blobs数组范围内,返回的实际上是&context->system_blobs[type + idx]——因此对AUTH这类"两槽位"类型,可以用idx区分第 1、第 2 个认证令牌。
3.4 Blob 设置 API
void lws_system_blob_direct_set(lws_system_blob_t *b, const uint8_t *ptr, size_t len);把 blob 设置为"指向ptr处len字节",不做任何堆分配。实现上设置b->is_direct = 1并记录指针与长度(见 system.c)。适合指向静态常量(如编译期固化的设备序列号、NTP 服务器字符串)。
int lws_system_blob_heap_append(lws_system_blob_t *b, const uint8_t *buf, size_t len);从buf拷贝len字节到堆上,并链接到已有内容的末尾(可多次追加)。实现内部断言 blob 当前不是 direct 模式,通过lws_buflist_append_segment()追加段,失败返回 -1(见 system.c)。适用于从流式数据源(如网络下载的证书)逐步构建完整 blob 的场景。
void lws_system_blob_heap_empty(lws_system_blob_t *b);清空 blob 全部内容;若内容在堆上则一并释放(is_direct = 0并销毁全部 buflist 段,见 system.c)。
3.5 Blob 读取 API
size_t lws_system_blob_get_size(lws_system_blob_t *b);返回 blob 总大小。direct 模式直接返回u.direct.len;heap 模式返回所有追加块的总长度(lws_buflist_total_len),见 system.c。
int lws_system_blob_get(lws_system_blob_t *b, uint8_t *buf, size_t *len, size_t ofs);从偏移ofs开始,把 blob 的部分或全部拷入buf。*len进入时是用户缓冲区长度,退出时被设为buf实际被使用的字节数。该函数对 direct 指针与堆两种存储方式一视同仁(direct 分支直接memcpy,heap 分支走lws_buflist_linear_copy),见 system.c。调用方可以先用lws_system_blob_get_size()获知总长,再据此分配缓冲区读取。
int lws_system_blob_get_single_ptr(lws_system_blob_t *b, const uint8_t **ptr);零拷贝快捷方式:仅当 blob 是单个 direct 指针或单个堆分配时,直接返回其内部指针。若 blob 由多个 buflist 段构成(b->u.bl->next非空),则返回失败,见 system.c。
3.6 Blob 销毁 API
void lws_system_blob_destroy(lws_system_blob_t *b);释放 blob 的任何堆分配(direct 模式无堆内容,无操作;heap 模式销毁全部 buflist 段),入参为 NULL 时安全返回,见 system.c。
四、系统状态机与通知器:为"启动就绪"建模
4.1 状态机的存在意义
lws_context内部维护一个反映系统就绪程度的状态机:它刻画了从 context 创建到"可以正常运行"之间的一系列里程碑。默认情况下,为了向后兼容,context 创建后系统会直接跃迁到LWS_SYSTATE_OPERATIONAL(可参见 lib/core/context.c 与 lib/core/context.c 中的相关调用)。
但其他 lws 组件以及用户代码可以注册通知处理器(notification handlers):状态每次增量变化时它们会被回调,并且可以否决或延迟状态变更,直到新状态所需的异步工作(例如拿到网络地址、同步时间、完成注册与认证)全部完成。
4.2 通用状态枚举
原文档给出的通用状态表如下:
| 状态 | 含义 |
|---|---|
LWS_SYSTATE_CONTEXT_CREATED | context 刚刚创建 |
LWS_SYSTATE_INITIALIZED | vhost 协议已完成初始化 |
LWS_SYSTATE_IFACE_COLDPLUG | 已遍历现有网络接口 |
LWS_SYSTATE_DHCP | 网络身份(IP 等)可用 |
LWS_SYSTATE_TIME_VALID | 系统已知当前时间 |
LWS_SYSTATE_POLICY_VALID | 若系统需要从网络获取"如何行动"的策略信息,则此时已具备 |
LWS_SYSTATE_REGISTERED | 设备已拥有注册身份 |
LWS_SYSTATE_AUTH1 | 设备身份已产生一个限时访问令牌 |
LWS_SYSTATE_AUTH2 | 为不同服务准备的第二个可选访问令牌 |
LWS_SYSTATE_OPERATIONAL | 系统就绪,用户代码可以正常运行 |
LWS_SYSTATE_POLICY_INVALID | 策略信息正在变更,所有连接被主动断开;随后将携带新策略从LWS_SYSTATE_INITIALIZED重新走到OPERATIONAL |
LWS_SYSTATE_CONTEXT_DESTROYING | context 正在销毁,状态管理随之终止 |
对照 lws-system.h 中的完整枚举,实际序列还包含LWS_SYSTATE_UNKNOWN(起始哨兵)、LWS_SYSTATE_CPD_PRE_TIME(无有效时间时的强制门户检测,适合非 HTTPS 测试)与LWS_SYSTATE_CPD_POST_TIME(时间有效后的强制门户检测,适合 HTTPS 测试)两个 CPD 相关状态。同时源码注释揭示了状态间的依赖关系:TLS 正常工作前必须到达TIME_VALID(要么 ntpclient 已运行,要么硬件时间有效),因为证书校验依赖可信的当前时间——这正是set_clock与 ntpclient 在系统集成中如此重要的原因。
状态对应的可读名字保存在system_state_names[]数组中(见 lib/core/context.c),用于日志与调试输出;状态管理器对象可通过lws_system_get_state_manager(context)获取,并配合lws_state_系列 API 使用。
4.3 插入一个通知器
插入通知器的步骤(原文档要求):
- 在非常量内存中创建一个
lws_system_notify_link_t对象并清零; - 设置其
notify_cb成员与name成员; - 用
lws_system_reg_notifier()或context 创建信息结构体(struct lws_create_context_info)中的.register_notifier_list成员注册它——后者接收一个以 NULL 结尾的notify_link指针数组(见 lws-context-vhost.h),好处是保证通知器在 context 创建早期就已就位,能够看到全部状态事件。
配合attach机制可以组合出强大的启动编排能力:例如把某个初始化回调的state参数设为LWS_SYSTATE_OPERATIONAL,它就会被自动推迟到"网络、时间、策略、注册、认证全部就绪"后才执行,业务代码完全无需关心底层各阶段由谁驱动、耗时多久。
五、小结:lws_system 在设备开发中的实践要点
lws_system从三个维度解决了嵌入式/物联网设备接入 lws 时的平台差异问题:
- 操作抽象:通过注入
lws_system_ops_t,把reboot、set_clock、强制门户检测、指标上报、JIT 信任查询等平台能力集中到单一实现点,业务代码通过lws_system_get_ops(context)统一调用,天然可移植; - 线程安全的事件循环桥接:
attach机制配合__lws_system_attach()的无锁核心与平台锁包装,让外来线程安全地向事件循环投递回调,且可用LWS_SYSTATE_OPERATIONAL等状态门槛控制回调时机,opaque必须指向堆内存是唯一的注意事项; - 数据与就绪状态建模:Blob 子系统以"直接指针 / 堆链"两种模式统一管理设备序列号、证书、认证令牌、NTP 服务器等系统数据,支持增量构建与偏移读取;系统状态机则把"上下文创建 → 初始化 → 网络就绪 → 时间有效 → 策略/注册/认证 → 可运行"的完整启动链路显式化,并通过通知器支持各阶段的否决与延迟。
对于需要在多种硬件平台上交付同一套 lws 业务的团队,把平台相关实现收敛进一份lws_system_ops_t与对应的 Blob 数据,再围绕系统状态机编排启动流程,是这套 API 最值得复用的工程模式。
参考资料(仓库内相对路径)
- lws_system 原始文档
- 函数与对象原型头文件
- system.c 实现:get_ops / Blob / attach 核心
- 系统 Helper 约定与跨线程 attach 说明
- context.c:系统状态名字表与状态推进
- context 创建信息结构体中的 register_notifier_list
- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
相关推荐
aspnetboilerplate 文件上传与存储:BLOB 存储系统全解析
aspnetboilerplate 文件上传与存储:BLOB 存储系统全解析 在现代 Web 应用开发中,文件上传与存储是一项基础且关键的功能。无论是用户头像、
后端Web框架依赖注入认证鉴权ReactXP StatusBar API 完全指南:跨平台系统状态栏控制的统一接口与平台差异解析
ReactXP StatusBar API 完全指南:跨平台系统状态栏控制的统一接口与平台差异解析 导读 本文以 ReactXP 官方 API 文档 statu
跨平台前端如何为DeepSeek-R1-Distill-Qwen-1.5B开发自定义工具调用功能
如何为DeepSeek R1 Distill Qwen 1.5B开发自定义工具调用功能 DeepSeek R1 Distill Qwen 1.5B是一款高效的开
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考