news 2026/10/13 1:56:52

libwebsockets 最小 D-Bus 客户端实战:在 lws 事件循环上发起异步 D-Bus 方法调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libwebsockets 最小 D-Bus 客户端实战:在 lws 事件循环上发起异步 D-Bus 方法调用
  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

本文以 libwebsockets 仓库中的minimal-dbus-client低层示例(示例 README)为核心,讲解如何在 libwebsockets 的事件循环中集成 libdbus,实现非阻塞、异步的 D-Bus 方法调用:客户端连接 D-Bus 服务、调用远程Echo方法、异步接收回复,并持续接收服务端主动推送的消息。读完本文,你将掌握 lws D-Bus 角色的构建配置、客户端连接建立、方法调用与回调处理的关键代码路径,并能独立复现、扩展这一示例。

示例定位:一个“跑在 lws 事件循环里”的 D-Bus 客户端

minimal-dbus-client位于 minimal-examples-lowlevel/dbus-client/minimal-dbus-client/,它演示的并不是一个常规的、自带dbus_connection_read_write_dispatch()循环的 D-Bus 客户端,而是把 D-Bus 连接对象的文件描述符监听(watcher)与超时(timeout)管理全部交给 lws 事件循环。这样一来,D-Bus 交互可以和 lws 的其它能力(WebSocket、HTTP、secure streams 等)共存于同一个服务循环中,这是 lws 把 D-Bus 作为一个“role(角色)”接入自身的根本目的。

从仓库目录结构看,D-Bus 角色在 lws 中是一等公民:

  • 角色实现位于 lib/io/dbus/dbus.c,其中注册了名为dbus的lws_role_ops(见该文件的role_ops_dbus);
  • 对外 API 头文件为 include/libwebsockets/lws-dbus.h;
  • 构建与依赖说明见 lib/io/dbus/README.md。

构建前提:启用LWS_ROLE_DBUS并配置 libdbus 头文件路径

由于 lws 的 D-Bus 支持依赖系统 libdbus(dbus-1),且 libdbus 的头文件目录并不规范(存在“架构相关”的第二个 include 目录),因此无论构建 lws 库本身还是编译这个示例,都需要额外指定 include 路径。相关细节完整记录在 lib/io/dbus/README.md:

  • 发行版依赖包:Fedora 为dbus-devel,Debian / Ubuntu 为libdbus-1-dev;
  • D-Bus 需要两个include 路径:LWS_DBUS_INCLUDE1通常可被 CMake 自动探测(Fedora 与 Debian/Ubuntu 一般为/usr/include/dbus-1.0);LWS_DBUS_INCLUDE2是架构相关目录,需要显式指定;
  • 库名默认是dbus-1,可通过LWS_DBUS_LIB强制覆盖,用于交叉编译或使用自建 dbus。

构建 lws 库本体时的 CMake 命令(来自 lib/io/dbus/README.md):

# Fedora $ cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" # Ubuntu $ cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"

而CMake 选项中LWS_ROLE_DBUS默认关闭(见仓库根 CMakeLists.txt 的option(LWS_ROLE_DBUS "Compile with support for DBUS" OFF)),构建前必须显式开启。同时 lib/io/CMakeLists.txt 中仅在LWS_ROLE_DBUS AND NOT LWS_PLAT_FREERTOS时才会把 dbus 角色纳入编译,因此该角色不支持 FreeRTOS 平台。

交叉编译与自建 dbus 时的覆盖方式

若你自建了 libdbus 并安装到/usr/local,可以像这样全部强制指定(沿用 lib/io/dbus/README.md 中的示例):

cmake .. -DLWS_ROLE_DBUS=1 \ -DLWS_DBUS_INCLUDE1="/usr/local/include/dbus-1.0" \ -DLWS_DBUS_INCLUDE2="/usr/local/lib/dbus-1.0/include" \ -DLWS_DBUS_LIB="/usr/local/lib/libdbus-1.so"

