Создание и отправка пакетов данных в C/С++
Скажем, я хочу отправить следующие данные в сокет, используя C или С++, все в одном пакете:
Headers
-------
Field 1: 2 byte hex
Field 2: 2 byte hex
Field 3: 4 byte hex
Data
----
Field1 : 2 byte hex
Field1 : 8 byte hex
Каким будет код, похожий на создание и отправку пакета, содержащего все эти данные?
Ответы
Ответ 1
Предположим, что ваша программа уже организована, чтобы заголовок содержался в одном struct
и данных в другом struct
. Например, у вас могут быть такие структуры данных:
#include <stdint.h>
struct header {
uint16_t f1;
uint16_t f2;
uint32_t f3;
};
struct data {
uint16_t pf1;
uint64_t pf2;
};
Позвоните в эту организацию "формат хоста". Для меня действительно не имеет значения, что такое формат хоста, если это полезно для остальной части вашей программы. Позвоните в формат, который вы передадите в send()
вызов "сетевой формат". (Я выбрал эти имена в соответствии с htons
(междугородняя-короткая) и htonl
(имена, основанные на сети).
Вот некоторые функции преобразования, которые могут оказаться удобными. Каждый из них преобразует структуры вашего хоста в буфер сетевого формата.
#include <arpa/inet.h>
#include <string.h>
void htonHeader(struct header h, char buffer[8]) {
uint16_t u16;
uint32_t u32;
u16 = htons(h.f1);
memcpy(buffer+0, &u16, 2);
u16 = htons(h.f2);
memcpy(buffer+2, &u16, 2);
u32 = htonl(h.f3);
memcpy(buffer+4, &u32, 4);
}
void htonData(struct data d, char buffer[10]) {
uint16_t u16;
uint32_t u32;
u16 = htons(d.pf1);
memcpy(buffer+0, &u16, 2);
u32 = htonl(d.pf2>>32);
memcpy(buffer+2, &u32, 4);
u32 = htonl(d.pf2);
memcpy(buffer+6, u32, 4);
}
void htonHeaderData(struct header h, struct data d, char buffer[18]) {
htonHeader(h, buffer+0);
htonData(d, buffer+8);
}
Чтобы отправить свои данные, сделайте следующее:
...
char buffer[18];
htonHeaderData(myPacketHeader, myPacketData, buffer);
send(sockfd, buffer, 18, 0);
...
Опять же, вам не нужно использовать структуры header
и data
, которые я определил. Просто используйте то, что требуется вашей программе. Ключ в том, что у вас есть функция преобразования, которая записывает все данные в четко определенных смещениях в четко определенном порядке байтов в буфер и передает этот буфер функции send().
С другой стороны сетевого подключения вам понадобится программа для интерпретации полученных ею данных. С этой стороны вам нужно написать соответствующие функции (ntohHeader
и т.д.). Эта функция будет memcpy
битов из буфера и в локальную переменную, которая может перейти к ntohs
или ntohl
. Я оставлю эти функции для вас.
Ответ 2
Ну, как правило, было бы похоже, что он готовит эту структуру пакетов в буфер памяти (что делает разумным вызов семейства функций htonl
).
Если бы затем использовать функции send
, sendto
, sendmsg
или write
, надеюсь, с большим вниманием к длине буфера и хорошей обработке ошибок/отчетности.
(Или один из Win32 apis для отправки, если это таргетинг на целевые формы.)
Вы найдете хорошее представление обо всем этом в Beej Guide to Network Programming.
В частности, для части упаковки байтов (с учетом конца) просмотрите тему serialization. (Там более подробно в этом разделе, чем то, что вам нужно для простых целых типов данных фиксированного размера.
Ответ 3
Код будет выглядеть по-разному в зависимости от сетевой библиотеки ОС (* nix использует сокеты Berkeley, Windows использует Winsock и т.д.). Однако вы можете создать структуру, содержащую все данные, которые вы хотите отправить в пакете, например,
typedef struct
{
short field1;
short field2;
int field3;
} HeaderStruct;
typedef struct
{
short field1;
long long field2;
} PacketDataStruct;
предполагая 32-разрядный размер int.
Edit:
Как кто-то любезно напомнил мне в комментариях, не забывайте о преобразовании в Network Order. Библиотеки сети будут иметь функции для этого, такие как ntohs
, nothl
, htons
и htonl
.
Ответ 4
Один простой ответ заключается в том, что он будет отправлен в формате, который ожидает получатель. Тем не менее, это вызывает вопрос. Предполагая, что данные являются фиксированным размером, как показано, и ожидающая сторона ожидает, вы можете использовать структуру упакованного (1 байт) и хранить данные в каждом поле. Причина использования 1-байтового выравнивания состоит в том, что обычно проще убедиться, что оба конца ожидают одни и те же данные. Без выравнивания по 1 байту структура, возможно, будет выглядеть по-разному в зависимости от параметров компилятора, 32-разрядной и 64-битной архитектуры и т.д.). И, как правило, ожидается, что вы отправите значения в порядке сетевого байта, если шестнадцатеричные значения являются целыми числами. Вы можете использовать такие функции, как htons
и htonl
(и, возможно, htobe64
, если они доступны) для их преобразования.
Предполагая, что данные находятся в структуре с желаемым порядком байта, тогда вызов отправки может быть примерно таким:
ret = send( socket, &mystruct, sizeof( mystruct ), 0 );
Предполагается, что mystruct
объявляется как экземпляр структуры, а не указатель на структуру.