linux 进程间通信 dbus-glib实例详解二(上) 消息和消息总线(附代码)

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 进程间通信 dbus-glib实例详解二(上) 消息和消息总线(附代码)相关的知识,希望对你有一定的参考价值。

linux 进程间通信 dbus-glib【实例】详解一(附代码)(d-feet工具使用)

linux 进程间通信 dbus-glib【实例】详解二(上) 消息和消息总线(附代码)

文章目录

dbus实例讲解(二上):消息和消息总线

应用程序A和消息总线连接,这个连接获取了一个众所周知的公共名(记作连接A)。应用程序A中有对象A1提供了接口I1,接口I1有方法M1。 应用程序B和消息总线连接,要求调用连接A上对象A1的接口I1的方法M1。

在上一讲的加法例子中,上面这段话可以实例化为:

应用程序example-service和会话总线连接。这个连接获取了一个众所周知的公共名“org.fmddlmyy.Test”。
应用程序example-servic中有对象“/TestObj”提供了接口“org.fmddlmyy.Test.Basic”,接口“org.fmddlmyy.Test.Basic”有方法“Add”。
应用程序d-feet和会话总线连接,要求调用连接“org.fmddlmyy.Test”上对象“/TestObj”的接口“org.fmddlmyy.Test.Basic”的方法“Add”。

应用程序B调用应用程序A的方法,其实就是应用程序B向应用程序A发送了一个类型为“method_call”的消息。 应用程序A通过一个类型为“method_return”的消息将返回值发给应用程序B。我们简单介绍一下D-Bus总线上的消息。

1、D-Bus的消息

上一讲说过最基本的D-Bus协议是一对一的通信协议。与直接使用socket不同,D-Bus是面向消息的协议。 D-Bus的所有功能都是通过在连接上流动的消息完成的。

1.1、消息类型

D-Bus有四种类型的消息:

  • method_call 方法调用
  • method_return 方法返回
  • error 错误
  • signal 信号

前面介绍的远程方法调用就用到了method_call和method_return消息。顾名思义,在发生错误时会产生error消息。 如果把method_call看作打电话,那么signal消息就是来电了。后面还会详细讨论。

1.2、dbus-send和dbus-monitor

dbus提供了两个小工具:dbus-send和dbus-monitor。我们可以用dbus-send发送消息。用dbus-monitor监视总线上流动的消息。 让我们通过dbus-send发送消息来调用前面的Add方法,这时dbus-send充当了应用程序B。用dbus-monitor观察调用过程中的消息。

启动example-service:

$ ./example-service 

在另一个控制台启动dbus-monitor:

$ dbus-monitor

dbus-monitor默认监视会话总线。执行:

$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.fmddlmyy.Test.Basic.Add int32:100 int32:999

输出为:

method return time=1642572373.469364 sender=:1.124 -> destination=:1.135 serial=118 reply_serial=2
   int32 1099

dbus-monitor的相关输出包括:

(我的ubuntu输出)

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.22"
   string ""
   string ":1.22"
method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello
method call sender=:1.22 -> dest=org.fmddlmyy.Test path=/TestObj; interface=org.fmddlmyy.Test.Basic; member=Add
   int32 100
   int32 999
method return sender=:1.21 -> dest=:1.22 reply_serial=2
   int32 1099
signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.22"
   string ":1.22"
   string ""

:1.22就是dbus-send在本次调用中与会话总线所建立连接的唯一名。:1.21是连接“org.fmddlmyy.Test”的唯一名。 在以上输出中我们可以看到:1.22向“org.fmddlmyy.Test”发送method_call消息,调用Add方法。 :1.21通过method_return消息将调用结果发回:1.22。其它输出信息会在以后说明。

dbus-send的详细用法可以参阅手册。调用远程方法的一般形式是:

$ dbus-send [--system | --session] --type=method_call --print-reply --dest=连接名 对象路径 接口名.方法名 参数类型:参数值 参数类型:参数值

dbus-send支持的参数类型包括:string, int32, uint32, double, byte, boolean。

2、消息总线(dbus-daemon?)的方法和信号

2.1、概述

消息总线(dbus-daemon?)是一个特殊的应用,它可以在与它连接的应用之间传递消息。 可以把消息总线看作一台路由器。正是通过消息总线,D-Bus才在一对一的通信协议基础上实现了多对一和一对多的通信。

消息总线虽然有特殊的转发功能,但消息总线也还是一个应用。 其它应用与消息总线的通信也是通过1.1节的基本消息类型完成的。作为一个应用,消息总线也提供了自己的接口,包括方法和信号。

