Полученный объект Protobuf не имеет всех полей

Я создаю реализацию HDFS на С++, используя ONCRPC и Google Protobuf. Проблема, с которой я сталкиваюсь, заключается в том, что я отправляю объект protobuf с несколькими заполненными полями (отправка сериализованной строки, анализ ее на принимающей стороне), однако на принимающей стороне ошибочно говорится, что одно из полей имеет не установлено/не существует.

Это часть моего файла hdfs.proto:

message AssignBlockRequest {
  optional int32 handle = 1; // obtain using call to OpenFile
}

message AssignBlockResponse {
  optional int32 status = 1;
  optional BlockLocations newBlock = 2;
       }

message BlockLocations {
  optional int32 blockNumber = 1;
  repeated DataNodeLocation locations = 2;
}

message DataNodeLocation {
  optional string ip = 1;
  optional int32 port = 2;
}

Я использую это в «клиентском» приложении, чтобы запросить у «сервера namenode» новый блок и список узлов данных, на которые он может отправлять данные для записи.

Итак, в моем клиенте:

AssignBlockResponse assignnewblock_ ( int fhandle, CLIENT* clnt ) {
  AssignBlockRequest req;
  req.set_handle(fhandle);

  //send request to nn
  string str;
  req.SerializeToString(&str);
  static char *cstr = new char[str.length() + 1];
  memcpy(cstr, str.c_str(), str.length()+1);
  char **result_abreq;
  result_abreq = assignblock_1( &cstr, clnt );

  //handle response
  AssignBlockResponse rsp;
  string str_arg (*result_abreq);
  rsp.ParseFromString(str_arg);
  cout << "NN RETURNED : " << rsp.status() << " " << rsp.has_newblock() << endl;

  return rsp;
}

в то время как в моем namenode server.cc

char **
assignblock_1_svc(char **argp, struct svc_req *rqstp)
{

  AssignBlockRequest req;
  string str_arg (*argp);
  req.ParseFromString(str_arg);

  AssignBlockResponse rsp;

  if ( DataNodeList.empty() ) { // no DN available
    rsp.set_status (1);
  }
  else {
    rsp.set_status (0);

    int BL_NUM = 0;
    vector<int> shuf;

    BlockLocations bl;// = new BlockLocations;
    bl.set_blocknumber(BL_NUM);

    rsp.mutable_newblock()->CopyFrom(bl);
  }
  cout << "NN RETURNED : " << rsp.status() << " " << rsp.has_newblock() << endl;


  string str;
  rsp.SerializeToString(&str);
  static char *cstr = new char[str.length() + 1];
  memcpy(cstr, str.c_str(), str.length()+1);

  return &cstr;
}

NN выводит «0 1», в то время как клиент при получении этого запроса типа AssignBlockResponse показывает «0 0», т. е. он получает правильный статус (проверено путем изменения статуса, установленного в сообщении AssignBlockResponse), но никогда не обнаруживает поле «newblock», отправленное server.cc к нему.

Любая помощь будет принята с благодарностью.

-- РЕДАКТИРОВАТЬ 1 --

Сериализация буфера протокола с наследованием. Производные классы пусты

Это может представлять интерес. Однако я все еще не могу заставить свой код работать.


person lip    schedule 26.03.2015    source источник


Ответы (1)


Я столкнулся с этим в своей ранней работе с буферами протоколов.

Не сериализуйтеToString. serializeToArray сначала построил достаточно большой вектор (вызовите ByteSize() в сообщении)

Проблема в том, что ваш сериализованный поток байтов содержит нулевой байт, который интерпретируется как конец строки при преобразовании char* в строку.

Это означает, что вы в конечном итоге анализируете неполное сообщение и, следовательно, отсутствующие поля.

person Richard Hodges    schedule 26.03.2015
comment
Я изменил его, чтобы использовать SerializeToArray и ParseFromArray, но безрезультатно. Я убежден, что ваше предложение должно работать, но мой код все еще не работает. - person lip; 27.03.2015
comment
Я заставил его работать, но только при жестком кодировании значения вызова ByteSize() для объекта на стороне сервера в ParseFromArray объекта размера клиента. Есть ли хороший способ отправить значение ByteSize() по сети/вычислить ByteSize из полученного c_str? Я мог бы просто использовать поле состояния, чтобы сохранить его. Спасибо ! - person lip; 27.03.2015
comment
Ах, это другое дело. protobuf не имеет понятия "фрейм". вы должны отправить сообщение в своем собственном протоколе кадрирования. Предполагается, что функции Parse будут работать с полным фреймом. - person Richard Hodges; 27.03.2015
comment
что-то очень простое, например, длина 4 байта (в сетевом порядке байтов), за которой следуют сериализованные байты, за которыми следует (если вы параноик) контрольная сумма данных или контрольная сумма данных, было бы разумным протоколом кадрирования через tcp. Если вы отправляете через http, то, конечно, протокол http имеет заголовок Content-Length:. - person Richard Hodges; 27.03.2015
comment
AssignBlockResponse rsp; rsp.ParseFromString(*result_abreq); int size = rsp.status(); rsp.ParseFromArray(*result_abreq, size); Я установил результат ByteSize() в качестве поля состояния AssignBlockResponse, и, похоже, он работает. Пожалуйста, дайте мне знать, если у этого есть какие-либо потенциальные предостережения. - person lip; 27.03.2015
comment
ну, как я уже упоминал, мои результаты использования ParseFromString требовали, чтобы сериализованное сообщение не содержало никаких 0 байтов. Это не то, что вы можете надежно предсказать во всех случаях. Я бы отправил длину сообщения до сериализованного сообщения, а затем использовал бы это значение для создания буфера массива для синтаксического анализа. - person Richard Hodges; 27.03.2015