Преобразование данных в big endian

Я использую WinSock для отправки UDP-пакетов на сервер, мне нужно отправлять данные с прямым порядком байтов. Я не уверен, как преобразовать порядок байтов в моей структуре перед отправкой.

У меня есть такая структура:

struct ConnectIn
{
    std::int64_t ConnectionID = 0x41727101980;
    std::int32_t Action = 0;
    std::int32_t TransactionID;

    ConnectIn(std::int32_t transactionID)
    {
        TransactionID = transactionID;
    }
};

А сейчас отправляю вот так:

ConnectIn msg(123);
int len = sizeof(msg);
int bytesSent = sendto(s, (char*)&msg, len, 0, (SOCKADDR*)&dest, sizeof(address));

Как преобразовать порядок байтов msg в прямой порядок байтов перед отправкой?

Если вам интересно, данные, которые я отправляю, предназначены для протокола UDP-трекера Bit Torrent.


person Drahcir    schedule 21.10.2014    source источник
comment
Вы можете посмотреть семейство hton функций.   -  person Jarod42    schedule 21.10.2014
comment
возможный дубликат передачи структуры через сокеты в C   -  person edmz    schedule 21.10.2014
comment
@black - не думаю, что это повторяющийся вопрос. Другой вопрос касается C, а не C++. Также другой вопрос касается сериализации или чего-то в этом роде, это плохо сформулировано с плохим английским языком, его нелегко понять.   -  person Drahcir    schedule 21.10.2014
comment
Отправка необработанной структуры через сокет (или сохранение ее в файле или что-то в этом роде) обычно является плохой идеей. Рано или поздно что-то изменится в структуре по множеству возможных причин, и тогда ваш протокол / формат сломается. Сериализуйте структуру в массив байтов, отправьте его, затем при получении и десериализуйте из полученного массива байтов.   -  person hyde    schedule 21.10.2014
comment
Вам необходимо сначала указать свой протокол на уровне бит, а затем строго его реализовать. Вы не можете отправлять структуры напрямую, поскольку структуры не имеют универсального представления в виде битовых строк. Целые числа делают, поэтому вам нужно сериализовать последовательность целых чисел разного размера (поля структуры) в битовую строку и отправить ее.   -  person n. 1.8e9-where's-my-share m.    schedule 21.10.2014
comment
@ n.m. Простите, я не понимаю. Протокол предназначен для торрент-трекеров UDP, он уже указан - ссылка под вопросом.   -  person Drahcir    schedule 21.10.2014
comment
Хорошо, поэтому вам нужно реализовать существующий протокол, просто пропустите указание части протокола.   -  person n. 1.8e9-where's-my-share m.    schedule 21.10.2014


Ответы (3)


Если вы хотите сделать это вручную, то вам нужно поменять местами каждого участника индивидуально. Вы преобразуете элементы из порядка байтов главного компьютера в порядок байтов в сети. В Win32 htonll() предназначен для 64-битных целых чисел, а htonl() - для 32-битных целых чисел:

#include <Winsock2.h>

ConnectIn msg(123);

msg.ConnectionID = htonll(msg.ConnectionID);
msg.Action = htonl(msg.Action);
msg.TransactionID= htonl(msg.TransactionID);

Затем вы также можете отправить членов по отдельности, чтобы не полагаться на структуру структуры хост-системы. Windows ABI не вставляет никаких отступов в эту структуру, но, возможно, для какой-либо другой структуры, которую вы используете, это делает. Итак, вот основная идея:

char buf[sizeof msg.ConnectionID + sizeof msg.Action + sizeof msg.TransactionID];
char *bufi = buf;

std::memcpy(bufi, &msg.ConnectionID, sizeof msg.ConnectionID);
bufi += sizeof msg.ConnectionID;
std::memcpy(bufi, &msg.Action, sizeof msg.Action);
bufi += sizeof msg.Action;
std::memcpy(bufi, &msg.TransactionID, sizeof msg.TransactionID);
bufi += sizeof msg.TransactionID;

