fe80::%lo0/64 fe80::1%lo0 Uc lo0
fe80::1%lo0 link#6 UHL lo0
fe80::%gif0/64 link#8 UC gif0
fe80::a00:20ff:fea7:686b%gif0 link#8 UHL lo0
ff01::/32 ::1 U lo0
ff02::/16 ::1 UGRS lo0
ff02::%hme0/32 link#1 UC hme0
ff02::%hem1/32 link#2 UC hme1
ff02::%lo0/32 ::1 UC lo0
ff02::%gif0/32 link#8 UC gif0
3. Имея имена интерфейсов, мы выполняем команду ifconfig, чтобы получить подробную информацию для каждого интерфейса:
linux % ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:C0:9F:06:B0:E1
inet addr:206.168.112.96 Bcast:206.168.112.127 Mask:255.255.255.128
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:49214397 errors:0 dropped:0 overruns:0 frame:0
TX packets:40543799 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100
RX bytes:1098069974 (1047.2 Mb) TX bytes:3360546472 (3204.8 Mb)
Interrupt:11 Base address:0x6000
При этом мы получаем IP-адрес, маску подсети и широковещательный адрес. Флаг MULTICAST указывает на то, что узел поддерживает широковещательную передачу. В некоторых реализациях поддерживается флаг -a, при указании которого печатается информация обо всех сконфигурированных интерфейсах.
4. Одним из способов определить IP-адрес нескольких узлов локальной сети является проверка широковещательного адреса (найденного нами на предыдущем шаге) с помощью утилиты ping.
linux % ping -b 206.168.112.127
WARNING: pinging broadcast address
PING 206.168.112.127 (206.168.112.127) from 206.168.112.96 : 56 (84) bytes of data.
64 bytes from 206.168.112.96: icmp_seq=0 ttl=255 time=241 usec
64 bytes from 206.168.112.40: icmp_seq=0 ttl=255 time=2 566 msec (DUP!)
64 bytes from 206.168.112.118: icmp_seq=0 ttl=255 time=2.973 msec (DUP!)
64 bytes from 206.168.112.14: icmp_seq=0 ttl=255 time=3.089 msec (DUP!)
64 bytes from 206.168.112.126: icmp_seq=0 ttl=255 time=3.200 msec (DUP!)
64 bytes from 206.168.112.71: icmp_seq=0 ttl=255 time=3.311 msec (DUP!)
64 bytes from 206.168.112.31: icmp_seq=0 ttl=255 time=3.541 msec (DUP!)
64 bytes from 206.168.112.7: icmp_seq=0 ttl=255 time=3.636 msec (DUP!)
...
1.10. Стандарты Unix
Когда писалась эта книга, наибольший интерес в сфере стандартизации Unix вызывала деятельность группы Остина по пересмотру общих стандартов (The Austin Common Standards Revision Group, CSRG). Ими было написано в общей сложности около 4000 страниц спецификаций, описывающих более 1700 интерфейсов программирования. Эти спецификации являются одновременно стандартами IEEE POSIX и The Open Group. Поэтому один и тот же стандарт может встретиться вам под разными названиями, например ISO/IEC 9945:2002, IEEE Std 1003.1-2001 и Single Unix Specification Version 3. В нашей книге мы будем называть этот стандарт просто: спецификация POSIX, за исключением разделов, подобных этому, где обсуждаются особенности различных более старых стандартов.
Проще всего получить копию этого консолидированного стандарта, заказав ее на компакт-диске или скачав из Сети (бесплатно). В любом случае начинать следует с http://www.UNIX.org/version3.
История POSIX
Слово «POSIX» представляет собой сокращение от «Portable Operating System Interface» (интерфейс переносимой операционной системы). POSIX — целое семейство стандартов, разрабатываемых организацией IEEE (Institute of Electrical and Electronics Engineers — Институт инженеров по электротехнике и радиоэлектронике). Стандарты POSIX также приняты в качестве международных стандартов ISO (International Standards Organization — Международная организация по стандартизации) и IEC (International Electrotechnical Commission — Международная комиссия по электротехнике), называемых ISO/IEC. История стандартов POSIX достаточно интересна, но мы рассмотрим ее кратко.
■ Первым из стандартов POSIX был IEEE Std 1003.1-1988 (317 страниц), и он определял интерфейс между языком С и оболочкой ядра типа Unix в следующих областях: примитивы процесса (fork, exec, сигналы, таймеры), среда процесса (идентификаторы пользователя, группы процессов), файлы и каталоги (все функции ввода-вывода), ввод-вывод на терминал, системные базы данных (файлы паролей и групп) и архивные форматы tar и cpio.
Первый стандарт POSIX был пробной версией, выпущенной в 1986 году и известной как IEEE-IX. Название «POSIX» было предложено Ричардом Столлмэном (Richard Stallman).
■ Следующим был IEEE Std 1003.2-1990 (356 страниц), который стал международным стандартом (ISO/IEC 9945-1:1990). По сравнению с версией 1988 году в версии 1990 года были внесены минимальные изменения. К названию было добавлено «Часть 1: Системный программный интерфейс приложений [язык С]», что указывало, что этот стандарт являлся интерфейсом API, написанным на языке С.
■ Затем был выпущен двухтомный стандарт IEEE Std 1003.2-1992 (около 1300 страниц). Второй том был озаглавлен «Часть 2: интерпретатор и утилиты» и описывал интерпретатор команд (Основанный на интерпретаторе System V Bourne Shell) и порядка сотни утилит (программ, запускаемых из интерпретатора, от awk и basename до vi и yacc). В тексте мы будем называть этот стандарт
■ IEEE Std 1003.1b-1993 (590 страниц) изначально именовался IEEE 1003.4. Он стал дополнением стандарта 1003.1-1990 и включал расширения реального времени, разработанные группой P1003.4. Стандарт 1003.1b-1993 добавил к стандарту 1990 года следующие пункты: синхронизацию файлов, асинхронный ввод-вывод, семафоры, управление памятью (вызов mmap и разделяемая память), планирование выполнения, часы, таймеры и очереди сообщений.
■ Следующий стандарт POSIX — IEEE Std 1003.1, редакция 1996 года [50], включил в себя 1003.1-1990 (базовый API), 1003.1b-1993 (расширения реального времени), 1003.1с-1995 (функции управления потоками) и 1003.1i-1995 (технические исправления 1003.1b). Этот стандарт также называется ISO/IEC 9945-1:1996. Были добавлены три главы, посвященные программным потокам, и общий объем стандарта составил 743 страницы. В тексте мы будем называть его
□ Часть 1. Системный API [язык С].
□ Часть 2. Оболочка и утилиты.
□ Часть 3. Системное администрирование (в стадии разработки).
Части 1 и 2 — это именно то, что мы называем POSIX.1 и POSIX.2.
Более четверти из 743 страниц отводится приложению, названному «Обоснование и замечания» («Rationale and Notes»). Это обоснование содержит историческую информацию и причины, по которым те или иные функции были включены или опущены. Часто обоснование бывает столь же информативным, как и официальный стандарт.
■ Стандарт IEEE Std 1003.1g: Protocol Independent Interfaces (PII) (интерфейсы, не зависящие от протокола) был принят в 2000 году. До появления единой спецификации Unix версии 3 этот стандарт имел наибольшее отношение к тематике данной книги, потому что он определяет сетевые API (называя их DNI — Detailed Network Interfaces, подробные сетевые интерфейсы):
1) DNI/Socket, основанный на API сокетов 4.4BSD;
2) DNI/XTI, основанный на спецификации X/Open XPG4.
Работа над этим стандартом началась в 80-х (рабочая группа P1003.12, позже переименованная в P1003.1g). В тексте мы будем называть его
Текущее состояние различных стандартов POSIX можно получить в Интернете по адресу http://www.pasc.org/standing/sd11.html.
История Open Group
The Open Group (Открытая группа) была сформирована в 1996 году объединением организаций X/Open Company (основана в 1984 году) и Open Software Foundation (OSF, основан в 1988 году). Эта группа представляет собой международный консорциум производителей и потребителей из промышленности, правительства и образовательных учреждений. Их стандарты тоже выходили в нескольких версиях.
■ В 1989 году X/Open опубликовала третий выпуск X/Open Portability Guide (Руководство по разработке переносимых программ) — XPG3.
■ В 1992 году был опубликован четвертый выпуск (Issue 4), а в 1994 — вторая его версия (Issue 4, Version 2). Последняя известна также под названием Spec 1170, где магическое число 1170 представляет собой сумму количества интерфейсов системы (926), заголовков (70) и команд (174). Есть и еще два названия: X/Open Single Unix Specification (Единая спецификация Unix) и Unix 95.
■ В марте 1997 года было объявлено о выходе второй версии Единой спецификации Unix. Этот стандарт программного обеспечения называется также Unix 98, и именно так мы называем эту спецификацию далее в тексте книги. Количество интерфейсов в Unix 98 возросло с 1170 до 1434, хотя для рабочей станции это количество достигает 3030, поскольку сюда входит CDE (Common Desktop Environment — общее окружение рабочего стола), которое, в свою очередь, требует системы X Window System и пользовательского интерфейса Motif. Подробно об этом написано в книге [55]. Полезную информацию можно также найти по адресу http://www.UNIX.org/version2. Сетевые службы, входящие в Unix 98, определяются как для API сокетов, так и для XTI. Эта спецификация практически идентична POSIX.1g.
К сожалению, X/Open обозначает свои сетевые стандарты с помощью аббревиатуры «XNS» — X/Open Networking Services. Например, версия этого документа, в которой определяются сокеты и технологии XTI для Unix 98 [86], называется «XNS Issue 5*. Дело в том, что в мире сетевых технологий аббревиатура «XNS» всегда служила акронимом для «Xerox Network Systems» (сетевые системы Xerox). Поэтому мы избегаем использования акронима «XNS» и называем соответствующий документ X/Open просто стандартом сетевого API Unix 98.
Объединение стандартов
Краткую историю POSIX и The Open Group продолжает опубликованная CSRG третья версия единой спецификации Unix (The Single Unix Specification Version 3), о которой уже шла речь в начале раздела. Добиться принятия единого стандарта пятьюдесятью производителями — заметная веха в истории Unix. Большинство сегодняшних Unix-систем отвечают требованиям какой-либо версии POSIX.1 и POSIX.2, а многие уже соответствуют третьей единой спецификации Unix.
Исторически для большинства Unix-систем четко прослеживалось родство либо с BSD, либо с SVR4, но различия между современными системами постепенно стираются по мере того, как производители принимают новые стандарты. Наиболее существенные из оставшихся отличий связаны с администрированием систем, которое пока не охватывается никакими стандартами.
Эта книга основана на третьей версии единой спецификации Unix, причем основное внимание уделяется API сокетов. Везде, где это возможно, мы используем исключительно стандартные функции.
Internet Engineering Task Force
IETF (Internet Engineering Task Force — группа, отвечающая за решение сетевых инженерных задач) — это большое открытое международное сообщество сетевых разработчиков, операторов, производителей и исследователей, работающих в области развития архитектуры Интернета и более стабильной его работы. Это сообщество открыто для всех желающих.
Стандарты Интернета документированы в RFC 2026 [13]. Обычно стандарты Интернета описывают протоколы, а не интерфейсы API. Тем не менее два документа RFC (RFC 3493 [36] и RFC 3542 [114]) определяют API сокетов для протокола IP версии 6. Это информационные документы RFC, а не стандарты, и они были выпущены для того, чтобы ускорить применение переносимых приложений различными производителями, работающими с более ранними реализациями IPv6. Разработка текстов стандартов занимает много времени, но в третьей версии единой спецификации многие API были успешно стандартизованы.
1.11. 64-разрядные архитектуры
С середины до конца 90-х годов развивается тенденция к переходу на 64-разрядные архитектуры и 64-разрядное программное обеспечение. Одной из причин является более значительная по размеру адресация внутри процесса (например, 64-разрядные указатели), которая необходима в случае использования больших объемов памяти (более 232 байт). Обычная модель программирования для существующих 32-разрядных систем Unix называется
Таблица 1.5. Сравнение количества битов для хранения различных типов, данных в моделях ILP32 и LP64
| Тип данных | Модель ILP32 | Модель LP64 |
|---|---|---|
| Char | 8 | 8 |
| Short | 16 | 16 |
| Int | 32 | 32 |
| Long | 32 | 64 |
| Указатель | 32 | 64 |
С точки зрения программирования модель LP64 означает, что мы не можем рассматривать указатель как целое число. Мы также должны учитывать влияние модели LP64 на существующие API.
В ANSI С введен тип данных size_t, который используется, например в качестве аргумента функции malloc (количество байтов, которое данная функция выделяет в памяти для размещения какого-либо объекта), а также как третий аргумент для функций read и write (число считываемых или записываемых байтов). В 32-разрядной системе значение типа size_t является 32-разрядным, но в 64-разрядной системе оно должно быть 64-разрядным, чтобы использовать преимущество большей модели адресации. Это означает, что в 64-разрядной системе, возможно, size_t будет иметь тип unsigned long (целое число без знака, занимающее 32 разряда). Проблемой сетевого интерфейса API является то, что в некоторых проектах по POSIX.1g было определено, что аргументы функции, содержащие размер структур адресов сокета, должны иметь тип size_t (например, третий аргумент в функциях bind и connect). Некоторые поля структуры XTI также имели тип данных long (например, структуры t_info и t_opthdr). Если бы стандарты остались неизменными, в обоих случаях 32-разрядные значения должны были бы смениться 64-разрядными при переходе с модели ILP32 на LP64. В обоих случаях нет никакой необходимости в 64-разрядных типах данных: длина структуры адресов сокета занимает максимум несколько сотен байтов, а использование типа данных long для полей структуры XTI было просто ошибкой.
Решение состоит в том, чтобы использовать типы данных, разработанные специально для борьбы с подобными проблемами. Интерфейс API сокетов использует тип данных socklen_t для записи длины структур адресов сокетов, a XTI использует типы данных t_scalar_t и t_uscalar_t. Причина, по которой эти 32-разрядные значения не заменяются на 64-разрядные, заключается в том, что таким образом упрощается двоичная совместимость с новыми 64-разрядными системами для приложений, скомпилированных под 32-разрядные системы.
1.12. Резюме
В листинге 1.1 показан полностью рабочий, хотя и простой, клиент TCP, который получает текущее время и дату с заданного сервера. В листинге 1.5 представлена полная версия сервера. На этих примерах вводятся многие термины и понятия, которые далее рассматриваются более подробно. Наш клиент был зависим от протокола, и мы изменили его, чтобы он использовал IPv6. Но при этом мы получили всего лишь еще одну зависимую от протокола программу. В главе 11 мы разработаем некоторые функции, которые позволят нам написать код, не зависимый от протокола. Это важно, поскольку в Интернете начинает использоваться протокол IPv6. По ходу книги мы будем использовать функции-обертки, созданные в разделе 1.4, для уменьшения размера нашего кода, хотя по-прежнему каждый вызов функции будет проходить проверку на предмет возвращения ошибки. Все имена наших функций-оберток начинаются с заглавной буквы.
Третья версия единой спецификации Unix, известная также под несколькими другими названиями (мы называем ее просто «Спецификация POSIX»), представляет собой результат слияния двух стандартов, осуществленного The Austin Group.
Читатели, которых интересует история сетевого программирования в Unix, должны изучить прежде всего историю развития Unix, а история TCP/IP и Интернета представлена в книге [106].
Упражнения
1. Проделайте все шаги, описанные в конце раздела 1.9, чтобы получить информацию о топологии вашей сети.
2. Отыщите исходный код для примеров в тексте (см. предисловие). Откомпилируйте и протестируйте клиент времени и даты, представленный в листинге 1.1. Запустите программу несколько раз, задавая каждый раз различные IP- адреса в командной строке.
3. Замените первый аргумент функции socket, представленной в листинге 1.1, на 9999. Откомпилируйте и запустите программу. Что происходит? Найдите значение errno, соответствующее выданной ошибке. Как вы можете получить дополнительную информацию по этой ошибке?
4. Измените листинг 1.1: поместите в цикл while счетчик, который будет считать, сколько раз функция read возвращает значение, большее нуля. Выведите значение счетчика перед завершением. Откомпилируйте и запустите новую программу-клиент.
5. Измените листинг 1.5 следующим образом. Сначала поменяйте номер порта, заданный функции sin_port, с 13 на 9999. Затем замените один вызов функции write на циклический, при котором функция write вызывается для каждого байта результирующей строки. Откомпилируйте полученный сервер и запустите его в фоновом режиме. Затем измените клиент из предыдущего упражнения (в котором выводится счетчик перед завершением программы), изменив номер порта, заданный функции sin_port, с 13 на 9999. Запустите этот клиент, задав в качестве аргумента командной строки IP-адрес узла, на котором работает измененный сервер. Какое значение клиентского счетчика будет напечатано? Если это возможно, попробуйте также запустить клиент и сервер на разных узлах.
Глава 2
Транспортный уровень: TCP, UDP и SCRIPT
2.1. Введение
В этой главе приводится обзор протоколов семейства TCP/IP, которые используются в примерах на всем протяжении книги. Наша цель — как можно подробнее описать эти протоколы с точки зрения сетевого программирования, чтобы понять, как их использовать, а также дать ссылки на более подробные описания фактического устройства, реализации и истории протоколов.
В данной главе речь пойдет о транспортном уровне: протоколах TCP, UDP и протоколе управления передачей потоков (Stream Control Transmission Protocol, SCRIPT). Большинство приложений, построенных по архитектуре клиент-сервер, используют либо TCP, либо UDP. Протоколы транспортного уровня, в свою очередь, используют протокол сетевого уровня IP — либо IPv4, либо IPv6. Хотя и возможно использовать IPv4 или IPv6 непосредственно, минуя транспортный уровень, эта технология (символьные сокеты) используется гораздо реже. Поэтому мы даем более подробное описание IPv4 и IPv6 наряду с ICMPv4 и ICMPv6 в приложении А.
UDP представляет собой простой и ненадежный протокол передачи дейтаграмм, в то время как TCP является сложным и надежным потоковым протоколом. SCRIPT тоже обеспечивает надежность передачи, как и TCP, но помимо этого он позволяет задавать границы сообщений, обеспечивает поддержку множественной адресации на транспортном уровне, а также минимизирует блокирование линии в начале передачи. Нужно понимать, какие сервисы предоставляют приложениям транспортные протоколы, какие задачи решаются протоколами, а что необходимо реализовывать в приложении.
Есть ряд свойств TCP, которые при должном понимании упрощают написание надежных клиентов и серверов. Знание этих особенностей облегчит нам отладку наших клиентов и серверов с использованием общеупотребительных средств, таких как netstat. В этой главе мы коснемся различных тем, попадающих в эту категорию: трехэтапное рукопожатие TCP, последовательность прерывания соединения TCP, состояние TCP TIME_WAIT, четырехэтапное рукопожатие и завершение соединения SCRIPT, буферизация TCP, UDP и SCRIPT уровнем сокетов и так далее.
2.2. Обзор протоколов TCP/IP
Хотя набор протоколов и называется «TCP/IP», это семейство состоит не только из собственно протоколов TCP и IP. На рис. 2.1 представлен обзор этих протоколов.
Рис. 2.1. Обзор протоколов семейства TCP/IP
На этом рисунке представлены и IPv4, и IPv6. Если рассматривать этот рисунок справа налево, то пять приложений справа используют IPv6. О константе AF_INET6 и структуре sockaddr_in6 мы говорим в главе 3. Следующие шесть приложений используют IPv4.
Приложение, находящееся в самой левой части рисунка, tcpdump, соединяется непосредственно с канальным уровнем, используя либо BPF (BSD Packet Filter — фильтр пакетов BSD), либо DLPI (Data Link Provider Interface — интерфейс канального уровня). Мы обозначили штриховую горизонтальную линию под девятью приложениями (интерфейс) как
Здесь существует исключение, описанное нами в главе 25: Linux предоставляет доступ к канальному уровню при помощи специального типа сокета, называемого SOCK PACKET.
На рис. 2.1 мы также отмечаем, что программа traceroute использует два сокета: один для IP, другой для ICMP. В главе 25 мы создадим версии IPv4 и IPv6 утилит ping и traceroute.