protobuf: прочитать сообщение на C ++ из C # - PullRequest
7 голосов
/ 03 июня 2011

Я собираюсь прочитать сообщения, которые последовательно хранятся в сокете в клиенте C ++, отправленные с сервера C #.Я ожидаю, что смогу прочитать размер сообщения, такого как:

google::protobuf::uint32 m;
coded_input->ReadVarint32(&m);
cout << m << endl;

Затем я хочу прочитать сообщение:

Person person;
CodedInputStream::Limit limit = coded_input->PushLimit(m);
person.ParseFromCodedStream(coded_input);
coded_input->PopLimit(limit);

код C # выглядит следующим образом:

var person = new Person { Id = 123456, Name = "Fred", Address = new Address { Line1 = "Flat 1", Line2 = "The Meadows ar garą " } };
Stream str = new NetworkStream(socket);
Serializer.SerializeWithLengthPrefix(str, person, PrefixStyle.Fixed32);

Не работает.

Я получаю ошибку C ++ (43 - результат cout << m << endl;) (id, name, address - все поля в Person) </p>

43

libprotobuf ОШИБКА google / protobuf / message_lite.cc: 123] Невозможно проанализировать сообщение типа «Персона», поскольку в нем отсутствуют обязательные поля: идентификатор, имя, адрес

Когда я не читаю вариант и сразу не разбираю сообщение с coded_input, все нормально (когда я изменяю SerializeWithLengthPrefix для сериализации в коде сервера).Однако мне нужен метод для различения последовательных сообщений, поэтому мне нужно знать размер сообщения, которое я собираюсь прочитать.Я просто не знаю, как отправить размер.

Я пытался:

Stream buf = new MemoryStream();
Serializer.Serialize(buf, person);
ProtoWriter writer = new ProtoWriter(str, null);
ProtoWriter.WriteInt32((int)buf.Length, writer);

, но затем я получаю:

Необработанное исключение: ProtoBuf.ProtoException: Недопустимая операция сериализации с проводным типом Нет в позиции 0 в ProtoBuf.ProtoWriter.WriteInt32 (значение Int32, модуль записи ProtoBuf.ProtoWriter) [0x00000] в: 0
в protobuftest.MainClass.Main (аргументы System.String [])[0x00097] в /home/lorddidger/studia/csharp/protobuf-test/protobuf-test/Main.cs:31

Я не могу отправить ни один int таким способом.Что не так?


Обновление: На самом деле моя цель - найти способ передать целое число (размер) с помощью protobuf.Мне нужен любой пример (как C ++, так и C #), как это сделать, потому что я не понимаю всех деталей в protobuf. * ​​1037 *

Я заметил, что SerializeWithLengthPrefix отправляет префикс (4 uint32), который выглядит следующим образом: размер 0 0 0.Размер - это количество байтов сообщения после сериализации (наверное).Я думал, что PrefixStyle.Fixed32 говорит, что перед сообщением есть только один uint32, но есть 4!

Наконец, я подумал, что вы должны использовать ProtoWriter.WriteInt32 ((int) buf.Length, writer) для передачи целых чисел, потому что япоследовало предложение от где-то в Интернете , которое, я полагаю, является обходным путем, относящимся к C ++.Теперь я вижу, что вы не должны писать varints - они связаны с движком, а это слишком сложно, чтобы тратить время на него.

Должен ли я отправить сообщение с int?Как мне отличить это от следующего сообщения, размер которого хранится в первом?

Я вижу, что префиксы хэдов C # могут работать нормально, но есть ли СООТВЕТСТВУЮЩИЙ С C ++ и C # способ указать размер сообщения?В противном случае ** не существует способа ставить в очередь сообщения, что я считаю иррациональным.

1 Ответ

3 голосов
/ 03 июня 2011

Ваш первый пример включает только длину; «с префиксом длины» фактически кодируется в протобуф-совместимом потоке.

Если вы декодируете из c ++, прочитайте two varints; первый номер поля и тип провода; вторая длина. Первый упакован как 3-битный проводной тип, остальное - номер поля. Вы также можете указать поле 0 для пропуска - я не могу вспомнить без проверки.


Update; Я проверил это, записав данные без номера поля (только префикс длины varint), а затем прочитав их обратно в C #, используя два разных API - индивидуально с Deserialize и как перечисляемый блок через DeserializeItems. Я должен был исправить ошибку в последнем, но следующее будет работать после следующего нажатия кода (примечание: только чтение кода имеет исправление, поэтому, если вы пишете на C # и чтение в C ++ это не повлияет на вас):

using (var ms = new MemoryStream())
{
    // write data with a length-prefix but no field number
    Serializer.SerializeWithLengthPrefix(ms, new Foo { Bar = 1 }, PrefixStyle.Base128, 0);
    Serializer.SerializeWithLengthPrefix(ms, new Foo { Bar = 2 }, PrefixStyle.Base128, 0);
    Serializer.SerializeWithLengthPrefix(ms, new Foo { Bar = 3 }, PrefixStyle.Base128, 0);

    ms.Position = 0;
    Assert.AreEqual(9, ms.Length, "3 lengths, 3 headers, 3 values");

    // read the length prefix and use that to limit each call
    TypeModel model = RuntimeTypeModel.Default;
    int len, fieldNumber, bytesRead;
    List<Foo> foos = new List<Foo>();
    do
    {
        len = ProtoReader.ReadLengthPrefix(ms, false, PrefixStyle.Base128, out fieldNumber, out bytesRead);
        if (bytesRead <= 0) continue;

        foos.Add((Foo)model.Deserialize(ms, null, typeof(Foo), len));

        Assert.IsTrue(foos.Count <= 3, "too much data!");
    } while (bytesRead > 0);

    Assert.AreEqual(3, foos.Count);
    Assert.AreEqual(1, foos[0].Bar);
    Assert.AreEqual(2, foos[1].Bar);
    Assert.AreEqual(3, foos[2].Bar);

    // do it using DeserializeItems
    ms.Position = 0;

    foos.Clear();
    foreach (var obj in model.DeserializeItems<Foo>(ms, PrefixStyle.Base128, 0))
    {
        foos.Add(obj);
        Assert.IsTrue(foos.Count <= 3, "too much data!");
    }
    Assert.AreEqual(3, foos.Count);
    Assert.AreEqual(1, foos[0].Bar);
    Assert.AreEqual(2, foos[1].Bar);
    Assert.AreEqual(3, foos[2].Bar);
}
...