DBus的理解与应用

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_tDBUS_TYPE_BOOLEAN
16位整形dbus_int16_tDBUS_TYPE_INT16
32位整形dbus_int32_tDBUS_TYPE_INT32
64位整形dbus_int64_tDBUS_TYPE_INT64
双精度浮点数doubleDBUS_TYPE_DOUBLE
字符串类型char *DBUS_TYPE_STRING
对象路径?DBUS_TYPE_OBJECT_PATH
DBUS_TYPE_SIGNATURE
UNIX文件描述符intDBUS_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, &param);
    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-senddbus-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

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值