#c #network-programming #bit-manipulation
#c #сетевое программирование #манипулирование битами
Вопрос:
Предположим, у меня есть три unsigned ints
, { a
, b
, c
, d
}, которые я хочу упаковать с нестандартными длинами, {9,5,7,11} соответственно. Я хочу создать сетевой пакет ( unsigned char pkt[4]
), в который я мог бы упаковать эти значения и надежно распаковать их на другой машине, используя тот же заголовочный файл, независимо от порядкового номера.
Все, что я читал об использовании упакованных структур, предполагает, что порядок битов не будет предсказуемым, так что об этом не может быть и речи. Таким образом, это оставляет мне операции с установкой битов и очисткой битов, но я не уверен в том, как гарантировать, что порядковый номер не вызовет у меня проблем. Достаточно ли следующего, или у меня возникнут проблемы с порядковым номером a
и d
отдельно?
void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *pkt){
uint32_t pkt_h = ((uint32_t)a amp; 0x1FF) // 9 bits
| (((uint32_t)b amp; 0x1F) << 9) // 5 bits
| (((uint32_t)c amp; 0x3F) << 14) // 7 bits
| (((uint32_t)d amp; 0x7FF) << 21); //11 bits
*pkt = htonl(pkt_h);
}
void unpack_pkt(uint16_t *a, uint8_t *b, uint8_t *c, uint16_t *d, uint8_t *pkt){
uint32_t pkt_h = ntohl(*pkt);
(*a) = pkt_h amp; 0x1FF;
(*b) = (pkt_h >> 9) amp; 0x1F;
(*c) = (pkt_h >> 14) amp; 0x3F;
(*d) = (pkt_h >> 21) amp; 0x7FF;
}
Если да, какие еще меры я могу предпринять для обеспечения переносимости?
Ответ №1:
Структуры с битовыми полями действительно практически бесполезны для этой цели, поскольку их порядок полей и даже правила заполнения не согласуются.
должен ли я столкнуться с проблемами с
a
d
порядком и отдельно?
Порядковый a
номер и d
не имеет значения, их порядок байтов никогда не используется. a
и d
не интерпретируются как необработанные байты, используются или присваиваются только их целочисленные значения, и в этих случаях порядковый номер не входит в картину.
Однако существует другая проблема: uint8_t *pkt
в сочетании с *pkt = htonl(pkt_h);
означает, что сохраняется только младший значащий байт (независимо от того, выполняется ли он с помощью машины с малым или большим порядковым номером, потому что это не переинтерпретация, это неявное преобразование). uint8_t *pkt
само по себе нормально, но затем результирующая группа из 4 байтов должна быть скопирована в буфер, на который она указывает, она не может быть назначена всем за один раз. uint32_t *pkt
это позволило бы такому однонаправленному назначению работать без потери данных, но это делает функцию менее удобной в использовании.
Аналогично unpack_pkt
, в настоящее время используется только один байт данных.
Когда эти проблемы будут устранены, это должно быть хорошо:
void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *buffer){
uint32_t pkt_h = ((uint32_t)a amp; 0x1FF) // 9 bits
| (((uint32_t)b amp; 0x1F) << 9) // 5 bits
| (((uint32_t)c amp; 0x3F) << 14) // 7 bits
| (((uint32_t)d amp; 0x7FF) << 21); //11 bits
uint32_t pkt = htonl(pkt_h);
memcpy(buffer, amp;pkt, sizeof(uint32_t));
}
void unpack_pkt(uint16_t *a, uint8_t *b, uint8_t *c, uint16_t *d, uint8_t *buffer){
uint32_t pkt;
memcpy(amp;pkt, buffer, sizeof(uint32_t));
uint32_t pkt_h = ntohl(pkt);
(*a) = pkt_h amp; 0x1FF;
(*b) = (pkt_h >> 9) amp; 0x1F;
(*c) = (pkt_h >> 14) amp; 0x3F;
(*d) = (pkt_h >> 21) amp; 0x7FF;
}
Альтернативой, которая работает, не беспокоясь о порядковом порядке в любой момент, является ручная деконструкция uint32_t
(вместо того, чтобы условно заменять его байтами htonl
, а затем переинтерпретировать его как необработанные байты), например:
void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *pkt){
uint32_t pkt_h = ((uint32_t)a amp; 0x1FF) // 9 bits
| (((uint32_t)b amp; 0x1F) << 9) // 5 bits
| (((uint32_t)c amp; 0x3F) << 14) // 7 bits
| (((uint32_t)d amp; 0x7FF) << 21); //11 bits
// example serializing the bytes in big endian order, regardless of host endianness
pkt[0] = pkt_h >> 24;
pkt[1] = pkt_h >> 16;
pkt[2] = pkt_h >> 8;
pkt[3] = pkt_h;
}
Оригинальный подход неплох, это просто альтернатива, которую стоит рассмотреть. Поскольку ничто никогда не интерпретируется заново, порядковый номер вообще не имеет значения, что может повысить уверенность в правильности кода. Конечно, в качестве недостатка требуется больше кода, чтобы сделать то же самое. Кстати, несмотря на то, что ручная деконструкция uint32_t
и хранение 4 отдельных байтов выглядит как большая работа, GCC может эффективно скомпилировать его в хранилище a bswap
и 32bit. С другой стороны, Clang упускает эту возможность, и другие компиляторы также могут, так что это не лишено недостатков.
Комментарии:
1. Большое спасибо! Ваше объяснение и альтернативный метод действительно помогли мне понять, как все это работает действительно хорошо.
Ответ №2:
для упаковки и упаковки я предлагаю использовать структуру, подобную этой
- помните, что размер структуры отличается на других машинах, таких как 8-битная система и 32-битная система. скомпилируйте ту же структуру с разными размерами, мы называем это заполнением в struct, поэтому вы можете использовать pack, чтобы убедиться, что размер структуры одинаков в передатчике и приемнике
typedef struct {
uint8_t A;
uint8_t B;
uint8_t C;
uint8_t D;
} MyPacket;
теперь вы можете передавать эту структуру в поток байтов, такой как SerialPort или UART или что-то еще
, и в приемнике вы можете упаковывать байты вместе
см. Следующие функции
void transmitPacket(MyPacket* packet) {
int len = sizeof(MyPacket);
uint8_t* pData = (uint8_t*) packet;
while (len-- > 0) {
// send bytes 1 by 1
transmitByte(*pData );
}
}
void receivePacket(MyPacket* packet) {
int len = sizeof(MyPacket);
uint8_t* pData = (uint8_t*) packet;
while (len-- > 0) {
// receive bytes 1 by 1
*pData = receiveByte();
}
}
помните, что порядок битов в байтах везде одинаков, но вы должны проверить свой порядок байтов, чтобы убедиться, что пакет не будет пропущен в приемнике
например, если размер вашего пакета составляет 4 байта, и сначала вы отправляете младший байт, вы должны получить младший байт в приемнике
в вашем коде вы получаете пакет в указателе uint8_t *, но ваш фактический размер пакета равен uint32_t и составляет 4 байта
Комментарии:
1. Привет @ali, спасибо за ваш вклад. На самом деле это не решает проблему отправки из системы с малым конечным порядком в систему с большим конечным порядком. Моя установка также включает в себя 32-разрядный компьютер на одном конце и 64-разрядный компьютер на другом конце.
2. если две конечные системы используют разные конечные системы, такие как little-endian и big-endian, мы должны использовать функцию подкачки байтов для обратных переменных uint16_t или uint32_t, но наши переменные uint8_t одинаковы, и помните, что переменные bits всегда хранятся в пакете, таком как uint32_t
3. Как я могу быть уверен, что порядок упаковки упакованной структуры согласован на обоих устройствах? Насколько мне известно, порядок не определен в стандарте и поэтому не переносим. Смотрите Первый ответ в этом сообщении о переполнении стека
4. @MarcianoPreciado в этом ответе не используются битовые поля, поэтому порядок определен, но он также бесполезен для ваших целей, поскольку в нем не рассматривается использование полей необычного размера