Электронный адрес материала: http://Piter.com
MegaLib.com/ ОПЕРАЦИОННЫЕ СИСТЕМЫ/ Linux, Unix, FreeBSD / Название материала(Оглавление): UNIX - Введение и протоколы TCP/IP Перед Вами отрывок из книги(бумажное издание)! Для заказа книги нажмите здесь! |
|
|
|
UNIX: разработка сетевых приложений У. Стивенс Часть 1 Введение и протоколы TCP/IPГЛАВА 1 Введение в сетевое программирование1.1. Введение Большинство сетевых приложений можно разделить на две группы: клиенты и серверы. Связь между ними демонстрирует рис. 1.1. ![]() Рис. 1.1. Сетевое приложение: клиент и сервер Можно привести множество примеров клиентов и серверов, с которыми, вероятно, читатель знаком: web-браузер (клиент), соединяющийся с web-сервером; клиент FTP, получающий файлы с сервера FTP; клиент Telnet, используемый нами для того, чтобы войти на удаленный узел через сервер Telnet на этом удаленном узле. Обычно за один сеанс клиенты устанавливают соединение с одним сервером, хотя если говорить о web-браузере, мы можем соединиться со множеством различных web-серверов, скажем, в течение 10 минут. Однако с точки зрения сервера нет ничего удивительного в том, что в любой момент времени он соединяется ![]() Рис. 1.2. Сервер, обслуживающий одновременно множество клиентов Взаимодействие клиентского и серверного приложений невозможно без сетевых протоколов. В этой книге мы сосредоточимся на наборе (стеке) протоколов TCP/IP, называемом также набором протоколов Интернета. Так, например, клиенты и серверы Web устанавливают соединения, используя протокол TCP. TCP, в свою очередь, использует протокол IP, а протокол IP устанавливает соединение с тем или иным протоколом канального уровня. Например, если и клиент, и сервер находятся в одной сети Ethernet, взаимодействие между ними будет соответствовать показанному на рис. 1.3. ![]() Рис. 1.3. Клиент и сервер в сети Ethernet, соединеннные по протоколу TCP Несмотря на то что клиент и сервер устанавливают соединение с использованием протокола уровня приложений, транспортные уровни устанавливают соединение, используя TCP, и т. д., нужно отметить, что действительный поток информации между клиентом и сервером идет вниз по стеку протоколов на стороне клиента, затем по сети и, наконец, вверх по стеку протоколов на стороне сервера. Отметим также, что клиент и сервер являются типично пользовательскими процессами, в то время как TCP и протоколы IP обычно являются частью стека протоколов внутри ядра. На рис. 1.3 мы выделили четыре уровня процессов. Мы будем обсуждать не только протоколы TCP и IP. Некоторые клиенты и серверы вместо протокола TCP используют UDP, и оба эти протокола более подробно обсуждаются в главе 2. Употребляя термин IP, мы подразумеваем последнюю официальную версию этого протокола — IPv4. Новая версия этого протокола, IP версии 6, была разработана в середине 90-х и, возможно, со временем заменит протокол IPv4. Начальные реализации протокола IPv6 были доступны на момент написания этой книги, и в тексте описана разработка сетевых приложений как под IPv4, так и под IPv6. В приложении A приводится сравнение протоколов IPv4 и IPv6 с другими протоколами, с которыми вы встретитесь. Вовсе не обязательно, чтобы клиент и сервер находились, как показано на рис. 1.3, в одной и той же локальной сети (local area network, LAN). Напротив, клиент и сервер могут относиться к разным локальным сетям (рис. 1.4), при этом обе локальных сети должны быть соединены в глобальной сети (wide-area network, WAN) с использованием маршрутизаторов. ![]() Рис. 1.4. Клиент и сервер в различных локальных сетях, соединенных через глобальную сеть Маршрутизаторы — это блоки, из которых строится глобальная сеть. На сегодняшний день наибольшей глобальной сетью является Интернет, хотя многие компании создают свои собственные глобальные сети, и эти частные сети могут быть, а могут и не быть подключены к Интернету. Оставшаяся часть этой главы представляет собой обзор различных тем, которые более подробно раскрываются далее по тексту книги. Мы начнем с полного, хотя и простого примера TCP-клиента, демонстрирующего вызовы многих функций и понятия, с которыми мы встретимся в книге. Клиент работает только с протоколом IP версии 4. Мы покажем изменения, необходимые для работы с протоколом IP версии 6. Разумнее всего создавать не зависящие от протокола клиенты и серверы, и это решение будет рассмотрено нами в главе 11. В этой главе также показан пример сервера TCP, работающего с нашим клиентом. Чтобы упростить написанный нами код, мы определяем наши собственные функции-обертки (wrapper functions) для большинства системных функций, которые будем вызывать. В большинстве случаев мы будем использовать функции-обертки для поиска ошибок, вывода соответствующих сообщений и завершения работы при обнаружении ошибки. Кроме того, в этой главе мы подробно расскажем о сети, использовавшейся для тестирования примеров из книги, приведем имена узлов, их IP-адреса и названия операционных систем, под управлением которых они работают. В настоящее время нельзя говорить о Unix, не упомянув Posix — стандарт, принятый большинством производителей. Мы опишем историю стандарта Posix и расскажем, каким образом он определяет API, рассматриваемые в этой книге наряду с другими конкурирующими стандартами. 1.2. Простой клиент времени и даты Рассматривая этот конкретный пример, мы введем многие понятия и термины, с которыми будем встречаться в процессе изучения этой книги. В листинге 1.11 представлена реализация клиента времени и даты, подключающегося к серверу по протоколу TCP. Этот клиент устанавливает TCP-соединение с сервером, а сервер просто посылает клиенту время и дату в удобочитаемом формате. Листинг 1.1. TCP-клиент для определения времени и даты //intro/daytimetcpcli.c ПРИМЕЧАНИЕ. Этот формат мы используем для всех вставок исходного кода в тексте. Каждая непустая строка пронумерована. Абзац текста, описывающий некоторую часть кода, начинается с двух номеров — начального и конечного номеров тех строк, о которых идет речь в данном абзаце. Как правило, абзацу предшествует короткий заголовок, в котором резюмируется содержание описываемого кода. В начале фрагмента кода указано имя файла исходного кода: в данном примере это файл daytimetcpcli.c в каталоге intro. Поскольку исходный код всех примеров является свободно доступным (см. предисловие), вы можете найти соответствующие исходные файлы. Наилучший способ изучить концепции сетевого программирования — компилировать, запускать и особенно модифицировать эти программы в ходе чтения книги. ПРИМЕЧАНИЕ. Примечания наподобие этого мы будем использовать для описания особенностей реализации и исторических справок. Если мы откомпилируем эту программу в определенный по умолчанию файл a.out и выполним его, на выходе мы получим следующее: solaris % a.out 206.62.226.35 наш ввод Fri Jan 12 14:27:52 1996 вывод программы ПРИМЕЧАНИЕ. Отображая интерактивный ввод и вывод, мы выделяем то, что вводим, полужирным шрифтом. Комментарии идут справа от вывода курсивом. Название системы мы включаем в приглашение оболочки (в данном примере solaris), чтобы показать, на каком узле выполняется команда. На рис. 1.7 показаны системы, используемые для выполнения большинства примеров этой книги. Имена узлов обычно соответствуют операционным системам. Теперь мы можем рассмотреть подробности этой программы, состоящей из 27 строк. Здесь мы лишь кратко опишем их — на тот случай, если это первая сетевая программа, с которой вы встретились, — а более полное представление о ней вы сможете получить далее по мере чтения книги. Подключение собственного заголовочного файла 1 Мы подключили наш собственный заголовочный файл, unp.h, текст которого приведен в разделе Г.1. Этот заголовочный файл подключает различные системные заголовочные файлы, необходимые большинству сетевых программ, и определяет используемые нами константы (например, MAXLINE). Аргументы командной строки 2–3 Это определение функции main вместе с аргументами командной строки. При написании кода примеров к этой книге подразумевалось, что для его компиляции должен использоваться компилятор ANSI C (American National Standards Institute — Американский национальный институт стандартов). Создание сокета TCP 10–11 Функция socket создает потоковый сокет (SOCK_STREAM) Интернета. AF_INET — это название, иногда используемое для обозначения сокета TCP. Функция возвращает целочисленный дескриптор, который мы используем для идентификации сокета во всех последующих вызовах функции (например, в последующих вызовах функций connect и read). ПРИМЕЧАНИЕ. Оператор if содержит вызов функции socket, присваивание возвращаемого значения переменной sockfd и последующую проверку, является ли это присвоенное значение отрицательным. Мы могли разбить этот оператор на два оператора C следующим образом: sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) Однако использованная в листинге запись является типичным для языка C способом объединения двух строк. Поскольку в языке C оператор «меньше чем» (<) имеет более высокий приоритет, чем оператор присваивания, необходимо заключить операции присваивания и вызова функции в скобки (как это и сделано в листинге, в строке 10). Между двумя открывающими скобками всегда вставляется пробел как указание на то, что левая часть операции сравнения содержит также операцию присваивания — это элемент собственного стиля автора. (Автор впервые нашел этот стиль в исходном коде Minix [97] и с тех пор всегда им пользуется.) Далее в выражении while также применяется этот стиль. Вы встретите множество различных вариантов применения термина сокет. Во-первых, программный интерфейс приложений (Application Program Interface, API), который мы используем, называется API сокетов. В предыдущем абзаце мы упоминали функцию socket, входщую в API сокетов. Там же мы говорили и о сокете TCP, что является синонимом точки доступа TCP (TCP endpoint). Если вызов функции socket оказывается неудачным, мы прерываем выполнение программы с помощью вызова функции err_sys. Она выдает сообщение об ошибке и ее описание (например, одна из возможных ошибок функции socket связана с отсутствием поддержки протокола) и прерывает выполнение процесса. Эта функция, как и некоторые другие созданные нами функции, начинающиеся с err_, встречаются далее по тексту книги. Они будут описаны в разделе Г.4. Задание IP-адреса и порта сервера 12–16 Мы заполняем структуру адреса сокета Интернета (структура sockaddr_in с именем servaddr) IP-адресом и номером порта сервера. Сначала мы заполняем структуру нулями, используя функцию bzero, затем устанавливаем номер порта, равный 13 (это номер заранее известного порта (well-known port) сервера времени и даты на любом узле TCP/IP, на котором работает эта служба, — табл. 2.1), а также присваиваем IP-адресу значение, определенное первым аргументом командной строки (argv[1]). В этой структуре поля IP-адреса и номера портов должны иметь определенный формат: мы вызываем библиотечную функцию htons (host to network short), чтобы преобразовать двоичный номер порта в требуемый формат, и библиотечную функцию inet_pton (presentation to numeric — преобразовать в двоичное представление), чтобы преобразовать аргумент командной строки в символах ASCII (например, 206.62.226.35 при выполнении данного примера) в двоичный формат. ПРИМЕЧАНИЕ. Функция bzero не является функцией ANSI C — она происходит от более раннего кода сетевого программирования Беркли. Тем не менее мы используем именно ее, а не функцию ANSI C memset (с тремя аргументами), потому что с функцией bzero работать проще. Почти каждый производитель, поддерживающий API сокетов, также реализует и функцию bzero, а на случай, если такой реализации нет, мы приводим ее макроопределение в нашем заголовочном файле unp.h. Сам автор допустил ошибку, поменяв местами второй и третий аргументы функции memset в 10 местах в первой редакции [95]. Компилятор C не может распознать эту ошибку, поскольку оба аргумента принадлежат к одному типу. В действительности второй аргумент принадлежит к типу int, а третий — size_t — обычно имеет тип unsigned int (то есть целое без знака), но заданные значения, соответственно 0 и 16, являются допустимыми для обоих типов аргумента. Вызов функции memset все равно осуществлялся, но функция фактически ничего не делала, поскольку задавалось нулевое число инициализируемых байтов. Программа работала, потому что только некоторые функции сокетов действительно требуют, чтобы последние 8 байтов в структуре адреса сокета были нулевыми. Тем не менее это ошибка, и ее можно избежать, используя функцию bzero, поскольку перестановка двух аргументов функции bzero будет всегда выявлена компилятором C, если используются прототипы функций. Возможно, вы впервые встречаете функцию inet_pton. Она является новой, и появилась она в протоколе IPv6 (о котором более подробно мы поговорим в приложении A). В более старом коде для преобразования точечно-десятичной записи ASCII (dotted-decimal string) в необходимый формат используется функция inet_addr, но у нее есть ряд ограничений, которых не имеет функция inet_pton. Не беспокойтесь, если ваша система (пока еще) не поддерживает эту функцию, — ее реализация приведена в разделе 3.7. Установление соединения с сервером 17–18 Функция connect, применяемая к сокету TCP, устанавливает соединение по протоколу TCP с сервером, используя адрес сокета из структуры, на которую указывает второй аргумент. Мы также должны задать длину структуры адреса в качестве третьего аргумента функции connect, а для структур адреса сокета Интернета мы всегда предоставляем вычисление длины компилятору, используя оператор C sizeof. ПРИМЕЧАНИЕ. В заголовочном файле unp.h мы используем директиву #define SA, чтобы определить SA как struct sockaddr, что соответствует общей структуре адреса сокета. Когда одна из функций сокетов требует указателя на структуру адреса сокета, этот указатель должен быть преобразован в указатель на общую структуру адреса сокета. Это происходит потому, что функции сокетов появились раньше, чем стандарт ANSI C. Соответственно, тип указателя void * еще не был доступен в начале 80-х, когда эти функции разрабатывались. Проблема состоит в том, что выражение "struct sockaddr" занимает 15 символов и часто заставляет выходить строку исходного кода за правую границу экрана (или страницы книги), — поэтому мы сократили ее до SA. Более подробно мы исследуем структуры с адресами сокетов общего типа с помощью листинга 3.2. Чтение и отображение ответа сервера 19–25 Мы читаем ответ сервера и отображаем результат с помощью стандартной функции ввода-вывода fputs. Нужно быть внимательным при использовании TCP, поскольку это потоковый (byte-stream) протокол без границ между записями. Обычно ответом сервера является 26-байтовая строка следующего формата: Fri Jan 12 14:27:52 1996\r\n В приведенном примере конец записи обозначается сервером, закрывающим соединение. Эта технология используется также протоколом передачи гипертекста (Hypertext Transfer Protocol, HTTP). Однако есть и другие способы обозначения конца записи. Например, протокол передачи файлов (File Transfer Protocol, FTP) и простой протокол передачи почты (Simple Mail Transfer Protocol, SMTP) обозначают конец записи 2-байтовой последовательностью, состоящей из ASCII-символов возврата каретки и перевода строки. Служба вызова удаленных процедур (Remote Procedure Call, RPC) и система именования доменов (Domain Name System, DNS) помещают перед каждой записью, отсылаемой по протоколу TCP, двоичное число, соответствующее длине этой записи. Здесь важно осознать, что протокол TCP сам по себе не предоставляет никаких меток записей: если приложение требует отделять записи одну от другой, оно должно сделать это самостоятельно, и для этого имеются стандартные методы. Завершение программы 26 Функция exit завершает программу. Unix всегда закрывает все открытые дескрипторы при завершении процесса, поэтому теперь наш TCP-сокет закрыт. Как уже отмечалось, далее в тексте книги вы найдете значительно более подробное обсуждение моментов, о которых мы здесь только упомянули. 1.3. Независимость от протокола Наша программа, представленная в листинге 1.1, является зависящей от протокола (protocol dependent) IP версии 4 (IPv4). Мы выделяем и инициализируем структуру sockaddr_in, определяем адрес как относящийся к семейству AF_INET и устанавливаем первый аргумент функции socket равным AF_INET. Если мы хотим, чтобы программа работала по протоколу IP версии 6 (IPv6), необходимо модифицировать код. В листинге 1.2 показана версия программы, работающая по протоколу IPv6, а внесенные в нее изменения отмечены полужирным шрифтом. Листинг 1.2. Версия программы из листинга 1.1 для IPv6 //intro/daytimetcpcliv6.c Изменились только пять строк, но в результате мы все равно получили программу, зависящую от протокола, в данном случае — от протокола IP версии 6. Полезнее была бы программа, не зависящая от протокола (protocol independent). В листинге 11.3 представлена версия этого клиента, не зависящая от протокола, что реализовано благодаря использованию функции getaddrinfo (вызываемой tcp_connect). Другим недостатком наших программ является то, что пользователь должен вводить IP-адрес сервера в точечно-десятичной записи (например, 206.62.226.35 для версии IPv4). Людям проще работать с именами, чем с числами (например, laptop.kohala.com или просто laptop). В главах 9 и 11 мы обсудим функции, обеспечивающие преобразование имен узлов в IP-адреса и имен служб в номера портов. Мы специально откладываем описание этих функций, продолжая использовать IP-адреса и номера портов, и поэтому точно знаем, что именно входит в структуры адресов сокетов, которые мы должны заполнить и проверить. Это также упрощает наши объяснения сетевого программирования, снимая необходимость описывать в подробностях еще один набор функций. 1.4. Обработка ошибок: функции-обертки В любой реальной программе существенным моментом является проверка каждого вызова функции на предмет возвращаемой ошибки. В листинге 1.1 мы проводим поиск ошибок в вызовах функций socket, inet_pton, connect, read и fputs, и когда ошибка случается, мы вызываем свои собственные функции err_quit и err_sys для вывода сообщения об ошибке и для прерывания выполнения программы. В отдельных случаях, когда функция возвращает ошибку, бывает необходимо выполнить еще какие-либо действия помимо прерывания программы (см., например, листинг 5.9, где требуется проверить прерванный системный вызов). Поскольку прерывание программы из-за ошибки — типичное явление, мы сократим наши программы, определив функции-обертки, которые будут вызывать сами рабочие функции, проверять возвращаемые ими значения и прерывать программу при возникновении ошибки. Соглашение, используемое нами, заключается в том, что название функции-обертки пишется с заглавной буквы, например: sockfd = Socket(AF_INET, SOCK_STREAM, 0); Наша функция-обертка для функции socket показана в листинге 1.3. Листинг 1.3. Функция-обертка для функции socket //lib/wrapsock.c ВНИМАНИЕ. В тексте книги вам будут встречаться функции с именами, начинающимися с заглавной буквы. Это будут наши собственные функции-обертки, предназначенные для вызова функций с теми же именами, но начинающимися со строчной буквы. При описании исходного кода в тексте книги мы всегда ссылаемся на вызываемую функцию низшего уровня (например, socket), а не на функцию-обертку (например, Socket). Хотя вам может показаться, что использование функций-оберток не дает больших преимуществ, на самом деле это не так. Когда мы будем обсуждать потоки в главе 23, вы обнаружите, что при возникновении ошибки функции потоков не присваивают стандартной переменной Unix errno определенную константу, специфическую для произошедшей ошибки. Вместо этого значение переменной errno просто возвращается функцией. Это значит, что каждый раз, когда мы вызываем одну из функций pthread, мы должны разместить в памяти переменную, сохранить возвращаемое значение в этой переменной и присвоить его переменной errno перед вызовом err_sys. Чтобы избежать загромождения кода скобками, мы можем использовать оператор языка C запятая для объединения присваивания переменной errno и вызова err_sys следующим образом: int n; В качестве альтернативы мы можем определить новую функцию выдачи сообщений об ошибках, которая в качестве аргумента получает системный номер ошибки. Однако проще всего будет выглядеть код с использованием функции-обертки, если мы определим нашу собственную функцию-обертку, как показано в листинге 1.4: Pthread_mutex_lock(&ndone_mutex); Листинг 1.4. Функция-обертка для функции pthread_mutex_lock //lib/wrappthread.c ПРИМЕЧАНИЕ. Если аккуратно программировать на C, можно использовать вместо функций макросы, что обеспечивает небольшой выигрыш в производительности, однако функции-обертки крайне редко бывают причиной недостаточной производительности программ. Наш выбор — первая заглавная буква в названии функции — является компромиссом. Мы рассмотрели множество других стилей: подстановка префикса "e" (как сделано на с. 182 [56]), добавление суффикса "_e" к имени функции и т. д. Выбранный стиль кажется наименее категоричным, и в то же время он представляет визуальное указание на то, что на самом деле вызывается какая-то другая функция. Эта технология имеет, кроме того, полезный побочный эффект: она позволяет проверять возникновение ошибок при выполнении таких функций, ошибки в которых часто остаются незамеченными, например close и listen. Мы всегда будем использовать функции-обертки, кроме тех случаев, когда нам понадобится проверить ошибку явно и обрабатывать ее другим, отличным от прерывания программы способом. Мы не приводим исходный код для всех наших функций-оберток, но он полностью доступен (см. предисловие). Значение системной переменной Unix errno Когда при выполнении функции Unix (например, одной из функций сокетов) происходит ошибка, глобальной переменной errno присваивается положительное значение, указывающее на тип ошибки, а возвращаемое значение функции при этом обычно равно –1. Наша функция err_sys проверяет значение переменной errno и выводит строку с соответствующим сообщением об ошибке (например, об истечении времени соединения, если значение функции errno равно ETIMEDOUT). Переменной errno присваивается определенное значение, только
если при выполнении функции произошла какая-либо ошибка. Ее значение не
определено, если функция не возвращает ошибки. Все положительные коды
ошибок являются константами с именами в верхнем регистре, начинающимися
с «E», и обычно определяются в заголовке Переменную errno нельзя хранить как глобальную переменную, если имеется множество программных потоков, у которых все глобальные переменные являются общими. О решении этой проблемы мы расскажем в главе 23. Говоря для краткости, что «функция connect возвращает ECONNREFUSED», мы подразумеваем, что при выполнении функции произошла ошибка (обычно при этом возвращаемое значение функции равно –1) и значение переменной errno в настоящий момент равно указанной константе. 1.5. Простой сервер времени и даты Напишем простую версию сервера TCP для определения времени и даты, который будет работать с клиентом, описанным в разделе 1.2. Воспользуемся функциями-обертками, описанными в предыдущем разделе. Сервер представлен в листинге 1.5. Листинг 1.5. TCP-сервер для определения времени и даты //intro/daytimetcpsrv.c Создание сокета TCP 10 Создание сокета TCP аналогично созданию клиентского кода. Связывание заранее известного порта сервера с сокетом 11–15 Заранее известный порт сервера (порт номер 13 в случае сервера времени и даты) связывается с сокетом путем заполнения структуры адреса сокета и вызова функции bind. Мы задаем IP-адреса как INADDR_ANY, что позволяет серверу устанавливать соединение с клиентом на любом интерфейсе в том случае, если узел сервера имеет несколько интерфейсов. Далее мы рассмотрим, как можно ограничить список интерфейсов, через которые может осуществляться подключение клиентов. Преобразование сокета в прослушиваемый сокет 16 С помощью вызова функции listen сокет преобразуется в прослушиваемый сокет, на котором входящие соединения от клиентов принимаются ядром. Вызов функций socket, bind и listen — это обычная последовательность шагов для любого сервера TCP, позволяющая создать прослушиваемый дескриптор (listening descriptor); в нашем примере это переменная listenfd. Константа LISTENQ взята из нашего заголовочного файла unp.h. Она задает максимальное количество клиентских соединений, которые ядро ставит в очередь на прослушиваемом сокете. Более подробно создание этих очередей мы рассмотрим в разделе 4.5. Принятие клиентского соединения, отправка ответа 17–21 Обычно процесс сервера блокируется при вызове функции accept и ждет подключения клиента. Соединение TCP устанавливается с помощью так называемого трехэтапного рукопожатия (three-way handshake). Когда рукопожатие состоялось, функция accept возвращает значение, и это значение является новым дескриптором (connfd), который называется присоединенным дескриптором (connected descriptior). Этот новый дескриптор используется для связи с новым клиентом. Новый дескриптор возвращается функцией accept для каждого клиента, соединяющегося с нашим сервером. ПРИМЕЧАНИЕ. В книге используется следующий способ обозначения бесконечного цикла: for ( ; ; ){
…
}
Текущее время и дата определяются с помощью библиотечной функции time, возвращающей количество секунд с начала эпохи Unix: 00:00:00 1 января 1970 года UTC (Universal Time Coordinated — унивресальное скоординированное время, среднее время по Гринвичу). Следующая библиотечная функция, ctime, преобразует целое значение в строку следующего формата, удобного для человеческого восприятия: Fri Jan 12 14:27:52 1996 Символ возврата каретки и пустую строку добавляет к строке функция snprintf, а функция write сообщает результат клиенту. ПРИМЕЧАНИЕ. Возможно, вы впервые встречаетесь с функцией snprintf. Во многих случаях используется другая функция — sprintf, но она не в состоянии обеспечить проверку на переполнение буфера получателя. Функция snprintf, наоборот, требует, чтобы в качестве второго аргумента указывался размер буфера получателя, что позволяет избежать переполнения буфера. Функция snprintf еще не является частью стандарта ANSI C, но существует вероятность ее включения в обновленный стандарт, в настоящее время называемый C9X. Тем не менее многие поставщики программного обеспечения уже сейчас включают эту функцию в стандартную библиотеку языка C. Во многих примерах в нашей книге мы используем нашу собственную версию функции snprintf, содержащую обращение к функции sprintf. Отметим, что во многих случаях проникнуть в систему хакерам помогало то, что в результате отправки данных серверу вызовы функции sprintf переполняли буфер сервера. Также следует проявлять осторожность при использовании функций gets, strcat и strcpy — вместо них лучше использовать fgets, strncat и strncpy. Дополнительную информацию по написанию защищенных сетевых программ вы найдете в [30]. Завершение соединения 22 Сервер закрывает соединение с клиентом, вызывая функцию close. Это инициирует обычную последовательность прерывания соединения TCP: пакет FIN посылается в обоих направлениях, и каждый пакет FIN распознается на другом конце соединения. Более подробно трехэтапное рукопожатие и четыре пакета TCP, используемые для прерывания соединения, будут описаны в разделе 2.5. Как и в случае клиента в предыдущем разделе, мы только кратко рассмотрели этот сервер, оставив подробности на потом. Запомните следующие моменты: 1.6. Список примеров технологии клиент-сервер, используемых в книге На протяжении всей книги мы иллюстрируем технологии сетевого программирования двумя основными приложениями модели клиент-сервер: Чтобы обеспечить удобный поиск различных тем, которых мы касаемся в этой книге, мы составили список разработанных нами программ. В табл. 1.1 перечислены версии клиента для определения времени и даты, две из которых вы уже видели. В табл. 1.2 перечисляются версии сервера для определения времени и даты. В табл. 1.3 представлены версии эхо-клиента, а в табл. 1.4 — версии эхо-сервера. Таблица 1.1. Различные версии клиента времени и даты, рассматриваемые в данной книге
Таблица 1.2. Различные версии сервера времени и даты, рассматриваемые в данной книге
Таблица 1.3. Различные версии эхо-клиента, рассматриваемые в данной книге
Таблица 1.4. Различные версии эхо-сервера, рассматриваемые в данной книге
1.7. Модель OSI Самым общим способом описания уровней сети является предложенная Международной организацией по стандартизации (International Standards Organization, ISO) модель взаимодействия открытых систем (Open Systems Interconnection, OSI). Эта семиуровневая модель взаимодействия показана на рис. 1.5, где она сравнивается со стеком протоколов Интернета. ![]() Рис. 1.5. Уровни модели OSI и набор протоколов Интернета Два нижних уровня модели OSI соответствуют аппаратному обеспечению драйверов устройств и сетей, которое поддерживается системой. Обычно нам не нужно заботиться об этих уровнях, так как достаточно знать только некоторые свойства канального уровня — например, что MTU (максимальная единица передачи) Ethernet, описываемая в разделе 2.9, имеет размер 1500 байт. Сетевой уровень управляется протоколами IPv4 и IPv6. Оба они описываются в приложении А. Из протоколов транспортного уровня мы можем выбирать TCP или UDP (о них рассказывается в главе 2). На рис. 1.5 между TCP и UDP изображен разрыв, означающий, что, возможно, приложение минует транспортный уровень и будет использовать непосредственно IPv4 или IPv6. В таких случаях говорят о символьном, или неструктурированном1, сокете (raw socket), который будет описан в главе 25. Три верхних уровня модели OSI соответствуют уровню приложений. Приложением может быть web-клиент (браузер), клиент Telnet, web-сервер, сервер FTP или любое другое используемое нами приложение. В случае протоколов Интернета три верхних уровня модели OSI разделяются очень редко. Два программных интерфейса, которые мы описываем в этой книге, — сокеты и XTI — являются интерфейсами между верхними тремя уровнями (уровнем приложений) и транспортным уровнем. Это один из важнейших вопросов книги: как создавать приложения, используя либо сокеты, либо XTI, которые используют, в свою очередь, либо TCP, либо UDP. Мы уже упоминали о символьных сокетах, и в главе 26 мы увидим, что можем даже полностью миновать уровень IP, чтобы читать и записывать свои собственные кадры канального уровня. Почему и сокеты, и XTI предоставляют интерфейс между верхними тремя уровнями модели OSI и транспортным уровнем? Для подобной организации модели OSI имеются две причины, которые мы отобразили на правой стороне рис. 1.5. Прежде всего, три верхних уровня отвечают за все детали приложения (например, FTP, Telnet, HTTP), но мало «знают» о составных частях соединения. Четыре нижних уровня, напротив, мало «знают» о приложении, но отвечают за все составляющие соединения: отправку данных, ожидание распознавания, упорядочивание данных, приходящих не в должном порядке, расчет и проверку контрольных сумм и т. д. Вторая причина: верхние три уровня часто формируют то, что носит название пользовательского процесса (user process), в то время как четыре нижних уровня обычно поставляются как часть ядра операционной системы. Unix, как и многие современные операционные системы, обеспечивает разделение пользовательского процесса и ядра. Следовательно, интерфейс между уровнями 4 и 5 является естественным местом для создания программного интерфейса приложения (API). 1.8. История сетей BSD Интерфейсы сокетов происходят от системы 4.2BSD (Berkeley Software Distribution — программный продукт Калифорнийского университета, в данном случае это адаптированная для Интернета реализация операционной системы Unix, разрабатываемая и распространяемая этим университетом), выпущенной в 1983 году. На рис. 1.6 показана эволюция реализаций BSD и отмечены главные этапы развития TCP/IP. Некоторые изменения API сокетов также имели место в 1990 году в реализации 4.3BSD Reno, когда протоколы OSI были включены в ядро BSD. На рис. 1.6 представлены реализации системы от 4.2BSD до 4.4BSD, созданные группой по разработке компьютерных систем университета Беркли (Computer System Research Group, CSRG). Для использования этих реализаций требовалось, чтобы у получателя уже была лицензия на исходный код Unix. Однако весь код сетевых программ — и поддержка ядра (например, доменные стеки протоколов TCP/IP и Unix, а также интерфейс сокетов), и приложения (такие, как клиенты и серверы Telnet и FTP), — был разработан независимо от кода Unix, созданного AT&T. Поэтому начиная с 1989 года университет Беркли представил первые реализации системы BSD, не ограниченные лицензией на исходный код Unix. Эти реализации распространялись свободно и, в конечном итоге, стали доступны с помощью анонимных FTP любому пользователю Интернета. Последними реализациями Беркли стали 4.4BSD-Lite в 1994 и 4.4BSD-Lite2 в 1995 году. Нужно отметить, что эти две реализации были затем использованы в качестве основы для других систем: BSD/OS, FreeBSD, NetBSD и OpenBSD, и все четыре до сих пор активно развиваются и совершенствуются. Более подробную информацию о различных реализациях BSD, а также общую историю развития различных систем Unix можно найти в главе 1 [67]. Многие системы Unix начинались с некоторой версии сетевого кода BSD, включая API сокетов. Мы называем их реализациями, происходящими от Беркли, или Беркли-реализациями (Berkeley-derived implementations). Многие другие коммерческие версии Unix основаны на Unix System V Release 4, и некоторые из них содержат сетевые программы, разработанные в университете Беркли (например, UnixWare 2.x), в то время как сетевой код других систем, основанных на SVR4, был разработан независимо (например, Solaris 2.x). Мы также должны отметить, что система Linux, популярная и доступная реализация Unix, не относится к классу происходящих от Беркли: ее сетевой код и API сокетов были разработаны «с нуля». ![]() Рис. 1.6. История различных реализаций BSD 1.9. Сети и узлы, используемые в примерах На рис. 1.7 показаны различные сети и узлы, используемые в примерах книги. Для каждого узла мы указываем операционную систему и тип аппаратного обеспечения (потому что некоторые операционные системы работают более чем на одном типе аппаратного обеспечения). Имя в каждой рамке — это имя узла, встречающееся в тексте. ![]() Рис. 1.7. Сети и узлы, используемые в примерах Узлы двух верхних сетей Ethernet с адресами подсетей 206.62.32/27 и 206.62.64/27 находятся в домене kohala.com. Узлы нижней сети Ethernet с адресами подсетей 140.252.1.0/24 находятся в домене tuc.noao.edu, который используется Национальной оптической астрономической обсерваторией. Обозначение /27 и /24 указывает число последовательных битов, начиная с крайнего левого бита адреса, и используется для идентификации сети и подсети. На рис. А.4 и А.5 эти два адреса IPv4 показаны более подробно, а в разделе А.4 рассказывается о записи /n, используемой в настоящее время для обозначения границ подсети. На рис. 1.7 мы по-разному обозначили узлы, работающие как маршрутизаторы (прямоугольники со скругленными углами), и узлы, которые являются обычными компьютерами (обычные прямоугольники). Этих обозначений мы будем придерживаться на протяжении всей книги, поскольку иногда нужно четко отделять обычный узел от маршрутизатора. ПРИМЕЧАНИЕ. Хотим подчеркнуть, что настоящее имя операционной системы Sun — SunOS 5.x, а не Solaris 2.x, однако все называют ее Solaris. Определение топологии сети На рис. 1.7 мы показываем топологию сети, состоящей из улов, используемых в качестве примеров книги, но вам нужно знать топологию вашей собственной сети, чтобы запускать в ней примеры и выполнять упражнения. Хотя в настоящее время нет стандартов Unix в отношении сетевой конфигурации и администрирования, большинство систем Unix предоставляют две основные команды, которые можно использовать для определения подробностей построения сети: netstat и ifconfig. Мы приводим примеры в различных системах, представленных на рис. 1.7. Изучите руководство, в котором описаны эти команды для ваших систем, чтобы понять различия в той информации, которую вы получите на выходе. Также имейте в виду, что некоторые производители помещают эти команды в административный каталог, например /sbin или /usr/sbin, вместо обычного /usr/bin, и этих каталогов может не быть в обычном пути поиска (PATH). 1. netstat -i предоставляет информацию об интерфейсах. Флаг -n мы используем, чтобы получить численные адреса, а не имена сетей. В результате выводится перечень интерфейсов. linux % netstat -ni Kernel Interface table Iface MTU Met RX-OK RX-ERR RK-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flags lo 3584 0 32 0 0 0 32 0 0 0 BLRU eth0 1500 0 483929 0 0 0 449881 0 0 0 BRU Интерфейс закольцовки называется lo, а Ethernet называется eth0. В следующем примере показан узел с поддержкой IPv6. alpha % netstat -ni Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll ln0 1500 <Link> 08:00:2b:37:64:26 11220 0 4893 0 4 ln0 1500 DLI none 11220 0 4893 0 4 ln0 1500 206.62.226. 206.62.226.42 11220 0 4893 0 4 ln0 1500 IPv6 FE80::800:2B37:6424 11220 0 4893 0 4 ln0 1500 IPv6 5F1b:DF00:CE3E:E200:20:800:2B37:6426 11220 0 4893 0 4 lo0 1536 <Link> Link#3 12432 0 12432 0 0 lo0 1536 127 127.0.0.1 12432 0 12432 0 0 lo0 1536 IPv6 ::1 12432 0 12432 0 0 turn0 576 <Link> Link#4 0 0 0 0 0 turn0 576 IPv6 ::206.62.226.42 0 0 0 0 0 2. netstat -r показывает таблицу маршрутизации, которая тоже позволяет определить интерфейсы. Обычно мы задаем флаг -n для вывода численных адресов. При этом также приводится IP-адрес маршрутизатора, заданного по умолчанию: aix % netstat –rn
Routing tables
Destination Gateway Flags Refs Use MTU Netif Expire
Route tree for Protocol Family 2 (Internet):
default 206.62.226.62 UG 0 0 - en0
127/8 127.0.0.1 U 0 0 - lo0
206.62.226.32/27 206.62.226.43 U 4 475 - en0
Route tree for Protocol Family 24 (Internet v6):
::/96 0.0.0.0 UC 0 0 1480 sit0 - =>
default fe80::2:0:800:2078:e3e3 UG 0 0 - en0
::1 ::1 UH 0 0 16896 lo0
5flb:df00:ce3e:e200:20::/80
link#2 UC 0 0 1500 en0 -
fe80::/16 link#2 UC 0 0 1500 en0 -
fe80::2:0:800:2078:e3e3
link#2 UHDL 1 0 1500 en0 -
ff01::/16 ::1 U 0 0 - lo0
ff02::/16 fe80::800:5afc:2b36 U 1 3 1500 en0
ff11::/16 ::1 U 0 0 - lo0
ff12::/16 fe80::800:5afc:2b36 U 0 0 1500 en0
Длинные строки мы разбили на части, чтобы уместить их на странице. 3. Имея имена интерфейсов, мы выполняем команду ifconfig, чтобы получить подробную информацию для каждого интерфейса: linux % ifconfig eth0 При этом мы получаем IP-адрес, маску подсети и широковещательный адрес. Флаг MULTICAST часто указывает на то, что узел поддерживает широковещательную передачу. alpha % ifconfig ln0 В некоторых реализациях предоставляется флаг -a, при указании которого выводится информация обо всех сконфигурированных интерфейсах. 4. Одним из способов определить IP-адрес узлов в локальной сети является проверка широковещательного адреса (найденного нами на предыдущем шаге) с помощью программы ping. bsdi % ping 206.62.226.63 PING 206.62.226.63 (206.62.226.63): 56 data bytes 64 bytes from 206.62.226.35: icmp_seq=0 ttl=255 time=0.316 ms 64 bytes from 206.62.226.40: icmp_seq=0 ttl=64 time=1.369 ms (DUP!) 64 bytes from 206.62.226.34: icmp_seq=0 ttl=255 time=1.822 ms (DUP!) 64 bytes from 206.62.226.42: icmp_seq=0 ttl=64 time=2.27 ms (DUP!) 64 bytes from 206.62.226.37: icmp_seq=0 ttl=64 time=2.717 ms (DUP!) 64 bytes from 206.62.226.33: icmp_seq=0 ttl=255 time=3.281 ms (DUP!) 64 bytes from 206.62.226.62: icmp_seq=0 ttl=255 time=3.731 ms (DUP!) ^? вводим символ прерывания (DEL) --- 206.62.226.63 ping statistics --- 1 packets transmitted, 1 packets received, +6 duplicates, 0% packet loss round-trip min/avg/max = 0.316/2.215/3.731 ms 1.10. Стандарты Unix Стандарты Unix на сегодняшний день определяются Posix и The Open Group. 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 был IEEE Std 1003.1-1988 (317 страниц). Он определял интерфейс между языком C и оболочкой ядра Unix в следующих областях: примитивы процесса (fork, exec, сигналы, таймеры), среда процесса (идентификаторы пользователя, группы процессов), файлы и каталоги (все функции ввода-вывода), терминал ввода-вывода, системные базы данных (файл пароля и файл группы) и архивные форматы tar и cpio. ПРИМЕЧАНИЕ. Первый стандарт Posix был пробной версией, выпущенной в 1986 году и известной как IEEEIX. Название Posix было предложено Ричардом Столлмэном (Richard Stallman). Стандарт был обновлен в 1990 году (IEEE Std 1003.2–1990, 356 страниц), и стал международным стандартом (ISO/IEC 9945–1:1990). По сравнению с версией 1988 года в нее были внесены минимальные изменения. К названию было добавлено: «Часть 1: Системный программный интерфейс приложений [язык C]», таким образом отмечалось, что этот стандарт являлся API, написанным на языке C. Следующим стандартом Posix стал IEEE Std 1003.2–1992, и в его названии содержалось: «Часть 2: Оболочка и утилиты». Он был издан в двух томах общим объемом около 1300 страниц. В этой части определяются оболочка (основанная на оболочке System V Bourne) и порядка сотни утилит (программ, обычно запускаемых из оболочки, от awk и basename до vi и yacc). В книге мы будем обозначать этот стандарт как Posix.2. Далее появился стандарт IEEE Std 1003.1b–1993, ранее известный как IEEE 1003.4. Он стал дополнением стандарта 1003.1–1990 и включал расширения реального времени, разработанные группой P1003.4. Стандарт 1003.1b–1993 добавил к стандарту 1990 года следующие пункты: синхронизация файлов, асинхронный ввод-вывод, семафоры, управление памятью (функция mmap и разделение памяти), планирование выполнения, часы, таймеры и очереди сообщений. Стандарт 1003.1b– 1993 имел объем 590 страниц. Следующий стандарт Posix — IEEE Std 1003.1, редакция 1996 года [40], который включает 1003.1–1990 (базовый программный интерфейс приложений), 1003.1b–1993 (расширения реального времени), 1003.1c–1995 (функции Pthread) и 1003.1i–1995 (технические исправления 1003.1b). Этот стандарт также называется ISO/IEC 9945-1:1996. Были добавлены три главы, посвященные программным потокам, и общий объем стандарта составил 743 страницы. Мы будем обозначать этот стандарт как Posix.1. ПРИМЕЧАНИЕ. Более четверти из 743 страниц отводится приложению «Rationale and Notes» («Логическое обоснование и замечания»). Это обоснование содержит историческую информацию и причины, по которым те или иные свойства были включены или опущены. Часто такое обоснование бывает столь же информативным, как и официальный стандарт. Данный стандарт также содержит послесловие, в котором говорится, что стандарт ISO/IEC 9945 состоит из следующих частей: Стандарт Posix, оказавший влияние на большую часть этой книги, — это IEEE Std 1003.1g: Protocol Independent Interfaces (PII) (интерфейсы, не зависящие от протокола), выпущенный рабочей группой P1003.1g. Это сетевой стандарт API, определяющий два API, называемые DNI (Detailed Network Interfaces — подробные сетевые интерфейсы): 1. DNI/Socket, основанный на API сокетов 4.4BSD. 2. DNI/XTI, основанный на спецификации X/Open XPG4. Работа над этим стандартом началась в 80-х (рабочая группа P1003.12, позже переименованная в P1003.1g), но она еще не закончена, хотя и близка к завершению. Проект 6.4 (май 1996) был первым проектом, 75% содержания которого было одобрено при голосовании. Проект 6.6 (март 1997), похоже, является окончательным проектом [41]. В 1998 или 1999 году появится новая версия стандарта IEEE Std 1003.1, которая будет включена в стандарт IEEE Std 1003.1g. Хотя стандарт IEEE Std 1003.1g не является официально законченным, в этой книге используются свойства Проекта 6.6 этого стандарта всюду, где это возможно. Этот проект мы будем называть Posix.1g. Например, третий аргумент функции connect (см. раздел 4.3) представлен типом данных socklen_t, хотя он является новым в Posix.1g. Аналогично мы описываем новую функцию Posix.1g sockatmark (раздел 21.3) и предоставляем ее реализацию, используя функцию ioctl. Мы также используем обозначение AF_LOCAL, принятое в протоколе Posix.1g, вместо AF_UNIX для сокетов домена Unix. Различия между используемыми на сегодняшний день стандартами и Posix.1g в книге оговариваются. На сегодня ни один из производителей не поддерживает Posix.1g (поскольку это не окончательная реализация), но когда стандарт будет завершен, его поддержка производителями будет обеспечена. Работа над всеми стандартами Posix продолжается, и попытки описать эти стандарты можно сравнить со стрельбой по движущейся мишени. О текущем состоянии различных стандартов Posix можно узнать на сайте http://www.pasc.org/standing/sd11.html. Open Group Open Group (открытая группа) была создана в 1996 году при объединении организаций X/Open Group Company (основанной в 1984 году) и Open Software Foundation (OSF — Фонд открытого программного обеспечения, основанный в 1988 году). Это международный консорциум производителей и конечных потребителей в сфере промышленности, правительства и образовательных учреждений. Группа X/Open в 1989 году выпустила руководство X/Open Portability Guide, Выпуск 3 (XPG3). Выпуск 4 вышел в 1992 году, а его вторая версия последовала в 1994 году. Эта последняя версия была известна также под именем Спецификации 1170 (Spec 1170), и «волшебное» число 1170 было суммой количества системных интерфейсов (926), числа заголовочных файлов (70) и числа команд (174). Последнее название этого набора спецификаций — «X/Open Single Unix Specification» (Единая спецификация Unix), хотя он также называется «Unix 95». В марте 1997 году вышел анонс версии 2 Единой спецификации Unix. Продукты, удовлетворяющие ей, можно называть «Unix 98». Именно так мы называем эту спецификацию в тексте книги. Число интерфейсов, требуемых Unix 98, возросло с 1170 до 1434, хотя в случае рабочей станции дошло до 3030, поскольку включает CDE (Common Desktop Environment — коллективная среда настольных вычислительных средств). Это, в свою очередь, требует наличия системы X Window System и пользовательского интерфейса Motif. Подробности можно получить в книге [47] и по адресу http:/www.opengroup.org/public/tech/unix/version2. Нас интересуют сетевые сервисы, являющиеся частью Unix 98. Они определены в [74] для API сокетов и XTI. Эта спецификация очень близка к Проекту 6.6 Posix.1g. ПРИМЕЧАНИЕ. К сожалению, X/Open обозначает свои сетевые стандарты с помощью аббревиатуры XNS: X/Open Networking Services. Например, версия этого документа, в которой определяются сокеты и технологии XTI для Unix 98 [74], называется «XNS Issue 5». Дело в том, что в мире сетевых технологий аббревиатура XNS всегда служила акронимом для Xerox Network Systems (сетевые системы Xerox). Поэтому мы избегаем использования акронима XNS и называем документ X/Open просто сетевым стандартом API Unix 98. Internet Engineering Task Force IETF (Internet Engineering Task Force — целевая группа инженерной поддержки Интернета) — это большое открытое международное сообщество сетевых разработчиков, операторов, производителей и исследователей, работающих в области развития архитектуры Интернета и повышения стабильности его работы. Это сообщество открыто для всех желающих. Стандарты Интернета документированы в RFC 2026 [15]. Обычно стандарты Интернета посвящаются вопросам протоколов, а не программированию API. Тем не менее два документа RFC [32] [96] определяют API сокетов для протокола IP версии 6. Это информационные документы RFC, а не стандарты, и они были выпущены для того, чтобы ускорить применение переносимых приложений различными производителями, работающими с более ранними реализациями IPv6. Разработка текстов стандартов занимает все больше времени. Тем не менее интерфейсы API IPv6 будут, возможно, приведены к более строгому стандарту. Версии Unix и переносимость Практически все версии Unix, с которыми можно столкнуться сегодня, соответствуют какому-либо варианту стандарта Posix.1 или Posix.2. Мы говорим «какому-либо», потому что после внесения изменений в Posix (например, добавления расширений реального времени в 1993 и потоков в 1996) производителям обычно требуется год или два, чтобы подогнать свои программы под эти стандарты. Исторически большинство систем Unix являются потомками либо BSD, либо System V, но различия между ними постепенно стираются, по мере того как производители переходят к использованию стандартов Posix. Основные различия лежат в области системного администрирования, поскольку ни один стандарт Posix на данный момент не описывает эту область. В большинстве примеров этой книги используется стандарт Posix.1g, и основное внимание мы уделяем API сокетов. По возможности мы везде используем функции Posix. 1.11. 64-разрядные архитектуры С середины до конца 90-х годов развивается тенденция к переходу на 64-разрядные архитектуры и 64-разрядное программное обеспечение. Одной из причин является более значительная по размеру адресация внутри процесса (например, 64-разрядные указатели), необходимая при использовании больших объемов памяти (более 232 байт). Обычная модель программирования для существующих 32-разрядных систем Unix называется ILP32. Ее название указывает на то, что целые числа (I), длинные целые числа (L) и указатели (P) занимают 32 бита. В 64-разрядных системах Unix сейчас завоевывает популярность модель LP64. Ее название говорит о том, что 64 бита требуется только для длинных целых чисел (L) и указателей (P). В табл. 1.5 приводится сравнение этих двух моделей. С точки зрения программирования модель LP64 означает, что мы не можем считать, что указатель хранится как целое число. Мы также должны учитывать влияние модели LP64 на существующие API. Таблица 1.5. Сравнение количества битов для хранения различных типов данных в моделях ILP32 и LP64
В 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-разрядные, когда система Unix менялась с модели ILP32 на LP64. В обоих случаях не было необходимости в 64-разрядных типах данных: длина структуры адресов сокета занимает максимум несколько сотен байтов, и использование типа данных long для элементов структуры XTI было ошибкой. Мы рассмотрим новые типы данных, введенные для решения подобных проблем. API сокетов, для того чтобы хранить длину структур адресов сокетов, использует тип данных socklen_t, а XTI использует типы данных t_scalar_t и t_uscalar_t. Причина, по которой эти 32-разрядные значения не заменяются на 64-разрядные, заключается в том, что таким образом упрощается двоичная совместимость с новыми 64-разрядными системами для приложений, скомпилированных в 32-разрядных системах. 1.12. Резюме В листинге 1.1 показан полностью рабочий, хотя и простой клиент TCP, получающий текущее время и дату с заданного сервера. В листинге 1.5 представлена полная версия сервера. Эти примеры помогают ввести многие из терминов и понятий, которые в дальнейшем рассматриваются в книге. Наш клиент зависел от протокола. Мы изменили его, чтобы он использовал IPv6, но при этом получили лишь еще одну зависящую от протокола программу. В главе 11 мы разработаем некоторые функции, которые позволят нам написать код, не зависящий от протокола. Это важно, поскольку в Интернете начинает использоваться протокол IPv6. В книге мы будем использовать функции-обертки, созданные в разделе 1.4, для уменьшения размера нашего кода, хотя по-прежнему каждый вызов функции будет проходить проверку на предмет возвращения ошибки. Все имена наших функций-оберток начинаются с заглавной буквы. Стандарты IEEE Posix — Posix.1, описывающий основной интерфейс C и Unix, Posix.2, определяющий стандартные команды, и Posix.1g, определяющий сетевые API — являются стандартами, к которым постепенно приходит большинство производителей. Однако стандарты Posix активно принимаются и расширяются коммерческими стандартами, особенно стандартами Unix, разработанными Open Group, такими как Unix 98. Читатели, которых интересует история сетевого программирования в Unix, могут посмотреть историю развития Unix в книге [89], а в книге [90] представлена история TCP/IP и Интернета. Упражнения 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-адрес узла, на котором работает измененный сервер. Какое значение клиентского счетчика будет выведено? Если есть возможность, попробуйте также запустить клиент и сервер на разных узлах. |
|
|
| Дата
индексирования:
0000-00-00 00:00:00 Электронный адрес материала: http://Piter.com
MegaLib.com/ ОПЕРАЦИОННЫЕ СИСТЕМЫ/ Linux, Unix, FreeBSD / Перед Вами отрывок из книги(бумажное издание)! Для заказа книги нажмите здесь! |