Что такое переносимый способ преобразования порядка байтов в C - PullRequest
2 голосов
/ 20 декабря 2009

Я пытаюсь написать сервер, который будет взаимодействовать с любым стандартным клиентом, который может устанавливать сокетные соединения (например, клиент telnet)

Он начинался как эхо-сервер, которому, конечно, не нужно было беспокоиться о порядке сетевых байтов.

Я знаком с функциями ntohs, ntohl, htons, htonl. Они были бы хороши сами по себе, если бы я передавал 16- или 32-битные целые числа или если символы в отправляемой строке были кратны 2 или 4 байта.

Я бы хотел создать функцию, которая работает с такими строками, как:

str_ntoh(char* net_str, char* host_str, int len)
{
    uint32_t* netp, hostp;
    netp = (uint32_t*)&net_str;
    for(i=0; i < len/4; i++){
         hostp[i] = ntoh(netp[i]);
    }
}

Или что-то подобное. Вышеуказанное предполагает, что размер слова 32-битный. Мы не можем быть уверены, что размер слова на отправляющем компьютере не 16-битный или 64-битный, верно?

Для клиентских программ, таких как telnet, они должны использовать hton * перед отправкой и ntoh * после получения данных, правильно?

РЕДАКТИРОВАТЬ: Для людей это дело, потому что 1-символ является байтом, который не имеет значения порядка байтов:

int main(void)
{
    uint32_t a = 0x01020304;
    char* c = (char*)&a;
printf("%x %x %x %x\n", c[0], c[1], c[2], c[3]);

}

Запустите этот фрагмент кода. Вывод для меня следующий:

$ ./a.out
  4 3 2 1

Те, кто на чипсетах PowerPC должны получить '1 2 3 4', но те из нас, кто на чипсете Intel, должны видеть то, что я получил выше.

Ответы [ 4 ]

17 голосов
/ 20 декабря 2009

Может быть, я что-то здесь упускаю, но вы посылаете строки, то есть последовательности символов? Тогда вам не нужно беспокоиться о порядке байтов. Это только для битовой комбинации в целых числах. Символы в строке всегда находятся в «правильном» порядке.

EDIT:

Деррик, в качестве примера вашего кода я запустил следующую (слегка расширенную) версию вашей программы на Intel i7 (little-endian) и на старом Sun Sparc (big-endian)

#include <stdio.h>
#include <stdint.h> 

int main(void)
{
    uint32_t a = 0x01020304;
    char* c = (char*)&a;
    char d[] = { 1, 2, 3, 4 };
    printf("The integer: %x %x %x %x\n", c[0], c[1], c[2], c[3]);
    printf("The string:  %x %x %x %x\n", d[0], d[1], d[2], d[3]);
    return 0;
}

Как видите, я добавил в вашу распечатку целого числа вещественный массив символов.

Вывод из младшей последовательности Intel i7:

The integer: 4 3 2 1
The string:  1 2 3 4

И вывод Солнца с прямым порядком байтов:

The integer: 1 2 3 4
The string:  1 2 3 4

Ваше многобайтовое целое число действительно хранится в разных байтах на двух машинах, но символы в массиве char имеют одинаковый порядок.

4 голосов
/ 20 декабря 2009

С вашей подписью функции, как опубликовано, вам не нужно беспокоиться о порядке байтов. Он принимает символ *, который может обрабатывать только 8-битные символы. С одним байтом на символ не может быть проблем с порядком байтов.

Вы столкнетесь с проблемой порядка байтов, только если отправите Unicode в кодировке UTF16 или UTF32. И порядковый номер отправляющего аппарата не совпадает с порядковым номером принимающего. Простым решением для этого является использование кодировки UTF8. Который является тем, что большая часть текста отправляется как через сети. Будучи ориентированным на байты, он также не имеет проблемы с порядком байтов. Или вы можете отправить спецификацию.

2 голосов
/ 20 декабря 2009

Если вы хотите отправить их в виде 8-битной кодировки (тот факт, что вы используете char подразумевает, что это именно то, что вы хотите), нет необходимости в байт-свопе. Однако для несвязанной проблемы не-ASCII-символов, чтобы один и тот же символ > 127 выглядел одинаково на обоих концах соединения, я бы посоветовал отправить данные в виде, подобном UTF-8, который может представлять все символы Юникода и может быть безопасно обработан как строки ASCII. Способ получения текста UTF-8 на основе кодировки по умолчанию зависит от платформы и набора используемых библиотек.

Если вы отправляете 16-битную или 32-битную кодировку ... Вы можете включить один символ с меткой порядка следования байтов , которую другой конец может использовать для определения порядкового номера символа. Или вы можете принять сетевой порядок байтов и использовать htons() или htonl(), как вы предлагаете. Но если вы хотите использовать char, см. Предыдущий абзац. : -)

1 голос
/ 20 декабря 2009

Мне кажется, что прототип функции не соответствует ее поведению. Вы передаете символ *, но затем приводите его к uint32_t *. И, присмотревшись повнимательнее, вы приводите адрес указателя, а не его содержимое, поэтому я обеспокоен тем, что вы получите неожиданные результаты. Возможно, следующее будет работать лучше:

arr_ntoh(uint32_t* netp, uint32_t* hostp, int len)
  {
  for(i=0; i < len; i++)
    hostp[i] = ntoh(netp[i]);
  }

Я основываю это на предположении, что у вас действительно есть массив uint32_t, и вы хотите запустить ntoh () на всех них.

Надеюсь, это полезно.

...