在使用 Protocol Buffers(Protobuf)时,如果你接收到的数据类型不确定,即你不知道该数据应该对应哪一个 .proto
文件定义的消息结构,那么你需要一种方法来区分这些不同的消息类型。以下是几种处理这种不确定性的常见策略,以 PHP 语言为例:
一个常见的做法是在你的 Protobuf 消息中添加一个字段来表示消息的类型。例如:
message WrapperMessage {
string type = 1;
google.protobuf.Any data = 2;
}
然后你可以为每种可能的消息类型定义单独的消息,并且在序列化之前将正确的类型信息和数据放入 WrapperMessage 中。
在接收端,你可以先解析出 WrapperMessage,检查 type 字段,然后根据这个字段选择合适的消息类型进行进一步的反序列化。
设计一个外层消息,其中包含一个 oneof 字段(如果 protobuf 版本支持,或通过类似逻辑模拟),该字段可以包含多个可能的内部消息类型。
在解析时,先解析外层消息,然后根据 oneof 的选择解析内部消息。
syntax = "proto3";
package c2s_common;
// BaseMessage 是最终发送的消息, 接收到后动态解析
message BaseMessage {
oneof Message {
Ping ping = 1;
EnterGame enter_game = 2;
LeaveGame leave_game = 3;
}
}
// 客户端发送ping
message Ping {
string topic = 1;
}
// 进入游戏上报
message EnterGame {
string topic = 1;
uint32 room_id = 2 [json_name = "room_id"];
}
// 离开游戏上报
message LeaveGame {
string topic = 1;
uint32 room_id = 2 [json_name = "room_id"];
}
public static function parseProtobuf2Json($string): string
{
try {
// 反序列化
$baseMessage = new BaseMessage();
$baseMessage->mergeFromString($string);
$data = match ($baseMessage->getMessage()) {
'ping' => $baseMessage->getPing(),
'enter_game' => $baseMessage->getEnterGame(),
'leave_game' => $baseMessage->getLeaveGame(),
default => throw new \Exception('unknown message type'),
};
return $data->serializeToJsonString();
} catch (\Exception $e) {
SelfWorker::Log($e->getMessage());
SelfWorker::Log($e->getTraceAsString());
return '{}';
}
}
如果消息类型较多且需要频繁扩展:推荐使用 方法1(使用字段标识符)。这种方法更灵活,可以轻松扩展新的消息类型。
如果消息结构相对固定且需要减少冗余:推荐使用 方法2(封装在一个外层消息中)。这种方法可以减少冗余并自动处理解析逻辑,使代码更简洁。