Уильям Стивенс - UNIX: разработка сетевых приложений
В следующей главе мы увидим, что размер структуры адреса сокета в действительности имеет тип socklen_t, а не int, но POSIX рекомендует определять socklen_t как uint32_t.
2. Четыре функции accept, recvfrom, getsockname и getpeername передают структуру адреса сокета от ядра к процессу, то есть в направлении, противоположном предыдущему случаю. Этим функциям передается указатель на структуру адреса сокета и указатель на целое число, содержащее размер структуры, как показано в следующем примере:
struct sockaddr_un cli; /* домен Unix */
socklen_t len;
len = sizeof(cli); /* len - это значение */
getpeername(unixfd, (SA*)&cli, &len);
/* значение len могло измениться */
Причина замены типа для аргумента «длина» с целочисленного на указатель состоит в том, что «длина» эта является и значением при вызове функции (сообщает ядру размер структуры, так что ядро при заполнении структуры знает, где нужно остановиться), и результатом, когда функция возвращает значение (сообщает процессу, какой объем информации ядро действительно сохранило в этой структуре). Такой тип аргумента называется аргументом типа «значение-результат» (value-result argument). На рис. 3.3 представлен этот сценарий.
Рис. 3.3. Структура адреса сокета, передаваемая от ядра к процессу
Пример аргументов типа «значение-результат» вы увидите в листинге 4.2.
Если при использовании аргумента типа «значение-результат» для длины структуры структура адреса сокета имеет фиксированную длину (см. рис. 3.1), то значение, возвращаемое ядром, будет всегда равно этому фиксированному размеру: 16 для sockaddr_in IPv4 и 24 для sockaddr_in6 IPv6. Для структуры адреса сокета переменной длины (например, sockaddr_un домена Unix) возвращаемое значение может быть меньше максимального размера структуры (вы увидите это в листинге 15.2).
ПРИМЕЧАНИЕМы говорили о структурах адресов сокетов, передаваемых между процессом и ядром. Для такой реализации, как 4.4BSD, где все функции сокетов являются системными вызовами внутри ядра, это верно. Но в некоторых реализациях, особенно в System V, функции сокетов являются лишь библиотечными функциями, которые выполняются как часть обычного пользовательского процесса. То, как эти функции взаимодействуют со стеком протоколов в ядре, относится к деталям реализации, которые обычно нас не волнуют. Тем не менее для простоты изложения мы будем продолжать говорить об этих структурах как о передаваемых между процессом и ядром такими функциями, как bind и connect. (В разделе В.1 вы увидите, что реализации System V действительно передают пользовательские структуры адресов сокетов между процессом и ядром, но как часть сообщений потоков STREAMS.)
Существует еще две функции, передающие структуры адресов сокетов: это recvmsg и sendmsg (см. раздел 14.5). Однако при их вызове поле длины не является отдельным аргументом функции, а передается как одно из полей структуры.
В сетевом программировании наиболее общим примером аргумента типа «значение-результат» может служить длина возвращаемой структуры адреса сокета. Вы встретите и другие аргументы типа «значение-результат»:
■ Три средних аргумента функции select (раздел 6.3).
■ Аргумент «длина» для функции getsockopt (см. раздел 7.2).
■ Элементы msg_namelen и msg_controllen структуры msghdr при использовании с функцией recvmsg (см. раздел 14.5).
■ Элемент ifc_len структуры ifconf (см. листинг 17.1).
■ Первый из двух аргументов длины в функции sysctl (см. раздел 18.4).
3.4. Функции определения порядка байтов
Рассмотрим 16-разрядное целое число, состоящее из двух байтов. Возможно два способа хранения этих байтов в памяти. Такое расположение, когда первым идет младший байт, называется прямым порядком байтов (little-endian), а когда первым расположен старший байт — обратным порядком байтов (big-endian). На рис. 3.4 показаны оба варианта.
Рис. 3.4. Прямой и обратный порядок байтов для 16-разрядного целого числа
Сверху на этом рисунке изображены адреса, возрастающие справа налево, а снизу — слева направо. Старший бит (most significant bit, MSB) является в 16-разрядном числе крайним слева, а младший бит (least significant bit, LSB) — крайним справа.
ПРИМЕЧАНИЕТермины «прямой порядок байтов» и «обратный порядок байтов» указывают, какой конец многобайтового значения — младший байт или старший — хранится в качестве начального адреса значения.
К сожалению, не существует единого стандарта порядка байтов, и можно встретить системы, использующие оба формата. Способ упорядочивания байтов, используемый в конкретной системе, мы называем порядком байтов узла (host byte order). Программа, представленная в листинге 3.5, выдает порядок байтов узла.
Листинг 3.5. Программа для определения порядка байтов узла
//intro/byteorder.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 union {
6 short s;
7 char c[sizeof(short)];
8 } un;
9 un.s = 0x0102;
10 printf("%s: ", CPU_VENDOR_OS);
11 if (sizeof(short) == 2) {
12 if (un.c[0] == 1 && un.c[1] == 2)
13 printf("big-endiann");
14 else if (un.c[0] == 2 && un.c[1] == 1)
15 printf("little-endiann");
16 else
17 printf("unknownn");
18 } else
19 printf('sizeof(short) = %dn", sizeof(short));
20 exit(0);
21 }
Мы помещаем двухбайтовое значение 0x0102 в переменную типа short (короткое целое) и проверяем значения двух байтов этой переменной: с[0] (адрес А на рис. 3.4) и c[1] (адрес А + 1 на рис. 3.4), чтобы определить порядок байтов.
Константа CPU_VENDOR_OS определяется программой GNU (аббревиатура «GNU» раскрывается рекурсивно — GNU's Not Unix) autoconf в процессе конфигурации, необходимой для выполнения программ из этой книги. В этой константе хранится тип центрального процессора, а также сведения о производителе и реализации операционной системы. Ниже представлены некоторые примеры вывода этой программы при запуске ее в различных системах (см. рис. 1.7).
freebsd4 % byteorder
i386-unknown-freebsd4.8: little-endian
macosx % byteorder
powerpc-apple-darwin6.6: big-endian
freebsd5 % byteorder
sparc64-unknown-freebsd5.1: big-endian
aix % byteorder
powerpc-ibm-aix5.1.0.0: big-endian
hpux % byteorder
hppa1.1-hp-ux11 11: big-endian
linux % byteorder
i586-pc-linux-gnu: little-endian
solaris % byteorder
sparc-sun-solaris2.9: big-endian
Все, что было сказано об определении порядка байтов 16-разрядного целого числа, конечно, справедливо и в отношении 32-разрядного целого.
ПРИМЕЧАНИЕСуществуют системы, в которых возможен переход от прямого к обратному порядку байтов либо при перезапуске системы (MIPS 2000), либо в любой момент выполнения программы (Intel i860).
Разработчикам сетевых приложений приходится обрабатывать различия в определении порядка байтов, поскольку в сетевых протоколах используется сетевой порядок байтов (network byte order). Например, в сегменте TCP есть 16- разрядный номер порта и 32-разрядный адрес IPv4. Стеки отправляющего и принимающего протоколов должны согласовывать порядок, в котором передаются байты этих многобайтовых полей. Протоколы Интернета используют обратный порядок байтов.
Теоретически реализация Unix могла бы хранить поля структуры адреса сокета в порядке байтов узла, а затем выполнять необходимые преобразования при перемещении полей в заголовки протоколов и обратно, позволяя нам не беспокоиться об этом. Но исторически и с точки зрения POSIX определяется, что для некоторых полей в структуре адреса сокета порядок байтов всегда должен быть сетевым. Поэтому наша задача — выполнить преобразование из порядка байтов узла в сетевой порядок и обратно. Для этого мы используем следующие четыре функции:
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
Обе функции возвращают значение, записанное в сетевом порядке байтов
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
Обе функции возвращают значение, записанное в порядке байтов узла