Проблемы с синтаксическим анализом протокола TLV на основе TCP в C

#c #linux #tcp

#c #linux #tcp

Вопрос:

Я пытаюсь реализовать протокол (T) LV через TCP, отправляя буферы протокола с клиента python и получая с помощью сервера C .

Мой код выглядит более или менее так:

 char* buffer[RCVBUFSIZE];
int recvsize= 0;
// Filling my buffer with as much as possible.
while(true) {
  if(recvsize == RCVBUFSIZE) {
    break;
  } else if(recvsize = recv(sock, buffer recvsize, sizeof(buffer)-recvsize, 0) < 1) {
    break;
  }
}
//Parsing LV protocol
while(true) {
  unsigned short protosize= 0;
  //Copy first two bytes into protosize
  memcpy((char *) amp;protosize, buffer, sizeof(unsigned short));
  if(protosize == 0) { break; } // Protocol indicates EOM be setting length to "0"
  void* protomsg[protosize];
  memcpy(protomsg, buffer sizeof(unsigned short), protosize);
  int msglength= sizeof(unsigned short) protosize;
  //Now I'll move the whole buffer to the left so that I don't have to keep track of where I'm at.
  memmove(buffer, buffer msglength, RCVBUFSIZE-msglength);
  protoMsg pm;
  if(!pm.ParseFromArray(protomsg, protosize)) { break; } // Parsing failed.
  // Do stuff with parsed message.
}
  

Теперь у меня есть несколько проблем:

  • Цикл while, принимающий сообщение, никогда не завершается. Я подозреваю, что вызов recv блокируется, когда данных больше не осталось, в то время как я ожидал, что он вернется с ошибкой. Я нашел функцию выбора, чтобы проверить, есть ли что-то для чтения. Я попробую. Но когда я вызываю receive только один раз, чтобы пропустить эту проблему (полученное сообщение содержит ~ 10 байт, поэтому я ожидаю, что все будет собрано за один вызов.) У меня другая проблема:
  • memcpy и memmove, похоже, работают не так, как ожидалось. В первом цикле короткое замыкание обрабатывается, как и ожидалось (я получаю то же значение, которое я отправляю на другую сторону), но затем все, что связано с анализом буфера протокола, завершается неудачей. Я что-то неправильно понял?

Редактировать: что касается комментария о ntohs — в настоящее время я передаю short как little-endian, забыл упомянуть об этом. (Кстати, я еще изменю это.)

Третье редактирование: теперь код работает, но мне пришлось изменить следующее:

 char* buffer[RCVBUFSIZE];
int recvsize= 0;
// Filling my buffer with as much as possible.
while(true) {
  if(recvsize == RCVBUFSIZE) {
    break;
  } else if((recvsize = recv(sock, buffer recvsize, sizeof(buffer)-recvsize, 0)) < 1) {
    break;
  } else if(recvsize > 1) {
    unsigned short msglength= 0;
    memcpy((char *) amp;msglength, buffer recvsize-sizeof(unsigned short), sizeof(unsigned short));
    if(msglength == 0) { break; } // Received a full transmission.
  }
}
  

Итак, сначала мне нужно было добавить скобки вокруг recvsize = recv() оператора, а во-вторых, поскольку неблокирующий метод по какой-то причине не сработал, я сейчас проверяю, переводятся ли последние два переданных байта в 0 при чтении беззнакового короткого. Это, вероятно, приводит к проблеме, если я случайно прочитал 0, который не является полем длины. Вероятно, я начну другой вопрос об этом.

Я также перешел protomsg на char[] , но я не думаю, что это действительно что-то изменило. (У меня уже был синтаксический анализ, работающий с массивом void ..)

Комментарии:

1. Вы должны вызывать ntohs полученный short (и вызывать htons при его отправке — «Сеть к хосту, короткая» /»Хост к сети, короткая»)

Ответ №1:

Если получаемое вами сообщение всегда составляет около 10 байт и RCVBUFSIZE больше, вы никогда не завершите работу, пока не произойдет ошибка чтения данных. Кроме того, buffer переменная в вашем коде представляет собой массив RCVBUFSIZE указателей, вероятно, не то, что вы хотите.

Измените свой код следующим образом:

 #define MINIMALMESSAGESIZE 10  // Or what the size may be
char buffer[RCVBUFSIZE];
int totalrecvsize= 0;
int recvsize= 0;
while(true) {
  if(totalrecvsize >= MINIMALMESSAGESIZE) {
    break;
  } else if(recvsize= recv(sock, buffer totalrecvsize, sizeof(buffer)-totalrecvsize, 0) < 1) {
    break;
  } else {
    totalrecvsize  = recvsize;
  }
}
  

Комментарии:

1. Ах да, я забыл » =» при расшифровке из моего источника, теперь он есть в моем первоначальном вопросе. Я не уверен, как определить минимальный размер сообщения, поскольку я могу это выяснить, только проанализировав поле длины. Это означало бы дополнительный вызов recv, которого я бы предпочел избежать.

2. Хорошо, просто прочитайте вашу правку. Дело в том, что в настоящее время я передаю только один буфер протокола, который имеет размер около 10 байт. При развертывании все от одного до 1000 из них должны быть переданы, и я хотел бы получить как можно больше из них как можно быстрее из сети.

3. @dinyar: не забудьте добавить общий полученный размер в буфер при вызове recv , иначе вы перезапишете то, что уже получили. Кроме того, если вы не знаете размер сообщения, вы можете вечно ждать вызова recv , если, конечно, сокет не блокируется, и в этом случае будет возвращена ошибка.

4. Да, я делаю это, recvsize = recv я думаю. Я рассмотрю возможность сделать сокет неблокирующим, спасибо за совет!

5. @dinyar: Теперь я видел ваш второй комментарий. В этом случае вам нужно только позаботиться о том, чтобы не перезаписывать ваш буфер в цикле приема. 🙂