int len = sizeof buf;
int bytesSent = sendto(s, buf, len, 0, (SOCKADDR*)&dest, sizeof(address));

Затем на принимающей стороне вы используете соответствующие ntoh*() функции для 64-битных и 32-битных типов, чтобы преобразовать сетевой порядок байтов в порядок байтов принимающего хоста.

person bames53    schedule 21.10.2014
comment
Вы сказали Если вы хотите сделать это вручную, означает ли это, что есть альтернатива ручному? - person Drahcir; 21.10.2014
comment
@Drahcir В качестве альтернативы вы можете использовать сериализацию или сетевую библиотеку, которая позаботится об этом за вас. - person bames53; 21.10.2014
comment
Обратите внимание, что вы используете параметр адреса назначения sendto. Это подразумевает протокол дейтаграммы, такой как UDP. IOW, код в этом ответе отправляет 3 отдельных пакета вместо одного, что, вероятно, не то, чего хочет спрашивающий. - person hyde; 21.10.2014
comment
В конце концов, я решил написать свою собственную функцию-шаблон, чтобы она могла менять местами байты других типов, но спасибо, что поделились со мной общей идеей. - person Drahcir; 22.10.2014
comment
@Drahcir Да, функция шаблона - хороший способ реализовать это без лишних повторений. Обычно это то, что я делаю сам, если в проекте еще нет метода. - person bames53; 22.10.2014

Да, сетевой порядок байтов (NBO) является прямым порядком байтов, поэтому вам нужно найти способ отправить эту структуру в Интернет.

То, что вы сейчас делаете, не сработает: вы отправляете всю структуру, но у получателя может быть другой порядок байтов, отступы и т. Д.
Самые простые варианты:

  • Отправка каждого поля с макетом, определяемым протоколом
  • Сторонние библиотеки, которые обрабатывают сериализацию: Google Protobuf - один из самых распространенных.

Для первого варианта есть несколько функций, которые заботятся об этом в библиотеке Winsock2. Эти:

  • (WSA) ntoh t (от сети к хосту t, где t может быть short и unsigned)
  • (WSA) hton t (от хоста к сети t, где t может быть short и unsigned)

Функции WSA немного отличаются и предназначены только для Windows.


Руководство по сетевому программированию
Справочник по Winsock

person edmz    schedule 21.10.2014
comment
Отправка всей структуры действительно работает для этого протокола, моя структура использует макет, определенный протоколом (за исключением порядка байтов), хотя после прочтения всех комментариев я могу понять, почему это может быть плохой практикой. - person Drahcir; 22.10.2014
comment
Google Protobuf выглядит неплохо, но я не думаю, что смогу использовать его для этого. Я думаю, что серверу также придется изменить протокол, чтобы принять этот новый сериализованный формат, это правильно? - person Drahcir; 22.10.2014
comment
@Drahcir Я думаю, да; Хотя я не уверен. - person edmz; 22.10.2014

Один из вариантов - преобразовать каждое из чисел по отдельности.

Для GCC:

int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

Для MSVC:

unsigned short _byteswap_ushort(unsigned short value);
unsigned long _byteswap_ulong(unsigned long value);
unsigned __int64 _byteswap_uint64(unsigned __int64 value);
person lisu    schedule 21.10.2014
comment
Только так вы преобразовываете данные. Однако эти данные могут уже быть в NBO. - person edmz; 21.10.2014
comment
@lisu: ваша платформа может уже быть с прямым порядком байтов, поэтому bswap преобразует ваши данные с прямым порядком байтов. - person Jarod42; 21.10.2014
comment
Хорошо, я понимаю, что вы имеете в виду, но вопрос конкретно о преобразовании в big endian, поэтому я не упомянул об этом. - person lisu; 21.10.2014
comment
@lisu Действительно. Эти функции не имеют понятия о BE / LE. - person edmz; 21.10.2014