Подтвердить что ты не робот

Передача структуры через Sockets в C

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

struct temp {
  int a;
  char b;
}

Я использую sendto и отправляю адрес структурной переменной и получая ее с другой стороны с помощью функции recvfrom. Но я не могу получить исходные данные, отправленные на принимающей стороне. В функции sendto я сохраняю полученные данные в переменной типа temp temp.

n = sendto(sock, &pkt, sizeof(struct temp), 0, &server, length);
n = recvfrom(sock, &pkt, sizeof(struct temp), 0, (struct sockaddr *)&from,&fromlen);

Где pkt - это переменная типа struct temp.

Хотя я получаю 8байт данных, но если я пытаюсь распечатать, это просто показывает значения мусора. Любая помощь для исправления на нем?

ПРИМЕЧАНИЕ. Нет сторонних библиотек.

EDIT1: Я действительно новичок в этой концепции сериализации. Но, не делая сериализации, я могу отправить структуру через сокеты?

EDIT2: Когда я пытаюсь отправить строку или целочисленную переменную с помощью функций sendto и recvfrom, я правильно получаю данные приемник конец. Почему не в случае структуры? Если мне не нужно использовать функцию сериализации, я должен отправлять каждый член структуры отдельно? Это действительно не подходящее решение, так как если есть "n" число членов, тогда существует "n" количество строк кода, добавленных только для отправки или получения данных.

4b9b3361

Ответ 1

Это очень плохая идея. Двоичные данные всегда должны быть отправлены таким образом, чтобы:

Никогда не пишите целую структуру двоичным образом, а не в файл, а не в сокет.

Всегда записывайте каждое поле отдельно и читайте их одинаково.

Вам нужно иметь такие функции, как

unsigned char * serialize_int(unsigned char *buffer, int value)
{
  /* Write big-endian int value into buffer; assumes 32-bit int and 8-bit char. */
  buffer[0] = value >> 24;
  buffer[1] = value >> 16;
  buffer[2] = value >> 8;
  buffer[3] = value;
  return buffer + 4;
}

unsigned char * serialize_char(unsigned char *buffer, char value)
{
  buffer[0] = value;
  return buffer + 1;
}

unsigned char * serialize_temp(unsigned char *buffer, struct temp *value)
{
  buffer = serialize_int(buffer, value->a);
  buffer = serialize_char(buffer, value->b);
  return buffer;
}

unsigned char * deserialize_int(unsigned char *buffer, int *value);

Или эквивалент, конечно, есть несколько способов установить это в отношении управления буфером и так далее. Затем вам нужно выполнить функции более высокого уровня, которые сериализуют/десериализуют целые структуры.

Это предполагает, что сериализация выполняется в/из буферов, что означает, что сериализация не требуется знать, является ли конечный пункт назначения файлом или сокетом. Это также означает, что вы оплачиваете некоторые издержки памяти, но обычно это хороший дизайн по соображениям производительности (вы не хотите делать запись() каждого значения в сокет).

После того, как вы получили выше, вот как вы можете сериализовать и передать экземпляр структуры:

int send_temp(int socket, const struct sockaddr *dest, socklen_t dlen,
              const struct temp *temp)
{
  unsigned char buffer[32], *ptr;

  ptr = serialize_temp(buffer, temp);
  return sendto(socket, buffer, ptr - buffer, 0, dest, dlen) == ptr - buffer;
}

Несколько замечаний об этом:

  • Строка для отправки сначала сериализуется полем по полю в buffer.
  • Процедура сериализации возвращает указатель на следующий свободный байт в буфере, который мы используем для вычисления количества байтов, которые он сериализовал для
  • Очевидно, что мои примерные процедуры сериализации не защищают от переполнения буфера.
  • Возвращаемое значение равно 1, если вызов sendto() преуспел, иначе он будет равен 0.

Ответ 2

Использование опции "pragma" pack решило мою проблему, но я не уверен, есть ли у нее какие-либо зависимости.

#pragma pack(1)   // this helps to pack the struct to 5-bytes
struct packet {
int i;
char j;
};
#pragma pack(0)   // turn packing off

Тогда следующие строки кода отлично работали без проблем

n = sendto(sock,&pkt,sizeof(struct packet),0,&server,length);

n = recvfrom(sock, &pkt, sizeof(struct packet), 0, (struct sockaddr *)&from, &fromlen);

Ответ 3

Если вы не хотите сами писать код сериализации, найдите правильную структуру сериализации и используйте это.

Может быть, Google протокольные буферы возможно?

Ответ 4

Нет необходимости писать собственные процедуры сериализации для short и long целых типов - используйте функции htons()/htonl() POSIX.

Ответ 5

Если вам нужна переносимость (чтение данных на других платформах/архитектурах), то вы должны сериализовать каждый элемент по отдельности из-за истечения конца и структуры.

Вот пример использования Binn:

  binn *obj;

  // create a new object
  obj = binn_object();

  // add values to it
  binn_object_set_int32(obj, "id", 123);
  binn_object_set_str(obj, "name", "Samsung Galaxy Charger");
  binn_object_set_double(obj, "price", 12.50);
  binn_object_set_blob(obj, "picture", picptr, piclen);

  // send over the network
  send(sock, binn_ptr(obj), binn_size(obj));

  // release the buffer
  binn_free(obj);

Это всего лишь 2 файла (binn.c и binn.h), поэтому его можно скомпилировать с помощью проекта вместо использования в качестве общей библиотеки.

Для сокетов TCP также полезно использовать кадрирование сообщений (также известное как кадрирование с префиксом длины).

Ответ 6

Сериализация - хорошая идея. Вы также можете использовать "wirehark" для отслеживания трафика и понимания того, что действительно передается в пакетах.

Ответ 7

Вместо сериализации и в зависимости от сторонних библиотек легко найти примитивный протокол с использованием тега, длины и значения.

Tag: 32 bit value identifying the field
Length: 32 bit value specifying the length in bytes of the field
Value: the field

При необходимости соедините. Используйте перечисления для тегов. И используйте сетевой порядок байтов...

Легко кодируется, легко декодируется.

Также, если вы используете TCP, помните, что это поток данных, поэтому, если вы отправляете, например. 3 пакета вы не обязательно получите 3 пакета. Они могут быть "объединены" в поток в зависимости от алгоритма nodelay/nagel среди других вещей, и вы можете получить их все в одном recv... Вам нужно разграничить данные, например, с помощью RFC1006.

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

Ответ 8

Если формат данных, которые вы хотите перенести, очень прост, преобразование в строку ANSI и из нее является простым и портативным.