Эффективное программирование TCP-IP

       

Не забывайте о порядке байтов


| | |

В современных компьютерах целые числа хранятся по-разному, в зависимости от архитектуры. Рассмотрим 32-разрядное число 305419896 (0x12345678). Четыре байта этого числа могут храниться двумя способами: сначала два старших байта {такой порядок называется тупоконечным - big endian)

12 34   56   78

или сначала два младших байта (такой порядок называется остроконечным - little endian)

78 56   34   12

Примечание: Термины «тупоконечный» и «остроконечный» ввел Коэн [Cohen П1981], считавший, что споры о том, какой формат лучше, сродни распрям лилипутов из романа Свифта «Путешествия Гулливера», которые вели бесконечные войны, не сумев договориться, с какого конца следует разбивать яйцо — с тупого или острого. Раньше были в ходу и другие форматы, но практически во все современных машинах применяется либо тупоконечный, либо остроконечный порядок байтов.

Определить формат, применяемый в конкретной машине, можно с помощью следующей текстовой программы, показывающей, как хранится число 0х12345б78 (листинг 3.34).

Листинг 3.34. Программа для определения порядка байтов

endian.c

1    #include <stdio.h>

2    #include <sys/types.h>

3    #include "etcp.h"

4    int main( void )

5    {

6    u_int32_t x = 0x12345678;   /* 305419896 */

7    unsigned char *xp = ( char * )&x;



9    printf( "%0x %0x %0x %0x\n",

10   xp[ 0 ], xp[ 1 ], xp[ 2 ], xp[ 3 ] );

11   exit(   0   );

12   }

Если запустить эту программу на компьютере с процессором Intel, то получится:

bsd:   $  endian

78   56   34   12

bsd:    $

Отсюда ясно видно, это - остроконечная архитектура.

Конкретный формат хранения иногда в шутку называют полом байтов. Он важен, поскольку остроконечные и тупоконечные машины (а равно те, что используют иной порядок) часто общаются друг с другом по протоколам TCP/IP- Поскольку такая информация, как адреса отправления и назначения, номера портов, длина датаграмм, размеры окон и т.д., представляется в виде целых чисел, необходимо, чтобы обе стороны интерпретировали их одинаково.


Чтобы обеспечить взаимодействие компьютеров с разными архитектурами» все целочисленные величины, относящиеся к протоколам, передаются в сетевом порядке байтов, который по определению является тупоконечным. По большей части, обо всем заботятся сами протоколы, но сетевые адреса, номера портов, а иногда и другие данные, представленные в заголовках, вы задаете сами. И всякий раз необходимо преобразовывать их в сетевой порядок.
Для этого служат две функции, занимающиеся преобразованием из машинного порядка байт в сетевой и обратно. Представленные ниже объявления этих функций заимствованы из стандарта POSIX. В некоторых версиях UNIX эти объявления находятся не в файле netinet/in.h. Типы uint32_t и uint16_t приняты в POSIX соответственно для без знаковых 32- и 16-разрядных целых. В некоторых реализациях эти типы могут отсутствовать. Тем не менее функции htonl и ntohl всегда принимают и возвращают беззнаковые 32-разрядные целые числа, будь то UNIX или Winsock. Точно так же функции htons и ntohs всегда принимают и возвращают беззнаковые 16-разрядные целые.
Примечание: Буквы «l» и «s» в конце имен функций означают long (длинное) и short (короткое). Это имело смысл, так как первоначально данные функции появились в системе 4.2BSD, разработанной для 32-разрядной машины, где длинное целое принимали равным 32 бит, а короткое - 16. С появлением 64-разрядных машин это уже не так важно, поэтому следует помнить, что 1-функции работают с 32-разрядными числами, которые не обязательно представлены как long, а s-функции - с 16разрядными числами, которые не обязательно представлены в виде short. Удобно считать, что 1-функции предназначены для преобразования длинных полей в заголовках протокола, а s-функции - коротких полей.
#include  <netinet/in.h>    /*  UNIX   */
#include  <winsock2 .h> /*  Winsock  */
uint32_t  htonl(   uint32_t  host32   );
uint16_t  htons(   uint16_t  host16  );
Обе функции возвращают целое число в сетевом порядке.
uint32_t  ntohl(   uint32_t  network32   ) ;


uint16_t  ntohs(   uint16_t  network16  );
Обе функции возвращают целое число в машинном порядке.
Функции htonl и htons преобразуют целое число из машинного порядка байт в сетевой, тогда как функции ntohl и ntohs выполняют обратное преобразова­ние. Заметим, что на «тупоконечных» машинах эти функции ничего не делают И обычно определяются в виде макросов:
#define htonl(x)   (x)
На «остроконечных» машинах (и для иных архитектур) реализация функций зависит от системы. Не надо задумываться, на какой машине вы работаете, по­скольку эти функции всегда делают то, что нужно.
Применение этих функций обязательно только для полей, используемых протоколами. Пользовательские данные для протоколов IP, UDP и TCP выглядят как множество неструктурированных байтов, так что неважно, записаны целые числа в сетевом или машинном порядке. Тем не менее функции ntoh* и hton* стоит применять при передаче любых данных, поскольку тем самым вы обеспечиваете возможность совместной работы машин с разной архитектурой. Даже если сначала предполагается, что приложение будет работать только на одной платформе обязательно настанет день, когда его придется переносить на другую платформу. Тогда дополнительные усилия окупятся с лихвой.
Примечание: В общем случае проблема преобразования данных между машинами с разными архитектурами сложна. Многие программисты решают ее, преобразуя все числа в код ASCII (или, возможно, в код EBCDIC для больших машин фирмы IBM). Другой подход связан с использованием компоненты XDR (External Data Representation -внешнее представление данных), входящей в состав подсистемы вызова удаленных процедур (RFC - remote procedure call), разработанной фирмой Sun. Компонента XDR определена в RFC 1832 [Srinivasan 1995] и представляет собой набор правил для кодирования данных различных типов, а также язык, описывающий способ кодирования. Хотя предполагалось, что XDR будет применяться как часть RPC, можно пользоваться этим механизмом в ваших программах. В книге [Stevens 1999] обсуждается XDR и его применение без RPC.
И, наконец, следует помнить, что функции разрешения имен, такие как gethostbyname и getservbyname (совет 29), возвращают значения, представ­ленные в сетевом порядке. Поэтому следующий неправильный код
struct  servant   *sp;
struct  sockaddr_in  *sap;
sp  = getservbyname(  name,   protocol   );
sap->sin_port  =  htons(   sp->s_port   );
приведет к ошибке, если исполняется не на «тупоконечной» машине.

Содержание раздела