Создание и отправка пакетов данных в 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 объявляется как экземпляр структуры, а не указатель на структуру.