DBUS源码剖析之DBusMessage消息类型及创建

DBusMessage 消息类型和创建

1. 消息类型概述

D-Bus 定义了 4 种基本消息类型,每种类型都有特定的用途和格式要求。

1.1 消息类型定义

#define DBUS_MESSAGE_TYPE_INVALID        0
#define DBUS_MESSAGE_TYPE_METHOD_CALL     1  /**< 方法调用 */
#define DBUS_MESSAGE_TYPE_METHOD_RETURN   2  /**< 方法返回 */
#define DBUS_MESSAGE_TYPE_ERROR           3  /**< 错误消息 */
#define DBUS_MESSAGE_TYPE_SIGNAL          4  /**< 信号 */

1.2 消息类型对比

类型方向期望回复使用场景
METHOD_CALL1客户端 → 服务端是(METHOD_RETURN 或 ERROR)RPC 调用
METHOD_RETURN2服务端 → 客户端方法调用成功返回
ERROR3服务端 → 客户端方法调用失败返回
SIGNAL4广播事件通知

2. METHOD_CALL(方法调用)

2.1 概述

方法调用是客户端向服务端发起 RPC 请求的消息类型。

2.2 必需字段

  • Path:对象路径(必需)
  • Member:方法名(必需)
  • Interface:接口名(可选,但推荐)
  • Destination:目标服务名(可选,点对点通信时可为 NULL)

2.3 创建函数

DBusMessage* dbus_message_new_method_call(
    const char *destination,  // 目标服务名(如 "org.freedesktop.DBus")或 NULL
    const char *path,          // 对象路径(如 "/org/freedesktop/DBus")
    const char *iface,         // 接口名(如 "org.freedesktop.DBus")或 NULL
    const char *method);       // 方法名(如 "Hello")

2.4 实现细节

创建流程

dbus_message_new_method_call() 的内部实现流程如下:

DBusMessage* dbus_message_new_method_call(const char *destination,
                                          const char *path,
                                          const char *iface,
                                          const char *method)
{
  DBusMessage *message;
  
  // 步骤 1: 创建空消息结构
  // 分配 DBusMessage 结构体,初始化引用计数为 1
  // 初始化消息头和消息体(使用预分配缓冲区)
  message = dbus_message_new_empty_header();
  if (message == NULL)
    return NULL;  // 内存不足
  
  // 步骤 2: 创建消息头
  // 调用 _dbus_header_create() 创建完整的消息头,包括:
  //   - 固定部分:字节序、消息类型、标志、协议版本、长度、序列号
  //   - 字段数组:根据参数添加 path、destination、interface、member 字段
  if (!_dbus_header_create(&message->header,
                           DBUS_COMPILER_BYTE_ORDER,  // 使用编译器字节序
                           DBUS_MESSAGE_TYPE_METHOD_CALL,  // 消息类型
                           destination, path, iface, method, NULL))  // 字段值
    {
      // 创建失败,释放消息
      dbus_message_unref(message);
      return NULL;
    }
  
  return message;
}

详细说明

  1. 创建空消息结构dbus_message_new_empty_header()):

    • 分配 DBusMessage 结构体内存
    • 初始化引用计数为 1
    • 初始化消息头(DBusHeader):创建空的 DBusString 用于存储网络数据
    • 初始化消息体(DBusString):预分配 32 字节缓冲区
    • 初始化其他字段(引用计数、锁定状态、数据槽等)
  2. 创建消息头_dbus_header_create()):

    • 写入固定部分(16 字节):

      • 字节序(1 字节):DBUS_COMPILER_BYTE_ORDER(‘l’ 或 ‘B’)
      • 消息类型(1 字节):DBUS_MESSAGE_TYPE_METHOD_CALL(值为 1)
      • 标志(1 字节):初始化为 0
      • 协议版本(1 字节):DBUS_MAJOR_PROTOCOL_VERSION(值为 1)
      • 消息体长度(4 字节):初始化为 0(消息体为空)
      • 序列号(4 字节):初始化为 0(发送时分配)
      • 字段数组长度(4 字节):稍后填充
    • 写入字段数组

      • 如果 path != NULL:添加 DBUS_HEADER_FIELD_PATH 字段
      • 如果 destination != NULL:添加 DBUS_HEADER_FIELD_DESTINATION 字段
      • 如果 interface != NULL:添加 DBUS_HEADER_FIELD_INTERFACE 字段
      • 如果 member != NULL:添加 DBUS_HEADER_FIELD_MEMBER 字段
      • 每个字段格式为:struct<byte, variant>(字段代码 + 类型签名 + 值)
    • 对齐填充:在消息头末尾添加 0-7 字节填充,使消息体从 8 字节边界开始

