Передача структуры через сокеты в C
Я пытаюсь передать всю структуру от клиента к серверу или наоборот. Давайте предположим, что моя структура выглядит следующим образом
struct temp {
int a;
char b;
}
Я использую sendto и отправляю адрес структурной переменной и получаю его с другой стороны, используя функцию recvfrom. Но я не могу получить исходные данные, отправленные на принимающей стороне. В функции sendto я сохраняю полученные данные в переменную типа struct 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: я действительно новичок в этой концепции сериализации.. Но без выполнения сериализации я не могу отправить структуру через сокеты?
РЕДАКТИРОВАТЬ 2: Когда я пытаюсь отправить строку или целочисленную переменную, используя функции sendto и recvfrom, я получаю данные правильно на стороне получателя. Почему не в случае структуры? Если мне не нужно использовать функцию сериализации, я должен отправить каждого члена структуры по отдельности? Это действительно не подходящее решение, так как, если есть "n" количество членов, тогда "n" количество строк кода добавляется только для отправки или получения данных.
7 ответов
Это очень плохая идея. Двоичные данные всегда должны отправляться таким образом, чтобы:
- Обращается с разным порядком байтов
- Ручки разные набивочные
- Обрабатывает различия в байтовых размерах внутренних типов
Никогда не пишите всю структуру в двоичном виде, ни в файл, ни в сокет.
Всегда пишите каждое поле отдельно и читайте их одинаково.
Вы должны иметь такие функции, как
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);
Или эквивалент, есть, конечно, несколько способов установить это в отношении управления буфером и так далее. Затем вам нужно выполнить функции более высокого уровня, которые сериализуют / десериализуют целые структуры.
Это предполагает, что сериализация выполняется в / из буферов, что означает, что сериализации не нужно знать, является ли конечный пункт назначения файлом или сокетом. Это также означает, что вы платите некоторые накладные расходы памяти, но, как правило, это хороший дизайн по соображениям производительности (вы не хотите выполнять write() каждого значения в сокет).
Если у вас есть вышеперечисленное, вот как вы можете сериализовать и передать экземпляр структуры:
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.
Использование опции пакета "pragma" решило мою проблему, но я не уверен, есть ли у нее какие-либо зависимости?
#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);
Нет необходимости писать собственные подпрограммы сериализации для short
а также long
целочисленные типы - используйте htons()
/htonl()
POSIX функции.
Если вы не хотите сами писать код сериализации, найдите подходящую платформу сериализации и используйте ее.
Может быть, буферы протокола Google были бы возможны?
Сериализация это хорошая идея. Вы также можете использовать Wireshark для мониторинга трафика и понимания того, что фактически передается в пакетах.
Вместо сериализации и в зависимости от сторонних библиотек легко придумать примитивный протокол, использующий тег, длину и значение.
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, среди прочего, и вы можете получить их все в одном рев... Вы должны разделить данные, например, используя RFC1006.
UDP проще, вы получите отдельный пакет для каждого отправленного пакета, но он намного менее безопасен.
Если формат данных, которые вы хотите передать, очень прост, то преобразование в и из строки ANSI является простым и переносимым.