D-BUS、GDBUS简述
D-BUS、GDBUS简述D-BUS、GDBUS简述
D-BUS简述
reference :
https://blog.csdn.net/f110300641/article/details/106823611,
https://dbus.freedesktop.org/doc/dbus-specification.html,
Linux进程间通信:dbus的使用(2)—— D-Bus介绍及signal、method测试例程_dbus signal-CSDN博客
D-BUS是一种低开销、易于使用的进程间通信系统(IPC)。
消息总线
[*]消息总线分为:
[*]会话总线(session bus)
[*]系统总线(system bus)
特性Session BusSystem Bus启动时机用户登录时系统启动时(由 systemd 管理)权限用户级权限root 权限配置文件session.conf,用户级服务描述文件system.conf,系统服务描述文件典型应用桌面应用集成(如通知系统)硬件事件(如 USB 插入)、系统服务通信地址固定性动态地址(环境变量指定)固定地址(/var/run/dbus/system_bus_socket)
[*]dbus-daemon(消息总线守护进程 )
[*]dbus-daemon与session bus是一一对应的,每个活跃的用户会话(Session)都拥有一个独立的 dbus-daemon 进程,专门负责管理该会话内的所有消息路由和服务注册。二者是同一实体的逻辑与物理表现。
[*]此进程的生命周期与用户会话绑定:用户登录时创建,用户注销时终止。
[*]system bus同样有一个对应的dbus-daemon。
消息
[*]dbus中的消息有四种类型:
[*]method call
[*]method return
[*]signal
[*]error
[*]signal与method的区别:
[*]signal是一对多,method是一对一
[*]signal是单向的,不返回。method消息发出后,发出方会收到返回消息。
对象路径、接口
[*]连接(Bus name)标识与会话总线的连接,表示一个dbus服务
[*]对象路径(Object Path)唯一标识服务中的对象实例(类似文件路径),标识资源的路径。命名规则:采用斜杠分隔的层次结构,如 /org/kde/Device1。
[*]接口名称(Interface Name),定义对象的能力契约,包含方法、信号和属性(类似抽象类)。可以被不同的对象实现。采用反向域名格式(如 org.freedesktop.DBus.Properties),需在总线范围内唯一。接口与对象是解耦的,二者名称并不需要严格对应。
[*]方法和信号隶属于接口。
层级关系:
Bus name -》Path-》Interface-》Method/Signal
D-BUS使用
[*]安装:sudo apt install dbus
[*]包含头文件:#include
[*]signal
[*]发送方步骤:
[*]建立与会话总线的连接
[*]给该连接注册名称(可选)
[*]创建信号
[*]发送信号
[*]释放资源
[*]接收方步骤:
[*]建立与会话总线的连接
[*]给该连接注册名称(可选)
[*]添加监听匹配规则(需要发送方的接口名、信号名,以及路径(可选),如果给定路径,则只接收该路径对象的信号,否则接收所有实现该接口的对象信号)
[*]循环监听
[*]信号参数解析
[*]method_call & method_reply
[*]被调用方步骤:
[*]建立连接
[*]注册连接名称(必须)
[*]循环监听
[*]解析调用消息并处理
[*]读取消息中携带的参数,进行相应的处理
[*]创建返回消息,将处理结果传入返回消息变量中
[*]发送返回消息
[*]调用方步骤:
[*]建立连接
[*]注册连接名称
[*]创建调用消息(需要被调用方的连接名、对象路径、接口名和方法名),给定调用参数
[*]发送消息
[*]接收响应
信号发送接收示例
发送
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <unistd.h>
<p>int send_a_signal(char *sigvalue)
{
// 创建所需变量
DBusError err;
DBusConnection *connection;
DBusMessage *msg;
DBusMessageIter arg;
dbus_uint32_tserial = 0;
int ret;</p>
// 步骤1:建立与session bus的连接
dbus_error_init(&err);// 初始化错误结构体
connection = dbus_bus_get(DBUS_BUS_SESSION, &err );// 获取一个与session bus的连接
if(dbus_error_is_set(&err)){
fprintf(stderr, "ConnectionErr : %s\n", err.message);
dbus_error_free(&err);
}
if(connection == NULL)
return -1;
// 步骤2:给连接注册一个名字,这个步骤不是必须的if 1
ret = dbus_bus_request_name(connection, "org.example.SignalSource", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr,"Name Err :%s\n", err.message);
dbus_error_free(&err);
}
if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return -1;endif
// 步骤3:发送一个信号
// 创建message,message参数中包含这个信号的路径,接口,以及信号名
if((msg = dbus_message_new_signal("/org/example/SignalService/Sender", "org.example.Signal.Test", "Test"))== NULL){
fprintf(stderr, "MessageNULL\n");
return -1;
}
// 给这个messge具体的内容
dbus_message_iter_init_append(msg, &arg);
if(!dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &sigvalue)){
fprintf(stderr, "Out OfMemory!\n");
return -1;
}
// 步骤4: 将信号通过连接发送
if(!dbus_connection_send(connection, msg, &serial)){
fprintf(stderr, "Out of Memory!\n");
return -1;
}
dbus_connection_flush(connection);
printf("Signal Send\n");
// 步骤5: 释放资源。
dbus_message_unref(msg);
return 0;}
int main( int argc, char **argv){
send_a_signal("Hello,world!");
return 0;
}
部分函数详述
dbus_connection_send
dbus_bool_t dbus_connection_send(
DBusConnection *connection,// D-Bus 连接对象指针
DBusMessage *message, // 待发送的消息对象指针
dbus_uint32_t*serial // 返回消息序列号(可选)
);
[*]connection:
[*]类型:DBusConnection*
[*]作用:通过 dbus_bus_get(DBUS_BUS_SESSION/SYSTEM, ...) 获取的会话或系统总线连接对象
[*]message:
[*]类型:DBusMessage*
[*]作用:需发送的消息对象
[*]serial:
[*]类型:dbus_uint32_t,实质就是无符号32位整数
[*]作用:可选参数。若传入非 NULL 指针,函数会将消息的唯一序列号写入该地址,用于跟踪消息(如调试),该序列号唯一企鹅递增。该消息的值由dbus内部程序写入,使用者不该写入值。
接收
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <unistd.h>
<p>void listen_signal()
{
// 创建所需变量
DBusMessage *msg;
DBusMessageIter arg;
DBusConnection *connection;
DBusError err;
int ret;
char * sigvalue;</p>
// 步骤1:建立与session bus的连接
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr, "ConnectionError %s\n", err.message);
dbus_error_free(&err);
}
if(connection == NULL)
return;
// 步骤2:给连接注册一个名称,非必需但推荐
ret = dbus_bus_request_name(connection, "org.example.SignalReceiver", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr, "Name Error%s\n", err.message);
dbus_error_free(&err);
}
if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return;
// 步骤3:添加监听匹配规则
dbus_bus_add_match(connection, "type='signal', interface='org.example.Signal.Test', path='/org/example/SignalService/Sender'", &err);
dbus_connection_flush(connection);// 立即将规则发送给 dbus-daemon,确保实时生效
if(dbus_error_is_set(&err)){
fprintf(stderr, "Match Error%s\n", err.message);
dbus_error_free(&err);
}
// 步骤4:在循环中监听,每隔开1秒,就去试图自己的连接中获取这个信号。这里给出的是从连接中获取任何消息的方式,所以获取后去检查一下这个消息是否我们期望的信号,并获取内容。我们也可以通过这个方式来获取method call消息。
while(1){
dbus_connection_read_write(connection, 0);// 非阻塞读写
msg = dbus_connection_pop_message(connection);
if(msg == NULL){
sleep(1);
continue;
}
if(dbus_message_is_signal(msg, "org.example.Signal.Test", "Test")){
if(!dbus_message_iter_init(msg, &arg))
fprintf(stderr, "MessageHas no Param");
else if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
fprintf(stderr, "Param isnot string");
else
dbus_message_iter_get_basic(&arg, &sigvalue);
printf("Got Singal withvalue : %s\n", sigvalue);
}
dbus_message_unref(msg);
}//End of while}
int main(int argc, char **argv){
listen_signal();
return 0;
}
编译
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
<p>project(dbus_test)</p>
<p>aux_source_directory(. SRCS)</p>
<p>add_executable(test ${SRCS})</p>
<p>target_link_libraries(test dbus-1)
</p>
编译可能遇到的问题
[*]缺少,解决方法:
[*]安装dbus库,若仍未解决,执行下一步
[*]sudo ln -sf /usr/include/dbus-1.0/dbus /usr/include/dbus,原因:
[*]默认安装的dbus可能在/usr/include/dbus-1.0下
[*]而程序中自动包含的头文件默认是去/usr/include下查找,上述操作是在/usr/include创建了所需头文件的软连接
[*]缺少dbus-arch-deps.h,解决方法:
[*]sudo ln -sf /usr/include/dbus-1.0/dbus/dbus-arch-deps.h /usr/include/dbus,原因同上
方法调用示例
被调用方
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <unistd.h>
<p>void reply_to_method_call(DBusMessage *msg, DBusConnection *conn)
{
DBusMessage *reply;
DBusMessageIter iter;
dbus_uint32_t serial = 0;
int param1, param2;
// 1 读取消息中携带的参数
if(!dbus_message_iter_init(msg, &iter))
{
printf("Message has noargs\n");
return;
}
// 读取第一个参数
if(dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32)
{
printf("arg1 type error, should intger!\n");
return;
}
else
{
dbus_message_iter_get_basic(&iter, ¶m1);
dbus_message_iter_next(&iter);// 迭代器后移一位
}
// 读取第二个参数
if(dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32)
{
printf("arg2 type error, should intger!\n");
}
else
{
dbus_message_iter_get_basic(&iter, ¶m2);
}</p>
printf("receive method call, param is %d and %d\n", param1, param2);
int result = param1 + param2;
// 2 创建返回消息reply
reply = dbus_message_new_method_return(msg);
// 通过消息迭代器在返回消息中填入结果
dbus_message_iter_init_append(reply, &iter);
if(!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &result)){
printf("Out ofMemory!\n");
exit(1);
}
// 3 发送返回消息
if(!dbus_connection_send(conn, reply, &serial)){
printf("Out of Memory\n");
exit(1);
}
dbus_connection_flush(conn);
dbus_message_unref(reply);}
void listen_dbus()
{
DBusMessage *msg;
DBusConnection *connection;
DBusError err;
// 步骤1 建立连接
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr, "ConnectionError %s\n", err.message);
dbus_error_free(&err);
}
if(connection == NULL)
return;
// 步骤2 设置连接名
int ret = dbus_bus_request_name(connection, "org.example.MethodCallable", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr, "Name Error %s\n", err.message);
dbus_error_free(&err);
}
while(1)
{
// 步骤3 循环监听
dbus_connection_read_write(connection, 0);
msg = dbus_connection_pop_message(connection);
if(msg == NULL){
sleep(1);
continue;
}
// 步骤4 解析调用消息并处理
if(strcmp(dbus_message_get_path(msg), "/org/example/MethodService/Object") == 0){
if(dbus_message_is_method_call(msg, "org.example.MethodService.Add", "AddMethod")){
reply_to_method_call(msg, connection);
}
}
dbus_message_unref(msg);
}}
int main(int argc, char **argv){
listen_dbus();
return 0;
}
调用方
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <unistd.h>
<p>DBusConnection* connect_dbus()
{
DBusError err;
DBusConnection *connection;
int ret;</p>
// 步骤1 建立连接
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr, "ConnectionErr : %s\n", err.message);
dbus_error_free(&err);
}
if(connection == NULL)
return NULL;
// 步骤2 注册连接名称
ret = dbus_bus_request_name(connection, "org.example.MethodCall", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr, "Name Err :%s\n", err.message);
dbus_error_free(&err);
}
if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return NULL;
return connection;}
void send_a_method_call(DBusConnection *connection, int param1, int param2)
{
DBusError err;
DBusMessage *msg;
DBusMessageIter iter;
DBusPendingCall *pending;
int result;
// 步骤3 创建调用消息
dbus_error_init(&err);
msg = dbus_message_new_method_call("org.example.MethodCallable", "/org/example/MethodService/Object",
"org.example.MethodService.Add", "AddMethod");
if(msg == NULL){
fprintf(stderr, "MessageNULL");
return;
}
// 给定调用参数
dbus_message_iter_init_append(msg, &iter);
if(!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, ¶m1)){
fprintf(stderr, "Out of Memory!");
exit(1);
}
if(!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, ¶m2)){
fprintf(stderr, "Out of Memory!");
exit(1);
}
// 步骤4 发送消息,pending用于接收调用后的方法响应
if(!dbus_connection_send_with_reply(connection, msg, &pending, -1)){
fprintf(stderr, "Out of Memory!");
exit(1);
}
if(pending == NULL){
fprintf(stderr, "Pending CallNULL: connection is disconnected ");
dbus_message_unref(msg);
return;
}
dbus_connection_flush(connection);
dbus_message_unref(msg);
// 步骤5 接收响应
dbus_pending_call_block(pending);
// 从响应中拿取数据
msg = dbus_pending_call_steal_reply(pending);
if (msg == NULL) {
fprintf(stderr, "ReplyNull\n");
exit(1);
}
dbus_pending_call_unref(pending);
// 读取响应数据
if(!dbus_message_iter_init(msg, &iter))
fprintf(stderr, "Message has no ret!\n");
else if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32)
fprintf(stderr, "Argument is not integer!\n");
else
dbus_message_iter_get_basic(&iter, &result);
printf("Got Reply: %d\n", result);
dbus_message_unref(msg);}
int main()
{
send_a_method_call(connect_dbus(), 5, 10);
return 0;}
GDBUS
reference
Gio – 2.0: 迁移到 GDBus - GTK 文档
常用函数
g_bus_own_name
声明
guint
g_bus_own_name (
GBusType bus_type,
const gchar* name,
GBusNameOwnerFlags flags,
GBusAcquiredCallback bus_acquired_handler,
GBusNameAcquiredCallback name_acquired_handler,
GBusNameLostCallback name_lost_handler,
gpointer user_data,
GDestroyNotify user_data_free_func
)描述
连接到bus_type指定的总线,并在该总线注册一个连接名称,在总线获取成功时调用bus_acquired_handler,并分别在名称获取或丢失时调用name_acquired_handler和name_lost_handler。
参数
[*]**bus\_type**
[*]类型:GBusType
[*]说明:一般都是G_BUS_TYPE_SESSION
[*]name
[*]类型:const gchar*
[*]要给本连接注册的well-known名称
[*]flags
[*]类型:GBusNameOwnerFlags
[*]说明:多数时取值G_BUS_NAME_OWNER_FLAGS_REPLACE| G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,意为如果另一个消息总线连接拥有该名称并指定了 G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,则从该连接中取走该名称。
[*]bus_acquired_handler
[*]类型:
void
(* GBusAcquiredCallback) (
GDBusConnection* connection,
const gchar* name,
gpointer user_data
)
[*]说明:在连接到类型为bus_type的总线或NULL时调用的处理程序。
[*]name_acquired_handler
[*]类型:
void
(* GBusNameAcquiredCallback) (
GDBusConnection* connection,
const gchar* name,
gpointer user_data
)
[*]说明:在名称被获取时调用。
[*]name_lost_handler
[*]类型:
void
(* GBusNameLostCallback) (
GDBusConnection* connection,
const gchar* name,
gpointer user_data
)
[*]说明:当名称丢失或连接被关闭时调用。
[*]user_data
[*]类型:gpointer,实质是void*
[*]说明:传递给处理器的用户数据。参数可以是NULL。数据由函数的调用者拥有。
[*]user_data_free_func
[*]类型
void
(* GDestroyNotify) (
gpointer data
)
[*]说明:指定在销毁数据元素时调用的函数类型。它将指针传递给数据元素,并应释放为此分配的任何内存和资源。参数可以为 NULL。
返回值
[*]类型:guint
[*]说明:一个可用于与 g_bus_unown_name() 一起使用的标识符(永远不会是 0),用于停止拥有该名称。
g_bus_unown_name
声明
void
g_bus_unown_name (
guint owner_id
)描述
停止拥有一个名称。
参数
[*]owner_id
g_bus_own_name的返回值
g_signal_connect
声明
#define g_signal_connect (
instance,
detailed_signal,
c_handler,
data
)说明:
将 GCallback 函数连接到特定对象的信号。这里是这样一种思想:XML文件中定义了Method,客户端调用该Method时,会向服务端发出一个处理该方法的信号。若是服务端使用此函数将信号与一个回调函数连接,就会在收到信号时触发该回调函数。这种handle-method信号由gdbus-codegen工具根据XML文件中定义的方法自动生成的信号。
详细说明:GObject.signal_connect - GTK 文档。
参数
[*]instance
[*]类型:形如ExampleAnimal,是由gdbus-codegen工具根据XML文件生成的文件中定义的结构体类型。
[*]具体内容:形如example_animal_skeleton_new()函数创建的对象实例。标识
example_animal_skeleton_new也是gdbus-codegen工具根据XML文件生成的函数。
example_animal是根据gdbus-codegen工具选项和XML文件内容生成的名称,会作为所有生成的函数或是宏的名称的前缀。其中example是gdbus-codegen工具选项--c-namespace Example定义的,animal则是XML文件中定义的接口名称。
[*]detailed_signal
[*]类型:一个标识XML文件中定义的方法的字符串。
[*]具体内容:handle-{methodname},其中handle是固定前缀,methodname是由XML文件中定义的方法名转换而来的。具体转换方式为从驼峰命名转换成小写且以-连接。如GetIp → get-ip。可以在gdbus-codegen生成的代码中查找信号名,形如
static const _ExtendedGDBusMethodInfo _example_animal_method_info_poke =
{
{
-1,
(gchar *) "Poke",
(GDBusArgInfo **) &_example_animal_method_info_poke_IN_ARG_pointers,
(GDBusArgInfo **) &_example_animal_method_info_poke_OUT_ARG_pointers,
NULL
},
"handle-poke",
FALSE
};形式的结构体赋值语句,结构体的原型为
typedef struct
{
GDBusMethodInfo parent_struct;
const gchar *signal_name;
gboolean pass_fdlist;
} _ExtendedGDBusMethodInfo;也就是说,"handle-poke"就是生成代码为Poke方法创建的信号。
[*]c_handler
[*]类型:形如
static gboolean on_animal_poke (
ExampleAnimal *object,
GDBusMethodInvocation *invocation,
gboolean arg_make_sad,
gboolean arg_make_happy,
gpointer user_data
)形式的函数指针。要使用G_CALLBACK转换该函数指针。
[*]具体说明:相较于生成的头文件中struct _ExampleAnimalIface
struct _ExampleAnimalIface
{
GTypeInterface parent_iface;
<p>gboolean (*handle_poke) (
ExampleAnimal *object,
GDBusMethodInvocation *invocation,
gboolean arg_make_sad,
gboolean arg_make_happy);</p>
<p>const gchar * (*get_foo) (ExampleAnimal *object);</p>
<p>const gchar * (*get_mood) (ExampleAnimal *object);</p>
<p>void (*jumped) (
ExampleAnimal *object,
gdouble arg_height);</p>
<p>const gchar * (*get_bar) (ExampleAnimal *object);</p>
<p>};
</p>
内定义的handle_poke函数指针,该函数中多了一个gpointer类型的参数,gpointer实质是void*类型,用于传递自定义的一些数据。此处的函数指针名称不作要求,但一般形式都是on_接口名_方法名。
[*]data
[*]类型:gpointer
[*]用于传递自定义的一些数据,如不需要,可为NULL
g_main_loop_new
声明
GMainLoop*
g_main_loop_new (
GMainContext* context,
gboolean is_running
)描述
创建一个新的 GMainLoop 结构体。GMainLoop 结构是一个不透明的数据类型,它表示 GLib 或 GTK 应用程序的主事件循环。
参数
[*]context
[*]类型:GMainContext*。GMainContext 结构体是不透明的数据类型,用于表示要主循环中处理的一组源。
[*]说明:此参数可以为NULL,如果为 NULL,将使用全局默认的主上下文。数据由函数的调用者持有。
[*]is_running
[*]设置为 TRUE 以指示循环正在运行。这并不很重要,因为调用 g\_main\_loop\_run() 无论如何都会将其设置为 TRUE。
返回值
[*]类型:GMainLoop
[*]说明:一个新的 GMainLoop。函数的调用者负责数据的所有权,并负责释放它。
g_main_loop_run
声明
void
g_main_loop_run (
GMainLoop* loop
)描述
运行主循环,直到在循环上调用 g\_main\_loop\_quit()。如果这是在循环的 GMainContext 线程上调用,它将处理循环的事件,否则它将只等待。
参数
[*]loop
g_main_loop_new返回的对象
g_main_loop_unref
声明
void
g_main_loop_unref (
GMainLoop* loop
)描述
通过一个 GMainLoop 对象的引用次数减一。如果结果是零,则释放循环及其所有相关内存。
g_main_loop_quit
声明
void
g_main_loop_quit (
GMainLoop* loop
)描述
停止 GMainLoop 的运行。对于此循环的任何 g\_main\_loop\_run() 调用都将返回。
注意,当调用 g\_main\_loop\_quit() 时,已调度的事件源仍将被执行。
流程
服务端流程
[*]g_main_loop_new创建主事件循环对象
[*]g_bus_own_name创建连接并注册连接名称,且注册回调函数(所有事件都在回调函数中处理)
[*]g_main_loop_run启动主事件循环
[*]g_bus_unown_name停止拥有之前注册的连接名称
[*]g_main_loop_unref释放主事件循环对象资源
客户端流程
[*]g_main_loop_new创建主事件循环对象
[*]example_object_manager_client_new_for_bus_sync同步创建并返回一个对象管理代理客户端(该函数是gdbus-codegen根据XML文件生成的头文件和源文件中包含的函数,并且有异步版本)
[*]使用上一步获取到的对象管理代理客户端,进行方法调用,以及可选的连接该对象的信号和回调函数。
[*]g_main_loop_run启动主事件循环
[*]停止循环,释放资源
编码流程
[*]创建XML文件,在其中写定接口,接口中包含方法、信号、属性等定义;
[*]使用gdbus-codegen工具根据XML文件生成代码,包括一个头文件和一个源文件;
[*]引入生成的头文件,利用其中包含的函数和宏等编写代码;
[*]编译运行。
示例代码
gdbus-example-objectmanager.xml
页:
[1]