内存布局示例(假设调用 dbus_message_new_method_call("com.example", "/obj", "com.example.IFace", "Method")):

消息头数据(header.data):
[字节序 'l'] [类型 1] [标志 0] [版本 1] [体长 0] [序列号 0] [字段数组长度]
[字段代码 1] [Variant 'o'] ["/obj" + \0] [填充]
[字段代码 6] [Variant 's'] ["com.example" + \0] [填充]
[字段代码 2] [Variant 's'] ["com.example.IFace" + \0] [填充]
[字段代码 3] [Variant 's'] ["Method" + \0] [填充]
[对齐填充到 8 字节边界]

消息体(body):
[空,长度为 0]

错误处理

  • 如果任何步骤失败(内存不足),函数返回 NULL
  • 已分配的资源会被正确释放(通过 dbus_message_unref()

2.5 使用示例

2.5.1 基本方法调用
// 创建方法调用消息
DBusMessage *msg = dbus_message_new_method_call(
    "org.freedesktop.DBus",      // 目标服务
    "/org/freedesktop/DBus",     // 对象路径
    "org.freedesktop.DBus",      // 接口
    "Hello");                    // 方法名

if (msg == NULL) {
    fprintf(stderr, "Failed to create method call message\n");
    return;
}

// 添加参数
const char *arg = "World";
if (!dbus_message_append_args(msg,
                               DBUS_TYPE_STRING, &arg,
                               DBUS_TYPE_INVALID)) {
    fprintf(stderr, "Failed to append arguments\n");
    dbus_message_unref(msg);
    return;
}

// 发送消息(需要连接)
// dbus_connection_send(conn, msg, NULL);
// dbus_connection_flush(conn);

// 释放消息
dbus_message_unref(msg);
2.5.2 无接口的方法调用
// 如果接口为 NULL,服务端需要根据方法名推断接口
DBusMessage *msg = dbus_message_new_method_call(
    "com.example.Service",
    "/com/example/Object",
    NULL,  // 接口为 NULL
    "DoSomething");
2.5.3 点对点通信
// 点对点通信时,destination 可以为 NULL
DBusMessage *msg = dbus_message_new_method_call(
    NULL,  // 无目标服务(点对点)
    "/com/example/Object",
    "com.example.Interface",
    "Method");

2.6 回复匹配

方法调用消息会自动分配序列号(serial),用于匹配回复:

// 发送方法调用
dbus_uint32_t serial;
dbus_connection_send(conn, msg, &serial);

// 等待回复(需要匹配 serial)
DBusMessage *reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, NULL);
if (reply != NULL) {
    if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
        // 处理成功回复
    } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
        // 处理错误
    }
    dbus_message_unref(reply);
}

3. METHOD_RETURN(方法返回)

3.1 概述

方法返回是服务端响应方法调用成功的消息类型。

3.2 必需字段

  • Reply Serial:回复序列号(必需,用于匹配原始方法调用)
  • Destination:目标服务名(自动设置为原始调用的发送者)

3.3 创建函数

DBusMessage* dbus_message_new_method_return(DBusMessage *method_call);

3.4 实现细节

自动设置字段

// 从 dbus-message.c
sender = dbus_message_get_sender(method_call);

// 创建返回消息,目标设置为原始调用的发送者
_dbus_header_create(&message->header,
                    DBUS_COMPILER_BYTE_ORDER,
                    DBUS_MESSAGE_TYPE_METHOD_RETURN,
                    sender, NULL, NULL, NULL, NULL);

// 设置 NO_REPLY_EXPECTED 标志
dbus_message_set_no_reply(message, TRUE);

// 设置回复序列号
dbus_message_set_reply_serial(message,
                              dbus_message_get_serial(method_call));

