DBus
连接 (通用部分)
DBus Name 是用来给应用程序进行标识自己的,所以每当程序连上 DBus Daemon 后,就会分配到一个 Unique Name
同时应用程序还可以要求自己分配另一个 Well-know name (通过 dbus_bus_request_name 函数)。
// 让应用程序和 DBus 之间取得连接
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (NULL == conn)
{
exit(1);
}
// 将自己的进程名字注册到 Daemon 上
ret = dbus_bus_request_name(conn, "test.method.server", 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 (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret)
{
exit(1);
}
DBUS_NAME_FLAG_REPLACE_EXISTING
选项将会申请替换当前已申请名字的DBus。有另一个选项DBUS_NAME_FLAG_ALLOW_REPLACEMENT
将允许其他DBus替换自己申请的DBus名字。成功申请后,应当返回DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
,表示当前连接已经获取成为本DBus的名字拥有者了。通常,如果申请失败,会返回值为3的DBUS_REQUEST_NAME_REPLY_EXISTS
宏。
一个标准的DBus连接与命名函数如下:
#include <dbus/dbus.h>
#include <stdio.h>
#define RES_SUCCESS -1
#define RES_FAILED 0
#define DBUS_NAME "test.method.server"
static DBusConnection *pConn;
int DBus_Init()
{
int dBusRequest;
DBusError err;
dbus_error_init(&err);
pConn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
return RES_FAILED;
}
dBusRequest = dbus_bus_request_name(pConn, DBUS_NAME,
DBUS_NAME_FLAG_ALLOW_REPLACEMENT, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Name Error (%s)\n", err.message);
dbus_error_free(&err);
return RES_FAILED;
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != dBusRequest)
{
return RES_FAILED;
}
return RES_SUCCESS;
}
gcc test.c -I /usr/include/dbus-1.0/ -l dbus-1 -c
Signal
DBus 提供的最简单的一种通信方式是信号(Signal),应用程序可以发送一个信号到 Daemon 上,之后,Daemon 会根据信号的种类和谁希望得到信号等信息,把相应的数据发给每个希望得到信号的进程。也就是 Signal 具有广播的功能。
信号具有两个基本的属性,一个是名称,用来标识各个不同的信号,一个是数据,信号是可以带一定的数据的。
DBusMessage 是 DBus 中的核心数据结构。可以这样理解:DBus中传递消息数据的时候,就是通过它来传递的。对于使用者来说,DBusMessage 中存储了两种重要的信息,一种是为通信机制服务的各种 Name,一种是通信的数据本身。
线程A
dbus_uint32_t serial = 0; // 将答复与请求关联的唯一编号
DBusMessage* msg;
DBusMessageIter args;
// 创建信号
msg = dbus_message_new_signal("/test/signal/Object", // 信号的对象名称
"test.signal.Type", // 信号的接口名称
"Test"); // 信号的名称
if (NULL == msg)
{
fprintf(stderr, "Message Null\n");
exit(1);
}
// 将参数附加到信号
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &sigvalue))
{
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// 发送消息并刷新连接
if (!dbus_connection_send(conn, msg, &serial))
{
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
dbus_connection_flush(conn);
// 释放消息对象
dbus_message_unref(msg);
类型 | DBus类型 | 描述 |
---|---|---|
DBUS_TYPE_BYTE | ||
布尔值 | dbus_bool_t | DBUS_TYPE_BOOLEAN |
16位整形 | dbus_int16_t | DBUS_TYPE_INT16 |
32位整形 | dbus_int32_t | DBUS_TYPE_INT32 |
64位整形 | dbus_int64_t | DBUS_TYPE_INT64 |
双精度浮点数 | double | DBUS_TYPE_DOUBLE |
字符串类型 | char * | DBUS_TYPE_STRING |
对象路径? | DBUS_TYPE_OBJECT_PATH | |
DBUS_TYPE_SIGNATURE | ||
UNIX文件描述符 | int | DBUS_TYPE_UNIX_FD |
线程B
如果一个进程 B 想接收接口名为 test.signal.Type 的信号,那么可以使用下面的函数向 Daemon 添加匹配信号,让 Daemon 知道自己对这种信号感兴趣。
dbus_bus_add_match(conn, "type='signal',interface='test.signal.Type'", &err);
dbus_connection_flush(conn);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Match Error (%s)\n", err.message);
exit(1);
}
进程 B 可以使用下面的函数来进行等待:
while (true)
{
// 非阻塞读取下一条可用消息
dbus_connection_read_write(conn, 0);
msg = dbus_connection_pop_message(conn);
// 如果没有读到消息,再次循环
if (NULL == msg)
{
sleep(1);
continue;
}
// 检查消息是否是来自正确接口且名称正确的信号
if (dbus_message_is_signal(msg, "test.signal.Type", "Test"))
{
// 读取参数
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "消息没有参数!\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "参数格式错误!\n");
else
{
dbus_message_iter_get_basic(&args, &sigvalue);
printf("获取信号的值: %s\n", sigvalue);
}
}
// 释放消息对象
dbus_message_unref(msg);
}
Method
方法调用消息,分三步,第一步,客户端创建、配置并发送一个MethodCall;第二步,服务端接收消息,获取消息内容,完成一些逻辑后,建立并配置返回消息;第三步,客户端接收到服务端返回的消息。
client
void DBus_MethodCall(char *str)
{
DBusMessage *msg;
DBusMessageIter args;
DBusPendingCall *pending;
msg = dbus_message_new_method_call("test.method.server", // 方法调用的目标
"/test/method/Object", // 要调用的对象
"test.method.Type", // 要调用的接口
"Method"); // 方法名称
if (NULL == msg)
{
fprintf(stderr, "Message Null\n");
exit(1);
}
// 添加参数
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &str))
{
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// 发送消息并获取回复
if (!dbus_connection_send_with_reply(pConn, msg, &pending, -1))
{ // -1是默认超时
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
if (NULL == pending)
{
fprintf(stderr, "Pending Call Null\n");
exit(1);
}
dbus_connection_flush(pConn);
// 释放消息对象
dbus_message_unref(msg);
前半部分,读取DBusMessage
参数部分的代码,同信号消息接收是一样的。只是在后面,调用函数dbus_message_new_method_return()
从客户端发来的DBusMessage
中,获取了一个新的DBusMessage
,并将其作为发向客户端的消息。接下来的过程和信号消息的发送是类似的。服务端这边将数据内容发送给客户端之后,客户端便使用pending
获取返回值:
bool stat;
char* level;
// 阻塞直到接受到回复
dbus_pending_call_block(pending);
// 获取回复消息
msg = dbus_pending_call_steal_reply(pending);
if (NULL == msg)
{
fprintf(stderr, "Reply Null\n");
exit(1);
}
// 释放消息句柄
dbus_pending_call_unref(pending);
// 读取参数
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "无参数!\n");
else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "格式错误!\n");
else
dbus_message_iter_get_basic(&args, &stat);
if (!dbus_message_iter_next(&args))
fprintf(stderr, "消息的参数太少!\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "参数格式错误!\n");
else
{
dbus_message_iter_get_basic(&args, &level);
}
printf("收到回复: %d, %s\n", stat, level);
// 释放消息对象并断开连接
dbus_message_unref(msg);
}
server
// 循环接收新消息
while (true)
{
// 非阻塞读取下一条可用消息
dbus_connection_read_write(conn, 0);
msg = dbus_connection_pop_message(conn);
// 如果没有消息,再次循环
if (NULL == msg)
{
sleep(1);
continue;
}
// 检查方法
if (dbus_message_is_method_call(msg, "test.method.Type", "Method"))
reply_to_method_call(msg, conn);
// 释放消息对象
dbus_message_unref(msg);
}
void reply_to_method_call(DBusMessage *msg, DBusConnection *conn)
{
DBusMessage *reply;
DBusMessageIter args;
bool stat = true;
char* level = "收到!";
dbus_uint32_t serial = 0;
char *param = "";
// 读取请求
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "没有参数!\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "参数不是字符串!\n");
else
dbus_message_iter_get_basic(&args, ¶m);
printf("收到请求: %s\n", param);
// 创建一个消息对象
reply = dbus_message_new_method_return(msg);
// 添加参数
dbus_message_iter_init_append(reply, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &stat))
{
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &level))
{
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// 发送请求并刷新连接
if (!dbus_connection_send(conn, reply, &serial))
{
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
dbus_connection_flush(conn);
// 释放消息对象
dbus_message_unref(reply);
}
dbus-send和dbus-monitor
dbus提供了两个小工具:dbus-send
和dbus-monitor
。我们可以用dbus-send
发送消息。用dbus-monitor
监视总线上流动的消息。 让我们通过dbus-send
发送消息来调用前面的Add方法,这时dbus-send
充当了应用程序B。用dbus-monitor
观察调用过程中的消息。
启动 ./server
启动dbus-monitor
"test.method.server" // 方法调用的目标
"/test/method/Object" // 要调用的对象
"test.method.Type" // 要调用的接口
"Method" // 方法名称
执行
dbus-send [--system | --session] --type=method_call --print-reply --dest=连接名 对象路径 接口名.方法名 参数类型:参数值 参数类型:参数值
dbus-send支持的参数类型包括:string, int32, uint32, double, byte, boolean。
dbus-send --session --type=method_call --print-reply=literal --dest=test.method.server /test/method/Object test.method.Type.Method string:haha int32:100
输出为:
method return time=1625735059.628295 sender=:1.599 -> destination=:1.611 serial=3 reply_serial=2
uint32 1
string "收到!"
dbus-monitor的相关输出:
method call time=1625735059.252440 sender=:1.611 -> destination=org.freedesktop.DBus serial=1 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello
method return time=1625735059.252488 sender=org.freedesktop.DBus -> destination=:1.611 serial=1 reply_serial=1
string ":1.611"
string ""
string ":1.611"
method call time=1625735059.252802 sender=:1.611 -> destination=test.method.server serial=2 path=/test/method/Object; interface=test.method.Type; member=Method
string "haha"
int32 100
method return time=1625735059.628295 sender=:1.599 -> destination=:1.611 serial=3 reply_serial=2
uint32 1
string "收到!"
signal time=1625735059.629542 sender=org.freedesktop.DBus -> destination=(null destination) serial=1992 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
string ":1.611"
string ":1.611"
string ""
:1.611就是dbus-send
在本次调用中与会话总线所建立连接的唯一名。:1.599是连接“test.method.server”的唯一名。 在以上输出中我们可以看到**:1.611**向“test.method.server”发送method_call
消息,调用Add方法。 :1.599通过method_return
消息将调用结果发回:1.611