我们可以通过向连接“org.freedesktop.DBus ”上对象“/”发送消息来调用消息总线提供的方法。 事实上,应用程序正是通过这些方法连接到消息总线上的其它应用,完成请求公共名等工作的。

2.2、清单

消息总线对象支持第一讲中提到的标准接口"org.freedesktop.DBus.Introspectable", 我们可以调用org.freedesktop.DBus.Introspectable.Introspect方法查看消息总线对象支持的接口。例如:

$ dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect
(arm摄像头上用:[root@RV1126_RV1109:~]# dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect

输出为:

(arm摄像头)

[root@RV1126_RV1109:~]# dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect
method return time=1642579767.439290 sender=org.freedesktop.DBus -> destination=:1.17 serial=3 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus">
    <method name="Hello">
      <arg direction="out" type="s"/>
    </method>
    <method name="RequestName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="ReleaseName">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="StartServiceByName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="UpdateActivationEnvironment">
      <arg direction="in" type="ass"/>
    </method>
    <method name="NameHasOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="b"/>
    </method>
    <method name="ListNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="ListActivatableNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="AddMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="RemoveMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="GetNameOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="s"/>
    </method>
    <method name="ListQueuedOwners">
      <arg direction="in" type="s"/>
      <arg direction="out" type="as"/>
    </method>
    <method name="GetConnectionUnixUser">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetConnectionUnixProcessID">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetAdtAuditSessionData">
      <arg direction="in" type="s"/>
      <arg direction="out" type="ay"/>
    </method>
    <method name="GetConnectionSELinuxSecurityContext">
      <arg direction="in" type="s"/>
      <arg direction="out" type="ay"/>
    </method>
    <method name="ReloadConfig">
    </method>
    <method name="GetId">
      <arg direction="out" type="s"/>
    </method>
    <method name="GetConnectionCredentials">
      <arg direction="in" type="s"/>
      <arg direction="out" type="asv"/>
    </method>
    <property name="Features" type="as" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
    </property>
    <property name="Interfaces" type="as" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
    </property>
    <signal name="NameOwnerChanged">
      <arg type="s"/>
      <arg type="s"/>
      <arg type="s"/>
    </signal>
    <signal name="NameLost">
      <arg type="s"/>
    </signal>
    <signal name="NameAcquired">
      <arg type="s"/>
    </signal>
  </interface>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Peer">
    <method name="GetMachineId">
      <arg direction="out" type="s"/>
    </method>
    <method name="Ping">
    </method>
  </interface>
  <node name="org/freedesktop/DBus"/>
</node>
"
[root@RV1126_RV1109:~]# 

从输出可以看到会话总线对象支持标准接口“org.freedesktop.DBus.Introspectable”和接口“org.freedesktop.DBus”。 接口“org.freedesktop.DBus”有16个方法和3个信号。下表列出了“org.freedesktop.DBus”的12个方法的简要说明:

(这个表日后可能要做搜索用,附个链接:http://www.fmddlmyy.cn/mytext.html)

接口“org.freedesktop.DBus”的3个信号是:

2.3、练习

让我们来试试消息总线提供的方法。

2.3.1、从ListName到d-feet的基本逻辑

用dbus-send调用:

$ dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames
(ubuntu里)
输出为:(略)
(arm摄像头里)
输出为:

[root@RV1126_RV1109:~]# dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames
method return time=1642596554.334145 sender=org.freedesktop.DBus -> destination=:1.13 serial=3 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string ":1.7"
      string ":1.8"
      string "rockchip.netserver"
      string "org.freedesktop.Avahi"
      string ":1.13"
      string ":1.0"
      string ":1.1"
      string ":1.2"
      string ":1.3"
      string "rockchip.system"
      string "net.connman"
      string ":1.4"
      string "fi.w1.wpa_supplicant1"
      string "rockchip.dbserver"
      string ":1.5"
      string "rockchip.StorageManager"
      string ":1.6"
   ]
[root@RV1126_RV1109:~]# 

这是会话总线当前已连接的连接名。在d-feet窗口的左侧窗口显示的就是ListNames返回的连接名。
(ubuntu d-feet软件)

聪明的读者也许已经想到使用消息总线的“org.freedesktop.DBus.ListNames”方法和各连接的“org.freedesktop.DBus.Introspectable.Introspect”, 我们就可以像d-feet一样查看总线上所有连接的所有对象的所有接口的所有方法和信号。

你的想法很好。但有一个问题,我们必须对连接中的对象调用“org.freedesktop.DBus.Introspectable.Introspect”方法。 ListNames只列出了连接名,我们怎么获取连接中的对象路径呢?

答案很简单,如果我们不知道对象路径就从根目录开始吧。连接中的对象是按照树型结构组织的。我们遍历连接的对象树就可以找到所有的对象。 调用对象的“org.freedesktop.DBus.Introspectable.Introspect”方法就可以查看对象的所有接口的所有方法和信号。

(如在arm摄像头里查看"rockchip.netserver"所有方法)

[root@RV1126_RV1109:~]# dbus-send --system --type=method_call --print-reply --dest=rockchip.netserver / org.freedesktop.DBus.Introspectable.Introspect
method return time=1642598000.976271 sender=:1.4 -> destination=:1.15 serial=19 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
	<interface name="org.freedesktop.DBus.Introspectable">
		<method name="Introspect">
			<arg name="xml" type="s" direction="out"/>
		</method>
	</interface>
	<interface name="rockchip.netserver.server">
		<method name="GetService">
			<arg name="type" type="s" direction="in"/>
			<arg name="json" type="s" direction="out"/>
		</method>
		<method name="GetNetworkIP">
			<arg name="type" type="s" direction="in"/>
			<arg name="json" type="s" direction="out"/>
		</method>
		<method name="GetConfig">
			<arg name="service" type="s" direction="in"/>
			<arg name="json" type="s" direction="out"/>
		</method>
		<method name="ScanWifi">
		</method>
		<signal name="PowerChanged">
			<arg name="name" type="s"/>
			<arg name="value" type="v"/>
		</signal>
	</interface>
<node name="net"/>
</node>"
[root@RV1126_RV1109:~]# 

例如:假设我们不知道连接"org.fmddlmyy.Test"里有什么对象,我们可以对根对象"/"执行:

$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test / org.freedesktop.DBus.Introspectable.Introspect

(当然,要先运行作者的example-service服务)

输出为:

[arnold@ubuntu ~/Arnold_test/20220119_test_dbus_demo4/hello-dbus3-0.1/src]1$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test / org.freedesktop.DBus.Introspectable.Introspect
method return time=1642599939.774764 sender=:1.89 -> destination=:1.90 serial=8 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="TestObj"/>
</node>
"
[arnold@ubuntu ~/Arnold_test/20220119_test_dbus_demo4/hello-dbus3-0.1/src]2$ 

“org.fmddlmyy.Test"的对象树的根节点只有一个子节点"TestObj”,再查看"/TestObj":

$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.freedesktop.DBus.Introspectable.Introspect

[arnold@ubuntu ~/Arnold_test/20220119_test_dbus_demo4/hello-dbus3-0.1/src]2$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.freedesktop.DBus.Introspectable.Introspect
method return time=1642601175.664496 sender=:1.89 -> destination=:1.91 serial=9 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
      <arg name="interface" direction="in" type="s"/>
      <arg name="propname" direction="in" type="s"/>
      <arg name="value" direction="out" type="v"/>
    </method>
    <method name="Set">
      <arg name="interface" direction="in" type="s"/>
      <arg name="propname" direction="in" type="s"/>
      <arg name="value" direction="in" type="v"/>
    </method>
    <method name="GetAll">
      <arg name="interface" direction="in" type="s"/>
      <arg name="props" direction="out" type="asv"/>
    </method>
  </interface>
  <interface name="org.fmddlmyy.Test.Basic">
    <method name="Add">
      <arg name="arg0" type="i" direction="in"/>
      <arg name="arg1" type="i" direction="in"/>
      <arg name="ret" type="i" direction="out"/>
    </method>
  </interface>
</node>
"
[arnold@ubuntu ~/Arnold_test/20220119_test_dbus_demo4/hello-dbus3-0.1/src]3$ 

作为一个练习,让我们来查看系统总线的上的bluez接口。执行:(我发现我没bluez这个接口😂)

那就在arm摄像头上看netserver这个接口吧

$ dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames

输出为:

method return time=1642603380.135709 sender=org.freedesktop.DBus -> destination=:1.22 serial=3 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string ":1.7"
      string ":1.8"
      string "rockchip.netserver"
      string "org.freedesktop.Avahi"
      string ":1.22"
      string ":1.0"
      string ":1.1"
      string ":1.2"
      string ":1.3"
      string "rockchip.system"
      string "net.connman"
      string ":1.4"
      string "fi.w1.wpa_supplicant1"
      string "rockchip.dbserver"
      string ":1.5"
      string "rockchip.StorageManager"
      string ":1.6"
   ]