关键点

  • 自动从原始调用中获取发送者作为目标
  • 自动设置回复序列号
  • 设置 NO_REPLY_EXPECTED 标志(避免循环回复)

3.5 使用示例

3.5.1 基本方法返回
// 在消息处理函数中
DBusMessage *reply = dbus_message_new_method_return(method_call);
if (reply == NULL) {
    // 处理内存不足
    return;
}

// 添加返回值
int result = 42;
if (!dbus_message_append_args(reply,
                               DBUS_TYPE_INT32, &result,
                               DBUS_TYPE_INVALID)) {
    dbus_message_unref(reply);
    return;
}

// 发送回复
dbus_connection_send(conn, reply, NULL);
dbus_connection_flush(conn);

// 释放消息
dbus_message_unref(reply);
3.5.2 多个返回值
DBusMessage *reply = dbus_message_new_method_return(method_call);

const char *str = "Success";
int code = 0;
dbus_bool_t flag = TRUE;

dbus_message_append_args(reply,
                         DBUS_TYPE_STRING, &str,
                         DBUS_TYPE_INT32, &code,
                         DBUS_TYPE_BOOLEAN, &flag,
                         DBUS_TYPE_INVALID);

3.6 序列号匹配

回复消息的序列号必须与原始方法调用的序列号匹配:

// 服务端处理
dbus_uint32_t call_serial = dbus_message_get_serial(method_call);

// 创建回复
DBusMessage *reply = dbus_message_new_method_return(method_call);

// 回复的序列号自动设置为 call_serial
dbus_uint32_t reply_serial = dbus_message_get_reply_serial(reply);
// reply_serial == call_serial

4. ERROR(错误消息)

4.1 概述

错误消息是服务端响应方法调用失败的消息类型。

4.2 必需字段

  • Error Name:错误名(必需,遵循 D-Bus 错误名规范)
  • Reply Serial:回复序列号(必需)
  • Destination:目标服务名(自动设置)

4.3 错误名规范

错误名必须遵循以下格式:

<命名空间>.<错误名>

例如:

  • org.freedesktop.DBus.Error.Failed
  • org.freedesktop.DBus.Error.InvalidArgs
  • org.freedesktop.DBus.Error.AccessDenied

4.4 创建函数

// 基本错误消息
DBusMessage* dbus_message_new_error(
    DBusMessage *reply_to,        // 回复的消息
    const char *error_name,       // 错误名
    const char *error_message);   // 错误消息(可选)

// 格式化错误消息
DBusMessage* dbus_message_new_error_printf(
    DBusMessage *reply_to,
    const char *error_name,
    const char *error_format,    // printf 格式字符串
    ...);

4.5 实现细节

错误消息创建

// 从 dbus-message.c
sender = dbus_message_get_sender(reply_to);

// 创建错误消息
_dbus_header_create(&message->header,
                    DBUS_COMPILER_BYTE_ORDER,
                    DBUS_MESSAGE_TYPE_ERROR,
                    sender, NULL, NULL, NULL, error_name);

// 设置标志和序列号
dbus_message_set_no_reply(message, TRUE);
dbus_message_set_reply_serial(message,
                              dbus_message_get_serial(reply_to));

// 如果提供了错误消息,添加到消息体
if (error_message != NULL) {
    dbus_message_iter_init_append(message, &iter);
    dbus_message_iter_append_basic(&iter,
                                   DBUS_TYPE_STRING,
                                   &error_message);
}

错误消息体

  • 错误消息字符串作为第一个参数(可选)
  • 可以包含额外的错误数据

4.6 使用示例

4.6.1 基本错误消息
// 创建错误消息
DBusMessage *error = dbus_message_new_error(
    method_call,
    "org.freedesktop.DBus.Error.Failed",
    "Operation failed: insufficient permissions");

if (error == NULL) {
    // 处理内存不足
    return;
}

// 发送错误
dbus_connection_send(conn, error, NULL);
dbus_connection_flush(conn);

dbus_message_unref(error);
4.6.2 格式化错误消息
// 使用 printf 格式化
DBusMessage *error = dbus_message_new_error_printf(
    method_call,
    "com.example.Error.InvalidValue",
    "Invalid value: %d (expected range: %d-%d)",
    value, min_value, max_value);
