Protobuf2与Protobuf-net通信问题:继承类解码失败求助
Protobuf C服务器与Protobuf-net客户端继承类解码问题解决方案
核心问题根源
Protobuf-net的ProtoInclude和原生Protobuf C的extend是完全不同的多态实现机制:
- Protobuf-net通过类型标记字段(默认是
__type,或自定义字段号)识别子类,序列化时将子类数据打包到基类指定字段中 - 原生Protobuf的
extend用于扩展已有消息的字段,并非为继承多态设计,两者无法直接兼容
不放弃继承的解决方案
方案1:统一用原生Protobuf推荐的oneof实现多态
这是兼容性最好的方式,两端都遵循原生Protobuf规范:
客户端(Protobuf-net)代码调整
[ProtoContract] public class MsgBase { // 约定类型ID,和服务器端一一对应 [ProtoMember(1)] public int TypeId { get; set; } // 基类原有字段... } [ProtoContract] public class MsgBase_Demo : MsgBase { [ProtoMember(11)] public float id; public MsgBase_Demo() { TypeId = 100; // 标记当前类型ID id = 110; } }
服务器端(Protobuf C)代码调整
message MsgBase { required int32 type_id = 1; // 和客户端TypeId对应 // 基类原有字段... // 用oneof存放所有子类消息,每个子类对应一个字段 oneof payload { MsgBase_Demo demo_msg = 100; } } message MsgBase_Demo { optional float id = 11; }
序列化/反序列化逻辑
客户端序列化MsgBase_Demo时,需将实例赋值给MsgBase的payload对应字段;服务器端解析时,先读取type_id,再从payload中取出对应子类的数据。Protobuf-net可通过自定义序列化钩子或手动映射实现这一逻辑。
方案2:让Protobuf-net适配服务器端的字段映射
如果服务器端无法大规模调整,可让Protobuf-net直接使用服务器端定义的字段号存放子类数据:
客户端代码修改
[ProtoContract] // 指定子类对应的字段号,和服务器端MsgBase中的字段号一致 [ProtoInclude(1000, typeof(MsgBase_Demo))] public class MsgBase { // 基类原有字段... } [ProtoContract] public class MsgBase_Demo : MsgBase { [ProtoMember(11)] public float id; public MsgBase_Demo() { id = 110; } }
服务器端代码调整
message MsgBase { // 基类原有字段... // 直接定义子类字段,字段号和客户端ProtoInclude指定的一致 optional MsgBase_Demo demo_msg = 1000; } message MsgBase_Demo { optional float id = 11; }
服务器端解析时,只需检查demo_msg字段是否存在,即可识别并解析子类数据。
放弃继承的替代方案
如果不想在多态兼容上耗费精力,用组合代替继承是更简单的选择:直接将子类作为基类的一个字段,而非继承关系。例如:
客户端代码:
[ProtoContract] public class MsgBase { [ProtoMember(1)] public MsgBase_Demo DemoMsg { get; set; } // 基类原有字段... } [ProtoContract] public class MsgBase_Demo { [ProtoMember(11)] public float id; public MsgBase_Demo() { id = 110; } }
服务器端代码和方案1中的MsgBase定义一致,这种方式逻辑清晰,完全没有兼容性问题。
总结
不用急于放弃继承,只要统一两端的多态实现逻辑即可:优先推荐原生Protobuf的oneof方案,兼容性最强;若服务器端改动受限,可采用字段映射的方式适配。如果上述方案都难以落地,放弃继承用组合的方式会更省心。
内容的提问来源于stack exchange,提问作者YueYuHai
相关产品推荐
相关产品推荐