我们看到连接"rockchip.netserver"。查看它的根对象:

$ dbus-send --system --type=method_call --print-reply --dest=rockchip.netserver / org.freedesktop.DBus.Introspectable.Introspect

输出为:

method return time=1642603716.790027 sender=:1.4 -> destination=:1.24 serial=27 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
	<interface name="org.freedesktop.DBus.Introspectable">
		<method name="Introspect">
			<arg name="xml" type="s" direction="out"/>
		</method>
	</interface>
	<interface name="rockchip.netserver.server">
		<method name="GetService">
			<arg name="type" type="s" direction="in"/>
			<arg name="json" type="s" direction="out"/>
		</method>
		<method name="GetNetworkIP">
			<arg name="type" type="s" direction="in"/>
			<arg name="json" type="s" direction="out"/>
		</method>
		<method name="GetConfig">
			<arg name="service" type="s" direction="in"/>
			<arg name="json" type="s" direction="out"/>
		</method>
		<method name="ScanWifi">
		</method>
		<signal name="PowerChanged">
			<arg name="name" type="s"/>
			<arg name="value" type="v"/>
		</signal>
	</interface>
	<node name="net"/>
</node>"

继续找下一层net
dbus-send --system --type=method_call --print-reply --dest=rockchip.netserver /net org.freedesktop.DBus.Introspectable.Introspect

