2
votes

Peut-on envoyer un message protobuf marshal vers un tableau d'octets déjà alloué sans copie?

J'implémente la communication client-serveur par paquets auto-définis. J'utilise Go net.conn . Il peut composer des schémas tcp / unix, ce qui est très pratique. J'utilise protocol buffer pour définir mes messages.

J'ai défini un Packet qui contient length et buffer

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
}

La fonction API est comme ceci:
Erreur func (api * API) Send (m * proto.Message)
Erreur de réception func (api * API) (p * Packet)

Prenez la fonction send comme exemple, elle prend un message protobuf, le marshalez dans Packet . Et écrivez-le dans le net.conn .

Voici une version simplifiée de la fonction Send:

type Packet struct {
    length uint32
    buffer []byte
}

Je copiais octets dans le buffer code>. Parce que l'API de tampon de protocole Go fournit uniquement
func Marshal (pb Message) ([] byte, error)

Dans le tampon de protocole C ++, il fournit bool SerializeToArray (void * data, int size) const , qui sérialise le message et le stocke dans le tableau d'octets donné. Mais je ne trouve pas la même chose dans l'API du tampon de protocole Go.

Y a-t-il un moyen d'éviter la copie si je veux stocker directement le résultat sérialisé dans le tableau d'octets donné?


2 commentaires

Pourriez-vous clarifier quelques points: Pourquoi le paquet est-il stocké dans un champ de l'API et d'où vient cette longueur? Est-ce que tout ce que vous voulez vraiment faire est d'écrire un tableau d'octets composé de la longueur (4 octets), puis du message réel à la connexion?


Qu'en est-il d'accepter une réponse?


3 Réponses :


0
votes

Ce que vous demandez n'est pas clair. Notez que la fonction proto Marshal () fait exactement ce que vous recherchez: elle sérialise le message en une tranche d'octets (ce que vous entendez probablement par tableau d'octets)

Voyez si l'un de ces éléments aide:

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
}

Ou

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
}


1 commentaires

Mon but est de mettre le tampon sérialisé dans le paquet , au lieu de l'écrire directement dans conn . car le paquet était déjà utilisé par d'autres composants pour mon projet. Je ne peux pas le refactoriser. proto.Marshal renvoie [] octet et a été copié dans mon paquet. Je cherche un moyen d'éviter la copie. Un exemple est l'API protobuf C ++, elle peut rassembler un message dans un tampon déjà alloué . Mais go protobuf ne peut que le retourner.



0
votes

Il semble que vous puissiez faire de Packet.buffer un 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)


0 commentaires

2
votes

Vous recherchez la méthode MarshalTo de gogo / protobuf , une autre implémentation de protobuf, compatible avec l'original.

Vous pouvez réutiliser le même tampon via plusieurs appels de marshal lorsque vous lui passez le tampon à remplir. Évidemment, le tampon doit être suffisamment grand.

func MarshalTo([]byte, m) error 


0 commentaires