news 2026/10/7 2:06:32

libwebsockets 最小化 HTTP 服务器示例详解:eventlib + SMP 多线程事件循环(minimal-http-server-eventlib-smp)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libwebsockets 最小化 HTTP 服务器示例详解:eventlib + SMP 多线程事件循环(minimal-http-server-eventlib-smp)
  • 人工智能
  • AI Agent
  • 多模态
  • 语音
  • AI 应用

【免费下载链接】ten-framework

Open-source framework for conversational voice AI agents

项目地址:https://gitcode.com/TEN-framework/ten-framework
点击查看免费下载

导读

本文以 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相比,它的核心特点是:

  1. 事件循环后端可切换:可通过命令行参数在 libuv、libevent、libev 三种外部事件库之间选择,不指定时则回落到 lws 默认的poll()实现;
  2. 支持多线程服务(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>调试日志级别,十进制数字,例如-d15lws_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 . && make

3. 多线程能力的编译期开关

如前所述,示例要真正跑多线程,宿主 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 中有明确说明:

  1. 线程与 wsi 的绑定关系:当有新的连接被 accept 后,lws 会把它绑定到当前 wsi 数量最少的 pt(per-thread 服务实例)上,以实现线程间负载均衡;
  2. 单线程独占服务:一个 wsi 只能由它所绑定的那个服务线程来服务,因此整个系统虽然可以同时服务与线程数等量的 wsi,但单个 wsi 的读写始终固定在同一个线程上,无需跨线程加锁竞争;
  3. 扩展效果取决于负载形态:由于每个线程独立轮询事件循环,多线程的收益主要体现在多连接、高并发场景(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 的三项高级能力:

  1. 外部事件库后端可插拔(libuv / libevent / libev / GLib,或回落 poll());
  2. 基于LWS_MAX_SMP > 1的多线程服务,通过lws_service_tsi()+lws_get_count_threads()驱动 per-thread 事件循环;
  3. 静态文件挂载、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

项目地址:https://gitcode.com/TEN-framework/ten-framework
点击查看免费下载

相关推荐

上一篇:Self-LLM 实战案例集:从 LoRA 微调到全栈大模型应用的四条完整链路
下一篇:终极炉石传说插件指南:55项功能全面提升游戏体验

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 2:04:54

Scaffolt 生成器实战:为 Brunch with Chaplin 骨架批量生成 MVC 代码

构建工具前端 【免费下载链接】brunch &#x1f374; Web applications made easy. Since 2011. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/br/brunch 点击查看 免费下载 导读 本指南围绕 brunch-with-chaplin 骨架内置的生成器集合展开&#xff0c;说明如何借助…

作者头像 李华
网站建设 2026/10/7 2:04:25

开源微型双足鸭机器人:强化学习从仿真到真机部署指南

最近在整理之前做的一个微小型双足鸭形机器人项目&#xff0c;很多朋友来问这个看起来像鸭子玩具的小东西到底有什么门道。说实话&#xff0c;虽然外形呆萌&#xff0c;但内部涉及的强化学习算法、硬件拓扑和仿真到实机的迁移过程&#xff0c;一点也不比大型人形机器人简单。这…

作者头像 李华
网站建设 2026/10/7 2:02:53

agent-skills 实战:用 skills CLI 为 Claude Code 打造标准化技能包

1. 从"agent-skills"这个标题能读出什么第一次看到agent-skills这个仓库名&#xff0c;我的直觉是&#xff1a;这不是又一个"提示词大全"&#xff0c;而是一套把 AI coding agent 的能力拆成可复用模块的工程化尝试。关键词里同时出现了skills CLI、Claude…

作者头像 李华
网站建设 2026/10/7 2:02:19

rkisp驱动代码解析:从V4L2框架到视频调试实战指南

简介&#xff1a;RK ISP 驱动代码包&#xff0c;面向嵌入式Linux下Rockchip图像信号处理器&#xff08;ISP&#xff09;的驱动开发与移植场景&#xff0c;适合内核驱动工程师和学习V4L2框架的开发者。资源以rk-isp11为例&#xff0c;重点展示设备树匹配使用的of_device_id&…

作者头像 李华