Инструменты пользователя

Инструменты сайта


// Гоняем структуры по сети, часть вторая

Тут уже писал про проблему пересылки/приёмки структурированных данных по сети.

На нашей целевой платформе нельзя применять сторонние библиотеки (типа libpack, как в предыдущей статье), и, так вышло (лучи ненависти), что #pragma pack() то работает, то не работает, да ещё стоит условие, что данные должны быть преобразованы к сетевому виду (ntohs(), ntohl(), htons(), htonl()), поэтому я сделал на макросах такой фит ушами:

/**************************************************************************************************/
/* Паковка и распаковка данных, для передачи по сети ---------------------------------------------*/
/**************************************************************************************************/
 
// Распаковка массива в структуры
/**
 Начало блока распаковки массива данных, на которые указывает \c ptr и размера \c size
 */
#define unpack_begin(ptr, size) { \
            size_t   __addr = 0; \
            size_t   __size = (size_t)(size); \
            uint8_t *__ptr  = (uint8_t*)(ptr); \
            union { \
                uint8_t        cc[8]; \
                uint16_t       ii[4]; \
                uint32_t       ll[2]; \
                float          ff[2]; \
                double         dd;    \
            } __swapd; (void)(__swapd)
 
/**
 Распаковка очередной порции данных в \c target
 */
#define unpack_next(target) \
            pack_printf("Unpack \"%s\"... Size: %d, current offset: %d, current size: %d\n", #target, __size, __addr, sizeof(target)); \
            if ((__addr + (int)sizeof(target)) <= (__size)) \
            { \
                memcpy(&(target), __ptr + __addr, sizeof(target)); \
                __addr += sizeof(target); \
            } (void)(0)
 
/**
 Распаковка очередной порции данных в \c target, с преобразованием из сетевого вида
 Сетевое преобразование работает для простых типов размерос 2 (short), 4 (int, float)
 и 8 (lint32_tlint32_t double) байтint16_t остальных данных
 */
#define unpack_net_next(target) \
            unpack_next(target); \
            switch(sizeof(target)) \
            { \
                case 2: \
                    memcpy(&__swapd.ii[0], &target, 2); \
                    __swapd.ii[0] = ntohs(__swapd.ii[0]); \
                    memcpy(&target, &__swapd.ii[0], 2); \
                    break; \
                case 4: \
                    memcpy(&__swapd.ll[0], &target, 4); \
                    __swapd.ll[0] = ntohl(__swapd.ll[0]); \
                    memcpy(&target, &__swapd.ll[0], 4); \
                    break; \
                case 8: \
                    memcpy(&__swapd.dd, &target, 8); \
                    __swapd.ll[0] = ntohl(__swapd.ll[0]); \
                    __swapd.ll[1] = ntohl(__swapd.ll[1]); \
                    memcpy(&target, &__swapd.dd, 8); \
                    break; \
            } (void)(0)
 
/**
 Конец блока распаковки
 */
#define unpack_end() } (void)(0)
 
 
// Паковка данных из элементов струтуры в массив данных
/**
 Начало блока паковки данных в \c ptr размером \c size
 */
#define pack_begin(ptr, size) unpack_begin(ptr, size)
 
// Внутренний вспомогательный макрос, как общая часть для pack_next()/pack_net_next()
#define _pack_next_intr(source, data) \
            printf("Pack \"%s\"... Size: %d, current offset: %d, current size: %d\n", #source, __size, __addr, sizeof(source)); \
            if ((__addr + (int)sizeof(source)) <= (__size)) \
            { \
                memcpy(__ptr + __addr, &(data), sizeof(source)); \
                __addr += sizeof(source); \
            } (void)(0)
 
/**
 Пакует очередной элемент
 */
#define pack_next(source) \
            _pack_next_intr(source, source)
 
/**
 Пакует очередной элемент, преобразовывая его в сетевой вид
 */
#define pack_net_next(source) \
            switch(sizeof(source)) \
            { \
                case 2: \
                    memcpy(&__swapd.ii[0], &source, 2); \
                    __swapd.ii[0] = htons(__swapd.ii[0]); \
                    _pack_next_intr(source, __swapd.ii[0]); \
                    break; \
                case 4: \
                    memcpy(&__swapd.ll[0], &source, 4); \
                    __swapd.ll[0] = htonl(__swapd.ll[0]); \
                    _pack_next_intr(source, __swapd.ll[0]); \
                    break; \
                case 8: \
                    memcpy(&__swapd.dd, &source, 8); \
                    __swapd.ll[0] = htonl(__swapd.ll[0]); \
                    __swapd.ll[1] = htonl(__swapd.ll[1]); \
                    _pack_next_intr(source, __swapd.dd); \
                    break; \
                default: \
                    pack_next(source); \
                    break; \
            } (void)(0)
 
/**
 Конец блока паковки данных
 */
#define pack_end()            unpack_end()

Можно ещё оптимизировать, тут оверхед с переменными вспомогательными, можно без них в большинстве случаев. Ну и, кажется, не совсем корректная работа с величинами больше 4 байт (double), но на серверной машине, по-моему, вообще залепа сделана, посмотрим как работать будет :)

Пример использования для отсылки данных:

struct Data {
  short     id;
  float      d;
  long      tm;
} data;
 
data.id = 0;
data.d  = 73.3;
data.tm = 0xFEFD;
 
int n;
 
size_t data_size = 2 + 4 + 4; // sizeof(short) + sizeof(float) + sizeof(long) - поля структуры
char *data_ptr;
 
data_ptr = calloc(data_size, sizeof(char));
 
pack_begin(data_ptr, data_size);
  pack_net_next(data.id);
  pack_net_next(data.d);
  pack_net_next(data.tm);
pack_end();
 
n = send(sock, data_ptr, data_size, 0);

Пример получения данных:

struct Data {
  short     id;
  float      d;
  long      tm;
} data;
 
int n;
 
size_t data_size = 2 + 4 + 4; // sizeof(short) + sizeof(float) + sizeof(long) - сколько данных получить нужно
char *data_ptr;
 
data_ptr = calloc(data_size, sizeof(char));
 
n = recv(sock, data_ptr, data_size, 0);
 
unpack_begin(data_ptr, data_size);
  unpack_net_next(data.id);
  unpack_net_next(data.d);
  unpack_net_next(data.tm);
unpack_end();
 
// Тут структура заполнена распакованными данными

Если преобразование в сетевой вид не нужно, используйте pack/unpack_next()

Комментарии