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_CALL | 1 | 客户端 → 服务端 | 是(METHOD_RETURN 或 ERROR) | RPC 调用 |
METHOD_RETURN | 2 | 服务端 → 客户端 | 否 | 方法调用成功返回 |
ERROR | 3 | 服务端 → 客户端 | 否 | 方法调用失败返回 |
SIGNAL | 4 | 广播 | 否 | 事件通知 |
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;
}
详细说明:
-
创建空消息结构(
dbus_message_new_empty_header()):- 分配
DBusMessage结构体内存 - 初始化引用计数为 1
- 初始化消息头(
DBusHeader):创建空的DBusString用于存储网络数据 - 初始化消息体(
DBusString):预分配 32 字节缓冲区 - 初始化其他字段(引用计数、锁定状态、数据槽等)
- 分配
-
创建消息头(
_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 字节):稍后填充
- 字节序(1 字节):
-
写入字段数组:
- 如果
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.Failedorg.freedesktop.DBus.Error.InvalidArgsorg.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.IOError | I/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 线程安全
- 消息创建是线程安全的
- 但创建后的消息不是线程安全的
- 多个线程访问同一消息需要外部同步
4150

被折叠的 条评论
为什么被折叠?



