【问题标题】:Can go protobuf marshal message to an already allocated byte array without copy?可以将 protobuf 编组消息发送到已分配的字节数组而无需复制吗?
【发布时间】:2019-01-15 17:08:25
【问题描述】:

我正在通过自定义数据包实现客户端服务器通信。 我正在使用 Go net.conn。它可以拨打tcp/unix方案,非常方便。我使用protocol buffer 来定义我的消息。

我定义了一个包含lengthbuffer 的数据包

type Packet struct {
    length uint32
    buffer []byte
}

API函数是这样的:
func(api *API) Send(m *proto.Message) error
func(api *API) Receive(p *Packet) error

send函数为例,它接受一个protobuf消息,将其编入Packet。并将其写入net.conn

这里是发送功能的简化版本:

func(api *API) Send(m *proto.Message) error {
    bytes, err := proto.Marshal(m)
    if err != nil {
        return err
    }
    buffer := api.packet[:length]
    copy(buffer, bytes)
    _, err := api.conn.Write(buffer)
    if err != nil {
        return err
    }
    return nil
}

我正在将bytes 复制到buffer。因为 Go 协议缓冲区 API 只提供了
func Marshal(pb Message) ([]byte, error)

在协议缓冲区 C++ 中,它提供 bool SerializeToArray(void * data, int size) const,正在序列化消息并将其存储在给定的字节数组中。 但我在 Go 协议缓冲区 API 中找不到相同的东西。

如果我想直接将序列化结果存储在给定的字节数组中,有什么方法可以避免复制?

【问题讨论】:

  • 能否请您澄清一些事情:为什么数据包存储在API的一个字段中,这个长度来自哪里?你真的只想写一个由长度(4 个字节)组成的字节数组,然后是连接的实际消息吗?
  • 接受答案怎么样?

标签: go protocol-buffers ipc packet


【解决方案1】:

您正在寻找来自gogo/protobufMarshalTo 方法,这是protobuf 的另一种实现,与原始版本兼容。

当您将要填充的缓冲区传递给它时,您可以通过多个 marshal 调用重复使用相同的缓冲区。显然缓冲区应该足够大。

func MarshalTo([]byte, m) error 

【讨论】:

    【解决方案2】:

    不清楚你在问什么。请注意,proto Marshal() 函数完全符合您的要求:它将消息序列化为字节切片(您可能指的是字节数组)

    看看这些是否有帮助:

    func(api *API) Send(m *proto.Message) error {
        p := Packet{}
        p.buffer, err := proto.Marshal(m)
        if err != nil {
            return err
        }
        _, err := api.conn.Write(p.buffer)
        if err != nil {
            return err
        }
        return nil
    }
    

    或者

    func(api *API) Send(m *proto.Message) error {
        buffer := api.packet[:length]
        buffer, err := proto.Marshal(m)
        if err != nil {
            return err
        }
        _, err := api.conn.Write(buffer)
        if err != nil {
            return err
        }
        return nil
    }
    

    【讨论】:

    • 我的目的是将序列化的缓冲区放入packet,而不是直接写入conn。因为该数据包已被其他组件用于我的项目。我无法重构它。 proto.Marshal 返回 []byte 并被复制到我的数据包中。我正在寻找一种避免复制的方法。一个例子是 protobuf C++ API,它可以将消息编组为already allocated buffer。但是go protobuf只能返回。
    【解决方案3】:

    看来你可以让Packet.buffer 成为proto.Buffer

    type Packet struct {
        length uint32
        buffer proto.Buffer
    }
    ...
    var packet Packet
    packet.length = YouLength
    packet.buffer = proto.NewBuffer(make([]byte, YouLength))
    //Then you can Marshall in Packet directly and it  may be reused.
    err := packet.Marshal(message)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-06
      • 1970-01-01
      • 2020-04-19
      • 1970-01-01
      相关资源
      最近更新 更多