如果系统目录里已存在可用的 dbus 库,运行时还需借助LD_PRELOAD让加载器优先使用你的版本,例如:

LD_PRELOAD=/usr/local/lib/libdbus-1.so.3.24.0 myapp

编译示例:Fedora / Ubuntu 两条命令即可

minimal-dbus-client的 CMake 工程文件(minimal-dbus-client/CMakeLists.txt)在构建时会通过require_lws_config(LWS_ROLE_DBUS 1 ...)和require_lws_config(LWS_WITH_CLIENT 1 ...)检查 lws 库的配置,然后借助pkg_check_modules(PC_DBUS1 dbus-1 QUIET)自动收集 dbus 的 include 与链接参数。若 lws 本体尚未配置,示例 CMake 无法找到 libdbus 的头文件,因此编译该示例前请先按上文完成 lws 库的构建。

示例自身在 Fedora / Ubuntu 上的构建命令(原文命令,见 示例 README):

# Fedora $ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" $ make # Ubuntu $ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include" $ make

需要说明:这两个命令假设LWS_DBUS_INCLUDE1可以被 CMake 自动探测;若探测失败或需要交叉编译,可参照上文把LWS_DBUS_INCLUDE1与LWS_DBUS_LIB一并显式传入。

命令行参数:-d <loglevel>

该示例只暴露一个用户可用的命令行选项(来自 示例 README):

命令行选项含义
-d <loglevel>十进制日志详细度,例如-d15

注意入口逻辑(minimal-dbus-client.c):main()中若没有任何参数、或显式传入--help,会打印帮助信息后退出,因此至少要带一个-d参数运行。-d及其它内置选项(如--help)由lws_cmdline_option_handle_builtin()统一解析(minimal-dbus-client.c),-d15即打印全部日志级别。

端到端行为:连接服务端 → 异步调用 Echo → 持续接收主动消息

按照 示例 README 的描述,客户端的完整行为是:

  1. 连接到 lws 的minimal-dbus-server示例(该服务端监听默认的 abstract unix domain socket 路径);
  2. 调用服务端的Echo方法,传入"Hello!",然后立即返回事件循环,不阻塞等待;
  3. 当回复到达时,打印返回的消息;
  4. 之后持续静默接收服务端发来的非请求消息(unsolicited messages,即信号),直到用户按Ctrl-C关闭。

README 中给出的真实运行输出:

$ ./lws-minimal-dbus-client ctx [2018/10/05 06:08:31:4901] NOTICE: pending_call_notify [2018/10/05 06:08:31:4929] USER: pending_call_notify: received 'Hello!' ^C[2018/10/05 06:09:22:4409] NOTICE: destroy_dbus_client_conn [2018/10/05 06:09:22:4691] NOTICE: Exiting cleanly ...

从输出可以看到两个关键生命周期节点:pending_call_notify打印异步回复'Hello!';Ctrl-C触发sigint_handler调用lws_default_loop_exit(context)退出事件循环(minimal-dbus-client.c),随后走destroy_dbus_client_conn清理并 "Exiting cleanly"。

与服务端示例的配合

客户端连接的目标地址定义在 minimal-dbus-client.c:

#define THIS_INTERFACE "org.libwebsockets.test" #define THIS_OBJECT "/org/libwebsockets/test" #define THIS_BUSNAME "org.libwebsockets.test" #define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test"

它对应的服务端是 minimal-examples-lowlevel/dbus-server/minimal-dbus-server/main.c,二者共享相同的 interface / object / busname / 监听路径常量。服务端通过 introspection XML 暴露了Ping、Echo、EmitSignal、Quit等方法和OnEmitSignal信号(minimal-dbus-server/main.c),客户端调用的Echo方法在服务端实现为“把收到的字符串原样返回”(minimal-dbus-server/main.c)。因此务必先启动 minimal-dbus-server,再启动本客户端。

