- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
导读
本文以 libwebsockets(lws)官方示例minimal-http-server-eventlib-smp为主线,深入讲解如何构建一个可自由切换事件库(libuv / libevent / libev)且支持多线程服务(SMP)的最小 HTTP 服务器。示例源码位于 third_party/libwebsockets/minimal-examples/http-server/minimal-http-server-eventlib-smp/,读者读完本文后将掌握:-d / -t / --uv / --event / --ev等命令行选项的完整含义、LWS_MAX_SMP > 1的编译前提、多线程服务(per-thread service)的实现原理,以及如何通过lws_service_tsi()让多个工作线程并行驱动 lws 事件循环。
注意:lws 官方在该示例 README 中明确标注 “WARNING: this is under development, it's not stable.”,即该示例仍处于开发阶段、行为尚未稳定,生产环境使用前需自行评估。
一、示例定位:一个可插拔事件库的多线程 HTTP 服务器
minimal-http-server-eventlib-smp是 lws 官方minimal-examples系列中的一员,与普通minimal-http-server相比,它的核心特点是:
- 事件循环后端可切换:可通过命令行参数在 libuv、libevent、libev 三种外部事件库之间选择,不指定时则回落到 lws 默认的
poll()实现; - 支持多线程服务(SMP):将 lws context 划分为多个 service thread,每个线程独立驱动事件循环,从而利用多核 CPU 提升并发处理能力。
该示例会从启动目录下的./mount-origin子目录对外提供静态文件服务(默认首页为index.html),并内置 404 错误页处理。相关文件结构如下:
third_party/libwebsockets/minimal-examples/http-server/minimal-http-server-eventlib-smp/ ├── CMakeLists.txt # 构建脚本 ├── minimal-http-server-eventlib-smp.c # 示例主程序 ├── localhost-100y.cert # TLS 证书(配合 -s 选项使用) ├── localhost-100y.key # TLS 私钥 └── mount-origin/ # 静态文件挂载目录 ├── 404.html ├── index.html ├── favicon.ico ├── libwebsockets.org-logo.svg └── strict-csp.svg二、命令行选项完整说明
原文档给出了如下选项表,本文结合 minimal-http-server-eventlib-smp.c 源码逐一扩充:
| 命令行选项 | 含义 | 源码中的对应实现 |
|---|---|---|
-d <loglevel> | 调试日志级别,十进制数字,例如-d15 | lws_cmdline_option(argc, argv, "-d")读取后调用lws_set_log_level(logs, NULL)设置日志级别 |
-t <threads> | 使用的服务线程数量 | 设置info.count_threads;若threads < 1或threads > LWS_MAX_SMP则直接返回 1 退出 |
--uv | 使用 libuv 事件库(lws 必须以-DLWS_WITH_LIBUV=1配置编译) | info.options \|= LWS_SERVER_OPTION_LIBUV |
--event | 使用 libevent 事件库(lws 必须以-DLWS_WITH_LIBEVENT=1配置编译) | info.options \|= LWS_SERVER_OPTION_LIBEVENT |
--ev | 使用 libev 事件库(lws 必须以-DLWS_WITH_LIBEV=1配置编译) | info.options \|= LWS_SERVER_OPTION_LIBEV |
1. 日志级别-d
源码中默认日志级别为LLL_USER \| LLL_ERR \| LLL_WARN \| LLL_NOTICE。注释特别提醒:要启用高于 NOTICE 级别的日志(如LLL_INFO、LLL_PARSER、LLL_HEADER、LLL_EXT、LLL_CLIENT、LLL_LATENCY、LLL_DEBUG),lws 必须以-DCMAKE_BUILD_TYPE=DEBUG(而非RELEASE)配置并构建,否则这些级别的日志不会被编入库中。示例中这些级别默认被注释掉,可按需取消注释。
2. 线程数-t与LWS_MAX_SMP上限
if ((p = lws_cmdline_option(argc, argv, "-t"))) { info.count_threads = (unsigned int)atoi(p); if (info.count_threads < 1 || info.count_threads > LWS_MAX_SMP) return 1; } else info.count_threads = COUNT_THREADS;- 未指定
-t时,默认线程数为源码中定义的COUNT_THREADS(值为 8); - 指定时,取值必须落在
[1, LWS_MAX_SMP]区间,否则程序直接退出; - 因此,要使用多线程,libwebsockets 必须以
LWS_MAX_SMP大于 1 编译(原文档明确说明 “lilbwebsockets must have been built withLWS_MAX_SMPgreater than 1 to use multiple threads”)。LWS_MAX_SMP是编译期常量,LWS_MAX_SMP == 1(默认值)时 lws 只支持单线程服务,相关 pthread 锁也不会编入。
3. 事件库选择选项的优先级
从源码main()中的if / else if链可以看出,事件库选项存在优先级:--uv>--event>--ev>--glib。当所有事件库选项都未给出时,示例注册SIGINT处理函数并回落至 lws 默认的poll()事件循环。此外源码还支持一个 README 未列出的--glib选项(对应LWS_SERVER_OPTION_GLIB),需要 lws 以 GLib 事件库支持编译。
三、构建前提与步骤
1. 依赖要求(CMake 侧)
CMakeLists.txt 通过find_package(libwebsockets CONFIG REQUIRED)定位已安装的 lws,并调用require_lws_config强制检查两项配置,任何一项不满足都会跳过编译:
LWS_ROLE_H1:HTTP/1.x 角色支持必须开启(示例本质是一个 H1 服务器);LWS_WITH_SERVER:服务器端支持必须开启;require_pthreads:多线程示例依赖 pthread,编译时需要链接PTHREAD_LIB。
链接时,若 lws 以共享库构建(websockets_shared),则链接websockets_shared与PTHREAD_LIB及LIBWEBSOCKETS_DEP_LIBS;否则链接静态库websockets。
2. 编译命令
$ cmake . && make3. 多线程能力的编译期开关
如前所述,示例要真正跑多线程,宿主 libwebsockets 必须以LWS_MAX_SMP大于 1 构建。LWS_MAX_SMP同时决定了:
- 示例可用的最大线程数上限(
-t参数不能超过它); - lws 是否编入额外的 pthread 锁与 per-thread(pt)基础设施。
另外,若要以--uv/--event/--ev使用外部事件库,lws 必须分别以-DLWS_WITH_LIBUV=1、-DLWS_WITH_LIBEVENT=1、-DLWS_WITH_LIBEV=1配置编译,否则对应选项虽可解析但 lws 内并无相应后端实现。
四、核心源码逐段解析
1. 静态文件挂载(mount)
static const struct lws_http_mount mount = { /* .mountpoint */ "/", /* 挂载点 URL */ /* .origin */ "./mount-origin",/* 实际服务目录 */ /* .def */ "index.html", /* 默认文件名 */ /* .origin_protocol */LWSMPRO_FILE, /* 来自目录中的文件 */ /* .mountpoint_len */ 1, /* mountpoint 字符数 */ };该结构把 URL 根路径/映射到启动目录下的./mount-origin目录,未指定具体文件时默认返回index.html。info.error_document_404 = "/404.html"指定了 404 错误页。因此访问不存在的页面(例如首页中的 notextant.html 链接)时会返回 mount-origin/404.html。
2. context 创建信息
info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; info.pcontext = &context; info.signal_cb = signal_cb; info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;- 监听端口固定为
7681; - 启用
LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE,即强制实施 HTTP 响应头安全最佳实践(CSP 等安全响应头由 lws 自动附加,首页中引用的strict-csp.svg正与此相关); info.count_threads由-t决定,默认 8;- 在
#if defined(LWS_WITH_TLS)保护下,若传入-s选项,会额外设置LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT并加载仓库内自带的localhost-100y.cert与localhost-100y.key,从而让示例同时支持 HTTPS。
3. 多线程服务循环(SMP 的关键)
void *thread_service(void *threadid) { while (lws_service_tsi(context, 10000, (int)(lws_intptr_t)threadid) >= 0 && !interrupted) ; pthread_exit(NULL); return NULL; }lws_service_tsi(context, timeout_ms, thread_index)是 SMP 模式下多线程服务循环的入口:每个服务线程通过自己的thread_index(Thread Service Index)驱动事件循环,超时值为 10000 毫秒。主线程在创建 context 后,根据lws_get_count_threads(context)实际生效的线程数,通过pthread_create逐个启动服务线程,最后用pthread_join等待所有线程退出。
4. 信号处理与优雅退出
void signal_cb(void *handle, int signum) { interrupted = 1; switch (signum) { case SIGTERM: case SIGINT: break; default: lwsl_err("%s: signal %d\n", __func__, signum); break; } lws_context_destroy(context); }收到SIGINT/SIGTERM时置位interrupted标志,服务线程的 while 循环随即退出,最后统一lws_context_destroy(context)销毁 context。注意:当使用外部事件库(--uv等)时,context 创建时传入的是signal_cb(由事件库后端接管信号);未使用外部事件库时,示例才显式调用signal(SIGINT, sigint_handler)注册信号处理。
五、SMP 多线程服务的工作原理
要理解该示例的意义,需要结合 lws 的 SMP 设计。相关机制在 minimal-http-server-smp 的 README 中有明确说明:
- 线程与 wsi 的绑定关系:当有新的连接被 accept 后,lws 会把它绑定到当前 wsi 数量最少的 pt(per-thread 服务实例)上,以实现线程间负载均衡;
- 单线程独占服务:一个 wsi 只能由它所绑定的那个服务线程来服务,因此整个系统虽然可以同时服务与线程数等量的 wsi,但单个 wsi 的读写始终固定在同一个线程上,无需跨线程加锁竞争;
- 扩展效果取决于负载形态:由于每个线程独立轮询事件循环,多线程的收益主要体现在多连接、高并发场景(README 建议配合
ab等压测工具验证),连接数较少时收益有限。
从本示例源码看,lws_get_count_threads(context)返回的实际线程数由LWS_MAX_SMP与info.count_threads共同决定,服务线程启动数量严格以此为基准。这也解释了为什么构建 lws 时必须保证LWS_MAX_SMP > 1——它决定了 SMP 基础设施与线程上限是否存在。
六、运行与验证
1. 启动
$ ./lws-minimal-http-server-eventlib-smp [2018/03/04 09:30:02:7986] USER: LWS minimal http server-eventlib | visit http://localhost:7681 [2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on [2018/03/04 09:30:02:7986] NOTICE: Service threads: 8日志中的Service threads: 8来自源码中的lwsl_notice(" Service threads: %d\n", lws_get_count_threads(context)),可用于确认 SMP 线程数是否按预期生效。
2. 功能验证点
- 默认首页:访问
http://localhost:7681,返回mount-origin/index.html(包含 lws logo 与 “Hello from the minimal http server event loop example” 文案); - 404 页:访问首页中提供的
notextant.html链接或任意不存在的路径,会返回404.html自定义错误页; - 事件库切换:分别以
--uv、--event、--ev启动,观察服务是否正常运行,以此验证对应事件库后端是否编入; - 线程数调整:用
-t 2等参数限制服务线程数,配合日志确认实际生效值; - HTTPS:若 lws 以 TLS 支持编译,可用
-s启动,访问https://localhost:7681(使用仓库自带的 100 年有效期本地证书)。
3. 运行示例
$ ./lws-minimal-http-server-eventlib-smp -d15 -t 4 --uv以上命令表示:日志级别 15、4 个服务线程、强制使用 libuv 事件库后端(前提是 lws 已按-DLWS_WITH_LIBUV=1构建)。
七、与其他官方示例的对比
| 示例 | 特点 | 与本示例关系 |
|---|---|---|
| minimal-http-server | 基础单线程 HTTP 服务器,默认 poll() | 本示例的基础形态 |
| minimal-http-server-eventlib | 支持事件库切换但为单线程 | 本示例去 SMP 化后的版本,二者命令行选项基本一致 |
| minimal-http-server-smp | 多线程服务但不支持外部事件库 | 与 minimal-http-server-smp 的 README 对照阅读,可完整理解 SMP 负载均衡与 wsi 绑定的设计 |
本示例本质上是“事件库可插拔 + SMP 多线程”两个特性的组合示范,适合作为评估 lws 在不同事件循环后端与多线程调度策略下并发表现的起点。
八、总结
minimal-http-server-eventlib-smp用约 170 行 C 代码完整演示了 lws 的三项高级能力:
- 外部事件库后端可插拔(libuv / libevent / libev / GLib,或回落 poll());
- 基于
LWS_MAX_SMP > 1的多线程服务,通过lws_service_tsi()+lws_get_count_threads()驱动 per-thread 事件循环; - 静态文件挂载、404 错误页、TLS 与安全响应头等常用 HTTP 服务器功能。
使用该示例前需明确三个前提:lws 必须以LWS_MAX_SMP > 1构建才能使用多线程;使用特定事件库后端需对应LWS_WITH_LIBUV / LWS_WITH_LIBEVENT / LWS_WITH_LIBEV编译选项;示例本身处于开发阶段、尚未稳定。在 TEN-framework 仓库中,libwebsockets 作为第三方依赖(见 third_party/libwebsockets/BUILD.gn)被集成,本文剖析的 SMP 与事件库机制,可作为理解 lws 在宿主项目中服务调度行为的参考。
- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
相关推荐
libwebsockets 最小 HTTP 服务器事件循环后端示例:libuv / libev / libevent 一键切换实战解析
libwebsockets 最小 HTTP 服务器事件循环后端示例:libuv / libev / libevent 一键切换实战解析 libwebsocket
人工智能AI Agent多模态语音AI 应用MCP Python SDK 服务端工具(Tools)开发指南:用 `@mcp.tool()` 把普通 Python 函数变成模型可调用的工具
MCP Python SDK 服务端工具(Tools)开发指南:用 @mcp.tool 把普通 Python 函数变成模型可调用的工具 本指南基于 python
人工智能AI Agent多模态语音AI 应用yay Lua钩子完整指南:用SearchFilter与RenderAUR定制AUR搜索结果的10个实战技巧
yay Lua钩子完整指南:用SearchFilter与RenderAUR定制AUR搜索结果的10个实战技巧 yay 是 Arch Linux 上以 Go 语言
人工智能AI Agent多模态语音AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考