ОС и версия protobuf
go1.18.1 linux/amd64, github.com/golang/protobuf v1.5.2
Вступление
Я пытаюсь использовать рекурсивные определения прототипов.
.proto
файл
message AsyncConsensus {
int32 sender = 1;
int32 receiver = 2;
string unique_id = 3; // to specify the fall back block id to which the vote asyn is for
int32 type = 4; // 1-propose, 2-vote, 3-timeout, 4-propose-async, 5-vote-async, 6-timeout-internal, 7-consensus-external-request, 8-consensus-external-response, 9-fallback-complete
string note = 5;
int32 v = 6 ; // view number
int32 r = 7;// round number
message Block {
string id = 1;
int32 v = 2 ; // view number
int32 r = 3;// round number
Block parent = 4;
repeated int32 commands = 5;
int32 level = 6; // for the fallback mode
}
Block blockHigh = 8;
Block blockNew = 9;
Block blockCommit = 10;
}
Вот как я маршал и немаршал
func (t *AsyncConsensus) Marshal(wire io.Writer) error {
data, err := proto.Marshal(t)
if err != nil {
return err
}
lengthWritten := len(data)
var b [8]byte
bs := b[:8]
binary.LittleEndian.PutUint64(bs, uint64(lengthWritten))
_, err = wire.Write(bs)
if err != nil {
return err
}
_, err = wire.Write(data)
if err != nil {
return err
}
return nil
}
func (t *AsyncConsensus) Unmarshal(wire io.Reader) error {
var b [8]byte
bs := b[:8]
_, err := io.ReadFull(wire, bs)
if err != nil {
return err
}
numBytes := binary.LittleEndian.Uint64(bs)
data := make([]byte, numBytes)
length, err := io.ReadFull(wire, data)
if err != nil {
return err
}
err = proto.Unmarshal(data[:length], t)
if err != nil {
return err
}
return nil
}
func (t *AsyncConsensus) New() Serializable {
return new(AsyncConsensus)
}
Мой ожидаемый результат
При маршалинге и отправке в тот же процесс через TCP он должен правильно демаршалировать и создавать правильные структуры данных.
Результирующая ошибка
ошибка "cannot parse invalid wire-format data"
Дополнительная информация
Я пробовал использовать нерекурсивные определения .proto
, и раньше у меня никогда не было этой проблемы.
Самая глупая ошибка, о которой я могу думать, заключается в том, что wire.Write(bs) не записывает столько байтов, сколько io.ReadFull(wire, bs) читает, поэтому я просто удостоверился, что их возвращаемое значение на самом деле равно 8 в оба случая.
Тогда я не очень хорошо знаю golang/protobuf, но я думаю, что это должно быть в состоянии сделать это. Разве вы не должны создать код перехода, а затем вызывать его? Я не знаю, как это назвать.
Если вы считаете, что на самом деле проблема заключается в реализации protobuf, есть несколько онлайн-декодеров protobuf, которые могут вам помочь. Но иногда они неправильно интерпретируют поток, что может иметь место в случае с рекурсивным шаблоном, так что вы должны быть осторожны. Но, по крайней мере, они не раз помогали мне отлаживать пакет dedis/protobuf.
В крайнем случае вы можете сделать минимальный пример с рекурсивными данными, проверить, работает ли он, а затем медленно добавлять поля, пока он не сломается…
Это не ошибка Protobuff, а вопрос того, как вы marshal
и unmarshal
создаете структуры protobuff.
В качестве конкретного руководства никогда не используйте одновременно marshal
и unmarshal
структуры protobuff, так как это может привести к условиям гонки.
В приведенном вами конкретном примере я вижу рекурсивные структуры данных, поэтому даже если вы используете отдельную структуру для каждого вызова marshal
и unmarshal
, вполне вероятно, что указатели в родительском элементе могут привести к общим указателям.
Используйте метод глубокого копирования, чтобы удалить любую зависимость, чтобы не столкнуться с условиями гонки.
func CloneMyStruct(orig *proto.AsyncConsensus_Block) (*proto.AsyncConsensus_Block, error) {
origJSON, err := json.Marshal(orig)
if err != nil {
return nil, err
}
clone := proto.AsyncConsensus_Block{}
if err = json.Unmarshal(origJSON, &clone); err != nil {
return nil, err
}
return &clone, nil
}