源码拆解:异步调用的四个关键环节

示例核心代码集中在 minimal-dbus-client.c,整个异步流程可拆成四个环节。

1. 建立连接:create_dbus_client_conn()

ctx->conn = dbus_connection_open_private(ads, &err); ... dbus_connection_set_exit_on_disconnect(ctx->conn, 0); if (!dbus_connection_add_filter(ctx->conn, client_message_handler, ctx, NULL)) ... if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) ...

(minimal-dbus-client.c)

三个要点:

  • 使用dbus_connection_open_private()建立私有连接,传入的ads即"unix:abstract=org.libwebsockets.test",这是与服务端相同的 abstract unix domain socket 地址;
  • dbus_connection_set_exit_on_disconnect(conn, 0)保证服务端断开时进程不会直接退出;
  • 通过dbus_connection_add_filter()注册client_message_handler,用于接收服务端主动推来的消息(例如OnEmitSignal信号或后续示例中的 ws proxy 状态消息);
  • 最关键的一步是lws_dbus_connection_setup(ctx, ctx->conn, cb_closing),它把 D-Bus 连接“绑定”到 lws 的 watcher 与 timeout 机制上,并登记关闭回调cb_closing。这正是本示例与“普通 libdbus 用法”的分水岭。

2. 发起方法调用:remote_method_call()

msg = dbus_message_new_method_call( /* dest */ THIS_BUSNAME, /* object-path */ THIS_OBJECT, /* interface */ THIS_INTERFACE, /* method */ "Echo"); dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload, DBUS_TYPE_INVALID); dbus_connection_send_with_reply(ctx->conn, msg, &ctx->pc, DBUS_TIMEOUT_USE_DEFAULT); dbus_pending_call_set_notify(ctx->pc, pending_call_notify, ctx, NULL);

(minimal-dbus-client.c)

调用链完全沿用 libdbus 标准 API:dbus_message_new_method_call()构造目标为org.libwebsockets.test上/org/libwebsockets/test对象的Echo方法;dbus_message_append_args()附加一个字符串参数"Hello!";dbus_connection_send_with_reply()发出调用并取出DBusPendingCall(存入ctx->pc);随后用dbus_pending_call_set_notify()挂上完成回调pending_call_notify后函数立即返回,调用方不等待回复——异步语义由此实现。

3. 处理回复:pending_call_notify()

if (!dbus_pending_call_get_completed(pending)) { lwsl_err("%s: timed out waiting for reply\n", __func__); goto bail; } msg = dbus_pending_call_steal_reply(pending); ... dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload, DBUS_TYPE_INVALID); lwsl_user("%s: received '%s'\n", __func__, payload);

(minimal-dbus-client.c)

回调先通过dbus_pending_call_get_completed()区分“回复已到”与“超时未回”两种情况;回复到达后用dbus_pending_call_steal_reply()取走回复消息并解析字符串,最终以lwsl_user打印received 'Hello!'(即 README 输出中的那一行)。注意回调末尾对pending做了dbus_pending_call_unref(),释放资源。

4. 关闭清理:cb_closing()与destroy_dbus_client_conn()

dbus_connection_remove_filter(ctx->conn, client_message_handler, ctx); dbus_connection_close(ctx->conn); dbus_connection_unref(ctx->conn); free(ctx);

(minimal-dbus-client.c)

cb_closing是注册进lws_dbus_connection_setup()的回调:当 lws 发现该连接的 fd 发生POLLHUP时会调用它,此时连接已事实上失效,回调负责移除 filter、关闭并释放连接与ctx(minimal-dbus-client.c)。main()退出路径上还会显式调用destroy_dbus_client_conn()并执行dbus_shutdown(),以保证 valgrind 下内存干净(minimal-dbus-client.c)。

底层原理:lws 如何“接管” D-Bus 的 fd 与超时