method return time=1642604470.802829 sender=:1.4 -> destination=:1.25 serial=28 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="connman"/>
</node>
"

继续找下一层connman
dbus-send --system --type=method_call --print-reply --dest=rockchip.netserver /net/connman org.freedesktop.DBus.Introspectable.Introspect

method return time=1642604607.622167 sender=:1.4 -> destination=:1.26 serial=29 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="connmanctl629"/>
</node>
"

继续:
dbus-send --system --type=method_call --print-reply --dest=rockchip.netserver /net/connman/connmanctl629 org.freedesktop.DBus.Introspectabl e.Introspect

method return time=1642605626.176534 sender=:1.4 -> destination=:1.27 serial=30 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
	<interface name="org.freedesktop.DBus.Introspectable">
		<method name="Introspect">
			<arg name="xml" type="s" direction="out"/>
		</method>
	</interface>
	<interface name="net.connman.Agent">
		<method name="Release">
		</method>
		<method name="Cancel">
		</method>
		<method name="RequestBrowser">
			<arg name="service" type="o" direction="in"/>
			<arg name="url" type="s" direction="in"/>
		</method>
		<method name="ReportError">
			<arg name="service" type="o" direction="in"/>
			<arg name="error" type="s" direction="in"/>
		</method>
		<method name="ReportPeerError">
			<arg name="peer" type="o" direction="in"/>
			<arg name="error" type="s" direction="in"/>
		</method>
		<method name="RequestInput">
			<arg name="service" type="o" direction="in"/>
			<arg name="fields" type="asv" direction="in"/>
			<arg name="fields" type="asv" direction="out"/>
		</method>
		<method name="RequestPeerAuthorization">
			<arg name="peer" type="o" direction="in"/>
			<arg name="fields" type="asv" direction="in"/>
			<arg name="fields" type="asv" direction="out"/>
		</method>
	</interface>
</node>
"

调用接口net.connman.Agent里的Cancel方法:(终于调成功了,发现其他几个都调不成功,是不是参数没传对?)

dbus-send --system --type=method_call --print-reply --dest=rockchip.netserver /net/connman/connmanctl629 net.connman.Agent.Cancel

method return time=1642605903.695343 sender=:1.4 -> destination=:1.29 serial=33 reply_serial=2

作者:我们看到了对象"/org/bluez"的所有接口。对象"/org/bluez"还有子节点"service_audio"、“service_input”、“service_network"和"service_serial”。 必要时我们可以接着查下去。d-feet的基本逻辑就是这样。 后面我们会自己实现一个dteeth。dteeth是命令行程序,可以遍历指定连接的对象树,列出所有对象的所有接口的方法和信号。

参考文章:dbus实例讲解(二上):消息和消息总线

以上是关于linux 进程间通信 dbus-glib实例详解二(上) 消息和消息总线(附代码)的主要内容,如果未能解决你的问题,请参考以下文章

linux 进程间通信 dbus-glib实例详解二(下) 消息和消息总线(ListActivatableNames和服务器的自动启动)(附代码)

linux 进程间通信 dbus-glib实例详解三(下) 数据类型和dteeth(类型签名type域)(层级结构:服务Service --> Node(对象object) 等 )(附代码)

linux 进程间通信 dbus-glib实例详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)

linux 进程间通信 dbus-glib实例详解一(附代码)(d-feet工具使用)

详解linux进程间通信-信号

[OS-Linux]详解Linux的进程间通信1------管道