4.6.3 带额外数据的错误
DBusMessage *error = dbus_message_new_error(
    method_call,
    "com.example.Error.ValidationFailed",
    "Validation failed");

// 添加额外的错误数据
DBusMessageIter iter;
dbus_message_iter_init_append(error, &iter);

const char *field = "username";
int code = 1001;
dbus_message_iter_append_args(&iter,
                              DBUS_TYPE_STRING, &field,
                              DBUS_TYPE_INT32, &code,
                              DBUS_TYPE_INVALID);
4.6.4 客户端处理错误
DBusMessage *reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, NULL);

if (reply != NULL) {
    if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
        const char *error_name = dbus_message_get_error_name(reply);
        const char *error_message;
        
        // 读取错误消息
        if (dbus_message_get_args(reply, NULL,
                                  DBUS_TYPE_STRING, &error_message,
                                  DBUS_TYPE_INVALID)) {
            fprintf(stderr, "Error: %s - %s\n", error_name, error_message);
        }
    }
    dbus_message_unref(reply);
}

4.7 标准错误名

D-Bus 定义了一些标准错误名:

错误名说明
org.freedesktop.DBus.Error.Failed一般性失败
org.freedesktop.DBus.Error.NoMemory内存不足
org.freedesktop.DBus.Error.ServiceUnknown服务不存在
org.freedesktop.DBus.Error.NameHasNoOwner名称无所有者
org.freedesktop.DBus.Error.NoReply无回复
org.freedesktop.DBus.Error.IOErrorI/O 错误
org.freedesktop.DBus.Error.BadAddress地址错误
org.freedesktop.DBus.Error.NotSupported不支持
org.freedesktop.DBus.Error.LimitsExceeded超出限制
org.freedesktop.DBus.Error.AccessDenied访问被拒绝
org.freedesktop.DBus.Error.AuthFailed认证失败
org.freedesktop.DBus.Error.NoServer无服务器
org.freedesktop.DBus.Error.Timeout超时
org.freedesktop.DBus.Error.NoNetwork无网络
org.freedesktop.DBus.Error.AddressInUse地址已使用
org.freedesktop.DBus.Error.Disconnected已断开连接
org.freedesktop.DBus.Error.InvalidArgs无效参数
org.freedesktop.DBus.Error.FileNotFound文件未找到
org.freedesktop.DBus.Error.FileExists文件已存在
org.freedesktop.DBus.Error.UnknownMethod未知方法
org.freedesktop.DBus.Error.UnknownObject未知对象
org.freedesktop.DBus.Error.UnknownInterface未知接口
org.freedesktop.DBus.Error.UnknownProperty未知属性
org.freedesktop.DBus.Error.PropertyReadOnly属性只读

5. SIGNAL(信号)

5.1 概述

信号是应用程序广播事件通知的消息类型,不期望回复。

5.2 必需字段

  • Path:对象路径(必需)
  • Interface:接口名(必需)
  • Member:信号名(必需)

5.3 创建函数

DBusMessage* dbus_message_new_signal(
    const char *path,        // 对象路径
    const char *iface,       // 接口名
    const char *name);       // 信号名

5.4 实现细节

信号创建

// 从 dbus-message.c
_dbus_header_create(&message->header,
                    DBUS_COMPILER_BYTE_ORDER,
                    DBUS_MESSAGE_TYPE_SIGNAL,
                    NULL, path, iface, name, NULL);

// 信号不期望回复
dbus_message_set_no_reply(message, TRUE);

关键点

  • 信号不需要目标(destination),因为可以广播给多个接收者
  • 自动设置 NO_REPLY_EXPECTED 标志
  • 不需要序列号匹配

5.5 使用示例

5.5.1 基本信号
// 创建信号
DBusMessage *signal = dbus_message_new_signal(
    "/com/example/Object",
    "com.example.Interface",
    "SomethingHappened");

if (signal == NULL) {
    return;
}

// 添加信号参数
const char *event = "user_login";
int user_id = 12345;
dbus_message_append_args(signal,
                         DBUS_TYPE_STRING, &event,
                         DBUS_TYPE_INT32, &user_id,
                         DBUS_TYPE_INVALID);