客户端的关键一行lws_dbus_connection_setup()实现在 lib/io/dbus/dbus.c:它等价于 libdbus 的dbus_connection_setup_with_g_main(),但替换为 lws 事件循环,具体做了三件事:

  1. dbus_connection_set_watch_functions()注册lws_dbus_add_watch/lws_dbus_remove_watch/lws_dbus_toggle_watch;
  2. dbus_connection_set_timeout_functions()注册lws_dbus_add_timeout/lws_dbus_remove_timeout/lws_dbus_toggle_timeout;
  3. 设置 dispatch 状态回调handle_dispatch_status。

shadow wsi:为 dbus fd 创建“影子 wsi”

dbus.c 头部的注释(lib/io/dbus/dbus.c)揭示了核心设计:dbus 不会告知其 fd 的生命周期,只会在需要等待POLLIN/POLLOUT时请求 watcher。因此 lws 为任何处于等待状态的 dbus fd 创建一个轻量的“shadow wsi(影子 wsi)”;当 dbus 不再等待时销毁该 wsi——这与其关闭路径不可区分,若之后又重新等待,就再建一个新的 shadow wsi。

__lws_shadow_wsi()(lib/io/dbus/dbus.c)在 fd 上查找或新建 wsi,将其sockfd指向 dbus fd、标记io->shadow = 1、并把opaque_parent_data指向lws_dbus_ctx;rops_handle_POLLIN_dbus()(lib/io/dbus/dbus.c)则在事件到达时把POLLIN/POLLOUT翻译成DBUS_WATCH_READABLE/WRITABLE调用dbus_watch_handle(),随后循环执行dbus_connection_dispatch()派发消息。

超时处理:接入 lws 的 sul 定时器

libdbus 的超时需求通过lws_dbus_add_timeout()转成 lws 的lws_sorted_usec_list_t(sul)定时任务(lib/io/dbus/dbus.c),到期后由lws_dbus_sul_cb()调用dbus_timeout_handle()派发(lib/io/dbus/dbus.c)。这解释了示例中pending_call_notify的超时分支如何被驱动——即便回复永远不来,事件循环也会在超时点唤醒回调。

API 速查:lws_dbus_ctx结构体

在需要自行实现客户端/服务端时,include/libwebsockets/lws-dbus.h 提供了两个核心 API 与一个上下文结构体:

API / 字段说明
lws_dbus_connection_setup(ctx, conn, cb_closing)把已有DBusConnection绑定到 lws 事件循环(watcher + timeout),并登记连接失效回调
lws_dbus_server_listen(ctx, ads, err, new_conn)创建DBusServer并绑定到 lws 事件循环(服务端侧用)
struct lws_dbus_ctx保存vh(逻辑绑定的 vhost)、tsi(线程服务索引)、conn/dbs、DBusWatch *w[4]、DBusPendingCall *pc、hup/timeouts标志与cb_closing回调(lws-dbus.h)

该头文件被刻意设计为必须显式包含:#include <libwebsockets/lws-dbus.h>会引入<dbus/dbus.h>,从而让非 D-Bus 用户无需处理 libdbus 的头文件路径问题(详见 lib/io/dbus/README.md 的 “Lws dbus api exports” 一节)。

配套进阶:minimal-dbus-ws-proxy-testclient

同目录下还有第二个客户端示例 minimal-dbus-ws-proxy-testclient,它演示了更复杂的用法:通过 D-Bus 请求一个“ws 代理”应用连接到wss://libwebsockets.org/的 mirror 协议,再把浏览器上绘制矢量产生的 ASCII 数据包经 D-Bus 代理回传(见其 README)。该示例与minimal-dbus-client共享同一套连接建立与异步回调骨架,但额外展示了:

  • 通过dbus_message_is_signal()区分不同类型的Status信号与Receive信号(minimal-dbus-ws-proxy-testclient.c);
  • 在收到回复后继续向代理发出Send方法调用(minimal-dbus-ws-proxy-testclient.c)。

