我正在实施 protobuf 扩展,以与我们现有的内部 protobuf 框架配合使用。 我们已经在多个服务中针对 C、Python 和其他几种语言实现了此功能,因此更改消息格式不是一个选择。 我想做的是找到最惯用的(也是最不痛苦的)方法。
我们所做的是创建一个小标头,将其添加到所有 protobuf 消息中,其中一个字段指示消息类型。 在这个框架中编码消息非常简单:
const (
MessageEmpty = iota
Message1
Message2
)
func Encode(msgType byte, msg proto.Message) ([]byte, error) {
buf, err := proto.Marshal(msg)
if err != nil {
return nil, err
}
hdr := Header {
msgtype: msgType,
}
out := new(bytes.Buffer)
err = binary.Write(out, binary.BigEndian, hdr)
if err != nil {
return nil, err
}
out.Grow(len(buf))
_, err = out.Write(buf)
if err != nil {
return nil, err
}
return out.Bytes(), nil
}
我还没有彻底测试过这一点,但乍一看似乎是正确的。 但是,我遇到的问题是尝试编写 Decode() 函数。 我想做的是以下几点:
func Decode(pkt []byte) (proto.Message, error) {
hdr := Header{}
buf := bytes.NewBuffer(pkt)
ret := proto.Message()
err := binary.Read(buf, binary.BigEndian, &hdr)
if err != nil {
return nil, err
}
msg := buf.Bytes()
switch hdr.msgType {
case Message1:
pbmsg := myproto.FirstMessage{}
err = proto.Unmarshal(msg, &pbmsg)
ret = &pbmsg
case Message2:
}
if err != nil {
return nil, err
}
return ret, nil
}
但是,这无法编译。 我将遇到的下一个问题是,一旦我收到消息,我将如何在运行时确定该消息的类型? (我想我也可以返回 msgType 字段作为我的返回值之一)。
欢迎任何建议和/或指导。
您可能不应该推出自己的机制。对于动态消息,可以使用 google/protobuf/any.proto ,如下所示:
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)
func Encode(msg proto.Message) ([]byte, error) {
anyMsg, err := anypb.New(msg)
if err != nil {
return nil, err
}
return proto.Marshal(anyMsg)
}
func Decode(buf []byte) (proto.Message, error) {
var anyMsg anypb.Any
if err := proto.Unmarshal(buf, &anyMsg); err != nil {
return nil, err
}
return anyMsg.UnmarshalNew()
}
返回的值可以与
switch
: 一起使用
msg, err := Decode(buf)
if err != nil {
// ...
}
switch msg := msg.(type) {
case *myproto.FirstMessage:
// Here msg is of type *myproto.FirstMessage
// ...
}
这也回答了你的第二个问题。
假设出于兼容性原因,您需要支持您的格式,则将编译以下内容:
func Decode(pkt []byte) (proto.Message, error) {
buf := bytes.NewBuffer(pkt)
var hdr Header
err := binary.Read(buf, binary.BigEndian, &hdr)
if err != nil {
return nil, err
}
msg := buf.Bytes()
var ret proto.Message
switch hdr.MsgType {
case Message1:
var pbmsg models.CreateUserRequest
err = proto.Unmarshal(msg, &pbmsg)
ret = &pbmsg
case Message2:
// ...
}
return ret, err
}