// 发送信号(广播)
dbus_connection_send(conn, signal, NULL);
dbus_connection_flush(conn);

dbus_message_unref(signal);
5.5.2 系统信号
// 系统服务发送信号
DBusMessage *signal = dbus_message_new_signal(
    "/org/freedesktop/DBus",
    "org.freedesktop.DBus",
    "NameOwnerChanged");

const char *name = "com.example.Service";
const char *old_owner = ":1.23";
const char *new_owner = ":1.24";

dbus_message_append_args(signal,
                         DBUS_TYPE_STRING, &name,
                         DBUS_TYPE_STRING, &old_owner,
                         DBUS_TYPE_STRING, &new_owner,
                         DBUS_TYPE_INVALID);
5.5.3 订阅信号
// 客户端订阅信号
DBusError error;
dbus_error_init(&error);

// 添加匹配规则
dbus_bus_add_match(conn,
                   "type='signal',"
                   "interface='com.example.Interface',"
                   "member='SomethingHappened'",
                   &error);

if (dbus_error_is_set(&error)) {
    fprintf(stderr, "Match error: %s\n", error.message);
    dbus_error_free(&error);
    return;
}

// 在消息循环中接收信号
while (dbus_connection_read_write_dispatch(conn, -1)) {
    DBusMessage *msg = dbus_connection_pop_message(conn);
    if (msg != NULL) {
        if (dbus_message_is_signal(msg,
                                   "com.example.Interface",
                                   "SomethingHappened")) {
            // 处理信号
            const char *event;
            int user_id;
            if (dbus_message_get_args(msg, NULL,
                                      DBUS_TYPE_STRING, &event,
                                      DBUS_TYPE_INT32, &user_id,
                                      DBUS_TYPE_INVALID)) {
                printf("Signal received: %s (user: %d)\n", event, user_id);
            }
        }
        dbus_message_unref(msg);
    }
}

6. 通用创建函数

6.1 dbus_message_new

DBusMessage* dbus_message_new(int message_type);

用途:创建一个指定类型的空消息

参数

  • message_type:消息类型(DBUS_MESSAGE_TYPE_*

返回值

  • 成功:返回新消息指针
  • 失败:返回 NULL(内存不足)

使用场景

  • 需要手动设置所有字段
  • 特殊消息类型(虽然只有 4 种标准类型)
  • 测试和调试

示例

// 创建空的方法调用消息
DBusMessage *msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);

// 手动设置字段
dbus_message_set_path(msg, "/com/example/Object");
dbus_message_set_interface(msg, "com.example.Interface");
dbus_message_set_member(msg, "Method");
dbus_message_set_destination(msg, "com.example.Service");

6.2 实现细节

// 从 dbus-message.c
DBusMessage* dbus_message_new(int message_type)
{
  DBusMessage *message;

  _dbus_return_val_if_fail(message_type != DBUS_MESSAGE_TYPE_INVALID, NULL);

  message = dbus_message_new_empty_header();
  if (message == NULL)
    return NULL;

  // 创建消息头,不设置任何字段
  if (!_dbus_header_create(&message->header,
                            DBUS_COMPILER_BYTE_ORDER,
                            message_type,
                            NULL, NULL, NULL, NULL, NULL)) {
      dbus_message_unref(message);
      return NULL;
  }

  return message;
}

7. 消息创建的最佳实践

7.1 错误处理

DBusMessage *msg = dbus_message_new_method_call(...);
if (msg == NULL) {
    // 处理内存不足
    fprintf(stderr, "Out of memory\n");
    return;
}

// 使用消息...

// 确保释放
dbus_message_unref(msg);

7.2 参数验证

创建函数会自动验证参数:

  • 路径格式
  • 接口名格式
  • 方法/信号名格式
  • 错误名格式

无效参数会导致函数返回 NULL

7.3 内存管理

  • 创建的消息引用计数为 1
  • 使用 dbus_message_unref() 释放
  • 可以多次引用(dbus_message_ref()
  • 发送后消息可能被缓存,不要假设立即释放

7.4 线程安全

  • 消息创建是线程安全的
  • 但创建后的消息不是线程安全的
  • 多个线程访问同一消息需要外部同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值