它对应的服务端是minimal-dbus-ws-proxy(位于 minimal-examples-lowlevel/dbus-server/ 下,监听unix:abstract=org.libwebsockets.wsclientproxy),可用于验证本文所述的客户端骨架在“方法调用 + 信号接收”混合场景下的扩展方式。

常见问题与注意事项

  • LWS_ROLE_DBUS默认关闭:构建 lws 库时必须显式开启,否则示例构建会因require_lws_config(LWS_ROLE_DBUS 1 ...)检查失败而无法生成。
  • include 路径必须给全:LWS_DBUS_INCLUDE2是架构相关目录,Debian/Ubuntu 下通常无法自动探测,务必按本文命令显式传入;交叉编译时LWS_DBUS_INCLUDE1与LWS_DBUS_LIB也建议显式指定。
  • 先启动服务端:客户端连接的是服务端的 abstract socketorg.libwebsockets.test,服务端未运行时dbus_connection_open_private()会失败并打印Failed to connect。
  • 至少要带一个参数运行:./lws-minimal-dbus-client -d15,否则程序打印帮助后退出。
  • valgrind 已知噪音:libdbus 的 abstract unix domain socket 与 libc 的getgrouplist()(经 nss)存在一处约 6KiB 的 "Still reachable" 报告,这是 2004 年以来的 libdbus 已知问题,与本示例无关(详见 lib/io/dbus/README.md)。

小结

minimal-dbus-client是理解“lws + libdbus 集成”的最佳入门样例:它以不到 300 行的源码完整展示了连接建立、lws_dbus_connection_setup()事件循环绑定、异步方法调用、回复回调与关闭清理的全链路,并可直接与minimal-dbus-server配对运行验证。把它与 lib/io/dbus/dbus.c 的 shadow wsi 机制和 include/libwebsockets/lws-dbus.h 的 API 定义对照阅读,即可掌握在 lws 单事件循环中整合任意 D-Bus 服务的基础能力。

  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载
上一篇:如何用Maqetta Designer快速创建专业级Web界面原型?新手入门教程
下一篇:center-randomize路线图:未来功能规划与版本迭代计划

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

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

Jetson Nano MIPI CSI摄像头全栈调试指南:从设备树到Argus API

1. 为什么非得用板载MIPI CSI接口——从“能跑通”到“真可用”的分水岭Jetson Nano上接摄像头&#xff0c;新手第一反应往往是USB免驱即插即用&#xff1a;插上一个罗技C920&#xff0c;cv2.VideoCapture(0)一调&#xff0c;画面就出来了。看起来很美&#xff0c;但只要项目往…

作者头像 李华
网站建设 2026/10/13 1:54:20

从零到第一个网页跑通:Scrapling 网页抓取环境配置指南

从零到第一个网页跑通&#xff1a;Scrapling 网页抓取环境配置指南 【免费下载链接】Scrapling &#x1f577;️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Dont be shy, join here: https://discord.gg/EMgG…

作者头像 李华
网站建设 2026/10/13 1:52:16

在 ASUS 主板上让 FanControl 拿回 CPU 风扇控制权,四步搞定

在 ASUS 主板上让 FanControl 拿回 CPU 风扇控制权&#xff0c;四步搞定 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trend…

作者头像 李华
网站建设 2026/10/13 1:51:05

koa2-note 实战:使用 koa-static 中间件为 Koa2 应用托管静态资源

文档教程后端 【免费下载链接】koa2-note 《Koa2进阶学习笔记》已完结&#x1f384;&#x1f384;&#x1f384; 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ko/koa2-note 点击查看 免费下载 本文基于《Koa2进阶学习笔记》开源仓库中的 note/static/middleware.md …

作者头像 李华
网站建设 2026/10/13 1:49:46

JMeter CSV参数化核心原理与实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华