Текст
                    Win
создание приложении «клиент-сервер»
TCP/IP, HTTP, FTP, SMTP и другие протоколы
применение Winsock №1.~ OvS<\
примеры программ на С, C++' • Visual Basic
jm
¥" ”St • F ? >


К. Джамса К. Коуп ПРОГРАММИРОВАНИЕ в среде Windows С^ППТЕР Санкт-Петербург Москва • Харьков • Минск 1996 
Kris Jamsa, Ph. D. Ken Cope PROGRAMMING 
К. Джамса, К. Коуп Программирование для Internet в среде Windows Перевел с английского И. Усов Главный редактор Заведующий редакцией Художественные редакторы Художник Корректор Верстка В. Усманов К. Крайн П. Кудряшов, С. Лебедев Д. Холодов B. Листова C. Широкая ББК 32.973 УДК 681.3.06 Джамса К., Коуп К. Д40 Программирование для Internet в среде Windows/Перев. с англ. — СПб: Питер, 1996. — 688 с.: ил. ISBN 5-88782-088-8 Популярнейшая в мире книга по программированию для Internet является прекрасным пособием для изучения TCP/IP и Winsock API. Изложение строится по принципу «от простого к сложному», никаких специальных знаний от читателя не требуется. Рассматриваются все основные службы Internet и соответствующие протоколы (в том числе SMTP, HTTP, FTP). Обсуждаются примеры программ на языках С, C++ и Visual Basic, в которых реализованы различные протоколы Internet. На сопровождающей книгу дискете содержатся исходные коды рассматриваемых в книге примеров программ. Предлагаемые утилиты, включая Web-сервер, FTP-клиент, утилиту преобразования IP-адреса в доменное имя и другие программы, могут оказаться полезными при разработке собственных сетевых приложений. Прочитав эту книгу, вы сможете не только досконально разобраться в протоколах Internet, но и создавать свои собственные приложения Windows для работы с электронной почтой, World Wide Web и другими ресурсами Сети. Original English Language Edition Copyright © Jamsa Press, 1995 © Перевод на русский язык, И. Усов, 1996 © Обложка, издательство «Питер Пресс», 1996 Published by arrangement with the original publisher, Jamsa Press, U.S.A. Подготовлено к печати издательством «Питер Пресс» по лицензионному договору с Jamsa Press, США. Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. ISBN 5-88782-088-8 ISBN 1-884133-12-6 (англ.) Все упомянутые в данном издании товарные знаки и зарегистрированные товарные знаки принадлежат своим законным владельцам. Издательство «Питер Пресс». 194044, С.-Петербург, Выборгская наб., 27. Лицензия ЛР № 063798 от 26.12.94. Подписано в печать 22.10.96. Формат 70X100 x/w Уел. п. л. 54,18. Доп. тираж 5000 экз. Заказ № 136. Отпечатано в ГПП «Печатный Двор» Комитета РФ по печати. 197110, С.-Петербург, Чкаловский пр., 15. 
Оглавление Глава 1. Введение в компьютерные сети 11 Что такое компьютерная сеть? 12 Соединяя сети вместе 13 Как компьютеры общаются между собой? 14 Что такое сетевая топология? 24 Объединения сетей 30 Структура сети 33 Подводя итоги 36 Глава 2. Принципы и понятия сетевой архитектуры 38 Сетевая терминология и концепции 39 Краткая история 40 О чем пойдет речь? 40 Здесь все разделено на уровни 41 Передача сообщений в сети 42 Что такое сетевые службы? 45 Вопросы разработки и проектирования сети 52 Сетевые уровни 54 Подробнее о сетевых уровнях 58 Модель клиент-сервер 71 Подводя итоги 74 Глава 3. Введение в TCP/IP 75 Значение семейства протоколов TCP/IP 76 Терминология TCP/IP 76 5 
Что такое поток данных? 78 Место, занимаемое TCP/IP 79 Данные двигаются по Интернет 80 Стек протоколов TCP/IP 80 Устройство физического уровня 82 Устройство уровня соединения 82 Словарь терминов расширяется 86 Подводя итоги 89 Глава 4. Сетевой протокол Интернет 91 Что такое сетевой уровень? 92 Адрес в сети Интернет 94 Что такое протоколы адреса Интернет? 101 Что такое 1Р-датаграмма? 102 Что такое IP-заголовок? 105 Что такое фрагментация? 114 Что такое 1Р-маршрутизация? 119 Подводя итоги 122 Глава 5. Транспортные протоколы 123 Что такое транспортный уровень? 124 Что такое порт транспортного уровня? 124 Как используется порт UDP? 126 Как используется порт TCP? 126 Как номер порта используется в программе? 127 Что такое протокол пользовательских датаграмм? 128 Что такое транспортный протокол? : 129 Что такое инкапсуляция? 143 Что такое прикладной уровень? 144 Подводя итоги 145 Глава 6. Протоколы SLIP и РРР 146 Обзор протоколов обмена 147 Соединение по протоколу SLIP 149 Протокол SLIP со сжатием (CSLIP) 157 Протокол Point-to-Point (РРР) 163 Подводя итоги 179 Глава 7. Интерфейс сокетов 180 Различные реализации сокетов 181 Ввод-вывод сетевых данных и данных в файловой системе 181 6 
Абстракция сокетов 183 Создание сокета 185 Использование сокета в программе 191 Настройка сокета 191 Передача данных через сокет 194 Прием данных через сокет 198 Процесс целиком 199 Сокеты и серверы 202 Процесс-сервер 205 Подводя итоги 207 Глава 8. Интерфейс сокетов Windows 209 Происхождение Winsock 210 Реализация Winsock 211 Общая картина 217 Концепция программирования сокетов 221 Сокеты Беркли по сравнению с сокетами Winsock 229 Что такое блокирование? 236 Не блокирующие сокеты и асинхронные функции 240 Еще раз о функции select 241 Подробнее о функции WSAAsyncSelect 241 Проблемы блокирования в Windows 3.1 243 Блокирование в сокетах Windows 244 Подводя итоги 248 Глава 9. Имена доменов в Интернет.. 250 Пример использования DNS 251 Иерархическое и простое пространство имен 252 Что такое система имен доменов? 253 Концепция сервера DNS 256 Преобразование имен в IP-адреса 258 Как устроен преобразователь адресов? 259 Подводя итоги 270 Глава 10. Протокол Finger и информация о пользователях....271 Еще раз о сетевом уровне представления 272 Что такое виртуальный сетевой терминал? 273 Учебная программа Finger 276 Что такое протокол Finger? 296 Подводя итоги 296 7 
Глава 11. Асинхронные сокеты Windows 298 Шаблон программы Sockman 299 В Sockman добавляется поиск в DNS 300 Как изменять функцию DoWinsockProgram? 301 Подробнее о диалоговом окне 302 Подробнее о диалоговой функции ,.303 Что такое диалоговая процедура? 305 Блокирующий поиск в DNS 310 Асинхронный поиск в DNS 313 Модифицируем функцию WndProc 319 Функция DisplayHostEntry 319 Подводя итоги 320 Глава 12. Дескрипторы задач Winsock 322 Добавление функции Finger к программе Sockman 323 Изменение функции DoWinsockProgram для Finger 324 Диалоговое окно Finger 326 Функция AsyncGetServicelnfo 330 Изменение функции WndProc 334 Функция LookupFingerHost 335 Еще раз изменяем функции WndProc 337 Выполнение асинхронной Finger-операции 339 Выполнение блокирующей Finger-операции 340 Функция DoFingerOperation 342 Общая картина 344 Подводя итоги 346 Глава 13. Время и сетевой порядок байтов 347 Сетевой порядок байтов 348 Протоколы времени Интернет 349 Протокол времени 350 Что такое порядок байтов? 352 Использование протокола Time Protocol 358 Создание программы Quick Time 358 Добавление запроса сервера времени в программу Sockman 372 Результаты запроса на сервер времени 384 Подводя итоги 386 Глава 14. Простые сокеты 388 Что такое ICMP? 389 Простые сокеты 405 Как к Sockman добавляются другие приложения? 422 Подводя итоги 423 8 
; : Оглавле||ие* *g.,f Глава 15. Электронная почта Интернет 424 Общая картина 425 Простой протокол передачи почты (SMTP) 427 Составные части сообщения электронной почты 440 Усовершенствования 441 Некоторые выводы 452 Протокол Post Office Protocol (POP) 453 Как все это работает? 457 Подводя итоги 460 Глава 16. Протоколы передачи файлов 461 Основы FTP 462 Что такое протоколы передачи файлов? 464 Модель FTP 466 Управление данными 468 Как FTP соотносится с TELNET? 471 Управление соединением 479 Команды FTP 483 Коды ответа FTP 493 Подводя итоги 496 Глава 17. Динамические библиотеки в приложениях Интернет 497 План работ 498 Краткий обзор протокола FTP 498 Первая стадия: управление сервером 499 Вторая стадия: передача данных 508 Третья стадия: разработка DLL 523 Подводя итоги 540 Глава 18. Визуальные приложения Интернет 542 SockFTP в действии 543 Цели проекта 546 Ваши возможности 549 Четвертая стадия QFTP: визуализация 550 Интерфейс между Visual Basic и DLL 551 Как работает SockFTP? 555 Процедуры SockFTP 568 Подводя итоги 584 9 
Оглавление Глава 19. Всемирная паутина World Wide Web 586 Протокол передачи гипертекста 587 Запросы клиента HTTP 588 Указатели ресурсов: URI и URL 588 Методы HTTP 591 Возможности WWW 591 Проблемы 592 Программирование WWW 593 Создание Web-сервера 602 Подводя итоги 627 Приложение А. Компьютеры-бастионы и безопасность в Интернет 628 Определяем цели 629 Что такое сетевой бастион? 631 Выводы 635 Приложение Б. Пособие по работе с учебными программами 636 Различные виды программ 637 Учебные программы 638 Разработка программ Sockman 638 Компиляция учебных программ 648 Алфавитный указатель 649 
лав Введение в компьютерные сети Если вы уже знаете, что такое компьютерная сеть, вы можете прочесть данную главу, чтобы освежить свои знания. Если вам незнакомы принципы построения сетей, но знакома сетевая терминология, здесь вы найдете описание того, как биты и байты перемещаются по сети. Если вы совсем новичок в мире компьютерных сетей — эта глава станет для вас введением в замечательный мир сетевого программирования. В предисловии к своей книге «Компьютерные сети» (Computer Networks, Prentice Hall, 1981) Эндрю Таненбаум (Andrew Tanenbaum) написал следующие строки: Ключ к успешному конструированию компьютерных сетей был впервые сформулирован Юлием Цезарем, произнесшим когда-то фразу: «Разделяй и властвуй». На самом деле этот принцип помогает не только в разработке, но также и в успешном понимании того, что происходит в современных компьютерных сетях. На первый взгляд, огромное количество материала, посвященного сетям, особенно сети без границ — Интернет, может отпугнуть вас. Не отчаивайтесь. Ключ к пониманию в ваших руках. Принцип «разделяй и властвуй» применительно к 11 
Глава 1. Введение в компьютерные сети получению новой для вас информации, означает, что вы должны разделить задачу изучения сети на множество более мелких подзадач, а их, в свою очередь — на еще более мелкие и т. д., пока не дойдете до задачки такого размера, охватить который не составит труда. Двигаясь вперед шаг за шагом, вы вскоре с удивлением обнаружите, что незаметно для самого себя обросли уже приличным багажом знаний. Например, чтобы понять, как сотня сетевых компьютеров взаимодействует друг с другом, вначале нужно понять, как это делают два. Точно так же нет нужды разбираться в работе тысяч составляющих Интернет сетей — достаточно рассмотреть это на примере всего двух или трех. Количество материала вначале может показаться чересчур большим, но, употребляя его как еду, кусочек за кусочком, вы вскоре приобретете понимание. Первая и вторая главы содержат информацию о сетях, поделенную на такие кусочки, и преподносят по очереди одну идею за другой. Закончив первую главу, вы будете знать следующее: ♦ Что такое компьютерная сеть. ♦ Чем сеть отличается от межсетевого объединения. ♦ Как компьютеры взаимодействуют друг с другом. ♦ Для переключения соединений требуются сетевые переключатели — маршрутизаторы электрических сигналов в сети. ♦ Процесс переключения пакетов делит данные на блоки меньших размеров для передачи. ♦ Топология сети — это способ, которым сетевые компьютеры соединяются между собой. ♦ Как администраторы сетей соединяют их друг с другом. ♦ Концепции, составляющие сеть. Что такое компьютерная сеть? Говоря общими словами, компьютерная сеть — это два компьютера, обменивающиеся сообщениями. Разумеется, большинство сетей состоят из большего количества компьютеров. Принципы сетевого общения не зависят от количества составляющих сеть компьютеров. Их может быть два-три, а может — больше тысячи. Чтобы понять, как сотни компьютеров общаются между собой, достаточно понять, как это делает пара. Сети бывают локальными или глобальными. Локальные сети (LAN, Local Area Network) объединяют находящиеся недалеко друг от друга, например в соседней комнате или здании, компьютеры. Иногда компьютеры могут находиться на расстоянии нескольких миль и все равно принадлежать локальной сети. Компьютеры глобальной сети (WAN, Wide-area network) могут находиться в других 12 
шшат Соединяя сети вместе городах или даже странах. Информация проделывает долгий путь, перемещаясь в глобальной сети. Интернет состоит из тысяч компьютерных сетей, разбросанных по всему миру. Однако программист должен рассматривать Интернет как единую глобальную сеть. Соединяя сети вместе Соединяя компьютеры между собой и давая им возможность общаться друг с другом, вы создаете сеть. Соединяя две и более сетей, вы создаете межсетевое объединение, называющееся «интернет» (internet) (обратите внимание на первую букву слова — она строчная). На рис. 1.1 показано, как соотносятся сети и межсетевые объединения. I I i \ i 1 i I :! ) I ! J ;1 I I § 1 I Рис. 1.1. Как соотносятся сети и межсетевые объединения Интернет (с заглавной буквы) — самое большое и популярное межсетевое объединение в мире. Оно объединяет более 20 000 компьютерных сетей, расположенных более чем в 130 странах. При этом объединены компьютеры тысяч различных типов, оснащенных широким спектром программного обеспечения. Когда вы пользуетесь сетью или даже пишете программы для нее, вы можете не обращать внимания на эти различия, скрывающиеся за библиотеками программ и интерфейсами. Начать изучение Интернет легче всего с принципов функционирования отдельных сетей. Осознав, что же происходит в отдельной сети, можно приступать к задаче посложнее — изучению Интернет, называемой «сетью сетей». 13 
Глава 1. Введение в компьютерные сети Как компьютеры общаются между собой? В простейшем случае сеть состоит всего из двух компьютеров. Чтобы узнать, как работает такая сеть, нужно понять, как происходит общение компьютеров, поэтому в следующих абзацах мы познакомимся с различными коммуникационными терминами и определениями. Общение компьютеров напоминает разговор людей. Для разговоров, как известно, используется язык. Все человеческие языки состоят из букв и символов, которые, соединяясь, формируют слова или идеи. На каком бы языке вы ни говорили, выразить свою мысль можно, только собрав предложение из слов. Следуя аналогии, компьютеры имеют в распоряжении две буквы —• единицу и ноль. Это так называемые двоичные цифры (символы), в комбинации образующие байты данных (слова, по аналогии). Чтобы передать осмысленное сообщение, байты данных собираются в последовательность (предложение). Отсюда следует: первое, что нужно усвоить для решения нашей задачи, — это правила двоичного представления информации. Конечная цель передачи информации по сети — доставить ее человеку. Для компьютеров двоичные данные — понятный язык, в случае человека это не так. Чтобы человек мог прочитать двоичные данные, компьютеры преобразуют их в буквы (символы). В следующих абзацах объясняется, как происходит преобразование. Язык компьютеров Вновь созданная программа состоит из набора двоичных цифр или битов; каждый бит представляет ноль или единицу. Если вы — прикладной программист, возможно, вам никогда не приходилось иметь дело с двоичными данными. Однако, программируя приложения Интернет, вам иногда придется считывать или записывать двоичные данные. Когда данные подготавливаются к передаче по сети, они всегда представлены в двоичном виде. Другими словами, чтобы понять, как данные передаются в Интернет, вы должны понять, как они преобразуются в двоичный формат. Для представления данных в сети используются электрические сигналы. Двоичные числа являются последовательностями из нулей и единиц, и при передаче часто принято считать, что отсутствие электрического сигнала в линии означает ноль, а его наличие — единицу. Люди пользуются числами в десятичном представлении. Двоичные данные с трудом преобразуются в десятичные, и одно из решений этой проблемы — использовать шестнадцатиричный формат, служащий как бы компромиссом между удобностью десятичного и неудобностью двоичного представлений. Если вы всерьез заинтересованы стать программистом Интернет, обдумайте возмож¬ 14 
Как компьютеры общаются между собой? ность облегчить задачу, купив недорогой калькулятор, умеющий преобразовывать данные между тремя упомянутыми форматами. В этой книге чаще всего будет использоваться шестнадцатиричная форма записи. Так же, как и в языке С, перед шестнадцатиричными числами будет ставиться префикс Ох, чтобы отличить их от десятичных. Шестнадцатиричные числа будут записываться группами по две цифры, включая ведущий ноль, если он есть. Двоичные числа записываются группами по восемь цифр, включая ведущие нули. В табл. 1.1 приведено несколько примеров трех форм записи. Таблица 1.1. Примеры записи десятичных, шестнадцатиричных и двоичных чисел Десятичное Шестнадцатиричное Двоичное 10 0х0А 00001010 100 0x64 01100100 255 OxFF 11111111 512 0x0200 0000001000000000 500 0x01F4 0000000111110100 В большом количестве документов Интернет числа упоминаются в двоичном представлении. Например, в документе RFC (Request for Comment), стандарте Интернет, может быть сказано, что определенное поле данных имеет ширину в 16 бит и что определенная их комбинация обозначает соответствующий тип сетевого сообщения. Разрабатывая сетевые приложения, вам придется не раз заглядывать в подобную документацию, так что учитесь думать и оперировать данными в двоичном представлении, иначе вы столкнетесь с трудностями. Десятичные, шестнадцатиричные и двоичные числа Любой язык программирования имеет средства для работы с числами и символами. Если вы знакомы с ASCII (American Standard Code for Information Interchange), вы знаете, что каждой букве алфавита, знакам пунктуации и т. д. там соответствует определенное число. Внутреннее представление кодов ASCII используется программами для вывода информации на экран или принтер. Внимательно посмотрев на таблицу ASCII, вы увидите, что коды там пронумерованы не только в десятичной форме, но также и в шестнадцатиричной, используемой программистами. Несмотря на то, что, говоря об ASCII-символах, большинство программистов подразумевает их восьмибитное кодирование, на практике очень часто можно обойтись лишь первой частью таблицы, имея дело с семью битами на символ. В табл. 1.2 приведены ASCII-коды для первых пяти букв английского алфавита. Каждая буква приведена в десятичном, шестнадцатиричном и двоичном представлении. 15 
Глава 1. Введение в компьютерные сети Таблица 1.2. Некоторые ASCII-коды в различных числовых представлениях Символ Десятичное Шестнадцатиричное Двоичное А 65 0x41 01000001 в 66 0x42 01000010 с 67 0x43 01000011 D 68 0x44 01000100 Е 69 0x45 01000101 Восьмибитный набор ASCII-символов называется еще «расширенным набором ASCII». Восьмой бит служит для кодирования символов псевдографики и некоторых специальных символов, например для рисования рамочек.1 Чтобы упростить изложение, под словом ASCII-символ в этой книге всегда будет пониматься то, что называется «расширенный набор ASCII», то есть совокупность символов в восьмибитной кодировке. В большинстве компьютеров байт состоит из восьми битов. Для представления байта удобно использовать шестнадцатиричную форму записи. Шестнадцатиричное представление основывается на числе 16. Числа в нем записываются при помощи цифр от ноля до девяти и латинских букв от «а» до «f». В табл. 1.3 показано, что четырьмя двоичными цифрами можно закодировать 16 различных значений. При помощи двух шестнадцатиричных цифр (по четыре бита в каждой) кодируется восьмибитный байт. Таблица 1.3. Шестнадцатиричная система счисления Десятичное Шестнадцатиричное Двоичное 0 0x00 0000 1 0x01 0001 2 0x02 0010 3 0x03 ООП 4 0x04 0100 5 0x05 0101 6 0x06 оно 7 0x07 0111 8 0x08 1000 9 0x09 1001 Буквы национальных алфавитов также кодируются с помощью восьмого бита. — Примеч. перев. 16 
Как компьютеры общаются между собой? Таблица 1.3 (окончание) Десятичное Шестнадцатиричное Двоичное 10 ОхОА 1010 11 0x0В 1011 12 ОхОС 1100 13 OxOD 1101 14 ОхОЕ 1110 15 OxOF 1111 Байт — наименьшая величина, с которой могут работать программы, поэтому программы всегда оперируют комбинациями восьмибитных последовательностей. Самое большое число, которое можно выразить восемью битами, — 255 (двоичное 11111111 или шестнадцатиричное OxFF). Шестнадцатиричным двузначным числом можно выразить 256 различных комбинаций и, таким образом, представить любой ASCII-символ. Если вы не сталкиваетесь с двоичным представлением данных, попробуйте рассматривать байт как двузначное шестнадцатиричное число. Любой ASCIIсимвол и, стало быть, букву алфавита можно выразить байтом или двузначным шестнадцатиричным числом. Если вы понимаете, как ASCII-код, двузначное шестнадцатиричное число, байт и восемь бит соотносятся между собой, вы можете эффективно управлять преобразованием данных в своих программах. Например, вы знаете, что каждое двузначное шестнадцатиричное число представляет байт (или восемь бит) данных. Значит, вы с легкостью преобразуете такое число в восемь бит двоичной информации. Если ваши данные являются текстом, при помощи ASCII-кодов символ текста легко преобразуется в восьмибитное двоичное число. Преобразуя, наоборот, из двоичной формы в текст, лучше облегчить задачу и применить шестнадцатиричное представление символов. При этом из каждой восьмерки битов получится шестнадцатиричное число. Соответствие между ним и символом ASCII легко устанавливается по таблице. Преобразовав данные из двоичного вида в текстовый, вы значительно упростите дальнейшую обработку данных в приложении. На рис. 1.2 показано, как двоичные данные преобразуются в читаемые людьми текстовые символы. Изначально данные представлены набором нулей и единиц (битов). Затем комбинация из восьми битов преобразуется в двузначное шестнадцатиричное число. Далее для преобразования его в текстовый символ используется таблица кодов ASCII. Изо дня в день люди подсчитывают разные вещи. Они хорошо научились это делать, пользуясь десятичной системой. Вам же придется освоиться и в других системах, особенно если дело касается обработки текста. Как и везде, здесь важна постоянная практика. Начните работать — и через некоторое время вы заметите, что можно с такой же легкостью считать предметы шестнадцатирично или двоично, с какой мы привыкли считать их в обыденности. 17 
Глава 1. Введение в компьютерные сети Двоичные данные в представлении компьютера Шестнадцатиричные данные в представлении программиста Текст ASCII в представлении пользователя 01001000 i 01001001 00100000 01001011 01000101 01001110 I 0x48 0x49 0x20 0x4В 0хВ5 0x4E I Н I К Е N g >>3- * v:*-;o: Щ. v.:b:h^MW^) Рис. 1.2. Преобразование двоичных данных в текст Программист Интернет должен уметь работать с двоичными и шестнадцатиричными числами. Опыт в работе с ними значительно облегчает понимание функционирования Интернет на низком уровне. Что такое сигналы сообщения? Компьютеры пользуются языком двоичных чисел, состоящих только из нулей и единиц. Программист должен знать, как этот небогатый выбор позволяет слать и принимать сетевые сообщения. В следующих абзацах кратко описывается механизм представления данных двоичными электрическими сигналами. Необязательно разбираться в подробностях этого механизма, однако если вам хотя бы немного интересно аппаратное обеспечение компьютеров, информация этого раздела будет полезна для вас. Независимо от того, на каком языке вы разговариваете, вы пользуетесь словами и предложениями. Компьютеры используют для разговора электрические сигналы. Ваши слова состоят из букв, чисел и символов. Слова компьютера состоят из нулей и единиц. Электрические сигналы бывают цифровые и аналоговые. Причем нули и единицы лучше всего выражать первым способом, поэтому внутри себя компьютеры говорят на цифровом языке. Аналоговые сигналы, по форме напоминающие океанские волны, используются при передаче информации через сеть. Изображение простого аналогового сигнала приведено на рис. 1.3. Примечание: Процесс получения и преобразования аналоговых сигналов изучается в электронике, а не в программировании, и в этой книге не рассматривается. Рис. 1.3. Простой аналоговый сигнал 18 
Как компьютеры общаются между собой? Частота аналогового сигнала — это количество возникновений волны в заданную единицу времени (обычно, в одну секунду). Она является важной характеристикой сигнала. Например, если одна электронная цепь генерирует волну 100 раз в секунду, а другая — 1000 раз, то говорят, что последняя имеет более высокую частоту по сравнению с первой. Некоторые электронные схемы умеют менять частоту, например, генерируя в одно время 100 волн в секунду, а в другое — 1000. Этот процесс называется частотной модуляцией (FM, Frequency Modulation) или просто модуляцией. На рис. 1.4 изображены два аналоговых сигнала. В сигнале слева помещается больше волн за единицу времени, чем в сигнале справа. Другими словами, он имеет более высокую частоту. Когда компьютер передает единицу, электронная схема генерирует более высокую частоту, а когда ноль — более низкую. Сигнал высокой частоты Сигнал низкой частоты Рис. 1.4. Аналоговые сигналы разной частоты Аналоговые сигналы работают на телефонных линиях для передачи информации. Для преобразования данных из цифрового вида в двоичный используются модемы. Модемы используют частотную модуляцию для представления двоичных данных. Термин «.модем» — сокращение от слов модулятор/демодулятор. Двоичный ноль преобразуется им в сигнал низкой, а единица — высокой частоты. Другими словами, преобразуя данные, модем модулирует частоту аналогового сигнала. Рис. 1.5. демонстрирует, как буква А (ASCII-код 0x41 или 01000001) будет выглядеть после преобразования модемом. Рис. 1.5. Буква А, преобразованная модемом в частотно-модулированный сигнал 19 
Глава 1. Введение в компьютерные сети Как компьютеры передают данные? Передача данных между компьютерами и прочими устройствами происходит параллельно или последовательно. Большинство персональных компьютеров пользуется параллельным портом для работы с принтером. Термин «параллельно» означает, что данные передаются одновременно по нескольким проводам. Чтобы послать байт данных по параллельному соединению, компьютер устанавливает одновременно восемь бит на восьми проводах. Схема параллельного соединения изображена на рис. 1.6. Рис. 1.6. Два компьютера, соединенные параллельным каналом Как видно из рисунка, параллельное соединение по восьми проводам позволяет передать байт данных одновременно. Напротив, последовательное соединение подразумевает передачу данных по очереди, бит за битом. В сетях чаще всего используется именно такой способ работы, когда биты выстраиваются друг за другом и последовательно передаются (и, стало быть, принимаются тоже). На рис. 1.7 показано, как передается двоичное число 10001110. Компьютер 1 Рис. 1.7. Два компьютерау соединенные последовательным каналом 20 
Как компьютеры общаются между собой? При соединении по проводам используются три различных метода, обозначаемые тремя различными терминами. Соединение бывает: симплексное, полудуплексное и дуплексное. О симплексном соединении говорят, когда данные перемещаются лишь в одном направлении. Полудуплексное соединение позволяет данным перемещаться в обоих направлениях, но в разное время, и, наконец, дуплексное соединение, это когда данные следуют в обоих направлениях одновременно. На рис. 1.8 указаны различия в потоках данных в зависимости от применяемого метода. (только в одном направлении) Полудуплексное соединение (в двух направлениях; только одно направление в один интервал времени) Дуплексное соединение (в двух направлениях одновременно) Рис. 1.8. Примеры потоков данных при симплексном, полудуплексном и дуплексном методах соединения Что такое переключение соединения? Переключение соединения используется сетями для передачи данных. Оно позволяет аппаратным средствам сети разделять один и тот же физический канал связи между многими устройствами. Рассмотрим, например, телефонные переговоры. 21 
Глава 1. Введение в компьютерные сети Возьмем ситуацию, когда вы не хотите использовать коммутируемые телефонные линии. Для того чтобы сохранить возможность звонить, например тысяче абонентов, вы будете должны воткнуть в телефонный аппарат тысячу проводов, соединяющих вас с ними напрямую. Поскольку вышеописанная ситуация чрезвычайно неудобна, большинство людей пользуется коммутируемыми линиями для переговоров. По этой же причине сети используют коммутацию (переключение) соединений. Два основных способа переключения соединения — переключение цепей и переключение пакетов. Переключение цепей Переключение цепей создает единое непрерывное соединение между двумя сетевыми устройствами. Пока эти устройства взаимодействуют, ни одно другое не сможет воспользоваться этим соединением для передачи собственной информации — оно вынуждено ждать, пока соединение освободится. Другими словами, переключение цепи позволяет устройствам делить между собой один и тот же коммуникационный канал, однако каждое должно ждать, пока наступит его очередь передавать или принимать данные. Простой пример переключателя цепей — переключатель типа А—В, служащий, чтобы два компьютера соединить с одним принтером. Чтобы один из компьютеров мог печатать, вы поворачиваете тумблер на переключателе, устанавливая непрерывное соединение между компьютером и принтером. Образуется соединение типа «точка-точка». Как изображено на рис. 1.9, только один компьютер может печатать в одно и то же время. Рис. 1.9. Переключение цепей на примере устройства А—В 22 
Как компьютеры общаются между собой? Переключение пакетов Большинство современных сетей, включая Интернет, используют переключение пакетов. Интернет — сеть с переключением пакетов (пакетной коммутацией). Программы передачи данных в таких сетях делят данные на кусочки, называемые пакетами. Рассмотрим два метода перевозки людей. Предположим, нужно перевезти группу из пятидесяти человек с одного конца города на другой. Для этого можно воспользоваться одним автобусом, забирающим всех сразу, а можно поделить группу на части и перевезти их на легковых автомобилях по отдельности. Предположим, что можно выбрать разные дороги, ведущие к одной и той же цели — другому концу города. Если все едут на автобусе, то все следуют одной дорогой. Если люди едут на автомобилях — они могут выбрать одну дорогу, но это не обязательно. Вы догадались, наверное, что наши пятьдесят пассажиров — не что иное как пятьдесят байтов данных. В сети пакетной коммутации эти пятьдесят байт могут следовать одновременно одним пакетом (автобус), а могут — в нескольких (отдельные автомобили). Так же, как и автобус и автомобили прибудут в одно и тоже место назначения, все наши пакеты несомненно сделают то же самое, несмотря на то, что пути, которыми они следовали, могут быть совершенно различны. Для сравнения двух видов соединения в сети, предположим, что мы прервали канал в каждом их них. Например, отключив принтер от менеджера на рис. 1.9 (переставив тумблер в положение В), вы напрочь лишили его возможности печатать. Соединение с переключением цепей требует наличия непрерывного канала связи. Наоборот, данные в сети с переключением пакетов могут двигаться различными путями. Это видно на примере рис. 1.10. Данные необязательно следуют одной Офисный компьютер Рис. 1.10. Данные путешествуют по сети с переключением пакетов 23 
Глава 1. Введение в компьютерные сети дорогой на пути между офисным и домашним компьютерами. Разрыв одного из каналов не приведет к потере соединения — данные просто пойдут другим маршрутом. На первый взгляд, сети с переключением пакетов кажутся проще, чем какие либо еще. Достаточно послать пакет, указав ему направление движения (при симплексной связи), и предоставить возможность найти дорогу самому. Однако сети, к сожалению, а может быть, к счастью, не так просты и состоят отнюдь не из пары компьютеров. Сети с переключением пакетов имеют множество альтернативных маршрутов для пакетов. Данные перемещаются в обоих направлениях. Следовательно, каждый пакет должен содержать адрес назначения. (Пакеты часто содержат и адрес отправления.) В дальнейшем вы узнаете, что концепция адресации пакетов — одна из важнейших в программировании для Интернет. Что такое сетевая топология? Существует невероятно большое число способов, которыми можно соединить компьютеры. Чем больше разных компьютеров, тем больше способов. Каждое соединение — это новый маршрут для данных. Топология сети — это ее геометрическая форма или физическое расположение компьютеров по отношению друг к другу. Топология сети дает способ сравнивать и классифицировать различные сети. Три основных типа топологии: звезда, кольцо и шина. Поскольку Интернет является «сетью сетей», в нем встречаются любые из перечисленных топологий. В следующих абзацах мы кратко объясним особенности каждой из них. Знание отличий между топологиями позволит легче решать некоторые задачи и разбираться в сетевых вопросах. Топология «звезда» В сети с топологией «звезда» все компьютеры соединены с центральным компьютером, или хабом (hub). Прямые соединения между двумя компьютерами такой сети отсутствуют. На рис. 1.11 показана сеть с топологией «звезда». Несмотря на то, что Federal Express доставляет письма и бандероли, а не данные, их система доставки похожа на передачу пакетов в топологии «звезда». Служба доставки Federal Express сначала накапливает все письма в своем центре (Мемфис, Иллинойс) и только затем отправляет к конечным получателям. На рис. 1.12 изображена работа службы доставки Federal Express, похожая на взрывающуюся звезду. Если бы Federal Express выбрал способ доставки «от отправителя к получателю», их служба больше походила бы на паутину и занимала бы куда больше ресурсов (самолетов и грузовиков), чем занимает на самом деле, ибо каждый путь в такой системе требовал бы отдельного транспортного средства. 24 
Что такое сетевая топология? Узел 2 Узел 4 Рис. 1.11. Топология звезда Рис. 1.12. Служба доставки Federal Express выглядит, как разлетающаяся звезда Используя топологию «звезда», Federal Express доставляет все посылки в центр, где сортирует их по месту назначения. На следующий день все посылки с одним и тем же местом назначения отправляются в одно и тоже время на одном и том же транспортном средстве. Такая система проста и эффективна. Точно так же пакеты данных от каждого компьютера направляются к центральному хабу. Хаб, в свою очередь, передает пакеты к месту назначения. Основное достоинство такой топологии в том, что если повредить отдельное соединение между компьютеров и хабом, вся сеть останется в строю. Если в Буффало, к примеру, случится сильная метель и доставка сообщений туда и оттуда прекра¬ 25 
Глава 1. Введение в компьютерные сети тится, это не значит, что Federal Express прекратит обслуживать клиентов в других городах и штатах. Почта для всех по-прежнему будет приходит с минимальными задержками. Недостаток топологии «звезда» — прямое следствие ее достоинств: если поврежденным окажется сам центральный хаб, это выведет из строя всю сеть без исключения. Топология «кольцо» Читатель, вероятно, уже понял из названия, что для топологии «кольцо» характерно отсутствие конечных точек соединения; сеть замкнута, образуя неразрывное кольцо (необязательно окружность), по которому передаются данные. Начав движение из одной точки такого кольца, данные в конце концов попадают-на его начало. Из-за такой особенности данные в кольце движутся всегда в одном и том же направлении. На рис. 1.13 показан пример сети с топологией «кольцо». Узел 4 Рис. 1.13. Топология кольцо В отличие от «звезды» «кольцу» необходим неразрывный путь между всеми сетевыми компьютерами, так как повреждение линии в одном месте приводит к остановке сети. Другое слабое место «кольца» состоит в том, что данные проходят через каждый сетевой компьютер, давая возможность не очень хорошим людям заниматься перехватом информации, не предназначенной посторонним. Топология «шина» Эта топология использует один передающий канал, обычно коаксиальный кабель, называемый «шина». Все сетевые компьютеры подсоединяются напрямую к шине. Пример такой сети изображен на рис. 1.14. 26 
Что такое сетевая топология? Шина Рис. 1.14. Топология шина В сети с топологией «шина» данные следуют в обоих направлениях одновременно. На обоих концах кабеля-шины устанавливаются специальные заглушки (терминаторы). Как и в случае «кольца», нарушение соединения в одном месте сети сразу прекратит ее работу. Безопасность данных в сети «шина» — такое же слабое место, как и в сети «кольцо»; данные всей сети проходят через каждый сетевой компьютер. Какая топология лучше? В предыдущем разделе были описаны отличия между различными сетевыми топологиями. Например, вы узнали, что в топологии «звезда» нарушение соединения между компьютером и центральным хабом не приводит к остановке работы остальной части сети. Также вы знаете, что такое нарушение в сетях с топологией «кольцо» или «шина» приводит к остановке работы всей сети. Вроде бы ясно, что топология «звезда» — лучшее, что мы имеем. Однако не забывайте, что центральный хаб в такой сети — весьма сложное и дорогое устройство, и во многих случаях его функции выполняет специальный компьютер. А теперь подумайте — что легче: чинить неисправный компьютер или неисправный кабель? В большинстве случаев починить кабель получается проще, дешевле и быстрее. До тех пор пока хаб не сломается, топология «звезда» обладает несомненными преимуществами перед остальными. Когда хаб ломается, стоимость его починки (включающая время и деньги) оказывается серьезной величиной. Чтобы сравнить достоинства и недостатки разных топологий, сетевые интеграторы и администраторы должны учесть множество факторов, и может случиться так, что, подсчитав разницу в стоимости ремонта хаба и починки кабеля, многие отдадут предпочтение топологии «кольцо» или «шина», несмотря на преимущества «звезды». Программисту Интернет необязательно углубляться в тонкости сетевых топологий — одна и та же программа будет работать на любой, однако для лучшего понимания предмета неплохо представлять себе разницу между ними. Шинный арбитраж Какую бы топологию мы не использовали, когда два компьютера начинают одновременно передавать данные, в сети происходит столкновение. Шинный арбитраж — процесс, призванный решить эту проблему. Он устанавливает 27 
Глава 1. Введение в компьютерные сети правила, по которым компьютеры узнают, когда шина свободна и можно передавать данные. Существуют два метода шинного арбитража: обнаружение столкновений и передача маркера. Обнаружение столкновений Родители учат детей переходить улицу, посмотрев сначала в одну сторону, а затем в другую. То есть сначала смотреть, а потом — идти. Когда на сети работает метод обнаружения столкновений, компьютер сначала слушает, а потом передает. Если компьютер слышит, что передачу ведет кто-то другой, он должен подождать окончания беседы и только потом предпринять повторную попытку. К сожалению, даже при соблюдении этих правил столкновения иногда случаются. В этой ситуации (два компьютера, передающие в одно и то же время) система обнаружения столкновений требует, чтобы передающий компьютер продолжал прослушивать канал и, обнаружив на нем чьи-то чужие данные, прекращал передачу, пытаясь возобновить ее через небольшой (случайный) промежуток времени. Такое поведение напоминает человеческое: «Только после вас!» Прослушивание канала до передачи называется «прослушивание несущей» (carrier sense), а прослушивание во время передачи — обнаружение столкновений (collision detection). Компьютер, поступающий таким образом, использует метод, называющийся «обнаружение столкновений с прослушиванием несущей», сокращенно CSCD. Передача маркера Системы с передачей маркера работают иначе. Для того чтобы передать данные, компьютер сначала должен получить разрешение. Это значит, он должен «поймать» циркулирующий в сети пакет данных специального вида, называемый маркером. Маркер перемещается по замкнутому кругу, минуя поочередно каждый сетевой компьютер. Хорошей аналогией служит комната, заполненная людьми (компьютерами), сидящими в кругу (в сети) и передающими поочередно друг другу микрофон (маркер) для того, чтобы получить возможность высказаться. Очередной, получивший микрофон, но не желающий говорить, просто передают его следующему в кругу. Так же как два компьютера могут начать передачу одновременно, несмотря на установленные системой обнаружения столкновений правила, в системе с передачей маркера он может внезапно потеряться. Такая ситуация, к счастью, предусмотрена разработчиками. Сеть имеет средства, чтобы обнаружить пропажу маркера и сотворить новый. В противном случае пропажа приводила бы к полной остановке сети. Каждый раз, когда компьютер должен послать сообщение, он ловит и держит маркер у себя. Как только передача закончилась, он посылает новый маркер в путешествие дальше по сети. Такой подход дает гарантию, что любой компьютер рано или поздно получит право поймать и держивать маркер до тех пор, пока его собственная передача не закончится. 28 
Топологии и технологии Что такое сетевая топология? Предыдущий материал был посвящен различиям в сетевых топологиях. Мы рассмотрели топологии «звезда», «кольцо» и «шина». К сожалению, понятия топологии и технологии часто путаются между собой. Например, Ethernet, ARCNET и IBM token ring — три образца хорошо известных и часто используемых сетевых технологий. Технология Ethernet разработана в 1973 году группой американских исследователей, возглавляемой Бобом Меткальфом (Bob Metcalf) в исследовательском центре Palo Alto (Xerox Palo Alto Research Center, PARC). Сети Ethernet могут строиться как в виде звезды, так и шины. Когда в качестве канала применен коаксиальный кабель, сеть Ethernet конфигурируется как шина. Если применена витая пара, Ethernet конфигурируется как звезда. Примечание: Образец витой пары вы без труда найдете, если посмотрите на то, чем соединен ваш телефон с телефонной розеткой на стене. Технология ARCNET (Attached Resource Computer NETwork) разработана фирмой Datapoint Corporation в 1968 году. Сеть технологии ARCNET, так же, как и сеть Ethernet, может строиться по двум топологиям — «звезда» или «шина». Технология Token Ring, разработанная IBM, представляет интересную смесь топологий. Как следует из названия, способом шинного арбитража сети Token Ring является передача маркера, однако на самом деле эта технология является гибридом (смесью) топологий «звезда» и «кольцо». Token Ring работает по топологии «звезда» со специальным устройством IBM, называющимся «станция многопользовательского доступа» (Multi-station Access Unit, MAU), в качестве центрального хаба. Однако для связи с ним каждый компьютер имеет два кабеля, по одному из которых он посылает данные, а по другому — принимает. Рис. 1.15. Маршрут данных в сети IBM Token Ring 29 
Глава 1. Введение в компьютерные сети Таким образом, получается, что сеть Token Ring — это как бы кольцо, но оформленное как звезда. На рис. 1.15 показан маршрут данных в сети IBM Token Ring. Объединения сетей Интернет — межсетевое объединение или, иначе, сообщество компьютерных сетей, соединенных друг с другом. Межсетевое сообщество невозможно описать в терминах той или иной конкретной топологии — оно объединяет любые сети. Межсетевые объединения пользуются рядом устройств, таких как повторители, мосты, маршрутизаторы и шлюзы для объединения разнородных сетей. Иногда этими устройствами пользуются локальные сети, для увеличения охватываемой территории, например. Несмотря на то что вам не нужно разбираться в подробностях работы всех этих устройств, нужно представлять себе, зачем они нужны и как они функционируют. Что такое затухание? Если вы когда-нибудь бросали камень в воду, вы видели круги от всплеска на поверхности. Со временем они становятся все меньше и меньше, постепенно исчезая совсем. Точно так же и аналоговая волна — чем дальше она уходит от источника, тем слабее становится. Если на поверхности воды выступают какиенибудь предметы, они заставляют круги от всплеска еще больше терять свою силу. Также аналоговый сигнал, встречая на пути сопротивление линии и шум в ней, теряет свою силу. Инженеры называют такую потерю «затуханием». Поскольку компьютеры используют аналоговый сигнал при работе в сети и он подвержен затуханию, в сети применяют специальные устройства для борьбы с этим явлением — «повторителш (repeaters)1. Что такое повторитель? Из названия ясно, что главная задача повторителя — повторять принятый сигнал. Кроме того, повторитель еще и усиливает его перед передачей. Это значит, что размер аналоговой волны увеличивается (без изменения ее частоты), исправляя последствия затухания на пути по кабелю к повторителю. Другими словами, повторитель усиливает принятый сигнал, перед тем как послать его дальше. Правильно размещенные по длине сети повторители позволяют разработчикам увеличить длину обслуживаемой сети и расстояния между соседними компьютерами. Сети большой протяженности используют большое количество повторителей. Сети технологии Ethernet также часто используют повторители для удлинения Повторители еще называют ретрансляторами. — Примеч. перев. 30 
Объединения сетей кабеля-шины локальной сети и устранения связанных с затуханием проблем. Повторители ставят и там, где надо соединить два участка одной и той же сети. В любом случае вы можете использовать повторители не только на участках объединения сетей, но и в отдельной локальной сети. Что такое мост? Мост — это устройство, соединяющее две сети, построенные по одной и той же технологии (например, Ethernet или ARCNET). Устройство мостов более сложно по сравнению с повторителями. Предположим, мост объединяет две сети, называющиеся «восточная» и «западная». В обязанности моста входит анализировать все пакеты данных, проходящие мимо него по обеим сетям. Когда мост замечает пакет в сети «восточная», предназначенный для компьютера в сети «западная», он передает его туда. Однако он не трогает пакеты внутри сети, предназначенные компьютерам той же сети. То есть если по сети «восточная» проходит пакет для ее же компьютера, мост ничего не делает. Мост действует, как постовой-регулировщик на забитом машинами перекрестке перед мостом. Регулировщик (мост) спрашивает у каждого водителя (пакета сети «восточная») о том, куда он направляется. Если водитель называет дом, находящийся на другой стороне реки (в сети «западная»), регулировщик направляет его на мост (в сеть «западная»). Если пункт назначения водителя на этой стороне реки (в сети «восточная»), регулировщик направляет его дальше по перекрестку (в сеть «восточная»). Регулировщик направляет на мост только те пакеты, местом назначения которых является другая сторона реки (место назначения пакета в сети «западная»). Мосты и производительность Кроме объединения двух сетей мост может заниматься вопросами безопасности данных, увеличением производительности и надежности сетей. Вы знаете, что система обнаружения столкновений требует прослушивания канала компьютером до начала передачи. Это вызывает некоторую задержку в работе. Чем больше компьютеров в сети, тем чаще случаются столкновения пакетов. Столкновения происходят чаще, так как большее число компьютеров пытаются одновременно начать передачу. Как и автомобили на шоссе, пакеты формируют сетевой трафик. Чем больше автомобилей (пакетов) следует по шоссе (шине данных), тем выше вероятность столкновения. Чем больше компьютеров, тем больше сетевой трафик. Начиная с некоторого момента, трафик возрастает настолько, что производительность сети начинает снижаться — возникают сетевые «пробки». Иногда решить проблему можно, разделив перегруженную локальную сеть на две и более отдельных частей и соединив их друг с другом через мост. Предположим, что сети «восточная» и «западная» раньше были одним целым. Теперь мост (регулировщик) не дает пакетам «западной» сети проскользнуть в 31 
Глава 1. Введение в компьютерные сети «восточную» и наоборот. Теперь они не мешают друг другу. Трафик сети снижается и, в результате, производительность сети приходит в норму. Мосты и надежность Даже если производительность сети нормальна, иногда администраторы делят сеть на некоторое количество более мелких сетей. Причиной является увеличение надежности. Повреждение кабеля в сетях с топологией «шина» или «кольцо» приводит к остановке сети. Дробление одной большой сети на части меньшего размера при помощи моста — ни что иное, как способ сохранить работоспособность большинства из них при разрушении кабеля в одной. Вернемся к воображаемым «западной» и «восточной» сетям. Разделенные мостом, они независимы друг от друга. Обрыв кабеля связи в сети «восточная» остановит работы всех ее компьютеров. Однако «западная» сеть останется по-прежнему работоспособной за исключением того, что ее компьютеры не смогут общаться с компьютерами «восточной». Делением сети на несколько меньшего размера и соединением их через мост достигается увеличение надежности сети в целом. Например, одиночное повреждение шины приведет к остановке только половины или еще меньшей части сети. Разделенную однажды сеть можно продолжать делить и дальше, постепенно наращивая уровень надежности. Мосты и секретность В сети с топологией «шина» или «кольцо» данные проходят через каждый сетевой компьютер. Любой пользователь сети может воспользоваться специальным устройством — анализатором трафика, с тем чтобы перехватить и получить доступ к любым пакетам сетевых данных. В зависимости от типа сети функции анализатора может выполнять и программное обеспечение. Специалисты по сетям анализируют трафик по совершенно разным причинам. Например, это может потребоваться для решения проблемы столкновения пакетов или чтобы отладить сетевую программу. Все это законные причины для анализа трафика. К сожалению, анализаторы не всегда используются для законных целей. Особенно остро проблема встает там, где по сети передается важная коммерческая или государственная информация, представляющая тайну для посторонних людей. Мост не в состоянии предотвратить мониторинг сетевой активности. Однако сетевой администратор может использовать мост для отделения части сети, по которой передается важная информация, от остальной ее части, снижая вероятность незаконного получения сведений. Что такое маршрутизатор? Это устройство, как ясно из названия, маршрутизирует данные между сетями. Маршрутизатор может соединять сети с одинаковой технологией, но чаще он 32 
Структура сети используется там, где технологии сетей различны, например между Ethernet и Token Ring. Маршрутизаторы — важная составляющая Интернет, поскольку важнейшая функция Интернет — соединять различные сети. Маршрутизатор в отличие от моста имеет свой собственный сетевой адрес. Это ключевой момент в понимании разницы между первым и вторым. Маршрутизатор в сетях часто используется как промежуточный пункт назначения для пакетов данных. Другими словами, компьютер может послать пакет в другую сеть, использовав маршрутизатор как промежуточный пункт назначения. С другой стороны, компьютеры никогда не посылают данные на мост — это излишне. Маршрутизатор никогда не анализирует принятый пакет, если только сам не является его конечным получателем, что узнается из полей адреса пакета. Что такое шлюз? Сетевой шлюз — общее название трех типов сетевых устройств. Маршрутизатор также может называться шлюзом. Шлюз в таком понимании является тем же самым устройством, работа которого описана в предыдущем абзаце. Бывают также шлюзы прикладного обеспечения. Шлюзы прикладного обеспечения занимаются преобразованием данных для определенных сетевых программ. Часто встречаются шлюзы для почтовых сообщений, преобразующие данные из одного формата электронной почты в другой. Например, если для связи между сотрудниками одной фирмы применяется почтовая система MCI Mail, а для связи с иногородним филиалом — почта Интернет, функции преобразования одного формата сообщений в другой берет на себя почтовый шлюз, то есть специальная программа, работающая на одном из сетевых компьютеров. Из второй главы вы узнаете, что в сетях применяются наборы правил, называемых протоколами, для всех видов передачи данных. Третий тип шлюзов обеспечивает преобразование одних протоколов в другие. Структура сети В предыдущем разделе мы познакомились с некоторыми определениями и концепциями. Внимание было сконцентрировано на физическом аспекте. Начиная с этого момента мы начнем изучать вопросы проектирования, послужившие причиной для появления именно такой, а не другой физической структуры. Читая предыдущие абзацы, вы наверное пытались соотнести вопросы проектирования сетей с их выражением в тех или иных аппаратных или физических компонентах. Для того чтобы просто и эффективно программировать в среде Интернет, вы должны хорошо освоить несколько основных принципов, на которых базируется проектирование сети, и понять, как эти принципы выражаются в физических элементах, из которых в конечном итоге складывается любая компьютерная сеть. 2 За к. N° 1949 зз 
Глава 1. Введение в компьютерные сети Основные компоненты Любая компьютерная сеть делится на две основные части: сетевые приложения и подсистему доставки сообщений. Сетевые приложения пользуются подсистемой доставки для передачи данных по сети. Другими словами, подсистема доставки — то транспортное средство, которое непосредственно переносит данные с одного места на другое. В целях успешного программирования необходимо хорошо представлять, как устроена эта подсистема. В различных источниках подсистема доставки данных называется по-разному: иногда ее называют транспортной системой, иногда — коммуникационной подсетью. Термин «транспортная система», наверное, лучше всего характеризует ее основное назначение — транспортировать данные между сетевыми компьютерами и прикладными программами. Программист в среде Интернет создает приложения, призванные обмениваться сообщениями по сети с другими такими же приложениями. Для эффективной работы приложения должны пользоваться средствами транспортировки, предоставляемыми подсистемой доставки сообщений. Понимание принципов работы подсистемы доставки необходимо для успешной деятельности на поприще сетевого программирования. Лучший способ понять работу подсистемы — это взглянуть на нее глазами сетевого разработчика. Подсистема доставки сообщений Вы знаете, что переключение соединений позволяет компьютерам и сетям делить одни и те же физические линии для передачи различных сообщений. Необходимо понимать, что концепция переключения соединений концентрирует внимание только на самом процессе соединения, а не на том, зачем же нужно такое соединение. Так, например, телефонные компании устанавливают телефонную сеть и используют на ней технику переключения цепей. С другой стороны, практически все компьютерные сети используют технику переключения пакетов. Интернет не является исключением. Сети с переключением пакетов состоят из двух важнейших компонентов: собственно переключателей и линий передач данных. Данные, посылаемые вашей программой, проходят по линиям передачи и встречают на пути сетевые переключатели. Сеть с переключением пакетов напоминает железную дорогу. Рельсы, по которым идут поезда, — это линии связи, а поезда в этой аналогии похожи на пакеты данных. Железнодорожные стрелки — это сетевые переключатели пакетов, служащие, чтобы изменять направление движения поезда (или пакета). Переводя стрелки (переключая пакет), диспетчер (переключатель) направляет поезд (пакет) в нужную сторону. Функции переключателя, в зависимости от ситуации, может выполнять сетевой компьютер (хост) или специальное устройство, например мост или маршрутизатор. В любом случае переключатель — это устройство, помогающее пакету данных выбрать правильный маршрут на пути к месту назначения. Литература, 34 
Структура сети посвященная Интернет, по-разному называет переключатели пакетов. В ходу такие термины, как «интерфейсный процессор сообщений» (interface message processor, IMP), «связной компьютер» (communications computer), «пакетный переключатель» (packet switch), «узел обмена и переключения данных» (data switching exchange) или просто «узел» (node). Чем дальше вы углубляетесь в подробности структуры сети, тем важнее становится точная терминология. В данной книге мы будем использовать термин «переключатель пакетов» по отношению к переключающим устройствам в Интернет. Нужно понимать, что в действительности переключателями пакетов могут быть мосты, маршрутизаторы, сетевые компьютеры (хосты) и некоторые другие устройства, выполняющие одинаковые функции. Линии передачи данных — это физические элементы, соединяющие переключатели пакетов между собой. Литература об Интернет иногда называет их цепями (circuits), коммуникационными каналами (communication channels) или просто каналами. Когда рассматривается поток данных через сеть, линии передачи данных иногда называются потоком данных (data stream). Часто под термином «поток данных» подразумевается процесс перемещения данных по линиям передачи. В литературе об Интернет этот процесс называется иногда «положить данные в канал» (to pump data into the channel) или «послать данные по соединению» (to send data across the circuit). Все эти фразы означают одно: вы размещаете данные на канале связи так, чтобы они дошли до места назначения где-нибудь в сети. Из чего состоит подсистема доставки сообщений? В своей книге «Компьютерные сети» Эндрю Таненбаум различает два основных способа работы подсистемы доставки: канал «точка-точка» и широковещательный (broadcast) канал. Во втором разделе книги будет показано, что структура прикладной программы зависит от типа используемых ею каналов. Приложения, использующие канал «точка-точка», значительно отличаются от широковещательных приложений. В приложениях «точка-точка» пакеты данных перемещаются между компьютерами от одного к другому. В широковещательных приложениях все сетевые компьютеры на локальной сети принимают все переданные пакеты данных. Каким бывает широковещательный канал? Тип широковещательного канала определяется тем, как именно сеть распределяет его ресурсы для подключенных к ней компьютеров. Широковещательный канал бывает с динамическим и статическим распределением ресурсов. Статическое распределение — это когда каждому компьютеру отведен определенный, заданный явно промежуток времени для передачи данных. Этот промежуток остается неизменным (статичным). Использование статического распределения 35 
Глава 1. Введение в компьютерные сети в компьютерных сетях неэффективно. Даже если компьютеру не нужно передавать данные, ни один, ни другой не смогут использовать освободившееся время в своих нуждах. В сетях обычно применяется второй тип распределения — динамический. В этом случае компьютер, которому есть что передать, не станет дожидаться окончания отведенного для своего пассивного соседа промежутка времени — он сразу попытается использовать свободный канал. Для поддержания работы динамического распределения широковещательного канала используется известный уже вам принцип: шинный арбитраж. Динамическое распределение ресурсов канала бывает централизованным и децентрализованным. Термины «централизованный» и «децентрализованный» используются для описания методов шинного арбитража, с которым вы знакомы. Например, централизованное динамическое распределение ресурсов широковещательного канала — не что иное, как метод передачи маркера. В зависимости от нужд определенного компьютера он может, получив маркер, захватить, а может и не захватывать канал для передачи. В этом случае перемещение маркера по сети означает распределение прав вести передачу. Маркер — центральный «управляющий» сети, дающий право вести передачу. Ни один компьютер, не завладевший маркером, не имеет право передавать данные. Услышав термин «децентрализованное динамическое распределение ресурсов», имейте в виду, что речь идет об обнаружении столкновений. В этом случае каждый компьютер самостоятельно решает, когда можно, а когда нельзя вести передачу. Здесь нет центрального «управляющего», дающего право передавать. В случае децентрализации невозможно полностью избежать столкновений пакетов данных. Таким образом, канал с децентрализованным динамическим распределением ресурсов должен содержать систему обнаружения столкновений. Подводя итоги Программирование в Интернет можно сравнить с вождением автомобиля. Для того чтобы успешно справляться с вождением, совсем необязательно быть специалистом в области автомобильных двигателей. Точно так же вам вовсе необязательно знать столько о сетях, сколько знают о них разработчики. Однако если вдруг автомобиль забарахлит, специалист исправит это быстрее и проще, чем если бы за дело взялся человек, не разбирающийся в двигателях и коробках передач. И если вдруг возникают проблемы или ошибки в программах для Интернет, для их устранения неплохо было бы разбираться в некоторых аспектах работы сетей, даже если для решения прикладной задачи таких знаний и не требуется. В этой главе мы рассмотрели компьютерные сети, пользуясь весьма обобщенной терминологией. Перед тем как продолжить чтение, проверьте, хорошо ли вы усвоили следующие понятия: 36 
Подводя итоги S Два или больше компьютеров, соединенных между собой и способных обмениваться сообщениями, составляют компьютерную сеть. Две или больше компьютерных сети составляют межсетевое объединение, называемое «интернет». S Переключение соединений — способ, позволяющий разделять одну и ту же линию связи между многими сетевыми устройствами. S Переключение пакетов — это такой тип переключения соединений, когда данные перемещаются отдельными блоками, называемыми пакетами. S Сетевая топология описывает компьютерную сеть в терминах физической формы или геометрической фигуры, образуемыми соединениями между компьютерами. Основные сетевые топологии: «звезда», «кольцо» и «шина». S Для соединения сетей друг с другом администраторы используют повторители, мосты, маршрутизаторы и шлюзы. S В любой сети различают два основных компонента: прикладные программы и подсистему доставки сообщений. 
Глав Принципы и понятия сетевой архитектуры Представьте себе архитектора, садящегося проектировать, скажем, современный небоскреб. Составить чертеж небоскреба за один присест — задача, разумеется, совершенно невыполнимая. Архитектор не сидит за кульманом, вырисовывая на одном листе бумаги габариты здания, планировку пола, детали фасада, план водоснабжения и электропроводки. И даже если бы такой чертеж появился, совершенно немыслимо было бы заставить подрядчиков изготавливать детали по нему, а строителей — строить. На самом деле архитектор делит задачу на меньшие размером составляющие и выполняет чертежи и наброски деталей по отдельности. Например, проект фундамента нового здания становится отдельной задачей и выполняется на отдельном чертеже. Любой прикладной программист, каким вы, по всей вероятности, являетесь, конструирует программное обеспечение произвольной сложности, пользуясь библиотеками, отдельными блоками и модулями исходного текста. Из первой главы вы узнали принцип «разделяй и властвуй». Оказывается, его можно приложить не только к собственно программированию, но также и к изучению, например Интернет. Интернет, как и все большое и необъятное, 38 
Сетевая терминология и концепции можно разделить на небольшие области, изучая которые вы сможете чувствовать себя намного уверенней, чем если бы вы взялись изучить весь предмет целиком. Должно быть вам известно, что грамотно и аккуратно разработанный интерфейс между модулями программы (список передаваемых параметров) позволяет легко модифицировать отдельный модуль, не внося изменений в остальную программу. Сетевые инженеры строят свои сети, используя такие же принципы. Термин «сетевая архитектура» относится именно к модульности компьютерных сетей. Сетевая архитектура, другими словами — это описание способов, которыми конструкторы сети соединяют в единое целое различные компоненты, получая в результате гибкую функционирующую сеть. В данной главе мы рассмотрим следующие ключевые моменты: ♦ Концепции конструирования сетей намного проще, чем сетевая терминология. ♦ Протоколы — это правила, по которым сетевые программы шлют и принимают данные. ♦ Сетевая модель ISO/OSI определяет функциональные уровни сетей. ♦ Каждый сетевой уровень выполняет четко очерченный круг функций. ♦ Разработка сетевого программного обеспечения облегчается, когда мы используем концепцию клиент/сервер. Сетевая терминология и концепции Знание сетевой терминологии облегчает понимание принципов функционирования. Встретив незнакомый термин, уделите немного времени, чтобы узнать его значение. Очень часто вы будете обнаруживать, что этот термин относится к давно знакомому вам сетевому аспекту. Формальные описания сетевых протоколов как бы специально призваны смутить умы неискушенных читателей. Не обращайте внимания, просто составляйте знания так, как пишите ваши программы. Используйте небольшие, хорошо организованные блоки. Проверяйте, хорошо ли вы усвоили из предыдущего, чтобы идти вперед. Читайте разнообразные журналы и книги, посвященные данной тематике. Наконец используйте накопленный опыт ваших коллег по работе. Главное — усвойте, что принципы сетевой архитектуры понять легче, чем запомнить различные, зачастую формальные термины, в которых эти принципы описываются. Познавая сетевые концепции, обращайте внимание на различные термины, в которых они описываются. Чем больше вы будете знать — тем шире раскроется горизонт перед вами. Запомните, что незнакомый термин может описывать уже знакомый принцип. К тому же профессионалы часто страдают небрежностями и неточностями в употреблении различных терминов. Не запутаться в них окончательно вам поможет прочный фундамент приобретенных знаний. Первое, что необходимо сделать, осваивая новый для вас аспект сетевой архитектуры, это определить: что это такое, как оно работает и зачем это нужно. Почти каждая глава этой книги отвечает на те или иные из этих вопросов. 39 
Глава 2. Принципы и понятия сетевой архитектуры Краткая история В период с 1977 по 1984 год профессионалы разработали модель сетевой архитектуры под названием «рекомендуемая модель взаимодействия открытых систем» (the Reference Model of Open Systems Interconnection, OSI). Термин «открытая система» означает, что свойства и структура данной системы не являются чьей-либо собственностью. Другими словами, вам доступно полное описание данной системы и возможность свободно использовать ее для собственных нужд. Разумеется, для этого вы должны обладать полной документацией на нее, достаточной, чтобы создавать собственные программы, использующие или расширяющие данную открытую систему. Специалисты, создающие открытые системы, часто преследуют целью распространить конкретную разработку среди как можно большего количества программистов и фирм и тем самым сделать ее стандартом де-факто. Модель OSI не возникла, однако, на пустом месте. Она базируется на модели, предложенной Международным институтом стандартов (International Standards Organization, ISO). Международный институт стандартов был основан в 1946 году. Имеющий в своем составе более 160 технических комитетов и 2300 подкомитетов и рабочих групп, он объединяет национальные организации по стандартам из более чем 75 стран мира. Американский национальный институт стандартов (ANSI) тоже является членом ISO. Термин «рекомендуемая модель взаимодействия открытых систем» часто встречается в литературе под названием «модель ISO/OSI», отмечая вклад ISO в ее формирование. Для некоторых профессиональных сетевых программистов эта модель представляет собой образец идеальной сетевой архитектуры. О чем пойдет речь? Прикладные программисты пользуются спектром методов, таких как структурное или объектно-ориентированное программирование, для создания модульного, хорошо воспроизводимого программного обеспечения. Для достижения аналогичных целей сетевые программисты используют метод, называемый «разложение на уровни» (layering). В двух словах, каждому уровню в сети соответствует определенный набор функций. Например, самый нижний уровень выполняет основные (элементарные) действия по поддержанию сетевого соединения. Следующий уровень, находящийся как бы над предыдущим, может выполнять собственные функции, например исправлять ошибки данных, обрабатываемых на нижнем уровне. Так, добавляя уровень над уровнем, сетевые архитекторы составляют сеть, каждая часть которой четко определена и обладает ясно ограниченным набором свойств и функций. В этой главе описана модель ISO/OSI с точки зрения программиста. Необходимо четко представлять себе, что эта модель является лишь рекомендацией, а не какой-то строго заданной инструкцией, требующей неукоснительного соблю- 40 
Здесь все разделено на уровни дения. В третьей главе вы познакомитесь с набором протоколов TCP/IP, основой Интернет, и знание модели ISO/OSI поможет вам понять его принципы. TCP/IP — протоколы реальной сети, поэтому они отличаются от идеальной модели ISO/OSI, однако чтобы понять, в чем они расходятся, нужно сперва изучить структуру обоих. Здесь все разделено на уровни Модель ISO/OSI использует деление на уровни, чтобы организовать общее представление о структуре сети в виде четко определенных, взаимосвязанных модулей. Сетевые программисты используют такое представление при конструировании настоящих сетей. Прочитав главу 3, вы узнаете, что при этом довольно часто допускаются различные отклонения от идеальной модели. Уровни в настоящей сети могут иметь отличающийся набор функций, их может быть другое количество, наконец они могут по-другому называться. В результате сети, построенные по одной и той же модели, могут существенно отличаться друг от друга. В сети, поделенной на уровни, каждый уровень служит для исполнения определенной функции или службы сети по отношению к окружающим соседним уровням. Каждый уровень как бы защищает соседний от избыточной информации, способной просочиться от более низкого уровня наверх. Другими словами, каждый уровень заботится только о своем интерфейсе с соседним. На рис. 2.1 изображены уровни в том виде, в каком они определены в модели ISO/OSI. В представлении прикладного программиста структура программы не обязана описываться вертикально расположенными блоками, однако в любом с луч ае понятие модульного программирования должно быть знакомо вам хотя бы по функциям ввода-вывода, когда, вместо того чтобы самому обслуживать внешние устройства, вы просто обращаетесь к определенной системной функции. 7 6 5 4 3 2 1 | Прикладной уровень ! Уровень представления Сеансовый уровень Транспортный уровень Сетевой уровень Уровень соединения Физический уровень Рис. 2.1. Сетевые уровни в модели ISO/OSI 41 
Глава 2. Принципы и понятия сетевой архитектуры Чтобы вызвать функцию ввода-вывода, вы вовсе не обязаны знать, как работает само устройство. Достаточно знать, какие параметры нужно передать функции и каких результатов можно ожидать от ее работы. Функция трактуется вами как некий черный ящик с неизвестным содержимым. Чтобы открыть файл, вы просто вызываете функцию и полагаетесь на возвращенный ею дескриптор или указатель. Вас не волнует, каким образом данные путешествуют между памятью компьютера и его жестким диском. Тем более не важно, каким образом жесткий диск записывает данные. Каждый сетевой уровень обеспечивает связь для вышележащего уровня. Грамотно спроектированный уровень должен скрывать все особенности своего функционирования от вышележащего. Опираясь на эти положения, можно создавать сеть, состоящую их функциональных модулей с четко описанным интерфейсом. Передача сообщений в сети Из первой главы вы узнали, что компьютерная сеть состоит из двух основных компонентов: прикладных программ и подсистемы передачи данных между ними. Основное внимание было уделено отношениям между подсистемой передачи данных и физическими составляющими сети. В этой главе мы рассмотрим вопрос более абстрактно — так, как это больше всего подходит прикладному программисту. Вы знаете, что практически все компьютеры общаются между собой, используя двоичное кодирование. Поскольку каждый компьютер в сети соединен с остальными, можно с достаточной степенью уверенности сказать, что и при работе в сети используется двоичное представление данных. При написании программ двоичный код, как правило, не используется. Вместо этого программы пишутся на каком-либо языке программирования высокого уровня. Язык программирования является своего рода абстракцией от излишних подробностей двоичного кода. Точно так же понятие протокола — абстракция, защищающая прикладного программиста от излишних подробностей в компьютерной сети. Что такое протокол? Протокол — это набор правил и соглашений, используемых при передаче данных (коммуникациях). Возможно, вам знаком термин «протокол» в контексте международных дипломатических отношений. Руководитель министерства может отозвать или даже уволить дипломата, нарушившего местный протокол, либо в ночных новостях могут объяснить странное поведение дипломата особенностями протокола той страны, в которую он прибыл. В обоих случаях мы имеем дело с протоколом, описывающем правила поведения, принятые в соответствующей стране. Каждая страна или культура обладает собственными протоколами. Например, рукопожатие — одна из форм приветствия, принятый во многих культурах 42 
Передача сообщений в сети протокол. В других культурах мужчина, приветствуя женщину, целует ее. Это протокол, которым пользуются, чтобы сказать «Привет!». Компьютеры в сети тоже общаются друг с другом, используя общий язык — протоколы связи, без которых было бы немыслимо управлять сетью и передавать данные. Другими словами, каждая программа, претендующая на работу в сети, должна следовать определенным правилам для приема и передачи сетевых данных. Сетевые протоколы Сети состоят из функциональных модулей, имя которым «уровни». Протокол — это набор правил для передачи и приема сообщений между уровнями. В сети, состоящей из набора уровней, каждый уровень следует строго определенным правилам и обеспечивает обмен между окружающими его сверху и снизу уровнями. На рис. 2.2 изображена простая сеть, созданная на основе модели ISO/OSI. Сеть состоит из двух компьютеров, которые, в свою очередь, составлены из уровней. Стрелки, соединяющие уровни, показывают путь следования данных в сети. L Прикладной уровень Уровень представления Сеансовый уровень Транспортный уровень Сетевой уровень Уровень соединения Физический уровень \j Компьютер 1 I Прикладной уровень ■■ ■' ■ - л. i 4. I Уровень представления —I— | Сеансовый уровень | Транспортный уровень Сетевой уровень i Уровень соединения ►| Физический уровень Компьютер 2 Рис. 2.2. Два компьютера в простой компьютерной сети, основанной на модели ISO/OSI Следуя правилам модели ISO/OSI, нужно называть протоколы, действующие на разных уровнях, именами соответствующих уровней. Например, протокол транспортного уровня будет носить название «транспортный протокол». Протокол сетевого уровня иначе называется «сетевой протокол»1. Тем самым создается повод для некоторой путаницы, ибо часто набор протоколов всех уровней называют «сетевыми». В контексте сети это неверно. — Примеч. перев. 43 
Глава 2. Принципы и понятия сетевой архитектуры Общение близлежащих уровней между собой иногда называется «обмен сообщениями» (conversations). Например, транспортный уровень обменивается сообщениями с сетевым уровнем и наоборот. Правила обмена сообщениями диктуются определенным протоколом. Что такое «равноправный процесс»? Когда два компьютера в сети общаются друг с другом, это значит на самом деле, что каждый из сетевых уровней в них обменивается сообщениями с себе подобным; такие уровни принято называть «равноправными» (peer process). На рис. 2.3 сообщения между равноправными уровнями изображены пунктирными линиями. Прикладной уровень |4-- Прикладной уровень ! прикладного уровня j Уровень представления |4яа Уровень представления уровня представления т ■чшг Сеансовый уровень Ц“" 4- Сеансовый уровень ! сеансового уровня Т Транспортный уровень |4- 4- Транспортный уровень протоколы Сетевой уровень |4" -► Сетевой уровень сетевого уровня т ^ Уровень соединения |4"" -н Уровень соединения уровня соединения j Физический уровень || Протоколы — —1 Физический уровень Компьютер 1 физического уровня Компьютер 2 Рис. 2.3. Сетевая модель с изображенными равноправными процессами Сообщения между равноправными процессами виртуальны по своей природе. На деле реальная передача данных происходит только на самом нижнем, физическом уровне, то есть там, где находится передающий электрические сигналы провод. Связь между компьютерами на физическом уровне называется иначе «физическое соединение». Что такое виртуальное соединение? Когда два компьютера общаются друг с другом — это значит, что между собой общаются различные сетевые уровни. На самом деле данные путешествуют сверху вниз по направлению к физическому уровню сетевой модели. В рамках физического уровня данные передаются горизонтально по физическому носите¬ 44 
Что такое сетевые службы? лю (коммуникационному каналу) к компьютеру-получателю сообщения. Полученные данные двигаются затем вверх по уровням сетевой модели. На рис. 2.4 изображен физический путь данных. Протоколы >—>>>) прикладного уровня Протоколы шяяяяяя^ уровня представления 4....... Протоколы сеансового уровня транспортные протоколы 4 Протоколы •■■■■■■+ сетевого уровня Протоколы •■■■ ■ ■■+ уровня соединения (■■■■ ■ Протоколы \ физического уровня Рис. 2.4. Сетевая модель с изображенным на ней физическим путем данных На рисунке видно, что данные, прошедшие через какой-либо сетевой уровень на передающем компьютере, в конце концов обязательно проходят через соответствующий уровень принимающего. Данные никогда не передаются прямо между равноправными процессами. Вместо этого устанавливается виртуальное соединение. В рамках виртуального соединения два равноправных процесса (уровня) общаются между собой, однако здесь не идет речь о прямом взаимодействии. Концепция виртуального соединения хороша тем, что позволяет разработчикам отвлечься от подробностей взаимодействия низлежащих сетевых уровней и сконцентрировать внимание только на некоторых из них. Повторим, что описанное взаимодействие двух сетевых уровней называется виртуальным соединением. Вышеописанная концепция, возможно, окажется новой для вас. Нужно особо отметить ее значительную важность, поскольку понимание виртуального соединения требуется при чтении литературы, посвященной сетевому программированию. Концепция виртуального соединения создана для облегчения программирования в сети. Прикладной: уровень Уровень представления Сеансовый уровень Транспортный уровень ' Сетевой уровень Уровень соединения [[ Физический уровень Компьютер 2 Прикладной уровень Уровень 1 представления Сеансовый уровень Транспортный уровень Сетевой уровень - Уровень соединения Физический уровень - Компьютер 1 Что такое сетевые службы? Каждый сетевой уровень модели ISO/OSI обеспечивает определенный сервис для вышележащих уровней. Другими словами, вышележащий уровень полагается на функции, выполняемые своим «соседом». Каждый сетевой уровень, за 45 
Глава 2, Принципы и понятия сетевой архитектуры исключением физического, добавляет функциональности предыдущему, расположенному ниже уровню. В среде разработчиков набор свойств и функций, которым обладает определенный сетевой уровень, называется сетевой службой. Каждый сетевой уровень подчиняется определенному сетевому протоколу, определяющему набор сетевых служб, присущих данному уровню. Короче говоря, сетевая служба — это набор функций, которые уровень выполняет для вышележащего уровня, например коррекция ошибок. С другой стороны, протоколы — это правила, которым должен следовать уровень, чтобы реализовать сетевую службу. Сетевая модель ISO/OSI четко определяет разницу между службой и протоколом. В следующих разделах главы описываются службы, характерные для каждого из уровней модели ISO/OSI. В данный момент вы должны четко представлять разницу между службой и протоколом. Службы и протоколы Мы воспользуемся аналогией с почтовой службой, для того чтобы лучше объяснить разницу между сетевой службой и протоколом. Чтобы отправить кому-нибудь письмо, мы надписываем адрес на конверте. Адрес нужен для того, чтобы письмо дошло до адресата. Таким образом, функция адреса на конверте заключается в обеспечении правильной доставки. Формат, в котором пишется адрес на конверте, строго определен. Первая строка содержит имя адресата, вторая — название улицы и последняя, как правило, — индекс, название страны и штата. Почтовые работники ожидают увидеть конверт, надписанный именно таким образом. Например, они ожидают, что на второй строчке будет указан номер дома, а за ним — название улицы, и именно в таком порядке.1 Формат адреса на конверте следует определенному протоколу. Сетевая служба таким же образом определяет выполнение какой-либо функции или задачи, например коррекции ошибок. Сетевой протокол описывает формат данных или пакетов данных, то есть правила оформления, которым данные должны подчиняться, чтобы программное обеспечение выполняло ту или иную функцию или сетевую службу. В случае с коррекцией ошибок протокол описывает, какие именно случаи ошибок данных сетевая служба должна исправлять. Таким же образом сетевые уровни запрашивают определенную сетевую службу от низ лежащего уровня. Протокол уровня определяет структуру данных и формат пакетов для выполнения запрашиваемой сетевой службы. Данные запросы в конце концов добираются до самого нижнего, физического уровня, где превращаются в пакеты данных. Рассматривая определенный сетевой уровень, спрашивайте себя: «Какую службу он выполняет?» Всякий раз, когда сетевой уровень нуждается в сетевой службе, он должен оформлять данные согласно определенному протоколу. Речь идет о формате адресов в США. — Примем. перев. 46 
Что такое сетевые службы? Во второй части книги вы будете писать программы для Интернет. Вы научитесь пользоваться общедоступными сетевыми службами сетей TCP/IP. Так вы сможете пользоваться набором протоколов Winsock для Windows, позволяющим выполнять специальные функции для доступа к сети, например выполнять поиск номера компьютера по его имени. Программы, использующие Winsock, легко раскладываются на сетевые уровни. Что такое режим сетевой службы? В рамках одной и той же сети для обеспечения одной и той же сетевой службы могут применяться различные методы передачи данных. Эти методы по-другому называются «режимы». Другими словами, режим определяет, каким образом сетевая служба осуществляет операцию. В одном режиме сетевая служба может корректировать ошибки данных, а в другом — нет. Разработчики реализуют различные режимы в сетевых протоколах. Если вам нужно, чтобы сетевая служба выполнила операцию в определенном режиме, обратитесь к тому протоколу, который его обеспечивает. Например, если вашему приложению требуется коррекция ошибок, обратитесь к тому протоколу, который ее осуществляет. Другими словами, приложение должно использовать протокол с коррекцией ошибок в качестве режима сетевой службы. Если существующие протоколы не обеспечивают требуемый режим, вам придется написать собственный. Ваша программа может запрашивать определенную сетевую службу и требовать предоставления определенного протокола у сети для обеспечения этой службы. Сеть обеспечит службу, если требуемый для нее протокол доступен. Если вы потребовали недоступный для данной службы протокол, вы получите сообщение об ошибке. Если вы обращаетесь к службе не указывая протокол, будет использоваться протокол по умолчанию. Выбор определенного протокола определяется режимом службы, который вы выбрали. Сетевые службы, ориентированные на соединение В первой главе была описана коммутация соединений, служащая для установления соединения между двумя устройствами. Коммутация цепи — одна из форм коммутации соединений. Коммутация цепи служит для установления непрерывного соединения между устройствами. Специалисты называют такое непрерывное соединение двухточечным. В общем случае, любое сетевое соединение подразумевает наличие пути между двумя устройствами и наличие самих взаимодействующих устройств. Двухточечное соединение — это просто непрерывная цепь между двумя устройствами. Режим службы, ориентированной на соединение, это почти то же самое, что и двухточечное соединение. Ориентированная на соединение служба образует виртуальное двухточечное соединение, создавая впечатление, что устройства соединены между собой физически. Разумеется, это только впечатление — на самом деле двухточечного физического соединения нет. Несмотря на это, для взаимодействующих сетевых приложений создается видимость наличия неразрывного канала связи. Таким образом, служба, ориентированная на соединение, создает для приложения условия двухточечного непосредственного соединения. 47 
Глава 2. Принципы и понятия сетевой архитектуры Служба, не ориентированная на соединение Тогда как ориентированная на соединение служба подобна телефонному разговору, служба, не ориентированная на соединение, похожа на доставку корреспонденции. Например, разговаривая по телефону, вы непосредственно общаетесь с собеседником. Однако посылая письмо, вы полагаетесь на того, кто доставит его корреспонденту. Разница между двумя режимами служб состоит в том, что не ориентированная на соединения служба не устанавливает двухточечного соединения. Не ориентированная на соединение служба похожа на доставку писем лошадьми. Кто-то везет ваше письмо, делая промежуточные остановки, передавая письмо дальше и так вплоть до того момента, когда оно достигнет адресата. В конце концов письмо доставят по назначению, однако никто не гарантирует, что резко изменившиеся погодные условия, наводнение или землетрясение не задержат доставку на неопределенный срок. Если вместо одного письма вы пошлете пять тяжелых пакетов, то, вероятно, что одна лошадь не сможет увезти их все вместе, и вашу посылку разделят на части. При этом разные части необязательно последуют одной дорогой и необязательно прибудут одновременно. Точно так же, как и ваша почта, данные, доставляемые не ориентированной на соединение службой, могут следовать различными маршрутами, задерживаться в пути, однако, в конце концов, все они достигнут пункта назначения. Контроль последовательности По некоторым причинам, о которых вы узнаете позже, данные могут разбиваться сетью на пакеты различной длины. Компьютер, посылающий сообщение, передает данные, разделенные на пакеты, в той последовательности, в которой они поступили. Однако не исключено, что пакеты появятся в пункте назначения совсем в другой последовательности. Контроль последовательности (sequencing) определяет порядок, в котором пакеты данных должны появиться на приемном конце соединения. Некоторые протоколы гарантируют, что данные будут доставлены в той последовательности, в которой были посланы, а некоторые — нет. В любом случае приемник сообщения должен собрать из пакетов первоначальное сообщение. Большинство ориентированных на соединение протоколов гарантируют правильную последовательность данных. Другими словами, для ориентированного на соединение протокола доставить данные в той же последовательности, в которой они были посланы по каналу связи, — обычное явление. Протоколы, не ориентированные на соединение, наоборот, обычно не гарантируют правильной последовательности принятых пакетов данных. Контроль ошибок Процесс передачи данных по сети не застрахован от ошибок. Ошибки возникают под воздействием шумов в линии или от затухания сигнала. Режим контроля ошибок гарантирует получателю доставку данных неповрежденными. Каким образом это становится возможным? На самом деле контроль ошибок не может 48 
Что такое сетевые службы? исправить пакет с поврежденными данными. Однако достаточно просто знать, что повреждение произошло. Тогда модуль коррекции ошибок запросит у передатчика переслать данные повторно. Таким образом, мы говорим о виртуальной коррекции ошибок, а не о фактической, поскольку поврежденные данные все равно достигают сетевых модулей. Другое дело, что они не достигнут того приложения, которое пользуется режимом коррекции ошибок. Система контроля ошибок обязана обнаруживать и обрабатывать два вида повреждения данных: их искажение или исчезновение. Другими словами, контроль ошибок обязан узнать, что в процессе передачи данные подверглись модификации. Либо он обязан узнать, что данные исчезли по пути к получателю. Это может произойти, например, в случае физического обрыва линии связи. Как правило, протоколы коррекции ошибок используют контрольную сумму и ее разновидность — CRC (Cyclic Redundancy Check). Значения контрольной суммы или CRC используются программами для проверки данных на наличие ошибок. Контрольная сумма Использование CRC требует более интенсивных вычислений, нежели контрольная сумма. Поэтому CRC обычно вычисляется при помощи аппаратных средств. С другой стороны, контрольную сумму вычислить гораздо легче, поэтому как раз ее обычно вычисляют программы. В следующих абзацах объясняются принципы вычисления контрольной суммы. Для вычисления контрольной суммы данные делятся на части одинаковой длины. Например, приложения Интернет обычно делят данные на сегменты размером в 16 бит. Программа трактует такие сегменты как целые числа. Эти числа суммируются, и полученный результат (контрольная сумма) посылается вместе с самим блоком данных. Принимающий данные компьютер аналогичным образом вычисляет новую контрольную сумму принятого блока данных и сравнивает ее с принятой. Их несовпадение служит сигналом об ошибке. Контрольные суммы позволяют определить повреждение одного бита данных и в большинстве случаев нескольких. На самом деле контрольная сумма не так эффективна, как CRC, однако ее проще реализовать, и поэтому она применяется гораздо чаще. Аппаратные средства, реализующие контроль ошибок данных, такие как сетевые карты Ethernet, используют CRC. Для обнаружения пропажи данных применяется метод подтверждения доставки. При этом приемник и передатчик обмениваются сообщениями, подтверждающими прием неповрежденного пакета данных. Такой обмен сообщениями называется «рукопожатие» (handshake). На рис. 2.5 показано, что такое «рукопожатие» может состоять из обмена тремя сообщениями. Для каждого принятого сообщения приемник посылает подтверждение (часто оно называется АСК (Acknowledged)). Обычно приемник остается пассивным участником обмена. То есть вся инициатива в обмене принадлежит передатчику, а приемник только отвечает сообщениями АСК. Как изображено на рис. 2.5, когда приемник получает Сообщение 1, он отвечает, посылая Подтверждение 1. После того как передатчик получит Подтвержде- 49 
Глава 2. Принципы и понятия сетевой архитектуры Передатчик Приемник [Сообщение 11 Подтверждение 1 Сообщение 2 ► i[Подтверждение 2 Сообщение з| ) i '[Подтверждение 3] Рис. 2.5. Пример реализации подтверждения о доставке ние 1, он посылает следующее Сообщение 2. Сообщение 3 не будет послано до тех пор, пока не вернется Подтверждение 2. Обычно, если передатчик не получает подтверждение о доставке в течение некоторого времени (тайм-аута), он посылает сообщение еще раз. Предположим, что сообщение на рис. 2.5 содержит контрольную сумму для модуля коррекции ошибок. Предположим также, что приемник принимает Сообщение 2 и контрольная сумма в нем не совпадает со вновь вычисленной (то есть случилась ошибка во время передачи). Приемник может просто отбросить такое сообщение и просто ждать, пока передатчик повторит его. Такое поведение может приводить к ощутимым задержкам в передаче данных. Приемник должен подтвердить доставку сообщения. Однако в нашем случае он не может этого сделать, так как данные повреждены. Если приемник пошлет нормальное подтверждение, то передатчик пошлет следующее, пребывая в неведении относительно настоящей судьбы поврежденного Сообщения 2. Многие протоколы коррекции ошибок выходят из ситуации, посылая сообщение о недоставке — Сообщение NAK1 (Not Acknowledged). Например, при несовпадении старой и новой контрольных сумм в Сообщении 2 приемник пошлет обратно Сообщение NAK. Когда передатчик получит его, он узнает, что данные были повреждены. Передатчик немедленно высылает Сообщение 2 еще раз, а не ждет тайм-аут для повтора передачи. Таким образом устраняется задержка в обмене сообщениями. В главе 4 описано, каким образом протоколы Интернет реализуют все описанные функции (контрольные суммы, CRC и подтверждения о доставке). Примечание: Протоколы с контролем ошибок называются иначе «надежными» (reliable) протоколами. В литературе иногда встречается синоним: «сообщение-переспрос— Примеч. перев. 50 
Что такое сетевые службы? Что такое управление потоком? Сообщения, посылаемые по сети, можно рассматривать, как поток данных. Для того чтобы передатчик не посылал данные быстрее, чем приемник может их обработать, сетевая служба должна управлять этим потоком. Такой режим службы называется «управление потоком» (flow control). Он служит для того, чтобы приемник не переполнился и не начал терять данные в результате слишком быстрой передачи. Механизм управления потоком должен контролировать скорость передачи и учитывать размер буфера приемника. Предположим, передатчик может посылать данные со скоростью 1000 байт в секунду. Предположим, что приемник может обрабатывать данные со скоростью, не превышающей 100 байт в секунду. Если у приемника отсутствует достаточно емкий буфер для хранения поступающих данных, он начнет их терять. Режим управления потоком гарантирует, что данные не потеряются в результате переполнения приемника. Из главы 4 вы узнаете, каким образом управление потоком реализовано в протоколах Интернет. Что такое потоковая служба? Потоковый режим сетевой службы рассматривает данные как единый, протяженный, последовательный поток. Другими словами, данные не делятся и не ограничиваются какими-либо определенными размерами. В рамках Интернет потоковые службы применяются в ориентированных на соединение протоколах с гарантированной доставкой данных — то есть с контролем ошибок и последовательности. Службы конечной и промежуточной доставки Протоколы, обеспечивающие сетевые службы, например контроля ошибок и управления потоком, могут действовать как на концах соединения, так и на промежуточных пунктах. Как известно, Интернет является сетью с переключением пакетов (пакетной коммутацией). Данные путешествуют от одного коммутатора пакетов до следующего, пока не достигнут пункта назначения. Например, пакету может понадобиться пройти через маршрутизатор (router), чтобы достичь компьютера-получателя. Коммутатор пакетов (или просто пункт промежуточной остановки) называется «скачок» (hop). Служба промежуточной доставки (hop-by-hop service) выполняет определенные функции в месте каждой остановки пакета на его пути. Предположим, что сетевой уровень соединения подсчитывает контрольную сумму пакета. Предположим, что контрольная сумма вставляется в пакет, передающийся по каналу связи. Пусть каждый коммутатор пакетов производит сравнение старой и новой контрольной суммы. В таком случае речь ведется о службе промежуточной доставки. Пакет данных подвергается проверке контрольной суммы на каждом «скачке» до места назначения. 51 
Глава 2. Принципы и понятия сетевой архитё!аурьк В противоположность службе промежуточной доставки служба конечной доставки (end-to-end service) работает только в местах передачи и получения пакета данных — то есть в конечных пунктах соединения. Например, протокол контроля ошибок может быть реализован на транспортном уровне. Тогда контрольная сумма проверяется только в передатчике и приемнике сообщения. Вопросы разработки и проектирования сети Вы знаете, что сеть состоит из двух основных концепций: прикладной и коммуникационной. Сетевые разработчики стремятся разделить структуру обеих концепций на уровни или функциональные модули. Совокупность уровней составляет сетевую модель. Коммуникационная подсистема включает в себя протоколы, определяющие правила и соглашения об обмене сообщениями между сетевыми уровнями. Наконец, вам известно, что протоколы реализуют различные режимы сетевых служб, такие как контроль ошибок и управление потоком. Сетевые программисты используют уровни, подсистемы, протоколы и режимы служб для разрешения встающих перед ними снова и снова проблем. Сейчас мы обсудим некоторые из них. В последующих главах описываются решения для проблем, которых мы не коснемся в этой. Управление соединением Каждый уровень сетевой модели должен уметь установить соединение с равноправным. Поскольку компьютер решает множество задач одновременно, встает проблема идентификации. Чтобы установить соединение, процесс должен идентифицировать удаленный компьютер и равноправный процесс на нем. Другими словами, правильно адресовать компьютер и процесс — одна из задач, стоящих перед разработчиками. Мы покажем в дальнейшем, что большинство сетевых сообщений имеют специальные идентифицирующие номера в своем составе. Передача данных Когда исполняется программа, она передает данные — на жесткий диск, на принтер или на удаленный компьютер. Раньше данные передавались в основном из памяти на жесткий диск. Если ваш компьютер работает в среде Unix или Windows, он умеет передавать данные между множеством процессов или между окнами. Передаваемые в сети данные состоят из пакетов или сообщений. Разработчики должны решать, каким образом данные должны передаваться. Обмен данными происходит в симплексном, полудуплексном или дуплексном режимах. Сетевое соединение обыкновенно состоит из двух каналов передачи для одного процесса. Один канал передает собственно данные, а другой — управляющую 52 
Вопросы разработки и проектирования сети информацию, называемую «данные для немедленной обработки» (urgent data). Примером данных для немедленной передачи может служить Esc или другое сочетание для прерывания обмена. Сеть должна передавать такие данные с повышенным по сравнению с обычным приоритетом. В дальнейшем будет показано, что некоторые протоколы Интернет имеют специальное поле для данных с повышенным приоритетом. Примечание: Канал передачи управляющих данных с повышенным приоритетом еще называется «данные-вне-диапазона» (out-of-band data). Обработка ошибок Обнаружение и коррекция ошибок, несомненно, важно для функционирования сети. Рассмотрим проблему, возникающую при ошибках данных из-за повреждения самого канала связи. Если канал поврежден, может оказаться, что приемник не в состоянии уведомить передатчик о случившейся ошибке, то есть само сообщение об ошибке окажется повреждено. Еще хуже, если исчезнет сообщение об исчезновении данных. В сети быстро возникнет порочный круг. К счастью, некоторые протоколы Интернет содержат эффективную обработку ошибок данных в качестве режима сетевой службы. Соблюдение правильной последовательности При проектировании сети необходимо учесть доставку данных в правильной (исходной) последовательности. Необходимо обеспечить как передачу в правильной последовательности, так и возможность восстановить ее программными средствами. Протоколы Интернет имеют возможность, пользуясь специальными полями в пакетах данных, восстановить нарушенную при передаче последовательность. Управление потоком Проблема переполнения данных возникает на каждом уровне сетевой модели. Проблема‘может возникнуть как между равноправными процессами в соединенных компьютерах, так и при передаче данных между уровнями в одном и том же компьютере. Например, разные процессы могут использовать один и тот же коммуникационный канал, и данные нескольких процессов могут появиться в канале одновременно, превысив вычислительную мощность уровня — произойдет потеря данных. Таким образом, проблема управления потоком весьма важна. Сетевые протоколы должны проектироваться с учетом этой проблемы. Протоколы Интернет учитывают возможность переполнения, снимая с ваших прикладных программ необходимость управления потоком. 53 
Глава 2. Принципы и понятия сетевой архитектуры Фрагментация данных Несмотря на то что сетевые протоколы делят данные на пакеты, длина их все равно может оказаться слишком велика для какого-либо участка сети. В этом случае сеть должна уметь поделить данные на пакеты еще меньшей длины. Этот процесс называется фрагментацией (fragmenting). Фрагментирование данных часто бывает обязательно, однако приводит к ухудшению надежности и производительности сети. Вероятность исчезновения маленького пакета данных более высока и часто приводит к необходимости передать исходный пакет целиком и повторить фрагментацию снова. Опять-таки, проблема фрагментации решается на сетевом уровне, и ваши прикладные программы могут ничего не знать о ней. Понимание процесса фрагментации нужно для того, чтобы уметь «помочь» в этом сетевому протоколу и, таким образом, повысить производительность сети. Сетевые уровни Перед тем как мы подробно рассмотрим каждый сетевой уровень в отдельности, давайте разберемся, каким образом разработчики назначают им ту или иную функцию. Это может оказаться*полезным при рассмотрении требований, предъявляемых к сетевым уровням. Вы знаете, что разработчики делят сеть на уровни с целью получить набор хорошо определенных, функциональных модулей. В своей книге «Компьютерные сети» Эндрю Таненбаум привел пять основополагающих принципов, применяющихся при разработке сетевых уровней и соответствующих модели взаимодействия открытых систем. 1. Новый сетевой уровень вводится, если программное обеспечение требует нового уровня абстракции. 2. Каждый уровень должен выполнять строго определенную функцию. 3. Набор функций, выполняемых сетевым уровнем, приводится в соответствие с общепринятыми международными стандартами. 4. Границы уровня выбираются таким образом, чтобы сделать поток данных через них минимальным. 5. Количество сетевых уровней выбирается достаточно большим, чтобы не заставлять разработчиков приложений размещать различные функции на одном и том же уровне. Наоборот, слишком большое количество уровней приводит к необъятности сетевой архитектуры. На рис. 2.6 изображена сеть, составленная из уровней в соответствии с вышеназванными принципами. Вы знаете, что совокупность этих уровней называется моделью взаимодействия открытых систем (моделью ISO/OSI). В следующих абзацах будет кратко описан каждый из сетевых уровней, составляющих модель ISO/OSI. Далее несколько глав посвящены детальному описанию некоторых уровней. 54 
Сетевые уровни * 7 ! Прикладной уровень ' i (Сообщения) j Уровень представления ° I (Сообщения) с ! Сеансовый уровень | (Сообщения) 4 Гтранспортный уровень i (Сообщения) 0 ! Сетевой уровень ° j (Пакеты) р Уровень соединения | (Кадры) 1 ; Физический уровень 1 | (Биты) (В скобках приведены наименования блоков данных) Рис. 2.6. Сетевые уровни модели ISO/OSI Каждый уровень пользуется различными единицами измерения количества данных. Уровни приложения (прикладной уровень), представления, сеансовый, транспортный, — используют термин «сообщение» в качестве единицы измерения. Сетевой уровень трактует данные как «пакеты», а уровень соединения — как «кядр». Физический уровень имеет дело с битами — последовательностью нулей и единиц. ^ Модель ISO/OSI не является стандартом — она просто рекомендация для разработчиков. Интернет состоит из сетей, которые легче всего описывать в терминах модели ISO/OSI. Таким образом, правильно понять ее — очень важно, если вы хотите грамотно писать программы для Интернет. Физический уровень Физический уровень состоит из физических элементов (hardware), служащих непосредственно для передачи информации по сетевым каналам связи. Поэтому линии связи — кабели, соединяющие компьютеры, — относятся к физическому уровню. К нему же относятся и методы электрического преобразования сигналов. Различные сетевые технологии, такие как Ethernet, ARCNET, или token ring, относятся к физическому уровню, как задающие параметры преобразования сигналов для передачи по сети. Уровень соединения Задача уровня соединения — передать данные от физического уровня к сетевому и наоборот. Сетевая карта в вашем компьютере — пример реализации уровня соединения. Как правило, уровень соединения следит за сохранностью данных, передаваемых физическим уровнем. 55 
Глава 2. Принципы и понятия сетевой архитектуры Новички часто задают вопрос: «Не является ли уровень соединения лишним звеном между физическим и сетевым уровнями?» Им кажется, что это противоречит концепции модели ISO/OSI. Однако давайте обратимся к пяти принципам конструирования сети. В соответствии с принципом 2, уровень соединения выполняет строго определенную функцию. Для описания этой функции нам требуется другой уровень абстракции (принцип 1: уровень соединения выполняет совершенно отдельную, специфическую функцию). Уровень соединения позволяет локализовать функции для потока данных, проходящего между физическим и сетевым уровнями (принцип 4). Наконец, уровень соединения позволяет разработчикам описывать в нем свои собственные функции, которые иначе размещались бы частью в физическом, а частью — в сетевом уровнях, внося некоторую путаницу (принцип 5). Сетевой уровень Сетевой уровень определяет путь следования данных по сети, позволяя им найти получателя. Это значит, что он должен заведовать вопросами возможного столкновения (congestion) данных и скоростью передачи по сети. Вопросы контроля целостности данных тоже находятся в его компетенции. Сетевой уровень можно рассматривать как службу доставки. В четвертой главе рассматривается протокол Интернет (IP), который выполняет все функции сетевого уровня (рис. 2.7). Транспортный уровень Так же, как сетевой уровень доставляет пакеты по сети, транспортный уровень доставляет (транспортирует) данные между самими компьютерами. Как только сетевой уровень доставит данные компьютеру-получателю, в работу вступает транспортный протокол, доставляя данные к прикладному процессу. \ т ~ i \ * \ I X Транспортный i | Транспортный! уровень : : | уровень 1 Сетевой уровень Компьютер 1 т Сетевой уровень _Г Компьютер 2 Рис. 2.7. Доставка данных сетевым и транспортным уровнями 56 
Сетевые уровни Сеансовый уровень Сеансовый уровень в качестве пользовательского сетевого интерфейса решает такие задачи по обработке соединений между процессами и приложениями на различных компьютерах, как обработка имен, паролей и прав доступа. Во многих сетях перед тем, как получить доступ к приложению, вы должны войти в систему, то есть ввести свое имя (идентификатор пользователя) и пароль. Скорее всего вам требуется делать это каждый раз, когда нужно выполнить определенное приложение. Выражаясь профессиональным языком, каждый раз вы начинаете сеанс. Во многих случаях в сеть можно «войти» несколько раз, открыв несколько сеансов одновременно. Каждый раз, открывая новый сеанс, ваш компьютер договаривается с удаленным о возможности соединения до того, как само соединение состоится. Уровень представления Уровень представления объединяет в себе некоторые общие функции, которые сеть неоднократно использует при сетевых соединениях. Уровень представления образует интерфейс сети к устройствам компьютера, таким как принтеры, мониторы, форматы файлов. Короче говоря, уровень представления определяет, как сеть выглядит с точки зрения программного обеспечения и аппаратуры сетевого компьютера. Чтобы уяснить причины появления уровня представления, давайте опять обратимся к пяти принципам создания сетевых уровней модели OSI. Несмотря на то, что большинство сетевых приложений могли бы выполнять функции уровня представления самостоятельно, по-прежнему возможно выделить необходимый набор функций, не имеющий отношения к другим уровням или приложениям (принцип 2), который можно разместить в отдельном уровне. Для этого мы должны создать новый уровень абстракции и, таким образом, отдельный сетевой уровень (принцип 1). В соответствии с принципом 4 мы размещаем всю информацию, касающуюся аппаратуры, на одном и том же уровне, минимизируя, таким образом, обмен информацией об аппаратных интерфейсах между ними. Уровень приложения На этом уровне сконцентрированы функции, относящиеся к общесетевым приложениям. Эти функции особенно важны для сетевых разработчиков. Прикладные программы вроде электронной почты или распределенной базы данных — образец использования функций уровня приложения. Программы, разрабатываемые вами и функционирующие в среде Интернет, являются частью сетевого уровня приложения. Таковыми являются все приложения, написанные для конечных пользователей. 
Глава 2. Принципы и понятия сетевой архитектуры Подробнее о сетевых уровнях В следующих абзацах каждый из сетевых уровней модели ISO/OSI будет рассмотрен подробнее. Не забывайте об определениях уровней, с которыми мы уже познакомились, так как это позволит вам легче усвоить последующий материал. Физический уровень в подробностях Как вы уже знаете, физический уровень — посредник в передаче данных по каналам связи. В рамках этой функции физический уровень определяет механические и электрические характеристики сетевого канала связи, а также процедуры, необходимые для работы с ним. Например, физический уровень определяет количество контактов или проводов у канала связи, тип сетевого кабеля (коаксиальный или витая пара) и электрические параметры данного кабеля. Кроме того, физический уровень имеет дело с топологией сети. При этом разработчику более высоких сетевых уровней нет нужды знать, с какой топологией сети будет работать его приложение — все подробности такого рода изолированы на физическом сетевом уровне. Поскольку данные на физическом уровне передаются нулями и единицами, физический уровень должен знать как эти значения будут преобразованы аппаратурой. Например, физический уровень определяет, какая частота аналогового сигнала представляет «О», а какая — «1». Физический уровень должен знать, какое именно изменение частоты приводит к переключению от единицы к нулю и наоборот. Предположим, что физический уровень кодирует биты данных различным напряжением. Тогда необходимо знать, при каком уровне напряжения ноль будет переключаться в единицу. Также необходимо следить, чтобы принимающий компьютер не принял единицу за ноль и наоборот. На физическом уровне определяется тип передачи данных: симплексный, полудуплексный или дуплексный. Подробнее об уровне соединения Тогда как физический уровень передает данные по битам, уровень соединения превращает их в нечто более понятное для сетевого уровня, часто называемое «кадр данных». Наоборот, уровень соединения принимает кадры от сетевого с целью преобразовать их в поток битов, соблюдая правильный формат, для физического уровня. В любом случае уровень соединения следит за целостностью данных, передающихся между двумя сетевыми уровнями. Реализация уровня соединения Для каждого варианта физической структуры сети необходимо создавать отдельный уровень соединения. Вы знаете, что информация о физической основе сети (Ethernet, ARCNET или token ring) содержится на физическом уровне. Чтобы 58 
Подробнее о сетевых уровнях подключить компьютер к сети, требуется вставить в него сетевую карту, рассчитанную на работу со строго определенным физическим типом сети. Конструкция сетевой карты Ethernet отличается от конструкции карты для ARCNET. Сетевая карта в компьютере выполняет функции уровня соединения в большинстве сетей. Другими словами, сетевая карта в компьютере играет роль сетевого уровня соединения. Что такое кадр данных? Кадром данных обычно называют отформатированный уровнем соединения поток битов, поступающий от физического уровня. Несмотря на то, что Международный институт стандартов называет такую последовательность данных блоки-данных-физического-уровня (physical-layer-service-data-units), большинство сетевой литературы называет ее просто кадрами. Содержимое кадра зависит от низлежащей сетевой технологии (физического уровня). Представьте, что вы подключили два одинаковых компьютера к двум различным сетям: Ethernet и token ring. На сетевом уровне все данные будут идентичны. Но на физическом уровне и уровне соединения формат данных будет совершенно различным. Посылая данные в сеть Ethernet, уровень соединения преобразует их совершенно иным образом, нежели при посылке в token ring. Содержимое кадра данных диктуется требованиями физического уровня сети, то есть сетевой технологией. Физический уровень независим от формата данных, циркулирующих между сетевым уровнем и уровнем соединения. Точно так же сетевой уровень независим от представления данных в кадрах между уровнем соединения и физическим уровнем. Основная функция уровня соединения — обеспечивать целостность данных, поэтому формат кадра включает необходимую для этого информацию. Пользователи часто считают, что главная цель уровня соединения — разделять данные на кадры. Однако формирование кадров — лишь способ контролировать ошибки данных на канале связи. Как контролируется целостность данных? Данные форматируются сетью в кадры с целью контролировать их целостность. На рис. 2.8 изображен кадр данных сети Ethernet. Уровень соединения обнаруживает повреждение данных, используя контрольную сумму CRC. 32-битное значение CRC получается в результате сложных вычислений, основанных на содержимом кадра данных. Передатчик вычисляет 64 бита 48 битов 48 битов 0D si Ю 368-12,000 битов 32 бита | [ Заголовок 1 Адрес получателя Адрес 1 отправителя | Тип кадра 1 ///-* Данные Контрольная 1 | сумма (CRC) 1 Рис. 2.8. Формат кадра данных Ethernet 59 
Глава 2, Принципы и понятия сетевой архитектуры CRC и посылает его в составе кадра. Приемник, получив кадр, повторно вычисляет CRC и сравнивает его с полученным от передатчика. Если вновь вычисленное значение совпадает с принятым, вероятность того, что данные не изменились при передаче, чрезвычайно высока. Можно считать, что для большинства практических задач совпадение CRC гарантирует, что принятые данные совпадают с посланными. Кроме CRC, кадр данных содержит и другую информацию, необходимую для его правильной идентификации и маршрутизации. Например, поле «заголовок» (Preamble) позволяет принимающему компьютеру правильно синхронизироваться с передающим. Другими словами, содержимое поля обозначает, какая по счету часть данных передается в данном кадре. Не забудьте, что сеть часто фрагментирует данные на пакеты меньшего размера. Кадр данных должен содержать адреса отправителя и получателя для маршрутизации и сообщений об ошибках. Приемник использует содержимое поля «тип кадра» (Frame Туре) для передачи данных тому сетевому уровню, которому они предназначены. Таким образом, сетевой уровень соединения обеспечивает обнаружение и предотвращение ошибок на канале связи. Поскольку для этого требуется делить данные на блоки конечной длины, для которых можно вычислить CRC, уровень соединения использует кадры данных. С другой стороны, CRC обеспечивает целостность данных на физическом уровне, а также их безошибочную передачу через сетевые каналы связи. Подробнее о сетевом уровне Как вы уже знаете, сетевой уровень — это внутрисетевая первичная служба доставки. В сети с переключением пакетов, каковой является Интернет, сетевой уровень доставляет данные в виде отдельных пакетов, каждый из которых содержит адреса отправителя и получателя в целях маршрутизации. В качестве службы доставки сетевой уровень служит интерфейсом между сетевыми компьютерами и промежуточными переключателями пакетов на пути следования данных. При разработке сетевого уровня принимается во внимание ответственность каждого элемента сети за маршрутизацию данных. Другими словами, ответственность за маршрутизацию должна разделяться между сетевыми компьютерами и промежуточными переключателями пакетов. Сетевой уровень следит за правильностью принимаемых пакетов данных. В соответствии с моделью ISO/OSI он должен также обеспечивать правильную последовательность (sequencing) принимаемых пакетов. Однако сети на базе Интернет передают эти полномочия более высокому уровню — транспортному. Маршрутизация данных Для маршрутизации данных в сети обычно используется таблица маршрутизации. Она похожа на базу данных, где описывается местонахождение возможных получателей пакетов. Маршрутизатор, используя такую таблицу, в состоянии найти путь пакета для любого получателя на сети. В зависимости от 60 
Подробнее о сетевых уровнях конкретных требований, таблицы маршрутизации могут быть статическими или динамическими. Информация в статических таблицах, как правило, обновляется сетевым администратором. Динамические таблицы обновляются разнообразными сетевыми программами автоматически. Статические таблицы маршрутизации лучше всего использовать, когда местоположение устройства на сети меняется редко, и, следовательно, усилия для обновления информации минимальны. Информация о маршрутизации хранится в месте, доступном для всей сети, и обновляется вручную при изменении сетевой конфигурации. Разумеется, программы, использующие статические таблицы, гораздо проще в настройке и управлении, чем те, что используют динамические. При динамической маршрутизации программы обновляют свои таблицы при запуске каждого нового сеанса и, возможно, при появлении каждого нового пакета. Управление сетевыми данными Подключение новых компьютеров к сети приводит к возрастанию потока пакетов данных, часто называемых трафиком (traffic), через нее. Так же как увеличение потока автомобилей на шоссе может привести к столкновениям автомобилей, увеличение трафика приводит к столкновениям пакетов данных. Как только пакеты данных начинают сталкиваться, происходит уменьшение скорости трафика — пользователи замечают снижение производительности сети. Для того чтобы избежать этого, сетевой уровень должен эффективно решать задачу маршрутизации пакетов данных. Первое, что при этом должен сделать сетевой уровень — прекратить посылать данные на переполненный переключатель пакетов. Если этого не сделать, то возникнет сетевая «пробка» и некоторые данные потеряются. Если уровень не в состоянии предотвратить столкновение пакетов, он все равно должен эффективно обработать сложившуюся ситуацию. Один из способов предотвратить столкновение пакетов — использовать контроль потока (flow control) данных. Сетевой уровень контролирует поток данных при маршрутизации пакетов, тем самым разрешая проблему, когда передатчик шлет данные быстрее, чем приемник может их обработать. Подсчет трафика В современном мире сети часто пересекают геополитические границы. Множество сетей, таких как Интернет, протянулись через границы двух или более государств. При этом часто возникает необходимость учитывать трафик сети на разных ее участках, с тем чтобы решать вопросы оплаты, например. Обеспечение работы сети требует денежных и временных затрат. Когда трафик пересекает границы между двумя странами, обе они хотят разделить затраты между собой. При этом становится необходимой информация о количестве данных, прошедших через сеть на территории каждой страны. Сетевые программы, подсчитывающие трафик, выдают такую информацию в терминах пакетов, сообщений, символов или битов данных. 61 
Глава 2. Принципы и понятия сетевой архитектуры Поскольку сетевой уровень заведует общесетевой информацией о маршрутизации, ему и принадлежит функция подсчета количества данных. Таким образом, разработчики сети должны обеспечить выдачу информации о трафике сетевым уровнем, если это необходимо. Тип выдаваемой информации быстро усложняется по мере того, как разные стороны используют различные формы представления данных. Усложнение информации приводит в свою очередь к усложнению программного обеспечения сетевого уровня. Интернет, к счастью, не использует такую информацию. Разделение ответственности Сетевой уровень определяет маршрут следования данных по сети. Он должен следить за трафиком, возможными столкновениями и скоростями передачи по каналам связи. Ответственность за надежную доставку данных лежит на физическом, сетевом уровнях и на уровне соединения. Сетевой уровень можно рассматривать в качестве службы доставки. Короче говоря, он заведует доставкой данных между сетевыми компьютерами и решает проблемы, связанные с доставкой и маршрутизацией трафика. Подробнее о транспортном уровне Когда два сетевых компьютера общаются между собой, это значит, что два процесса или программы обмениваются данными. Сетевой уровень доставляет сообщения до компьютера-получателя и передает их дальше транспортному уровню. Последний, передает данные соответствующему приложению или программе. Вы знаете, что равноправные процессы устанавливают виртуальное соединение между собой. Сетевой уровень одного компьютера, установившего соединение со вторым, устанавливает виртуальное соединение с сетевым уровнем компьютера-получателя. Точно так же транспортный уровень первого компьютера устанавливает виртуальное соединение с транспортным уровнем второго. Виртуальное соединение транспортного уровня На рис. 2.9 изображена сетевая модель, уже приводившаяся ранее. Пунктирные линии между соответствующими уровнями обозначают виртуальные соединения. В сетях с переключением пакетов сетевой уровень часто занимается тем, что посылает данные на переключатели пакетов, чтобы они дошли до места назначения. Рис. 2.10 воспроизводит более точную картину того, как происходят виртуальные соединения в сети с переключением пакетов. Виртуальные соединения действительно устанавливаются между сетевыми уровнями, уровнями соединения и физическим уровнями. Однако обмен сообщениями между ними происходит «скачками», а не от компьютера-передатчика до компьютера-приемника. Наоборот, сообщения между транспортным и выше лежащими уровнями происходят на базе «компьютер-компьютер». 62 
Подробнее о сетевых уровнях Прикладной уровень 4- Протоколы прикладного уровня Прикладной уровень Уровень представления 4- Протоколы уровня представления •4 Уровень представления Сеансовый уровень f" Протоколы сеансового уровня Сеансовый уровень Транспортный уровень 4- ’ Транспортные ПРОТОКОЛЫ Транспортный уровень Сетевой уровень i**a Протоколы сетевого уровня яяя1 Сетевой уровень Уровень соединения Протоколы уровня соединения Уровень соединения Физический уровень 4- Протоколы .физического уровня Ж Физический уровень Компьютер 1 Компьютер 2 Рис. 2.9. Сетевая модель с изображенными равноправными провесами (Сообщение) (Сообщение) (Сообщение) (Сообщение) (Пакет) (Кадр) (Биты) | Коммуникационная система } Прикладной уровень Уровень представления Сеансовый уровень Транспортный уровень Сетевой уровень + Уровень соединения Физический уровень Протоколы ■ прикладного уровня Протоколы уровня представления Протоколы —•сеансового уровня ■ Транспортные —■• протоколы — 4 Сетевой “ уровень Сетевой уровень 4 Уровень f соединения Уровень соединения . Физический у уровень Физический уровень f-H Прикладной уровень Уровень представления ^Сеансовый уровень Транспортный уровень Сетевой уровень Уровень соединения Физический уровень Рис. 2.10. Сетевая модель с изображенными виртуальными соединениями на базе скачков На рис. 2.10 указано, что промежуточные переключатели пакетов можно называть также коммуникационной подсетью. Коммуникационная подсеть состоит из промежуточных элементов сети, служащих для доставки сообщений между двумя компьютерами. Управление трафиком на транспортном уровне Сетевой уровень доставляет данные между компьютерами и решает проблемы, связанные с маршрутизацией и доставкой. В сети с переключением пакетов транспортный уровень должен фрагментировать данные, поступающие с сеан¬ 63 
сового уровня на пакеты меньшего размера, с тем чтобы передать их дальше на сетевой уровень. Принимающая сторона, наоборот, должна собрать данные из пакетов меньшего размера в большие, с тем чтобы передать на вышележащий уровень. От транспортного уровня зависит количество пакетов, путешествующих по сети. Другими словами, транспортный уровень генерирует трафик пакетов данных, которым должен управлять сетевой уровень. На одном и том же компьютере одновременно могут быть запущены несколько процессов, использующих сеть тем или иным образом. Транспортный уровень занимается доставкой данных к определенной пользовательской программе или от нее, следовательно, он должен управлять данными нескольких программ в одно и то же время. Увеличение пропускной способности Под пропускной способностью (bandwidth) подразумевается максимальное количество данных, проходящих в заданный интервал времени по каналу связи. Рассмотрим, например, струю воды, вытекающую их кухонного крана. Сделав струю воды больше, вы наполните стакан водой за меньшее время. Либо вы сможете наполнить за то же самое время два стакана вместо одного. Пропускная способность сети — то же самое, что и количество воды, проходящей через кухонный кран за известный период времени. Сетевые разработчики создают транспортный уровень, стремясь увеличить количество обрабатываемых за одно и то же время пакетов данных. Для увеличения пропускной способности (и производительности) транспортный уровень открывает несколько сетевых соединений для одного и того же транспортного соединения. Чтобы сделать это, транспортному уровню требуется мультиплексировать и демультиплексировать передаваемые данные. Термин «мультиплексирование» означает процесс, укладывающий несколько потоков данных в один коммуникационный канал. Термин «демультиплексирование» означает обратное действие. Транспортный уровень передающего компьютера мультиплексирует (объединяет) множество сообщений в одно транспортное соединение. Принимающий данные транспортный уровень, наоборот, демультиплексирует одно соединение во множество сообщений. То, каким образом происходят вышеописанные действия, определяет, насколько эффективно транспортный уровень управляет сетевыми пакетами. Более того, это определяет производительность сети, использование ее ресурсов и пропускную способность. Когда проектируется магистраль между двумя городами, инженеры могут выбрать двухполосное шоссе или шестиполосное, более дорогое, однако пропускающее больше автомобилей за одно и то же время. Точно так же сетевые разработчики, конструируя канал передачи данных между двумя компьютерами, могут предусмотреть создание множественных сетевых соединений на одном и том же канале связи, работающих в обоих направлениях. Выбор правильного подхода при проектировании шоссе диктуется ожидаемым потоком автомобилей и важностью самой магистрали. Если будет решено, что 64 
Подробнее о сетевых уровнях задержки потока автомобилей недопустимо, будет выбрано более дорогое, но эффективное решение о постройке многополосной дороги. Количество полос будет определяться исходя из предполагаемой интенсивности движения. Точно также сетевые программисты должны определить важность производительности сети. Если транспортный уровень будет устанавливать множество сетевых соединений по одному транспортному, он должен уметь фрагментировать данные для сетевого уровня. Соответственно, он должен уметь собирать данные при обратном процессе. Все это добавляет сложности в проектировании транспортного уровня и связанного с ним программного обеспечения. Ведь оно должно решать задачу управления множеством пакетов для множества процессов одновременно. С другой стороны, если ваши программы используют функции транспортного уровня, они могут больше не беспокоиться о проблеме разделения одного и того же канала с другими программами. Подводя итоги, скажем, что сетевой уровень управляет потоком данных между сетевыми компьютерами, а транспортный уровень — между различными процессами в одном и том же компьютере. Обе задачи важны и при этом совершенно различны. В соответствии с принципами ISO, каждый сетевой уровень должен выполнять четко определенную функцию. Следовательно, сетевой уровень не должен мультиплексировать и демультиплексировать данные — это задача транспортного уровня. Контроль за использованием сетевых ресурсов Стоимость использования сетевых ресурсов оказывается весьма важной величиной. В некоторых случаях условия таковы, что она становится важнее, чем производительность. В этих случаях разработчик должен создать такой транспортный уровень, который мультиплексировал бы несколько транспортных соединений в одно сетевое. Другими словами, если снижение стоимости сетевого соединения оказывается важнее пропускной способности, магистраль данных должна быть однополосной дорогой, а не многополосным шоссе. Рассмотрим проблему, встающую перед инженерами, когда нужно построить автомобильный туннель под большим озером. Каждая полоса дороги в туннеле обходится в невероятно большую сумму денег. Поэтому, например, туннель Sumner под Бостонским проливом на шоссе, соединяющем Бостон с международным аэропортом Logan, состоит только из одной полосы в каждом направлении. На концах туннеля дорога расширяется до трех полос в каждом направлении. Такое расширение можно рассматривать, как мультиплексирование трех полос, ведущих в туннель, в одну (мультиплексор 3 в 1). Поток автомобилей, выходя из туннеля, демультиплексируется в три полосы (демультиплексор 1 в 3). Интенсивное движение и однополосный туннель приводят к возникновению транспортных пробок на пути из Бостона в аэропорт и обратно. Хотя путешественники могут и не согласиться с таким мнением, но инженеры проекта прокладки туннеля (или те политики, что финансировали ее), сочли стоимость более важной, нежели возникновение транспортных пробок. 3 Зак. № 1949 65 
Глава 2. Принципы и понятия сетевой архитектуры Сетевые разработчики сталкиваются с точно такими же проблемами при конструировании транспортного уровня. Когда требования к производительности сети перевешивают возможные материальные затраты, транспортный уровень устанавливает сетевое соединение для каждого транспортного. Если ситуация меняется до обратной, несколько транспортных соединений будут мультиплексироваться в одно сетевое. Управление потоком Проблема, возникающая, когда передатчик шлет пакеты данных быстрее, чем приемник может их обработать, решается при помощи управления потоком. Транспортный уровень, так же как и сетевой, должен уметь управлять потоком данных. Однако транспортный уровень делает это на базе соединения «процесспроцесс», а не «компьютер-компьютер». Другими словами, сетевой уровень управляет потоком между конечными пунктами соединения, представленными компьютерами. Транспортный уровень управляет потоком между равноправными процессами или программами. Транспортные уровни многих сетей, и Интернет в частности, управляют потоком данных так же, как и обработкой ошибок. При этом транспортные уровни обмениваются сообщениями с подтверждением о доставке. Формируя сообщения, транспортный уровень шлет среди них запросы на контроль потока, получая возможность попросить другой транспортный уровень увеличить или уменьшить скорость передачи. Сеансовый уровень Как уже говорилось, в большинстве сетей, перед тем как выполнить то или иное приложение, требуется зарегистрироваться, введя имя и пароль пользователя. Каждый такой ввод запускает отдельный сеанс. Любой пользователь, вошедший в сеть, создает отдельный сеанс. Один и тот же пользователь может запустить одновременно несколько сеансов, войдя в сеть несколько раз. Запуск нового сеанса (или вход в сеть) активизирует сеансовый уровень, который в свою очередь устанавливает соединение между процессами или приложениями на удаленных друг от друга сетевых компьютерах. Примечание: Выражаясь профессиональным языком, процесс активизации сеанса (установления связи между двумя компьютерами) называется связыванием (binding). Управление соединениями Непосредственно до установления соединения оба конца должны договориться о таких параметрах соединения, как скорость передачи, контроль ошибок, ожидаемый тип передачи данных (симплексный, полудуплексный или дуплексный). Любой из участников переговоров может запросить изменить тот или иной параметр. Сеансовый уровень должен обработать любой запрос на изменение параметров соединения, который может возникнуть в любой момент в течение процесса входа в сеть. Сеансовый уровень обязан идентифицировать личность входящего в сеть пользователя. Пользователь должен доказать, что он имеет право сделать это. Способ авторизации доступа выбирается на усмотрение сеансового уровня. 66 
Подробнее о сетевых уровнях Взаимодействие с транспортным уровнем Любой участник сеанса может запросить изменения в параметрах соединения. Такая необходимость часто возникает в связи с проблемами транспортного уровня. Например, если соединение транспортного уровня ненадежно, сеансовый уровень должен устранить возможность потери данных приложением. Предположим, вы эксплуатируете распределенную базу данных, основывающуюся на транзакциях. Если на транспортном уровне происходит сбой и данные теряются, сеансовый уровень при этом обязан прекратить текущую транзакцию и откатиться назад, то есть отменить все внесенные до сбоя частичные изменения. Для обеспечения такой возможности сеансовый уровень должен группировать относящиеся к одной транзакции сообщения и либо передавать приложению всю группу целиком, либо не передавать ничего. Таким образом база данных предохраняется от сбоев на транспортном уровне и прерывания работы посередине транзакции. Если транспортный уровень не обеспечивает правильной последовательности передачи данных, сеансовый уровень обязан сделать это за него, разумеется, когда приложение нуждается в правильной последовательности. Сеансовый уровень устраняется Сеансовый уровень иногда устраняется из сетевой модели. Например, когда сеть не нуждается в доставке сообщений от приложения к приложению. В дальнейшем вы узнаете, что сеансовый уровень в Интернет вообще отсутствует. Несмотря на необходимость процедуры входа в Интернет, программы, проверяющие права доступа пользователя, не являются составляющими сетевой модели. Чаще всего они представляют собой отдельные продукты, работающие на уровне приложения. Протоколы транспортного уровня Интернет выполняют почти все функции сеансового уровня сетевой модели. Еще раз о сеансовом уровне Сеансовый уровень преобразует формат данных, подготовленных для передачи по сети, в формат, годный для передачи приложениям. В дополнение он обрабатывает запросы на изменение таких параметров соединения, как скорость передачи и контроль ошибок. Также он проверяет достаточность полномочий пользователя на установление соединения или использование сетевого процесса. Таким образом, сеансовый уровень является сетевым интерфейсом пользователя, служащим для установления и управления соединением между ним и сетевыми приложениями. Сетевые разработчики часто объединяют сеансовый уровень с транспортным. В большинстве случаев с исчезновением сеансового уровня функции, выполняемые им, переходят в ведение другого программного обеспечения, работающего как на транспортном, так и на прикладном уровнях. 67 
Глава 2. Принципы и понятия сетевой архитектуры Уровень представления Уровень представления содержит функции общего применения, реализующие интерфейсы к принтерам, дисплеям и форматам файлов. Уровень представления определяет, в каком виде данные поступят к пользователю. В связи с этим работа уровня представления связана с многочисленными преобразованиями данных. Хорошо спроектированная сеть позволяет использовать разнообразные компьютеры. Интернет — превосходный пример такой сети. К ней подключены компьютеры от персональных до мини- и больших мэйнфреймов. Внутри каждой группы компьютеров существует еще большее разнообразие дисплеев и принтеров для них. Операционные системы, на которых они работают, также славятся несовместимостью между собой. Уровень представления должен объединять это разнообразие способом, позволяющим сделать вопросы несовместимости «прозрачными» для сети. К сожалению, в Интернет отсутствует стандартный уровень представления, и его функции приходится выполнять сетевым приложениям. Разрабатывая программы для Интернет, необходимо придерживаться рекомендаций ISO/OSI, касающихся реализации функций уровня представления. Реализация уровня приложения Профессиональные программисты часто реализуют функции уровня приложения в виде процедур и библиотек функций, которые вызываются пользователем. В дальнейшем вы узнаете, что интерфейс прикладного программиста в среде Windows, называющийся Winsock API, существует как раз для того, чтобы выполнять функции уровня представления и сеансового уровня. Так или иначе, уровни, находящиеся ниже уровня представления, служат для выполнения сетевых операций. Уровень представления выполняет полезные, но не критические для сети функции. В последующих абзацах будут описаны некоторые функции, которые может выполнять уровень представления. Шифрование данных Цель шифрования данных — их защита. Шифрование данных превращает вразумительное сообщение во что-то неосмысленное перед тем, как передать его по сети. Уровень представления на приемном конце, наоборот, дешифрует сообщения, превращая из абракадабры в осмысленные фразы или выражения. Шифрование на уровне представления гарантирует, что данные пройдут через низ лежащие сетевые уровни в уже недоступном для посторонних виде. Сжатие данных и увеличение пропускной способности Уровень представления может заниматься сжатием данных. Так же, как и шифрование, сжатие приводит к преобразованию данных. Методика сжатия данных похожа на методику шифрования, однако они преследуют разные цели. 68 
Подробнее о сетевых уровнях В то время как пользователь шифрует данные, чтобы скрыть их смысл, сжатие данных производится с целью уменьшить их в размере. Приложения, занимающиеся как шифрованием, так и сжатием, широко доступны в Интернет. Сжимая данные в самом начале иерархии сетевой модели, уровень представления уменьшает их количество, необходимое для транспортировки сетью. Таким образом, сжатие данных приводит к существенному улучшению пропускной способности сети. Пропускная способность сети — это количество данных, способных пройти через сеть за заданный промежуток времени. Один из способов увеличить пропускную способность — использовать несколько сетевых соединений для одного транспортного (больше полос для движения данных). Другой способ увеличить пропускную способность — снизить количество данных, подлежащих транспортировке. Разнообразные способы сжатия данных применяются при хранении данных на жестких дисках, например. Проблемы сжатия хранящихся на дисках данных свойственны в полной мере и сетевым компьютерам. Однако сжатие данных в сети важнее всего при передаче данных, так как пропускная способность коммуникационного канала — один из наиболее дорогих факторов. Предположим, что ваше приложение шлет один пакет данных вместо трех, передавая все тот же объем информации. Это значит, что за одно и то же время вы сможете передать в три раза больше информации, а значит, вместо того, чтобы заплатить за три, оплатите только один пакет данных. Итак, вы увеличили производительность сети в три раза. Сетевой трафик также уменьшился, так как, сжав данные в три раза, вы передаете только один пакет вместо трех. Меньше сообщений — меньше трафик. При этом уменьшается вероятность столкновений в сети. Ее снижение, в свою очередь, увеличивает производительность сети. Отображение данных Правильное отображение информации на мониторе компьютера — еще одна проблема, решаемая на уровне представления. Мониторы характеризуются множеством параметров, что хорошо для вас, как прикладного программиста, однако правильно спроектированное сетевое приложение должно учитывать следующие различия в мониторах разных производителей: ♦ Один и тот же код символа может отображаться по-разному. ♦ Разное количество символов на строку. ♦ Функция «эхо» (автоматическое отображение на экране введенного с клавиатуры символа). ♦ Режим впечатывания символов (раздвижка или печать поверх строки). ♦ Символы перевода строки (Line feed) и возврата каретки (Carriage return). ♦ Символы табуляции (горизонтальной и вертикальной). ♦ Обработка символа «Backspace». 69 
Глава 2. Принципы и понятия сетевой архитектуры ♦ Обработка символа «Form Feed». ♦ Команды позиционирования курсора. Различные типы компьютеров могут по-разному обрабатывать вышеуказанные параметры мониторов. Хорошо спроектированный уровень представления должен уметь сделать эти различия независимыми от самого сетевого приложения. Различные сети используют протоколы виртуального терминала, делающие проблемы совместимости принтеров и мониторов независимыми от сетевых приложений. Работая в такой сети, вы можете послать данные с разделителями CR и LF на виртуальный терминал, и он правильно отобразит ваши данные. Протоколы, решающие такие проблемы, размещаются на сетевом уровне представления. Короче говоря, уровень представления скрывает различия в работе принтеров, мониторов, структуре файлов компьютера, способные повлиять на правильное воспроизведение доставленной пользователю информации. В составе Интернет уровня представления по крайней мере официально не существует. Однако существует протокол Telnet, выполняющий функции виртуального терминала. Прикладной уровень Как следует из названия, прикладной уровень ведает всеми тонкостями, касающимися взаимодействия пользователя и сетевых приложений. В качестве прикладного программиста вы разрабатываете именно программы прикладного уровня. Во многих случаях обнаруживается, что разработка сетевого приложения очень похожа на разработку любой другой программы. Однако иногда приходится разрабатывать приложения, использующие те или иные специфические свойства сети. Например, сеть предлагает практически неограниченные возможности по организации распределенных вычислений или хранения данных на многих сетевых компьютерах. Используя сеть, вы можете атаковать вычислительную задачу множеством процессоров одновременно. Однако для написания приложений такого рода вы должны научиться решать проблемы, с которыми вам еще не приходилось сталкиваться. Необходимо осознать разницу между написанием сетевого и не сетевого приложений. Основная цель данной книги — помочь вам в этом, а также помочь научиться разрабатывать программы для использования в Интернет. Прикладной уровень и уровень представления Линия раздела между прикладным уровнем и уровнем представления важна, тем более, что в Интернет отсутствует официальный стандарт, описывающий последний. Как программист приложений Интернет вы всегда должны спрашивать себя: «Чья это проблема? Сетевая или прикладного уровня?» Соответственно, приложение должно делиться на модули, один из которых решал бы задачи, ставящиеся перед вами сетью, а другой — относящиеся непосредственно к приложению. 70 
Модель клиент-сервер Например, вы можете разместить в одном и том же модуле код для управления монитором и код, относящийся непосредственно к приложению. Поступая так, вы смешиваете функции приложения и уровня представления, ухудшая тем самым переносимость программы на другие платформы, а также дальнейшее сопровождение. Как только вы подсоедините новое аппаратное средство к компьютеру, вам потребуется переписать все ваше приложение заново. Такой подход оказывается дорогим и неэффективным. Разработка программного обеспечения На протяжении многих лет сетевая модель ISO/OSI доказывала свою эффективность. Понимание назначения составляющих и принципов функционирования модели позволит вам достичь грандиозного выигрыша в проектировании собственных приложений. Помните, что в качестве сетевого программиста вам чаще всего потребуется писать программы, относящиеся к прикладному уровню сетевой модели. Следование сетевой модели обеспечивает высокую модульность вашим программам. Это свойство позволяет минимизировать возможные затраты в будущем на сопровождение и доработку сетевого приложения. Немаловажно также, что это поднимает вашу репутацию разработчика в глазах окружающих сотрудников. Таким образом, сетевой прикладной уровень решает проблемы, связанные с работой определенных сетевых приложений. Структура и функции прикладной программы, написанной вами, определяет структуру и функции сетевого прикладного уровня. Модель клиент-сервер В предыдущей части книги была описана модель ISO/OSI, служащая для разработки сетевого программного обеспечения. Эта доказавшая свою эффективность модель, использует концепцию деления на уровни, тем самым облегчая жизнь разработчикам. Прикладной уровень в модели ISO/OSI служит для обеспечения работы различных прикладных программ. В следующей главе вы узнаете, как реально функционирующая сеть — Интернет описывается в терминах этой модели. Перед тем как перейти к главе 3, давайте рассмотрим еще одну не менее важную сетевую концепцию — модель «клиент-сервер». Определение модели «клиент-сервер» Модель «клиент-сервер» применяется сетевыми программистами в подавляющем большинстве случаев, когда речь идет о написании приложения. Общение по сети подразумевает, что между двумя компьютерами или программами устанавливается сетевое соединение, включающее как приемник, так и передатчик, а также канал связи между ними. Модель «клиент-сервер» предполагает, что сетевое соединение (и, стало быть, приложение) является двухсторонним. Одно и то же сетевое приложение при таком подходе выполняется двумя сторонами по-разному и состоит из части-клиента и части-сервера. Считается, 71 
Глава 2. Принципы и понятия сетевой архитектуры что клиент запрашивает информацию или услугу, а сервер — выдает ее, то есть отвечает на запрос. Другими словами, сетевое приложение в рамках модели «клиент-сервер» выполняет две различные и строго определенные функции: запрашивает и отвечает на запросы. Запрашивающая программа называется «клиент», а отвечающая на этот запрос — «сервер». В большинстве случаев сетевое приложение состоит из двух независимых частей: «клиента» и «сервера». Однако никто не запрещает объединить обе функции в одной и той же программе. Из главы 11 вы узнаете, что сервер, оказавшийся не в состоянии выполнить запрос клиента, может сам стать клиентом и обратиться к другому серверу. Примечание: В главе 9 описывается система имен доменов Интернет и программысерверы для поиска адресов сетевых компьютеров. Зачем она нужна? Вы знаете, что цель существования сети — позволить компьютерам (и пользователям) общаться друг с другом. Два взаимодействующих компьютера в сети могут разделяться двумя футами, находясь в той же комнате, а возможно, океанами и горными цепями. Сеть объединяет множество различных типов компьютеров, так же как сетевых технологий. Сетевая модель ISO/OSI скрывает и делает прозрачным это многообразие от сетевых приложений. Она позволяет конструировать сеть, составляя ее из аппаратных или программных модулей с четко определенными функциями. Модель «клиент-сервер» служит руководством для разработки приложений, которые бы легко интегрировались в среду сетевых коммуникаций. Так же, как и модель ISO/OSI, модель «клиент-сервер» позволяет разделять прикладные программы на модули с четко описанными свойствами. Прикладная программа в модели «клиент-сервер» разделена на модуль-клиент и модуль-сервер. Что такое виртуальная цепь? Сетевые уровни позволяют устанавливать виртуальное соединение между процессами или компьютерами. Считается, что виртуальное соединение образует виртуальную цепь (virtual circuit). Вы знаете, что виртуальная цепь создает иллюзию прямого физического соединения, в то время как на самом деле оно отсутствует. Многие проблемы исчезают, когда вы трактуете виртуальную цепь как настоящее соединение «точка-точка», перекладывая ответственность за доставку данных на низлежащие сетевые уровни. Модель «клиент-сервер» поступает как раз таким образом. Что такое клиент и что такое сервер? Каждая из сторон виртуального соединения называется «сокет» (socket). Каждая сторона, или сокет, установленного виртуального соединения обычно выполняет определенные функции. Сокет на стороне клиента, запрашивающий 72 
Модель клиент-сервер соединение, называется клиентом, а отвечающий на запрос — сервером. Программное обеспечение на стороне сервера называется серверной частью приложения, а на стороне клиента — клиентской частью. В чем между ними разница? Сперва вы можете оказаться в затруднении, пытаясь определить, какая часть приложения является клиентом, а какая — сервером. Во многих случаях обе части выполняют функции как сервера, так и клиента. В последующих абзацах мы попробуем показать некоторые характерные отличия в поведении серверов и клиентов. Приложение-сервер, или просто сервер, как правило инициализируется при запуске и далее бездействует, ожидая поступления запроса от клиента. Каждый сервер предоставляет определенную услугу пользователям сети по всему миру, внутри корпорации или группы. Например, распределенная база данных выдает информацию о резервировании билетов авиакомпаниям. База использует процесс-сервер, обеспечивающий доступ к ее ресурсам. При этом к услугами сервера прибегает целая авиатранспортная индустрия. Точно также, корпоративный сервер электронной почты доступен каждому компьютеру корпоративной сети, выполняя поступающие запросы на чтение и отправку корреспонденции. Запрос серверу от сетевого компьютера обычно активизируется пользователем. Процесс-клиент посылает запрос на установление соединения с сервером, требуя выполнить для него определенную функцию. Например, клиент может запросить передать ему точное время или определенный файл. Клиент сетевого приложения Telnet требует входа в удаленный компьютер. Клиент приложения Telnet, таким образом, взаимодействует с сервером приложения Telnet. Два типа приложений-серверов Приложения-серверы делятся на два типа: последовательный и параллельный, в зависимости от метода обработки запросов. В последующих абзацах мы кратко обсудим каждый их них. Сервер последовательной обработки запросов Как следует из названия, сервер с последовательной обработкой запросов обслуживает их один за одним, в порядке поступления. Хороший пример такого сервера — выдача текущего времени суток. Как только поступает запрос, такой сервер немедленно приступает к его обработке и не обслуживает следующие запросы до окончания текущей работы. По сравнению с параллельной обработкой такая методика значительно упрощает программную реализацию серверной части. Время, требуемое для выполнения запроса клиента, хорошо прогнозируется, и информация для выдачи доступна сразу. 73 
Глава 2. Принципы и понятия сетевой архитектуры Сервер параллельной обработки запросов Когда время для выполнения запроса невозможно предсказать или оно неизвестно, процесс-сервер выполняет параллельную обработку запросов. Такой сервер создает отдельный процесс для каждого поступившего запроса. Другими словами, создается столько процессов-серверов, сколько поступило запросов. Обычно после создания процесса-обработчика сервер снова «засыпает», ожидая обращений от новых клиентов. Конструкция сервера параллельной обработки требует, чтобы операционная система также обеспечивала параллельность, то есть способность выполнять несколько процессов в одно и то же время. Серверы с параллельной обработкой запросов обычно создаются, чтобы передавать файлы по сети, так как, вследствие неизвестных заранее размеров, время, уходящее на передачу файла, также невозможно предсказать. Подводя итоги Прочитав эту главу, вы тем самым завершили вводный курс по предмету «компьютерные сети». В этой и предыдущей главах мы обсуждали вопросы структуры и функционирования компьютерных сетей, пользуясь самой общей терминологией. Начиная с третьей главы мы будем обсуждать более конкретные вопросы (касающиеся программирования и Интернет), поэтому, прежде чем продолжить, убедитесь, что вы усвоили следующие ключевые моменты: S Протоколы определяет правила, следуя которым сетевые программы шлют и принимают данные. S Сетевая модель ISO/OSI определяет сеть в терминах нескольких функциональных уровней. Каждый сетевой уровень выполняет строго определенные функции и применяет для этого один или несколько протоколов. S Физический сетевой уровень передает данные по сетевым каналам связи и включает в себя аппаратные средства, необходимые для этого. S Уровень соединения предохраняет данные от повреждений на физическом уровне. S Сетевой уровень передает данные от одного сетевого компьютера к другому. S Транспортный уровень передает данные от одного приложения к другому. S Сеансовый уровень — это сетевой интерфейс пользователя. S Уровень представления занимается проблемами сетевого интерфейса к принтерам, мониторам и преобразованием форматов файлов. S Прикладной уровень — это набор широко используемых сетевых приложений. S Модель «клиент-сервер» упрощает разработку сетевого программного обеспечения, разделяя задачу на две части — клиент и сервер. 
Глава 3 Введение в TCP/IP Впервой и второй главах вы познакомились с основными сетевыми понятиями, теперь же мы подошли к изучению конкретного типа сетей — сетей, базирующихся на семействе протоколов TCP/IP. Интернет состоит из тысяч сетей, использующих TCP/IP. Знание основ работы протоколов семейства TCP/IP совершенно необходимо всем программистам приложений для Интернет. Прочитав эту главу, вы познакомитесь со следующими основными понятиями: ♦ Определение семейства протоколов TCP/IP. ♦ Назначение стека протоколов TCP/IP. ♦ Как в TCP/IP обрабатываются данные. ♦ Как TCP/IP работает на сетях различных технологий, например Ethernet. ♦ Компьютеры с несколькими сетевыми адресами содержат несколько сетевых карт. ♦ Каким образом компьютер превращается в маршрутизатор. ♦ Различия между ориентированными и не ориентированными на соединение протоколами. ♦ Различия между надежными и ненадежными протоколами. ♦ Различия между потоковой службой доставки и службой доставки датаграмм. ♦ Определение виртуальной цепи (virtual circuit). 75 
Глава 3. Введение в TCP/IP Значение семейства протоколов TCP/IP В целях дальнейшего повествования будем считать, что протоколы — это правила работы программного обеспечения. Вы знаете, что операционные системы пользуются протоколами для описания того, как поток информации распределяется между пользователями, приложениями и компьютерами. Точно так же протоколы описывают поток информации между сетевыми компьютерами и вашими сетевыми приложениями. Если ваша сеть — Интернет, то протоколы на ней — TCP/IP. Именно они управляют всей информацией в Интернет. Необходимо хорошо представлять, как именно это происходит. Семейство TCP/IP состоит из многих протоколов, и каждый занят своим делом. Каждый переносит сетевые данные в различных форматах и обладает различными возможностями (например, контролирует ошибки). В зависимости от требований вашего приложения для передачи данных по Интернет вы пользуетесь определенным протоколом из семейства TCP/IP. Терминология TCP/IP TCP — сокращение от слов «протокол управления транспортировкой» (Transport Control Protocol). IP — от слов «протокол Интернет» (Internet Protocol). Комбинируя оба понятия в одно, мы получаем больше, нежели просто два протокола. Поэтому термин TCP/IP часто смущает непосвященных новичков. Определение семейства протоколов TCP/IP Вы знаете, что Интернет полагается на набор протоколов TCP/IP. Семейство TCP/IP — это набор взаимодополняющих и тесно связанных друг с другом протоколов. Семейство включает в себя как протокол контроля транспортировки (TCP) протокол Интернет (IP) и множество других протоколов. Все они предназначены для передачи сообщений в сети Интернет. В табл. 3.1 приведены самые распространенные протоколы TCP/IP. Таблица 3.1. Широко известные протоколы семейства TCP/IP Протокол Назначение IP (Internet Protocol) Протокол Интернет. Протокол сетевого уровня, перемещающий данные между сетевыми компьютерами. TCP (Transport Control Protocol) Транспортный протокол (протокол контроля транспортировки). Перемещает данные между прикладными программами Интернет. 76 
Терминология TCP/IP Таблица 3.1 (окончание) Протокол Назначение UDP (User Datagram Protocol) Протокол пользовательских датаграмм. Он также перемещает данные между приложениями, однако является более простым и менее надежным, чем TCP. ICMP (Internet Control Message Protocol) Протокол управляющих сообщений Интернет. Управляет сетевыми сообщениями об ошибках и другими ситуациями, требующими вмешательства сетевых программ. В документе RFC 1180, «Учебное пособие по TCP/IP» (A TCP/IP Tutorial), авторы, Теодор Соколовски (Theodore Socolofsky) и Клавдия Кейл (Claudia Kale), придерживаются мнения, что термин «технология интернет» (internet technology) точно соответствует семейству протоколов TCP/IP и приложениям, использующим его. Термин «интернет» применяется у них везде, где идет речь о сети, базирующейся на технологии интернет. Этот термин — просто еще одно название для семейства TCP/IP. Вначале может показаться, что термин «семейство протоколов Интернет» удобней в применении, чем термин «семейство протоколов TCP/IP». Однако большинство авторов, программистов и просто профессионалов пишут «TCP/IP», подразумевая все семейство TCP/IP. Когда возникает необходимость указать отдельный протокол (TCP или IP), их названия (или сокращения) просто пишутся отдельно. Другими словами, термин «TCP/IP» — то же самое, что и «набор протоколов TCP/IP», или «набор протоколов Интернет» или, наконец, «технология Интернет». Для всего этого в нашей книге будет использоваться первый вариант. В этой главе вы узнаете разницу между транспортным протоколом (TCP), протоколом Интернет (IP) и семейством протоколов, обозначаемом TCP/IP. К окончанию главы вы сможете пользоваться ими как настоящий сетевой профессионал. Что такое стек протоколов? Во второй главе вы познакомились с моделью взаимодействия открытых систем (OSI). В соответствии с этой моделью сеть делится на уровни, выполняющие специфические функции. В рамках модели каждому уровню соответствует набор протоколов, определяющих его функциональность. Например, сетевой уровень, управляющий доставкой данных в Интернет, состоит из протокола Интернет (IP), осуществляющего доставку между сетевыми компьютерами (хостами). 77 
Глава 3. Введение в TCP/IP На рис. 3.1 показано, как модель ISO/OSI представляет сеть в образе вертикального стека, состоящего из модулей или уровней, расположенных друг над другом. Термин «стек протоколов» как раз и происходит из такой концепции представления сети в виде вертикально расположенных уровней и сложенных в стек протоколов. Термин «стек протоколов» относится к любой комбинации сетевых уровней и соответствующих протоколов. Стек протоколов TCP/IP — лишь один из | Прикладной уровень | Уровень представления Сеансовый уровень | Транспортный уровень Сетевой уровень Уровень соединения Физический уровень Прикладной уровень Программа Программа 1 1 1 1 J |Т„ГД ЦеР^ ^ JUDP] j I Сетевой licMPk-—I Й МЮМР1 ! j Сетевой Iуровень L.I I Уровень [соединения ARP Интерфейс физического уровня RARP Рис. 3.1. Модель ISO/OSI и стек протоколов множества стеков, соответствующих модели ISO/OSI. Когда кто-то говорит: «Моя сеть использует стек TCP/IP», вы должны понимать, что речь идет о наборе протоколов различных сетевых уровней, и этот набор — семейство TCP/IP. Что такое поток данных? Семейство протоколов TCP/IP ведает перемещением данных по сети. Поскольку семейство TCP/IP состоит из множества тесно взаимодействующих между собой протоколов, то можно вести речь о потоке данных, проходящих от одного сетевого уровня к другому или от одного протокола к следующему. Из рис. 3.1 видно, что верхний уровень модели ISO/OSI называется прикладным. Нижний, физический, уровень непосредственно соединяет компьютер с линиями передачи данных. На пути сквозь стек протоколов данные проходят от прикладного уровня к физическому, а затем — передаются по сети. Пройдя по сети до места назначения, данные принимаются физическим уровнем и проходят далее к месту назначения - прикладному уровню. На рис. 3.2 78 
Место, занимаемое TCP/IP изображено путешествие данных от приложения-клиента на одном компьютере к приложению-серверу другого. Проходя сквозь уровни, данные могут дробиться на части (блоки) меньшего размера. В дальнейшем вы узнаете, что такие блоки данных в рамках TCP/IP часто называются по-разному. Даже сменив имя, блок данных может быть успешно прослежен на протяжении всего пути, но только если имеется четкое представление о происходящих с ним преобразованиях. Прикладной уровень | Программа-сервер | [ Т ранспортный уровень i [TCP] UDP Сетевой уровень IP [Уровень соединения Интерфейс физического уровня iiLI J Прикладной уровень I Программа-клиент j Транспортный уровень L___ TCP UDP j Сетевой уровень L ! IP j Уровень соединения H- -f- -Ь I Интерфейс физического уровня L_________ „Xi I I Канал передачи данных Рис. 3.2. Данные, путешествующие сквозь стек протоколов Место, занимаемое TCP/IP Необходимо иметь четкое представление об изменениях формы данных, проходящих через стек протоколов. Представляйте сеть в виде вертикально расположенных функциональных уровней. Верхний уровень — это интерфейс между вашим приложением и сетевым программным обеспечением. (Оно также состоит из протоколов уровней модели ISO/OSI.) Нижний уровень представляет интерфейс между сетевым программным обеспечением и физическими (аппаратными) элементами сети. Для передачи сетевых сообщений приложение посылает данные вниз сквозь стек протоколов. Сеть в свою очередь шлет информацию вверх сквозь стек протоколов вашей прикладной программе. Программируя приложение в Интернет, рассматривайте его, как будто бы сидящим на самом верху стека протоколов. Другими словами, стек протоколов TCP/IP занимает место между приложением и сетевым аппаратным обеспечением. Семейство TCP/IP можно представить как ряд ваших помощников, стоящих друг под другом на лестнице. Вы остаетесь на самом верху и, если вам нужно 79 
Глава 3, Введение в TCP/IP отправить почту, просто передаете ее ближайшему помощнику. Он в свою очередь передает почту дальше, ближайшему под ним, и т. д., до самого нижнего, который уже никуда не передает (он — почтовый работник сети), а просто переносит ее до места назначения, где вручает работнику вашего корреспондента. У корреспондента весь процесс повторяется, только наоборот, снизу вверх. Модель «клиент-сервер» и TCP/IP В главе 2 вы познакомились с сетевой моделью «клиент-сервер». Если вы помните, она полезна в качестве руководства по разработке сетевых приложений. В последующем обсуждении семейства протоколов TCP/IP мы сконцентрируемся на программном обеспечении — протоколах, позволяющих обмениваться сетевыми сообщениями. Конечная цель работы любого протокола — передать информацию между приложением-клиентом и приложением-сервером так, как это описывалось во второй главе. Данные двигаются по Интернет Возможно, TCP/IP станет понятнее, если мы рассмотрим три шага, необходимых для передачи данных по сети Интернет. 1. Информация должна пройти между приложением и сетью. Это путь сквозь стек протоколов вниз к физическому уровню. 2. Сеть должна определить место назначения информации или, строго говоря, определить адрес получателя сетевых данных. 3. Сеть должна физически переместить данные к месту назначения, воспользовавшись для этого маршрутизацией. Появившись в месте назначения, данные должны пройти сквозь стек протоколов вверх к сетевому приложению. На прохождение сквозь стек протоколов обычно уходит много времени, поэтому, чтобы приложение хорошо себя вело, то есть выполнялось с приемлемой скоростью, нужно представлять себе сущность операций, производимых над данными протоколами каждого сетевого уровня. Стек протоколов TCP/IP Модель ISO/OSI состоит из семи функциональных уровней, предназначенных для построения сети. Однако не будем забывать, что она всего лишь руководство к действию, а не готовая конструкция. Так, например, стек TCP/IP состоит всего из пяти, а не семи уровней ISO/OSI. 80 
Стек протоколов TCP/IP На рис. 3.3 показана простая пятиуровневая сетевая модель с протоколами TCP/IP в качестве уровней. В дальнейшем мы рассмотрим назначение и функции каждого уровня в отдельности. Сейчас нам важно понимать, что сетевые уровни и соответствующие им протоколы образуют модель, сквозь которую проходят данные приложений на пути от программ к физическим элементам сети и обратно. Пур<^ень°Й 1 Программа] |Программа] |Программа[ ]Программа! \ т Т I Т г *4г .. I . TCP1 ! : IUDP' Транспортный уровень Li- [ Сетевой t уровень t \ щ 4- [ ICMP'f- T\ Уровень * ■ соединения if! 4jPj IGMP; i i Интерфейс H f, физического i $ RARPj •| уровня i. Ц——— Физический уровень Канал передачи данных Рис. 3.3. Модель сети на базе протоколов TCP/IP 'I Стрелками на рисунке обозначены возможные пути перемещения данных при обмене между различными коммуникационными системами и сетевой аппаратурой. Например, чтобы передать данные между приложением и транспортным уровнем, необходимо воспользоваться протоколом UDP (User Datagramm Protocol, протокол передачи датаграмм пользователя) и TCP (Transport Control Protocol, транспортный протокол). Чтобы связаться с сетевым уровнем, приложение может обратиться к протоколу ICMP (Internet Control Message Protocol, протокол управляющих сообщений Интернет), либо к протоколу IP. Независимо от выбранного маршрута от приложения к сетевому уровню, данные, чтобы достичь аппаратных средств, обязаны пройти через модуль протокола IP. В большинстве случаев данные проходят по очереди все уровни сетевой модели. Однако это не обязательно. Например, можно сконструировать программу которая будет обращаться сразу к сетевому уровню, минуя транспортный В главе 16 будет показано, как этого достичь. Случаи, когда программа должна так себя вести, достаточно редки, и для разработки таких программ требуется гораздо больше усилий, так как в них вы собственными средствами реализуете функции, в нормальных условиях выполняемые транспортным уровнем. А сейчас мы рассмотрим путь данных в обратном направлении, снизу вверх сквозь стек протоколов. Начав с самого нижнего, физического уровня, мы 81 
Глава 3. Введение в TCP/IP постепенно поднимемся вверх, к прикладному. Из рис. 3.3 видно, что самые сложные преобразования данные испытывают на средних уровнях модели — транспортном и сетевом, с которыми и должно общаться приложение. Поскольку наибольшее время занимает программирование трех верхних уровней модели, можно подумать, что избранный нами ранее подход «сверху вниз» вполне оправдывает себя. Однако, чтобы достаточно ясно представить себе, что же происходит в средних уровнях модели, необходимо сначала узнать, как работают более низкие уровни. Иначе многое из происходящего может показаться бессмысленным. В последующих абзацах будет показано, как данные движутся вверх по стеку протоколов. Это облегчит понимание обратного процесса — движения данных от приложения к сети. Также вы поймете, почему данные, передаваемые по Интернет, необходимо сопровождать той или иной служебной информацией. Устройство физического уровня С точки зрения протоколов TCP/IP, физический сетевой уровень выглядит точно так же, как и в модели ISO/OSI. Он состоит из физического носителя данных — канала связи. Канал связи обычно представляет собой коаксиальный кабель или витую пару различных модификаций. Сетевые разработчики и системные интеграторы хорошо представляют физические и электрические характеристики таких носителей. Прикладному программисту достаточно знать, что подсоединенный к компьютеру кабель является физическим уровнем сети. Устройство уровня соединения На рис. 3.3 показано, что уровень соединения состоит из интерфейса аппаратного обеспечения и двух модулей протоколов: ARP (Address Resolution Protocol, протокол преобразования адреса) и RARP (Reverse Address Resolution Protocol, протокол обратного преобразования адреса). Оба этих модуля подробно описаны в четвертой главе, мы же пока просто будем иметь в виду, что они служат для правильного определения адресов в сети. Примечание: Протокол ARP преобразует формат адреса сетевого уровня в формат адреса уровня соединения. RARP выполняет обратные действия, преобразуя формат адреса уровня соединения в формат адреса сетевого уровня. Причины, по которым сеть нуждается в таком преобразовании, подробно обсуждаются в четвертой главе. Уровень соединения, называемый в модели ISO/OSI «data-link» (уровень обмена данными или канальный уровень), расположен между физическим и сетевым уровнями. Как следует из названия, он соединяет своих соседей между собой. Чтобы понять его предназначение, необходимо рассмотреть интерфейсы к соседям, лежащими ниже и выше его — к физическому и сетевому уровням соответственно. В главе 4 эти интерфейсы описаны подробнее. Мы же сейчас рассмотрим интерфейс между физическим уровнем и уровнем соединения. 82 
Устройство уровня соединения Зачем нужен уровень соединения? Уровень соединения, как показано на рис. 3.4, обрабатывает поток данных между физическим и сетевым уровнями. В случае TCP/IP функции сетевого уровня выполняются модулем IP. Сетевой уровень ip _ ;;4 соединения Интерфейс дрр t } физического уровня I Ethernet, Token Ring и др. ( ►'rarp Г - Физический уровень ’ • ■ • : :У ► Канал передачи данных (витая пара, коаксиальный кабель и др.) Рис. 3.4. Интерфейс уровня соединения Кроме обработки данных, каждый уровень сетевой модели скрывает от соседей детали конкретной реализации протоколов. Уровень соединения скрывает от сетевого подробности, связанные с функционированием физического уровня. Хорошо спроектированный уровень соединения сделает так, что протоколам сетевого уровня будет все равно, на какой сети работать — будь то технология Ethernet или Token Ring. Данные будут просто передаваться уровню соединения, который и обеспечит всю дальнейшую обработку. Для написания приложений Интернет необязательно знать подробности той или иной сетевой технологии, однако нужно знать, как данные проходят сквозь протокольный стек. Для того чтобы понять работу сетевого уровня соединения, необходимо познакомиться с работой хотя бы одной конкретной сетевой технологии. Краткое введение в технологию Ethernet Наиболее широко используются сети, построенные на технологии Ethernet. Технология Ethernet хорошо знакома большинству профессионалов. Большое количество сетей, подключенных к Интернет, тоже использует эту технологию. В последующих абзацах мы дадим краткий обзор технологии Ethernet. После вы узнаете несколько ключевых моментов технологии, что поможет лучше понять структуру потока данных между физическим уровнем и уровнем соединения сетевой модели TCP/IP. 83 
Глава 3. Введение в TCP/IP Сетевые компьютеры оборудованы одной или несколькими сетевыми картами, соединяющими их с сетью. Конструкция сетевой интерфейсной карты полностью зависит от сетевой технологии. Если ваша сеть — Ethernet, то и карта должна быть только Ethernet. Если ваша сеть — Token Ring, то нужна специальная карта для работы со средой этой технологии и т. д. Зачем нужны две сетевые карты в одном компьютере' Чтобы включить компьютер в сеть, в него необходимо вставить как минимум одну сетевую карту. Иногда, однако, компьютер оборудуется большим количеством сетевых карт. Сетевые карты могут принадлежать различным технологиям. Компьютер с несколькими сетевыми картами имеет не один, а несколько сетевых адресов (multihomed). Если он объединяет карты различных сетевых технологий, он может работать в качестве маршрутизатора. Например, компьютер, оборудованный картой Ethernet и картой Token Ring, может маршрутизировать данные между двумя различными сетями: Ethernet и Token Ring. Компьютер с двумя одинаковыми сетевыми картами (например, Ethernet) имеет два сетевых адреса, но не является маршрутизатором, хотя и перенаправляет данные между двумя сетями одинаковой технологии. В прошлом маршрутизаторы часто назывались шлюзами. На сегодняшний день такая терминология потеряла свое значение. Шлюзом теперь называют шлюз прикладного обеспечения, то есть программу, преобразующую данные из одного типа протоколов в другой. Такое преобразование часто производится с электронной почтой. Например, чтобы послать электронную почту из сети на базе TCP/IP в сеть, не поддерживающую этот протокол, требуется воспользоваться шлюзом. К сожалению, в литературе об Интернет термин «шлюз» часто трактуется как маршрутизатор. Поэтому будьте осторожны, встречая термин «шлюз». Судите о его значении только на основании контекста. Термин «маршрутизатор», однако, всегда обозначает именно маршрутизатор. Термин «шлюз» может обозначать как маршрутизатор, так и шлюз прикладного обеспечения. Компьютер соединяется с сетью Ethernet посредством сетевого кабеля и интерфейсной карты. Все компьютеры в локальной сети Ethernet соединены с одним и тем же кабелем. Когда программное обеспечение посылает данные по сети, они перемещаются от одной сетевой карты к другой. Каждая сетевая карта имеет уникальный адрес. Данные, проходящие по сети Ethernet, упаковываются в кадры. Кадр Ethernet состоит из двух адресов: получателя и отправителя, из собственно пользовательских данных и специального поля, обозначающего тип данных, переносимых кадром. Адрес формата Ethernet состоит из шести битов. Каждая сетевая карта Ethernet следит, не появится ли ее адрес в кадрах, проходящих мимо нее по шине. Кроме собственного адреса, сетевые карты следят за появлением специального шестнадцатиричного адреса FF FF FF FF FF FF, служащего уведомлением о том, что ведется широковещательная передача, предназначенная всем. Все карты Ethernet могут одновременно слушать, но лишь одна может при этом передавать. Если две из них одновременно начнут передачу, возникнет столкновение. Технология Ethernet опознает такие ситуации и требует, чтобы устройства прекратили передачу и продолжили ее после короткого, но случайного 84 
Устройство уровня соединения промежутка времени. Методика распознавания столкновений называется «множественный доступ с прослушиванием несущей и обнаружением столкновений» (CSMA/CD). Нам неважно, как конкретно она работает, однако если вам интересно, обратитесь к документу RFC 1180, «Учебное пособие по TCP/IP», где авторы, Т. Соколовски и К. Кейл, объясняют работу CSMA/CD, пользуясь замечательной аналогией. Представьте себе группу людей, разговаривающих в темной комнате. Каждый в комнате может услышать говорящего и каждый имеет равную возможность заговорить (множественный доступ, прослушивание несущей). Каждый следует правилу: никто не должен говорить одновременно с другим человеком. Если вдруг два человека заговорят одновременно, оба они тут же замечают нарушение — каждый слышит что-то, что сказал другой (обнаружение столкновений). Оба человека сразу прекращают говорить и ждут момента, когда будет можно продолжить. Уровень соединения и аппаратная независимость Перед тем как соединить компьютер с сетью Ethernet, в него необходимо вставить сетевую карту. Далее, сетевую карту необходимо подключить к кабелю. Кабель соединит остальные сетевые компьютеры с вашим. Кабель — это канал, непосредственно переносящий данные от одного компьютера к другому. Интерфейсная карта соединяет компьютер (сетевые программы) с кабелем (физическим уровнем), являясь, таким образом, уровнем соединения сетевой модели. Изучая сетевой уровень, вы узнаете, что протоколы TCP/IP работают одинаково, независимо от применяемой сетевой технологии. Компьютер можно подключить к сети Ethernet, Token Ring и любой другой сети, поддерживающей TCP/IP, и везде этот модуль будет работать одинаково. Это происходит потому, что уровень соединения (интерфейсная карта) скрывает от него все особенности и отличия более низких уровней, определяемых сетевой технологией. Рассмотрим, что это значит для вас, как разработчика программного обеспечения. Написав программу TCP/IP, вы можете быть уверены, что она без всяких изменений будет одинаково работать везде, в каких бы сетях ее ни применяли. Приложение клиент-сервер, управляющее медицинской базой данных и основанное на TCP/IP, можно продать врачам, чьи локальные сети построены как на основе Ethernet, так и на основе Token Ring безо всяких изменений. Если вдруг больница перейдет к использованию других сетевых карт, кабелей или операционной сйстемы, ни вам, ни программистам в больнице не понадобится вносить поправки в приложение. В каждом из приведенных примеров мы заменяли уровень соединения (сетевую карту), не оказывая воздействия ни на один из вышележащих уровней. В этом проявляется вся мощь многоуровневой сетевой модели и, в частности, протоколов TCP/IP. 85 
Глава 3. Введение в TCP/IP Словарь терминов расширяется Сейчас мы рассмотрим шесть дополнительных важных сетевых терминов в дополнение к уже знакомым. Они относятся к набору протоколов TCP/IP и объясняют различия между двумя протоколами транспортного уровня TCP/IP: протоколом пользовательских датаграмм (UDP) и протоколом управления транспортировкой (TCP). Термины характеризуют протоколы транспортного уровня с точки зрения сетевых соединений, надежности и службы обработки данных. Сетевые соединения Протоколы бывают двух различных видов: ориентированные на соединение (connection-oriented) и не ориентированные на соединение (connectionless). Ориентированный на соединение протокол обязан установить соединение с другим приложением перед тем, как передавать какие-либо данные ему. Например, вы не можете поговорить по телефону, пока кто-нибудь на другом конце провода не возьмет трубку. Точно так же ориентированный на соединение протокол не может передать данные до тех пор, пока не установит соединение с собеседником. Транспортный протокол (TCP) — один из ориентированных на соединение протоколов. Напротив, не ориентированные на соединение протоколы для передачи данных не нуждаются в предварительном установлении соединения. Как следствие, все пакеты данных такого протокола обязаны содержать информацию, достаточную для успешной передачи по сети. Например, отправляя письмо, вы должны точно и аккуратно заполнить графы адреса получателя, иначе почтовые работники могут ошибиться, и письмо уйдет не по тому адресу. Вы не доставляете письмо собственноручно, а просто опускаете в почтовый ящик. Точно так же не ориентированный на соединение протокол аккуратно заполняет адрес назначения пакета данных и передает его следующему сетевому уровню, всецело полагаясь на возможности этого уровня по доставке пакета. Протокол пользовательских датаграмм — пример не ориентированного на соединение протокола. Надежность протоколов Протоколы бывают надежными и ненадежными. Данные, обрабатываемые надежным протоколом, будут гарантированно доставлены по назначению. Ниже описаны некоторые свойства надежных протоколов. Прежде всего надежный протокол обменивается сообщениями-подтверждениями о доставке с приложениями клиента. То есть программа, посылая данные, ожидает услышать в ответ: «Эй, я только что успешно принял последний пакет переданных тобой данных!» Если вдруг программа не получила подтверждения, она посылает данные повторно снова и снова, пока не получит его. 86 
Словарь терминов расширяется Далее, чтобы убедиться в том, что данные не повреждены, надежный протокол подсчитывает одну или несколько контрольных сумм при каждой передаче. (Вы знаете из второй главы, что компьютер вычисляет контрольную сумму, складывая по очереди значения каждого байта в блоке данных.) Принимающий компьютер снова подсчитывает контрольную сумму блока, и если старая сумма не совпадает с новой, считается, что данные повреждены. Если вы не очень хорошо представляете, что же такое контрольная сумма, не беспокойтесь. Позже мы расскажем в подробностях о том, как она подсчитывается. На данный момент достаточно понимать, что наличие подсчитанной заранее контрольной суммы позволяет определить, что данные не были повреждены при передаче по сети. Транспортный протокол (TCP) — надежный протокол, использующий весь доступный технический арсенал: контрольные суммы, сообщения-подтверждения и другие методы, позволяющие гарантировать надежную доставку данных. Напротив, ненадежные протоколы не могут гарантировать надежную доставку данных. Ненадежный протокол просто пытается передать данные, но не гарантирует, что они попадут по назначению. Более того, если данные вдруг пропадут, такой протокол даже не уведомит приложение о случившемся. Ненадежный протокол похож на письмо без обратного адреса. Если вдруг оно не дойдет до получателя, вы никогда об этом не узнаете, так как работникам почты неизвестно, кто его отправил. Даже если адрес на конверте правильный, никто не застрахован от утери своего письма почтовыми работниками. Сообщение, передаваемое ненадежным протоколом, может случайно исчезнуть. Ненадежный протокол не только не гарантирует доставку, но также и не уведомляет о потере данных. Однако так же, как содержимое письма защищено конвертом, сетевое сообщение, если уж оно дошло, может защищаться ненадежным протоколом при помощи одной или нескольких контрольных сумм. Протокол Интернет (IP) всегда использует контрольные суммы, а протокол доставки пользовательских датаграмм (UDP) может применять, а может и не применять контрольные суммы. Оба этих протокола ненадежны. Вы можете спросить: «Почему же кто-то пользуется ненадежными протоколами?» Все дело в цене, которую мы платим за надежность. Ненадежный протокол гораздо проще в реализации и применении. Его стоимость в терминах сложности использования и пропускной способности сети оказывается значительно ниже стоимости надежных протоколов. Несмотря на название, ненадежный протокол все равно можно заставить работать надежно. Просто функции, гарантирующие доставку данных, нужно встроить в прикладную программу, а в остальном положиться на ненадежный протокол. Немного позже вы узнаете, что надежный сетевой протокол TCP пользуется услугами ненадежного протокола Интернет (IP) для передачи всех своих данных. Тем не менее этот альянс вполне надежен, так как функции обеспечения гарантированной доставки встроены в TCP. 87 
Глава 3. Введение в TCP/IP Как протоколы трактуют данные? В протоколах TCP/IP существуют два основных режима обработки данных: ориентированный на передачу потока байтов или потоковый (byte-stream) и ориентированный на передачу датаграмм или датаграммный (datagram). Потоковый протокол укладывает информацию в последовательность байтов. Другими словами, он рассматривает данные как единый поток байтов, независимо от их длины и количества передач, требуемых для посылки или приема всех данных. Предположим, вам требуется передать 5 сегментов данных (каждый длиной в 10 байтов) и один сегмент длиной в 50 байтов, всего 100 байтов. Приемник сообщения может прочесть ваши данные за пять раз блоками длиной по 20 байтов. Протокол управления транспортировкой (TCP) относится к потоковым протоколам. Разговаривая по телефону, вы не беспокоитесь о длине слов и предложений. Вы уверены, что собеседник услышит их в том порядке, в котором они произносились. Точно так же потоковый протокол не заботится о длине каждого передаваемого сегмента данных. Когда приложение выбирает услуги потокового протокола для передачи своих данных, оно знает, что программа на другом конце соединения примет данные точно в том порядке, в каком они были посланы. Датаграммный протокол не гарантирует такого же порядка в доставке. Он переносит данные отдельными независимыми блоками или датаграммами. Несколько датаграмм, посланных в одно и то же место, необязательно появятся там в исходном порядке. Если приложению необходим строгий порядок в появлении данных, оно должно самостоятельно провести соответствующие проверки принятых данных. К датаграммным протоколам относятся протокол передачи пользовательских датаграмм (UDP) и протокол интернет (IP). Это значит, что они доставляют данные, преобразовав их предварительно в датаграммы. Датаграмма похожа на письмо. Когда вы посылаете два письма одному и тому же адресату в тот же день, вы не в состоянии предсказать, которое из них он откроет первым. Также, отправив два письма двум корреспондентам за два следующих друг за другом дня, вы не можете быть уверены в том, кто из них получит письмо раньше. Вполне вероятно, что посланное позже письмо дойдет раньше. Что касается датаграмм, то они ведут себя точно таким же непредсказуемым образом. Если приложению не важен порядок появления данных, оно должно использовать датаграммный протокол. В этом случае конструкция приложения значительно упрощается. Виртуальные цепи Когда два сетевых устройства считают, что между ними создан выделенный канал связи, а на самом деле физически его не существует, считается, что создана виртуальная цепь. Когда вы звоните из Лос-Анджелеса в Нью-Йорк, создается виртуальная цепь. Кажется, что вы соединены прямым проводом с междугород- 88 
Подводя итоги ным абонентом, как если бы существовал физический кабель, связывающий два телефонных аппарата. На самом деле на пути сигнала телефонная компания установила множество коммутаторов или переключателей в нескольких городах, разделяющих Нью-Йорк и Лос-Анджелес. Другими словами, хотя вам и кажется, что есть прямой соединяющий провод, на самом деле он отсутствует. Во многих случаях вашей программе требуется соединение «точка-точка» или виртуальная цепь. Например, для передачи файла с одного компьютера на другой. В этом случае программа не должна дожидаться появления датаграмм, тем более, что их правильный порядок не гарантирован — следовательно, она хочет установить виртуальную цепь. В наборе протоколов TCP/IP функции установления виртуальной цепи берет на себя протокол управления транспортировкой (TCP). Ни протокол датаграмм пользователя (UDP), ни протокол интернет (IP) не рассчитаны на такое применение. Октет Термин «октет» не имеет никакого отношения к излагаемой теме. Тем не менее, читая сетевую литературу, вы непременно столкнетесь с ним, особенно при обсуждении организации и структуры данных в различных протоколах Интернет. Ниже приведен отрывок из замечательного определения термина «октет», данного Ричардом Стивенсом (W. Richard Stevens) в разделе 1.6 своей книги «TCP/IP в иллюстрациях» (TCP/IP Illustrated, Volume 1, Addison-Wesley, 1994): Немного странный, хотя и точный термин «октет» появился и широко используется в литературе, посвященной Интернету благодаря томуу что ранние разработки по Интернет производились на вычислительной технике, подобной DEC-10, где байт отнюдь не равнялся восьми битам. Далее, Стивенс настаивает на том, что в большинстве современных вычислительных систем байт все-таки равен восьми битам, и для их обозначения можно безболезненно перейти от термина «октет» к знакомому термину «байт». В данной книге мы поступим точно так же и откажемся от употребления слова «октет». Подводя итоги Прочитав первую и вторую главы, вы узнали, что сетевые протоколы передают данные и обмениваются сообщениями на сети. Из этой главы вы узнали, что набор протоколов TCP/IP представляет собой комплекс тесно связанных друг с другом протоколов, совместно координирующих усилия, прилагаемые для доставки сетевых данных по сетям TCP/IP, из которых и состоит глобальное сетевое объединение — Интернет. 89 
Глава 3. Введение в TCP/IP Чтобы успешно программировать приложения Интернет, необходимо хорошо представлять, каким образом протоколы TCP/IP выполняют свои задачи. Данная глава служит введением в семейство протоколов TCP/IP и дает представление о терминологии. В следующих двух главах мы исследуем протокол Интернет и транспортные протоколы TCP/IP. Перед тем как двигаться дальше, внимательно проверьте, хорошо ли вы усвоили следующие понятия: S Стек протоколов TCP/IP — это набор протоколов, вместе выполняющих работу по доставке данных между сетевыми компьютерами и устройствами. S Стек протоколов — это набор уровней сетевой модели, расположенных вертикально. S Когда данные передаются по сети, они последовательно проходят вниз сквозь стек протоколов и далее движутся по физическому носителю. S Достигнув места назначения, данные поднимаются вверх по стеку протоколов к программе-получателю удаленного компьютера. S В сети с технологией Ethernet сетевая интерфейсная карта выполняет функции уровня соединения. S Сетевой уровень соединения может быть заменен на новый безболезненно для приложений TCP/IP. S Компьютер с несколькими сетевыми адресами (multihomed) имеет несколько сетевых интерфейсных карт, позволяющих ему взаимодействовать с более чем одной сетью одновременно. S Маршрутизатором называется устройство, распределяющее пакеты данных между сетями различных технологий, например Ethernet и Token Ring. S Компьютер с несколькими адресами в различных сетях (и имеющий несколько сетевых карт) может служить маршрутизатором. S Протокол, ориентированный на соединение, устанавливает соединение между приложениями до того, как передать данные. S Не ориентированный на соединение протокол не устанавливает прямого сетевого соединения. S Ориентированный на соединение протокол работает подобно телефону. Напротив, не ориентированный на соединение протокол больше похож на службу доставки почты. S Надежный протокол гарантирует доставку данных; ненадежный — нет. S Потоковый протокол рассматривает данные в качестве непрерывного последовательного потока. S Датаграммный протокол рассматривает данные в качестве одиночных самостоятельных блоков. S Виртуальная цепь — соединение, выглядящее для двух сетевых устройств как выделенный канал «точка-точка», установленный между ними, хотя на самом деле таковым не является. 
Глав Сетевой протокол Интернет В каждом трамвайном парке есть ответственное лицо, например диспетчер, принимающий решения: сколько вагонов к какому трамваю прицепить, когда и на какой маршрут его отправить. Если трамвай грузовой и прицепляемые вагоны неодинаковы по размеру, необходимо решить, в какой последовательности расположить их. Опытный диспетчер не допустит, чтобы какой-нибудь из подопечных трамваев получил повреждения по дороге. Неудачное решение может привести к аварии. Трамвай либо вовсе не доедет, либо появится со значительным опозданием. Сетевой уровень протокола TCP/IP выполняет диспетчерские функции в парке пакетов данных Интернет. Сетевой уровень пользуется услугами протокола Интернет (Internet Protocol, IP). Сетевой уровень и программный IP-модуль, таким образом, служат одной и той же цели. В данной главе мы детально изучим структуру и функционирование протокола Интернет. Прочитав эту главу, вы овладеете следующими ключевыми понятиями: ♦ Назначение сетевого уровня TCP/IP. ♦ Как интерпретируются IP-адреса. ♦ Что такое классы адресов Интернет и как они соотносятся с количеством адресов подсетей. ♦ Назначение адресных протоколов Интернет. 91 
' 1 Глава 4. Сетевой протокол Интернет ***** • "'чй- w ♦ Какую информацию содержат 1Р-датаграммы. ♦ Назначение полей данных в заголовке 1Р-датаграммы. ♦ Каким образом происходит фрагментация данных в сети TCP/IP. ♦ Назначение таблиц маршрутизации. Что такое сетевой уровень? Сетевой уровень любой базирующейся на TCP/IP сети — это ее сердце. На рис. 4.1 показано, что сетевой уровень включает протокол Интернет (IP), протокол управляющих сообщений Интернет (ICMP) и протокол групповых сообщений Интернет (IGMP). Программа | I Программа | | Программа | Программа уровень Транспорт уровень IGMP Сетевой уровень Интерфейс [ < физического ШшМ RARP уровня Уровень Канал передачи данных Рис. 4.1. Сетевой уровень TCP/IP состоит из модулей IP, ICMP и IGMP Модуль IP выполняет львиную долю всей работы сетевого уровня. ICMP и IGMP лишь дополняют его, помогая обрабатывать некоторые сетевые ситуации, такие как возникновение ошибки или распространение групповых сообщений (сообщений, посланных двум и более системам одновременно). Примечание: Ввиду тесных взаимоотношений между протоколом Интернет и сетевым уровнем, в литературе часто объединяют оба понятия, называя сетевой уровень уровнем IP. Что внутри IP? Протокол Интернет — это служба доставки для всего семейства TCP/IP. Практически каждый протокол семейства пользуется услугами IP для передачи своих сообщений. К ним относятся TCP, UDP и ICMP. Работая в сети TCP/IP, 92 
Что такое сетевой уровень? протокол Интернет инкапсулирует (размещает в своих пакетах) данные практически всех остальных протоколов, за исключением протоколов распознавания адресов (ARP и RARP). Последующая врезка подробнее разъясняет понятие «инкапсуляция». Что такое инкапсуляция' Вы знаете, что для передачи данных по поделенной на уровни сети требуется пропустить их от приложения вниз сквозь стек протоколов. Протокол, получивший и обработавший данные, передает их дальше низлежащему протоколу и т. д. до физического носителя. В процессе обработки данных каждый протокол вкладывает или инкапсулирует данные в форму, пригодную для передачи соседнему по стеку протоколу. Таким образом, инкапсуляция — это процесс преобразования данных прикладного процесса в форму, требуемую соседним, более низким уровнем стека сетевых протоколов. По мере прохождение данных сквозь стек, каждый уровень инкапсулирует их по-своему, подготавливая к передаче дальше. Рис. 4.2 демонстрирует, как сетевое программное обеспечение инкапсулирует данные при работе протокола управления транспортировкой (TCP) на сети технологии Ethernet. Данные пользователя Приложение I Т TCP л?и^алных Данные ПРданныхЫХ пользователя I IP Заголовок TCP Прикладное сообщение Прикладные данные I Заголовок IP Заголовок TCP Сегмент TCP Прикладные данные Драйвер Ethernet 4- IP-датаграмма (пакет) ■4 Заголовок f Заголовок Заголовок _[ Ethernet > IP » TCP Прикладные данные кадра I I 1 Ethernet I Кабель ■ 14 байтов: 20 байтов 20 байтов Переменная длина : 4 байта передачи • л i г данных ;« ' Т Кадр Ethernet | г Ethernet : : < от 46 до 1500 байтов —— Рис. 4.2. Инкапсуляция данных TCP на сети технологии Ethernet Модуль приложения инкапсулирует данные пользователя в формат прикладного сообщения. Конструкция программы предусматривает использование того или иного метода инкапсуляции. Например, программа может инкапсулировать данные в формат сетевого протокола, скажем, TCP. Модуль TCP на рис. 4.2 преобразует прикладное сообщение в сегмент данных TCP. Сегмент данных TCP состоит из заголовка TCP, как того требует протокол, и прикладных данных. Прикладные данные, в свою очередь, могут иметь заголовок прикладного сообщения и поле прикладных данных. Данные, проходя сквозь модуль IP сетевого уровня, подвергаются дальнейшему преобразованию, и сегмент TCP превращается в IP-пакет (или IP-датаграмму). Драйвер сетевой карты Ethernet инкапсулирует IP-пакет в кадр протокола Ethernet. 93 
Глава 4. Сетевой протокол Интернет Рис. 4.2 — это простой пример процесса инкапсуляции данных. Некоторые программы могут не использовать форматы прикладных сообщений. Программа, работающая с протоколом передачи пользовательских датаграмм (UDP), может инкапсулировать данные в формат UDP вместо TCP. Важно помнить, что протоколы TCP/IP инкапсулируют данные, располагая их в требуемой протоколом последовательности и дополняя некоторой служебной информацией. Подробнее знакомясь с каждым сетевым уровнем, вы больше узнаете об инкапсуляции данных, происходящей в нем. Процесс перемещения данных по сети, как вы помните, состоит из трех основных стадий: прохождения данных сквозь стек протоколов, определения сетевого адреса назначения и собственно транспортировки или маршрутизации их до места назначения. Как вы уже, наверное, догадываетесь, протокол Интернет играет самую существенную роль в этих процессах. Необходимо понимать, как протокол Интернет соотносится с вышеописанным процессом перемещения сетевых данных. Эта глава разделена на три части, в каждой из которых протокол Интернет обсуждается с трех различных точек зрения: • Во-первых, вы узнаете, что представляет собой адрес в сети TCP/IP. • Во-вторых, вы узнаете, как образуется 1Р-датаграмма. • Наконец вы изучите, как IP-датаграммы маршрутизируются в Интернет. Адрес в сети Интернет Адрес сети Интернет называется IP-адресом. Большинство пользователей обычно считает, что IP-адрес соответствует адресу сетевого компьютера. Строго говоря, сетевой компьютер не имеет определенного IP-адреса. В третьей главе описывалась технология Ethernet. Оттуда мы узнали, что каждая сетевая карта Ethernet обязана иметь уникальный сетевой адрес. Так же, как и в случае с картами Ethernet, каждый интерфейс сети Интернет обязан иметь уникальный 1Р-адрес. Сетевой адрес Ethernet связывается с определенной картой Ethernet. Точно так же в сети TCP/IP каждому интерфейсу (а не компьютеру) соответствует собственный IP-адрес. Один компьютер может иметь несколько сетевых интерфейсов. Следовательно, каждый компьютер в сети Интернет может иметь несколько IP-адресов. Для упрощения IP-адрес может связываться с определенным сетевым компьютером. Это приводит к сложностям, только если компьютер имеет несколько сетевых интерфейсов (сетевых карт). В литературе об Интернет часто предполагается, что IP-адрес соответствует компьютеру, а не интерфейсу. Наша книга не является исключением. В ней мы тоже будем исходить из предположения, что IP-адрес соответствует компьютеру. Просто не забывайте как прикладной программист, что, строго говоря, это не так, и на самом деле IP-адрес соответствует сетевому интерфейсу. 94 
~v-‘ Ш,. -* Адрес в сети Интернет ТШ сё 1 Система записи IP-адресов Длина адреса Интернет — 32 бита или 4 байта. В языке С IP-адрес можно представить типом long integer. (Тип long integer как раз имеет длину 32 бита.) Однако удобнее записывать его в форме dotted-decimal (десятичное с точкой) или десятичными числами через точку. Система записи «десятичное с точкой» представляет IP-адрес как четыре разделенных точками десятичных цифры, следующих друг за другом. Следующие числа — это одно и то же число, но записано оно по-разному. IP-адрес в двоичном виде: 10000110000110000000100001000010 IP-адрес в десятичном виде: 2249721922 (или -2045245374) IP-адрес в шестнадцатиричном виде: 0x86180842 IP-адрес в записи десятичное с точкой: 134.24.8.66 Примечание: Если вы никогда не программировали на языке С или C++, все эти преобразования одного и того же числа могут показаться неочевидными. Вы должны знать, что один байт состоит из восьми битов, а четыре байта — это длина типа данных long integer. Число типа long integer может иметь знак, а может не иметь. Также одним байтом можно выразить любое число в диапазоне от 0 до 255. Табл. 4.1 поможет понять эти преобразования. В ней показаны отношения между каждым байтом в IP-адресе. Таблица 4.1. Как различные формы представления IP-adpeca соотносятся между собой Десятичное Шестнадцатиричное Двоичное 134 0x86 10000110 24 0x18 00011000 8 0x08 00001000 66 0x42 01000010 Запись dotted-decimal («десятичное с точкой») — просто соглашение или правило, как следует записывать IP-адреса. Это правило, облегчающее запись и чтение IP-адреса вами и другими людьми. Число в форме dotted-decimal проще воспринять, нежели его двоичный, шестнадцатиричный или десятичный эквивалент. Если у вас есть калькулятор, позволяющий преобразовывать числа из одного формата в другой, вы с легкостью проверите правильность приведенной выше таблицы. Попробуйте также преобразовать следующие числа, и вы увидите, что они тоже эквивалентны одному числу. IP-адрес в двоичном виде: IP-адрес в десятичном виде: IP-адрес в шестнадцатиричном виде: IP-адрес в записи dotted-decimal: 11000000 01100110 11111001 00000011 3227973891 (или -1066993405) 0xC066F903 192.102.249.3 95 
Глава 4. Сетевой протокол Интернет VTi Что скрывается за IP-адресом? 32-битный IP-адрес скрывает за собой номера сети и сетевого компьютера (на самом деле — интерфейса). Вы знаете, что Интернет состоит из тысяч взаимосвязанных сетей. Чтобы отличить одну сеть от другой, Информационный центр сети Интернет (Internet Network Information Center, InterNIC) следит за тем, чтобы номер каждой входящей в Интернет сети нигде больше не повторялся. Когда-то разработчики Интернет договорились, что старший байт IP-адреса будет идентифицировать сеть, а младшие три байта — номер компьютера (интерфейса), входящего в эту сеть. Например, IP-адрес 134.24.8.66 можно было рассматривать, как входящий в состав сети номер 134. В общем случае сетевое программное обеспечение Интернет рассматривает поле, заполненное двоичными единицами, как адрес «всех». Поле адреса, заполненное единицами, обозначает адрес «для всех», то есть широковещательный (broadcast) адрес, предназначенный для всех компьютеров в данной сети. Поле адреса, заполненное одними нулями, обычно рассматривается, как «этот». Адрес, состоящий из одних нулей, означает «этот компьютер», находящийся на этой же сети. Эти специальные форматы поля IP-адресов не могут присваиваться компьютерам Интернет и служат только для специального пользования. Примечание: В своей книге «Межсетевое взаимодействие сетей на базе TCP/IP» (Internetworking with TCP/IP, Volume 1, Prentice Hall, 1991) Дуглас Камер (Douglas E. Comer) отмечает, что ранние версии Berkeley UNIX использовали адрес типа «все нули» для передачи широковещательных сообщений. Некоторые коммерческие системы, созданные на базе Berkeley UNIX, унаследовали такой метод широковещания. Классы IP-адресов Старший байт IP-адреса применялся раньше для идентификации номера сети. Отсюда следовало, что максимальное количество IP-сетей равно 255 (с учетом того, что комбинацию «все единицы» для нумерации использовать нельзя). Для преодоления такого ограничения по адресному пространству был разработан простой, но эффективный метод деления на сети. Старший байт IP-адреса больше не нумерует сети; вместо этого старшие биты старшего байта IP-адреса определяют класс адреса в сети Интернет. Класс IP-адреса означает, сколько байтов в адресе служат для идентификации сети. Система классификации адресов внешне выглядит запутаннее, чем есть на самом деле. Читая следующие абзацы, не сочтите за труд вспоминать иногда табл. 4.2. Она поможет понять, как работают правила образования IP-адреса Интернет. Таблица 4.2. Классы IP-adpecoe Класс Старшие биты Свободные для нумерации сети байты А 0 1 В 10--- 2 С 110-- 3 D 1110 для широковещания Е 11110 зарезервировано на будущее 96 
Адрес в сети Интернет ‘ X В разделе «Адрес в сети Интернет» отмечалось, что для нормальной работы сети TCP/IP необходимо, чтобы каждый сетевой интерфейс, присоединенный к одной и той же физической сети, имел одинаковый адрес сети и уникальный собственный номер. В дальнейшем, изучив раздел «Что такое IP-маршрутизация?», вы поймете, откуда появилось это требование. Сейчас мы рассмотрим подробнее каждый класс сети Интернет и поймем, каким образом классы помогают чрезвычайно расширить адресное пространство — ведь на сегодняшний день Интернет насчитывает тысячи подключенных сетей. Сети класса А Как видно из табл. 4.2, биты класса и номера этой сети занимают максимум один байт, оставляя три для нумерации принадлежащих ей компьютеров: 1 бит 7 битов 24 бита ; 0 , Номер сети ! Номер компьютера | Сеть класса А Принадлежность классу А определена одним старшим битом, поэтому для нумерации сетей класса А остается семь бит. Это значит, что максимальное количество сетей класса А в Интернет составляет 127 (а не 128, так как значение «все нули» зарезервировано). Поскольку сети класса А содержат 24 бита для нумерации компьютеров, теоретически адресное пространство позволяет адресовать 16777216 из них. Реально, адреса класса А используются тем небольшим количеством сетей, в состав которых входит более 65536 компьютеров. Сети класса В Сети класса В, как показано в табл. 4.2, используют два байта для класса и номера сети, остальные 16 битов доступны для нумерации компьютеров. 2 бита 14 битов 16 битов ; 1 0 i Номер сети I Номер компьютера | Сеть класса В Два старших байта за вычетом двух битов, определяющих класс, то есть 14 битов, задают адресное пространство сети класса В. Таким образом, теоретически в Интернет могут входить 16384 сети класса В. Шестнадцать доступных для номера компьютера битов теоретически позволяют адресовать 65536 сетевых компьютеров. Для сетей с большим количеством компьютеров требуется выделять сеть класса А. Сеть класса В выделяется, если количество компьютеров на ней превышает 256 штук. Сети класса С У сети класса С поля класса и номера сети умещаются в три байта. Таким образом, для нумерации компьютеров остается только 8 битов. 4 Зак. № 1949 97 
VfA [ 3 бита 21 бит 8 битов [Номер компьютера! 1 1 о! Номер сети Сеть класса С После вычитания трех битов класса сети для их нумерации остается 21 бит. В результате в Интернет теоретически может входить до 2 097 152 сетей класса С. Поскольку максимальное количество компьютеров в сети класса С не может превышать 256, правила распределения адресов сетей Интернет отводят сети данного класса, если количество компьютеров в них не превышает 256. Другими словами, класс С предназначен для небольших сетей. Сети классов D и Е Класс D предназначается для групповой передачи. Адрес групповой передачи представляет группу компьютеров на Интернет и используется, чтобы передать сообщение более чем одному компьютеру. InterNIC зарезервировал адреса класса Е для использования в будущем. Еще не известно точно, но предполагается, что это будет широковещательная (broadcasting) или групповая передача (multicasting). Вряд ли адреса класса Е будут когда-либо присваиваться одиночным сетевым компьютерам. Скорее всего до этого момента InterNIC выработает новый алгоритм адресации вместе с новой схемой обработки адресов. Дальше в этой главе вы узнаете подробнее об особенностях широковещательной адресации и адресации для нескольких компьютеров. Если у вас поблизости есть калькулятор, можно ради интереса посчитать емкость адресного пространства Интернет, поделенного посредством классификации сетей. Если сложить вместе допустимые диапазоны адресов всех классов сетей, то получится, что Интернет способен вместить более двух миллионов сетей — не одиночных компьютеров, а настоящих компьютерных сетей. Допуская, что на один компьютер приходится один сетевой интерфейс и каждая сеть содержит максимально допустимое количество компьютеров, получается, что Интернет способен объединить более чем 3,7 миллиарда компьютеров. Если эта цифра кажется внушительной на первый взгляд, вам будет интересно узнать, что разработчики и сейчас предлагают способы расширить адресное пространство Интернет в еще большей степени. Давайте рассмотрим, каким бы было адресное пространство Интернет без классификации сетей. Если бы адресное пространство распределялось, как было предложено изначально (один байт на номер сети и три — на номера компьютеров), Интернет смог бы вместить более четырех миллиардов компьютеров, однако они принадлежали бы всего 256 сетям, и не более того. Рассмотренная выше схема классификации адресов уменьшает потенциальное количество компьютеров в Интернет приблизительно на десять процентов. Однако она Складываем числа 98 
всет и Интернет увеличивает при этом допустимое количество сетей от 255 до более чем двух миллионов. Другими словами, схема классификации сетей Интернет за счет некоторого уменьшения количества индивидуальных адресов значительно увеличивает допустимое количество сетей. Некоторое уменьшение количества адресов компьютеров одновременно с увеличением количества сетей благотворно сказывается на сетевом администрировании. Если бы в Интернет применялась простая 32-разрядная адресация, информационному центру InterNIC пришлось бы отслеживать более 4 миллиардов сетевых адресов. Эта задача была бы неразрешима. Применение простой адресной схемы оговаривает количество компьютеров на одной сети — 16 миллионов. В дополнение к прочим неприятностям, таким, как ограничение количества сетей в Интернет до 255, эта схема заставляла бы администраторов сетей отслеживать эти 16 миллионов адресов. Кошмар 16 миллионов компьютеров на одной сети на сегодня испытывают лишь 127 администраторов, ответственных за сети класса А. Схема классификации сетей также уменьшает количество компьютеров, с которыми приходится иметь дело миллиону с небольшим администраторов сетей классов В и С. Присвоение IP-адреса Количество возможных сетевых адресов ограничено, и вам наверное интересно знать, кто несет ответственность за правильность и уникальность каждого из 3,7 миллиарда IP-адресов. К счастью, такого ответственного лица или организации не существует. Распределением адресов занимается Информационный центр Интернет (InterNIC). Он же следит, чтобы адрес не выдавался повторно. Адреса сетевых интерфейсов присваиваются администраторами внутри самостоятельных сетей. Напомним еще раз, что класс A InterNIC присваивает большим сетям (количество компьютеров превышает 65536), сетям среднего размера (от 256 до 65536 компьютеров) присваивается класс В, а малым сетям, в которых меньше 256 компьютеров, присваивается класс С. Адрес подсети Номера сетей присваиваются администрацией InterNIC, а номера компьютеров в сетях — непосредственно сетевыми администраторами. Такая схема обладает значительной гибкостью. Адресное пространство, отведенное отдельной сети, используется наиболее удобным для администратора образом. Нужно только следить за тем, чтобы присвоенные сетевым интерфейсам адреса не повторялись. В целях увеличения эффективности одна сеть может разделяться на несколько подсетей путем деления адресного пространства. Рассмотрим сеть класса В. Администратору такой сети отведено 16 битов для нумерации компьютеров. 16 битов — это два байта, один из которых можно использовать для нумерации подсетей, а второй — для нумерации компьютеров. Таким образом, исходная 99 
Глава 4. Сетевой протокол Интернет сеть класса В разделяется сетевым администратором на несколько подсетей меньшего размера. Примечание: Разумеется, все эти адреса подсетей — производное введенной администратором схемы и не имеют отношения к рассмотренным выше классам сетей Интернет. Теоретически, наш сетевой администратор может разделить сеть на 256 подсетей, к каждой из которых могут подключиться 256 компьютеров. Такая схема позволит вместо одной большой физической сети иметь множество меньшего размера. На самом деле любой внешний по отношению к нашей сети компьютер ничего не знает о том, как у нас организовано деление, и все равно будет слать данные по определенному адресу формата Интернет. То есть концепция деления на подсети работает только внутри самой сети. Внешний мир ничего не знает об этом. Групповая передача Сетевые адреса делятся на три категории: обычные, групповые и широковещательные. Адреса классов А, В и С относятся к обычным, так как служат для обращения к одиночному сетевому компьютеру. Указывая широковещательный адрес, мы тем самым требуем, чтобы данные маршрутизировались сразу ко всем компьютерам в сети. Другими словами, указание широковещательного адреса направляет сообщение ко всем компьютерам определенной сети. Групповой адрес, как следует из названия, обозначает определенную группу сетевых компьютеров. Компьютеры такой группы могут принадлежать разным сетям. Группа может состоять из неограниченного количества компьютеров. Далее, принадлежность определенного компьютера к группе является динамической, то есть он может присоединяться или выходить из состава группы по своему усмотрению. Типичное приложение, которому нужна групповая адресация, — проведение интерактивных видео- или аудиоконференций. Информация должна попадать одновременно к группе компьютеров, но необязательно к каждому. Хосты и маршрутизаторы группового вещания используют «протокол групповых сообщений Интернет» (IGMP), проиллюстрированный на рис. 4.1. Он подробно описан в документе RFC 1112, «Оснащение сетевых компьютеров для передачи групповых сообщений» (Host extensions for IP multicasting, S. Deering, 1989). Образцы групповых адресов: 224.0.1.1 протокола сетевого времени (Network Time Protocol, NTP), 224.0.0.9 протокола RIP-2 (Routing Information Protocol version 2) и 224.0.1.2 протокола игры «dogfight» фирмы Silicon Graphics. Комиссия по присвоению номеров Интернет (The Internet Assigned Number Authority, I AN А) назначила набор официально известных групповых адресов. Официально известные групповые адреса представляют постоянно существующую группу сетевых компьютеров. Термин «постоянно» относится не к составу этой группы, а исключительно к присвоенным адресам. Действующий в насто- 100 
тЗиЛшШ Что такое протоколы адреса Интернет? ящий момент список адресов всегда можно найти в последней редакции документа RFC под названием «Присвоенные номера» (Assigned Numbers). Примечание: Официально известные адреса или постоянные группы компьютеров похожи на официальные номера портов TCP или UDP, о которых вы узнаете в главе 5. Что такое протоколы адреса Интернет? Как показано на рис. 4.1, уровень соединения содержит два адресных протокола: протокол преобразования адреса (ARP) и протокол обратного преобразования адреса (RARP). В предыдущем разделе мы изучали IP-адрес Интернет, но не рассматривали разницу между IP-адресом и адресом в представлении уровня соединения. Адрес компьютера в формате Ethernet (физического уровня) имеет длину в шесть байтов, в отличие от IP-адреса длиной в четыре байта. Все данные, проходящие в сети технологии Ethernet, заключаются в кадры формата Ethernet. Сетевые карты Ethernet наблюдают за проходящими мимо кадрами и за тем, не появится ли в кадре ее собственный адрес Ethernet. При этом сетевой карте нет никакого дела до IP-адреса пакета данных. Другими словами, протоколы TCP/IP работают с IP-адресами, а кадры сети Ethernet — с адресами формата Ethernet. Несоответствие форматов этих адресов между собой порождает коммуникационную проблему. Протоколы преобразования и обратного преобразования адресов призваны ее решить. То есть они трансформируют адрес из формата IP в формат уровня соединения и наоборот. На рис. 4.3 изображены основные функции каждого протокола. | 32-битный адрес Интернет Протокол преобразования адреса I Протокол обратного преобразования адреса 1 I 48-битный адрес Ethernet J Рис. 4.3. Протоколы преобразования адресов трансформируют IP-adpec в адрес уровня соединения и наоборот Протокол преобразования адресов Модуль протокола преобразования адресов (ARP) ставит в соответствие каждому IP-адресу нужный адрес уровня соединения. Формат адреса уровня соединения зависит от применяемой сетевой технологии. Например, адреса в сети Ethernet имеют длину в шесть байтов; сети Token Ring — два или шесть байтов, a ARCNET — всего один байт. По мере присоединения новых и выключения старых сетевых компьютеров конфигурация сети меняется. К счастью, протокол ARP работает динамически, 101 
Глава 4. Сетевой протокол Интернет *< Ш ill ШШЯШШт отслеживая изменения на ходу. Необязательно знать, как это происходит, в подробностях. В общих словах, ARP использует возможности уровня соединения по широковещательной передаче с целью передачи сетевого запроса, определяя таким образом выбывшие и вновь появившиеся компьютеры. Получив ответ, модуль ARP кэширует (сохраняет) результаты для последующего использования. Протокол обратного преобразования адресов Как следует из названия, протокол обратного преобразования адресов транс формирует адрес уровня соединения в IP-адрес. Как и прежде, процесс трансформации зависит от конкретной сетевой технологии (Ethernet, IBM Token Ring, ARCNET и т. д.). Сейчас мы немного отойдем от темы и расскажем, как протокол RARP используется в бездисковых компьютерах. Бездисковая рабочая станция считывает собственный адрес уровня соединения из сетевой интерфейсной карты. Дальше передается широковещательный запрос с просьбой к другому компьютеру определить и передать IP-адрес станции на основании ее адреса уровня соединения. Получив IP-адрес, рабочая станция передает широковещательный запрос к другому компьютеру с просьбой загрузить ее операционную систему. Таким образом, протокол RARP позволяет подсоединять бездисковые рабочие станции к Интернет и загружать их из удаленных сетевых компьютеров. Что такое 1Р-датаграмма? Мы знаем, что протокол Интернет (IP) является подсистемой доставки семейства протоколов TCP/IP и, следовательно, Интернет. Протокол Интернет — это ненадежный, не ориентированный на соединение протокол, пользующийся датаграммами для доставки информации в сети TCP/IP. Датаграмма протокола IP называется IP-датаграммой. Все приложения TCP/IP передают информацию, упаковав ее предварительно в 1Р-датаграмму. IP-датаграмма состоит из заголовка и собственно данных. IP-пакет Термины «IP-датаграмма» и «IP-пакета являются синонимами. В литературе, посвященной вопросам Интернет, употребляется как тот, так и другой. Заметим, что такое вольное обращение с терминологией может привести к некоторой путанице. Например, изучив точное определение датаграммы и то, что некоторые протоколы типа IP и UDP используют датаграммы, можно прийти к выводу, что датаграммы IP и датаграммы UDP суть одно и то же. Это представление неверно. 102 
Независимо от того, какие и сколько сообщений переносится, датаграмма представляет собой «самодостаточный» блок данных, в отличие от потока байтов. Термин «датаграмма» характеризует тип службы доставки. То есть любой протокол использует либо датаграммы, либо поток байтов. Тип датаграммы, например IP или UDP, определяет ее формат и содержимое. Более общий термин «пакет» обозначает просто блок данных. Термин IPпакет, например, обозначает блок данных протокола IP. Другими словами, термин «пакет» относится к данным, а термин «датаграмма» — к службе доставки. Отслеживая данные в Интернет Следуя сквозь сетевые уровни, блок данных изменяет свое название. Как правило, данные называются по названию сетевого модуля, через который они в данный момент проходят. Как только данные выходят из одного модуля и попадают в следующий, они меняют имя на новое. В следующем разделе показано, как данные изменяют название, следуя через стек протоколов Интернет. Где это и как оно называется? Данные, проходящие между прикладным и транспортным уровнями, называются прикладными сообщениями. Из главы 5 вы узнаете, что транспортный уровень инкапсулирует ваши данные при помощи либо протокола управления транспортировкой (TCP), либо протокола пользовательских датаграмм (UDP). TCP и UDP, в свою очередь, пользуются потоковой и датаграммной службами доставки соответственно. Блок данных протокола TCP называется сегментом, а протокола UDP — датаграммой. Название соответствует блоку данных на пути между транспортным и сетевым уровнями. На рис. 4.4 показано, как изменяется название блока данных на пути сквозь стек протоколов TCP/IP. Примечание: Сегмент TCP, как правило, является частью передачи потока байтов. Хоть это и не показано на рис. 4.4, сегмент TCP иногда называется транспортным сообщением. Имейте в виду, что этот термин относится только к сегменту TCP, несмотря на то, что UDP тоже является протоколом транспортного уровня. Датаграмма UDP — всегда только датаграмма, и не более того. В рамках Интернет термин «сообщение» всегда относится к данным определенного протокола или процесса виртуального соединения, или потока байтов. Датаграммы очень редко называются сообщениями. Если датаграмму нужно назвать по-другому, скорее всего употребляют термин «пакет». Как показано на рис. 4.4, сегменты TCP и датаграммы UDP превращаются в IP-пакеты на пути между модулем IP и уровнем соединения. Сеть TCP/IP инкапсулирует все сегменты TCP и датаграммы UDP в датаграммы IP, перемещая данные между сетевым уровнем и уровнем соединения. Таким образом, все 103 
V ■' -V— : "ШМЙ1 * ** Глава 4. Сетевой протокол Интернет «■■■■■■■■■■■■к...,».. ..лшвяяяшяшш уровень1НОЙ 1 ! Программа I [ Программа Транспортный уровень Сетевой уровень Sg; 1£ ЕТЗ X Физический уровень Рис. 4.4. Как изменяется название блока данных на пути сквозь стек протоколов TCP/IP поступающие на уровень соединения данные называются IP-датаграммами или IP-пакетами. Если применяется сеть технологии Ethernet, программное обеспечение инкапсулирует проходящие из уровня соединения на физический уровень данные в формат кадров Ethernet. В дальнейшем эти данные называются либо «кадр данных», либо «кадр Ethernet». Как только блок данных достиг физического уровня, терминология становится более запутанной и менее определенной. Например, в разделе 1.3.3 RFC 1122, «Требования к уровню соединения хостов Интернет» (Requirements for Internet Hosts' Communication Layers), на этот счет приведены некоторые определения. Однако в RFC 1122 для обозначения блока данных Интернет предпочитается термин «пакет», а значит, и для всех данных, проходящих между сетевым уровнем и уровнем соединения. 104 
F • -г Что такое IP-заголовок? ЯЯЯЯЯЯШШЩШЯШШЯЯЛ Не допускайте путаницы Разработка программ Интернет не сильно отличается от разработки любой другой программы. Однако вам придется не раз заглядывать в официальные документы и стандарты, например RFC. Раз так, то для того, чтобы не запутаться, необходимо хорошо понимать функционирование протоколов TCP/IP и иметь представление о том, как данные перемещаются по Интернет. Кроме того, необходимо свободно ориентироваться в терминологии TCP/IP и Интернет, иначе чтение официальной документации, несмотря на ее техническую корректность, приведет к неправильным представлениям. Множество недоразумений исчезнет, лишь только вы начнете хорошо представлять, как именно перемещаются данные. Если вы разберетесь в этом вопросе, вы сможете с легкостью воспринимать документацию, несмотря на порою невнятное и неточное использование терминологии. В следующих разделах мы рассмотрим большое количество официальной терминологии Интернет, касающейся полей данных в IP-заголовке. Читая, представляйте в уме картину следования данных в Интернет и соотносите функции и назначение полей заголовка с потоком данных. Другими словами, помните, что назначение протокола IP-уровня состоит в перемещении данных сетей на базе TCP/IP. Информация в IP-заголовках призвана решать эту задачу. Представьте общую картину перемещения данных в Интернет, и сущность и содержание информации в заголовках протокола IP станут более осмысленны. Вы знаете, что почти вся информация в сетях TCP/IP инкапсулируется в формат IP-датаграмм. Процесс инкапсуляции создает IP-датаграмму, состоящую из IP-заголовка и данных. Размер IP-заголовка всегда кратен 32-битному слову, даже если для этого он должен дополниться нулями до нужной величины. IP-заголовок содержит всю необходимую для доставки инкапсулированных данных по сети информацию. Информация в IP-заголовке Протокол Интернет (IP) — это подсистема доставки данных по всему Интернет, поэтому заголовок его пакета содержит достаточно много информации. Несмотря на важность и количество этой информации, размер IP-заголовка равен всего лишь 20 байтам. IP-заголовок всегда имеет такую длину, за исключением специальных случаев. Рис. 4.5 иллюстрирует IP-датаграмму и названия различных полей IP-заголовка в ней. На рис. 4.5 структура заголовка представлена в виде нескольких уровней, однако на самом деле заголовок — просто последовательный поток данных длиной как минимум 20 байтов. Что такое IP-заголовок? 105 
ЩРг Позиции битов 15 i 16 31 Версия (VERS) 4 бита 20 байтов Длина заголовка (HLEN) 4 бита Тип службы (TOS) 8 битов Идентификатор фрагмента 16 битов Время существования (TTL) 8 битов Протокол 8 битов Общая длина пакета в байтах 16 битов Флаги 3 бита Смещение фрагмента 13 битов Контрольная сумма заголовка 16 битов IP-адрес источника 32 бита IP-адрес получателя 32 бита Опции (если есть) : Заполнение (если нужно) ^ Данные Рис. 4.5. Формат IP-датаграммы и поля заголовка в ней Номер версии (VERS) Протокол Интернет все время развивается. То есть в одно и то же время в Интернет существуют как старые, так и более новые версии одного и того же протокола Интернет. Первые четыре байта в IP-заголовке (поле «номер версии») обозначают версию протокола Интернет, который создал данную датаграмму. Все программное обеспечение IP обязано как минимум считывать информацию из этого поля. Когда формат данных протокола Интернет меняется, номер его версии в датаграмме увеличивается, позволяя, таким образом, другим модулям IP откидывать датаграммы, обработать которые они не в состоянии из-за устаревшего формата данных. Модуль IP, принявший IP-датаграмму версии, которую он не может обработать, может доложить о возникновении проблемы. Таким образом, наличие поля версии IP помогает сетевому программному обеспечению отфильтровать датаграммы тех версий, которые заведомо невозможно обработать. Иначе данные датаграмм «не того» формата могли бы быть неправильно интерпретированы. На момент написания данной книги самая свежая версия протокола IP имела номер 4. Длина заголовка (HLEN) Следующие четыре бита IP-заголовка (поле «длина заголовка») задают длину заголовка в 32-битных словах. Мы уже указывали, что длина IP-заголовка всегда кратна 32 битам. Длина IP-заголовка всегда равна 20 байтам, за исключением некоторых случаев, которые мы рассмотрим позже. Таким образом, данное поле обычно содержит число 5 (пять 32-битных слов равны 20 байтам). В двоичном виде это поле равно 0101. 106 
Инкапсулированные данные начинаются сразу после IP-заголовка. Зная длину IP-заголовка, программное обеспечение всегда может определить, где искать начало инкапсулированных данных. Таким образом, формула для поиска начала данных выглядит так: Начало_инкапсулированных_данных = Первый_байт_1Р-датаграммы + (HLEN х 4) Примечание: Число 4 в формуле означает, что единица измерения поля HLEN равна четырем байтам или одному 32-битному слову. Тип службы (TOS) Перед любым разработчиком программ встает вопрос: что выгоднее — увеличение производительности или уменьшение используемой памяти? Или: что лучше — добавить новую функцию или сэкономить время на ее разработку? Все это вопросы приоритетов. Точно так же следующие восемь битов в IP-заголовке (поле «тип службы») определяют приоритет IP-пакета. Сетевой уровень TCP/IP управляет доставкой данных при помощи протокола Интернет. Биты поля «тип службы» в 1Р-заголовке позволяют сетевому уровню принять обоснованное решение по поводу передачи пакетов, основываясь на их, возможно различных, приоритетах. Поле «тип службы» разделено на пять подразделов, проиллюстрированных ниже. Биты 0-2 3 4 5 | 6 7 Приоритет Задержка Производительность Надежность Стоимость Не используется 1 4 Первые три бита поля «тип службы» образуют подраздел «приоритет» (precedence). Подраздел «приоритет» может принимать значения от нуля до семи (от 000 до 111) и обозначает уровень «важности» переносимых данных. Число пользователей Интернет постоянно возрастает, порождая проблему столкновения сетевых данных. Несмотря на то, что подраздел «приоритет» в наши дни используется редко, в будущем он вполне может решить или по крайней мере облегчить решение этой проблемы. Большинство сетевых компьютеров и маршрутизаторов попросту игнорируют этот подраздел, однако, как пишет Дуглас Камер (Douglas Е. Comer) в разделе 7.7.2. первого тома книги «Взаимодействие сетей TCP/IP»: «...эта концепция важна, поскольку обеспечивает механизм, дающий в конце концов возможность управляющей информации получить преимущество перед обычными данными.» Камер указывает, что контролирование ситуации столкновения данных — один из способов использования подраздела «приоритет». Обработка компьютерами 107 
и маршрутизаторами управляющей и обычной информации с различными приоритетами по-разному дает возможность сконструировать алгоритм контроля за столкновением, который будет работать независимо от самого столкновения. Одно не вызывает сомнений: по мере возрастания сетевого трафика Интернет, проблема столкновения данных будет становиться все важнее и важнее. На сегодняшний день поле приоритета в пакете данных — тот сетевой полицейский на информационной супермагистрали, указаний которого не сможет нарушить никто. Следующие четыре подраздела поля «тип службы» также определяют приоритеты, которые игнорируются большинством маршрутизаторов и сетевых компьютеров. Несмотря на это, они не лишены интереса. В то время как подраздел «приоритет» определяет общий приоритет пакета, эти четыре подраздела определяют приоритеты, зависящие от конкретного приложения. Например, подраздел «задержка» (delay) говорит о том, что задержку в распространении пакета желательно сделать минимальной. Подраздел «пропускная способность» (throughput) говорит о вашем желании увеличить полосу пропускания до максимально возможной величины. Установленный в единицу бит «надежность» (reliability) свидетельствует о желании сделать доставку пакета максимально надежной. Установка бита «стоимость» (cost) требует минимизировать затраты на доставку данного пакета. Разрабатывая программы, приходится часто учитывать те или иные параметры из вышеперечисленных. Четыре рассмотренных подраздела поля «тип службы» позволяют диктовать сети ваши программистские решения. К сожалению, большинство маршрутизаторов и компьютеров так и не научились до сих пор пользоваться этими данными. Как пользоваться подразделами поля «тип службы»? В настоящее время нет широко известных приложений Интернет, которые бы обрабатывали значения этих подразделов. Однако существует описание метода, как это следовало бы делать. Оно приведено в RFC 1340 (Assigned Numbers, Reynolds and Postel, 1992), в котором описано, как приложение Интернет должно устанавливать различные биты подразделов поля «тип службы». В RFC 1349, «Тип службы в семействе протоколов Интернет» (Type of Service in the Internet Protocol Suite, Almquist, 1992), обсуждается этот же вопрос. Чтобы понять, как можно было бы использовать свойства поля «тип службы», рассмотрим несколько примеров: • Telnet — это интерактивная программа, не передающая больших массивов данных. Для работы Telnet желательно минимизировать задержку при распространении данных. Абсолютное большинство пользователей Telnet согласится, что длительное ожидание ответа от удаленного компьютера — не самое приятное занятие. • В отличие от Telnet, программы FTP, как правило, перемещают значительные массивы данных, и поэтому им требуется высокая пропускная способность. 108 
Что такое IP-заголовок? • Система телеконференций Usenet также оперирует большим количеством данных. Однако в отличие от FTP временные параметры передачи данных не имеют большого значения. В этом случае требуется минимизировать стоимость. • Протокол управления сетью (Simple Network Management Protocol, SNMP) критичен к ошибкам в передаче его данных. Для управления сетью требуется обеспечить высокую надежность. Разрабатывая сетевое приложение, необходимо иметь в виду, какой из вышеперечисленных показателей наиболее важен для него. Это может быть пропускная способность, скорость, стоимость и/или надежность доставки. Минимизируя затраты, будьте готовы пожертвовать производительностью, скоростью или надежностью, не доводя, конечно, процесс до абсурда. Расставив приоритеты приложения, известите о ваших намерениях сеть, установив соответствующие подразделы в поле «тип службы». Длина пакета Следующее поле IP-заголовка — поле «длина пакета». Оно имеет длину в шестнадцать битов и задает длину IP-пакета, включая сам заголовок. Стандарт предписывает считать длину пакета в байтах, а не в 32-разрядных словах, как это было в поле «длина заголовка». Данные из этих двух полей позволяют легко найти начало и конец инкапсулированных данных, а также подсчитать их длину: Начало__данных = Первый_байт_1Р-датаграммы + (HLEN х 4) Конец_данных = Первый_байт_1Р-датаграммы + Длина_пакета Длина_данных = Длина_пакета - Начало_данных Для поля «длина пакета» отведено 16 битов, а это значит, что максимальная длина пакета равна 65535 байтов. Однако необходимо учесть, что TCP/IP инкапсулирует IP-датаграммы при передаче через уровень соединения. Если локальная сеть построена по технологии Ethernet, уровень соединения инкапсулирует IP-датаграммы в кадры Ethernet перед передачей в Интернет. Каждая сетевая технология оговаривает максимальную длину пакета данных. Такая длина называется «максимальный размер пакета» (maximum transfer unit, MTU). Спецификация Ethernet ограничивает длину пакета величиной 1500 байтов. Максимальная длина пакета технологии Token Ring равна 4464 байта. В других сетях MTU может быть всего 128 байтов или даже меньше. Если приложение пытается передать IP-пакет с длиной, превышающей сетевое MTU, происходит фрагментация. Фрагментация заключается в делении блоков данных на последовательности меньшей длины и поочередной их передаче. Данные могут фрагментироваться также при прохождении через маршрутизатор с MTU меньшим, чем в локальной сети. Как это ни печально, в Интернет невозможно избежать фрагментации. К счастью, кроме увеличения задержки распространения данных, последствия фрагментации совершенно незаметны для сетевых приложений, поскольку задача разбиения и последующей сборки блоков данных из фрагментов целиком возложена на уровень соединения. Мы рассмотрим фрагментацию подробнее немного позже в этой же главе. 109 
Идентификация Наличие поля идентификации в IP-заголовке обусловлено часто случающейся фрагментацией пакетов в Интернет. Сетевые компьютеры используют поле идентификации с целью однозначно идентифицировать каждый посланный ими пакет данных. Когда сетевой компьютер получает пакет, он определяет, к какой датаграмме относится определенный фрагмент при помощи поля идентификации. Флаги и смещение фрагмента Как правило, информация, содержащаяся в полях идентификации, флагов и смещения фрагмента, позволяет правильно собрать фрагментированный IPпакет. Позже в этой главе вы узнаете больше о полях флагов и смещения фрагмента. Сейчас для нас важно, что поле флагов состоит из трех битов, а поле смещения фрагмента — из тринадцати. Время существования (TTL) Восьмибитовое поле «время существования» (Time-to-Live, TTL) задает срок существования пакета в сети. Назначение этого поля в том, чтобы не дать пакету безвозвратно затеряться в киберпространстве. В следующем абзаце описан процесс, предотвращающий бесконечное путешествие пакета, к которому могла бы привести случайная ошибка в сети. Первым делом протокол TCP/IP предписывает каждому маршрутизатору на пути между отправителем и получателем уменьшать значение поля «время существования» пакета. Примечание: В соответствии с книгой Смута Карл-Митчела и Джона Кватермана «Практика межсетевого взаимодействия сетей на базе TCP/IP и UNIX» большинство систем устанавливают начальное значение поля «время существования» равным 30. Далее, каждый маршрутизатор должен контролировать время появления IP-пакета. Отправляя пакет дальше, маршрутизатор уменьшает поле «время существования» на количество секунд, которое пакет ждал внутри буфера (если, разумеется, пакет был задержан). Если поле «время существования» становится равным нулю до того, как пакет достигнет места назначения, протокол TCP/IP уничтожит его и предупредит компьютер-источник пакета об этом печальном событии, пользуясь услугами протокола управляющих сообщений Интернет ICMP. Позже мы еще встретимся с ICMP и изучим его более обстоятельно, разрабатывая приложения, использующие простые сокеты. Примечание: Довольно интересно, что правильная работа концепции «время существования» совершенно не зависит от синхронности таймеров или часов в компьютерах сетевого сообщества. 110 
Протокол Транспортный уровень TCP/IP состоит из двух протоколов: протокола управления транспортировкой и протокола пользовательских датаграмм. Они оба используют IP для доставки данных. Восьмибитовое поле «протокол» в IP-заголовке указывает на протокол-источник данных, инкапсулированных в IP-датаграмму. Например, если значением поля «протокол» является шестерка (двоичное 00000110), это значит, что данные сформированы из сегмента TCP. Значение 17 (двоичное 00010001) означает, что данные принадлежат UDP-датаграмме. Сетевой уровень пользуется значением в поле «протокол» при передаче данных на транспортный уровень. Значение поля указывает на определенный модуль транспортного уровня, которому принадлежат данные IP-пакета. В табл. 4.3 приведены возможные значения поля «протокол» и соответствующие им протоколы. Таблица 4.3. Возможные значения поля протокол в IP-заголовке Протокол Десятичное Двоичное ICMP i 00000001 IGMP 2 00000010 TCP 6 00000110 UDP 17 00010001 Контрольная сумма заголовка И надежные и ненадежные протоколы используют контрольные суммы, чтобы убедиться в правильности и целостности передаваемых данных. Им необходимо убедиться, что данные дошли до места назначения в том же виде, в каком отправились. Как TCP, так и IP, несмотря на то, что он ненадежный, используют контрольную сумму. Поле контрольной суммы в IP-заголовке содержит шестнадцатибитное число, являющееся контрольной суммой только для IP-заголовка. Данные пакета в формировании контрольной суммы участия не принимают. Проверка целостности данных IP-датаграммы возлагается на протоколы более высоких уровней, тех что создали данные для 1Р-датаграммы. Для подсчета контрольной суммы IP-заголовок трактуется, как последовательность шестнадцатибитных чисел. В поле контрольной суммы помещается результат дополнения до единицы суммы всех чисел, составляющих заголовок. Поле контрольной суммы при подсчете принимается равным нулю, то есть компьютер игнорирует значение поля при всех подсчетах контрольной суммы заголовка. Компьютер, принявший датаграмму, подсчитывает новую контрольную сумму, в том числе и с полем старой контрольной суммы, подсчитанной ранее при передаче. Если заголовок не изменился, то есть повреждения не произошло, 111 
: Г Глава 4. Сетевой протокол Интернет •: V Г:.-. " • ". новая контрольная сумма должна быть «все единицы». Если это не так, то есть произошла ошибка при передаче, TCP/IP просто отбрасывает датаграмму. При этом никакого сообщения компьютеру-источнику датаграммы не передается. IP — ненадежный протокол. Он не гарантирует доставки данных. Однако проверка контрольной суммы заголовка пакета гарантирует, что, если данные дойдут до пункта назначения, они дойдут в неизменном виде, иначе модуль IP распознает и отбросит ошибочный пакет. TCP/IP не требует от модуля IP выдавать какое-либо сообщение о появлении поврежденного пакета. Надежные протоколы, к каковым относится TCP, никогда не полагаются на способность IP выдавать такие сообщения. Вместо этого они используют собственные механизмы. IP-адрес источника и получателя 32-битное поле «адрес источника» содержит IP-адрес компьютера-отправителя данных (вернее, адрес его сетевого интерфейса). Это поле никогда не изменяется, сколько бы маршрутизаторов ни находилось на пути следования пакета. Поле «адрес источника» всегда содержит адрес первоначального отправителя пакета. Так же как и адрес источника, адрес получателя является стандартным 32-битным IP-адресом пункта назначения пакета. Он может быть либо индивидуальным, либо состоять из единиц в случае широковещательной передачи. Опции IP Восьмибитное поле опций IP позволяет тестировать и отлаживать разнообразные сетевые приложения. Опции IP управляют фрагментацией и маршрутизацией сетевых пакетов. В силу своего назначения, опции IP редко используются обыкновенным программным обеспечением. Как правило, отладка и тестирование сетевых приложений происходит на специально подготовленных компьютерах, снабженных отладочными программами. По той же причине протоколы TCP/IP не требуют от маршрутизаторов и компьютеров обязательно устанавливать или считывать поле опций — в обычной ситуации это происходит чрезвычайно редко. В следующих абзацах кратко описывается назначение поля опций IP. Если в ваши планы входит написание приложений, работающих в Интернет, опции IP позволят отладить и протестировать их работу. Поле опций IP состоит из трех подразделов, «копирование» (Сору), «класс опции» (Option Class) и «номер опции» (Option Number), проиллюстрированных ниже: 0 1-2 3-7 Копирование Класс опции Номер опции \ < 8 битов — и Подраздел «копирование» определяет порядок обработки опций в случае, если данные фрагментируются маршрутизатором. Установленный в единицу бит «копирование» требует, чтобы маршрутизатор копировал опции в каждый 112 
ш P-заголовок? переданный фрагмент пакета данных. Установленный в ноль, он требует, чтобы маршрутизатор скопировал опции только в первый фрагмент. В табл. 4.4 приведены возможные значения битов подраздела «класс опции». Таблица 4.4. Подраздел класс опции поля опций в заголовке IP-пакета Класс опции Биты Назначение опции 0 00 Управление датаграммами или сетью 1 01 Зарезервировано (не используется) 2 10 Отладка и измерения 3 11 Зарезервировано (не используется) Значение подраздела «номер опции» определяет назначение опции в рамках конкретного класса. В табл. 4.5 приведены возможные значения подразделов «класс опции», «номер опции», а также длина и краткое описание опций. Таблица 4.5. Возможные значения опций IP и их краткое описание Класс Номер Длина Описание 0 2 И Безопасность (Security) 0 7 переменная Запись маршрута (Record route) 0 3 переменная Управление маршрутизацией, приблизительное (Source Routing, Loose) 0 9 переменная Управление маршрутизацией, строгое (Source Routing, Strict) 2 4 переменная Временная метка Интернет (Internet Timestamp) Опции безопасности применяются в приложениях, критичных к конфиденциальности передаваемых по сети данных, например военных. Опция регистрации маршрута пакета требует, чтобы маршрутизаторы записывали в поле опций, каким путем пакет перемещается по сети. Опция управления маршрутизацией позволяет источнику пакета данных самостоятельно выбирать его маршрут и бывает двух видов: строгая (strict) и приблизительная (loose). Строгое управление маршрутизацией подразумевает, что пакет может перемещаться только через заданные в списке маршрутизаторы. Опция приблизительного управления сопровождается списком маршрутизаторов, через которые пакет должен обязательно пройти. 113 
у Н ж чт ш Глава 4. Сетевой протокол Интернет Что такое фрагментация? Вы уже знаете, что некоторые сетевые технологии, например Ethernet, имеют ограничение на максимальную длину передаваемого блока данных, называемую «блок данных максимальной длины» или MTU. MTU определяет максимальную длину блока данных, которую данная сетевая среда в состоянии перенести. Если блок данных имеет большую длину, он автоматически разбивается на кусочки меньшей длины, каждый из которых передается затем по отдельности. Фрагментация — это процесс разбиения отдельного пакета данных на некоторое количество пакетов помельче. Фрагментация происходит в случае, если длина пакета превосходит MTU физического сетевого уровня. Однако она также происходит, когда пакет попадает в маршрутизатор с MTU меньшим, нежели MTU локальной сети источника. В предыдущем разделе вы познакомились с полями идентификатора, флагов и смещения фрагмента IP-заголовка. Число в поле идентификатора нумерует каждый пакет, переданный определенным сетевым компьютером. Для управления фрагментацией IP использует первый и последний биты в трехбитовом поле флагов. Первый бит называется «фрагментация запрещена» и устанавливается сетевым программным обеспечением для тестирования и отладки. Кроме того, существуют некоторые приложения, данные которых действительно нельзя фрагментировать. Включая режим «фрагментация запрещена», то есть устанавливая соответствующий бит, имейте в виду, что если модуль IP обнаружит, что фрагментация все-таки должна произойти, протокол TCP/IP отбросит IP-пакет и вернет сообщение об ошибке источнику пакета. Сообщения об ошибках протокола IP Протокол Интернет (IP) ненадежен, не ориентирован на соединение и для доставки данных использует датаграммы. IP не гарантирует доставки данных и не уведомляет источник сообщения о неудачной попытке доставить их. Похоже, что IP держит данные в ежовых рукавицах. Несмотря на это, существуют обстоятельства, при которых IP все-таки возвращает сообщение об ошибках. Мы знаем, что TCP/IP возвращает сообщение об ошибке (по протоколу ICMP), когда истекает время существования, TTL, указанное в заголовке IP-пакета. Ошибка контрольной суммы заставляет TCP/IP молча отбросить IP-пакет, то есть без какого-либо диагностического сообщения. Только что вы узнали, что установка бита «фрагментация запрещена» приводит к генерации сообщения об ошибке, если пакет необходимо фрагментировать. Итак, мы должны выяснить, в каком случае сообщение об ошибке генерируется, а в каком — нет. Для этого сфокусируем внимание на слове «гарантирует». Протокол Интернет не гарантирует надежной доставки данных. Для уведомления об ошибках и просто ситуациях, требующих внимания, TCP/IP использует протокол управляющих сообщений Интернет (Internet Control Message Protocol, ICMP). ICMP является частью сетевого уровня, на котором находится и IP. Несмотря на это, ICMP действует как протокол более высокого уровня, поскольку свои данные он, как и большинство остальных протоколов, передает через IP. Таким образом, ICMP ненадежен и не ориентирован на соединение. 114 
Что такое фрагментация? ........... Ненадежность определенного протокола вовсе не означает, что данные скорее всего не будут доставлены или что TCP/IP никогда не пришлет сообщения об ошибке. Ненадежный протокол просто не гарантирует доставку и уведомление об ошибке. Разработчики ненадежного протокола хорошо понимают, для каких целей он будет использоваться. Свойство ненадежности сильно упрощает разработку и реализацию протокола. Ответственность за надежную доставку и соответственно сложность в реализации перекладывается на плечи протоколов более высокого уровня. Протокол IP возвращает сообщения об ошибках. Для этого используется ICMP. Он пользуется услугами IP для доставки сообщений. Таким образом, гарантии доставки сообщения об ошибках отсутствуют, даже если само сообщение и генерируется сетью. Это значит, что ваше приложение может и должно проверять наличие ошибок при работе протокола Интернет и любого другого ненадежного протокола, но ни в коем случае не должно полностью полагаться на эти сообщения. IP не гарантирует, что сообщение об ошибке дойдет до приложения. Последний бит поля флагов называется «фрагмент-продолжение». В процессе фрагментации пакета IP устанавливает этот бит в единицу во всех фрагментах, кроме последнего. В последнем фрагменте данных бит равен нулю. Для повышения эффективности и производительности IP всегда пытается послать пакет наибольшего допустимого размера. Однако иногда фрагментации не избежать. До начала фрагментации IP рассчитывает точку деления (breaking point), равную значению MTU низлежащего уровня сети. Точка деления — это расположение байта в пакете, на котором произойдет разделение. Точка деления представляет собой смещение или расстояние от начала датаграммы. Каждая точка деления записывается IP в поле «смещение фрагмента» заголовка только что собранной IP-датаграммы. Другими словами, IP-заголовок каждой датаграммы содержит смещение данного фрагмента относительно начала данных. Значение точки деления используется IP на другом конце соединения, чтобы правильно собрать фрагментированный пакет. В действительности размер каждого фрагмента слегка отличается от значения MTU, поскольку всегда кратен восьми байтам. На рис. 4.6 приведена диаграмма IP-заголовка. Вы уже встречались с ней в предыдущем разделе. Обратите внимание на то, что длина поля длины пакета равна 16 битам, а поля смещения фрагмента — тринадцати. Длина поля длины пакета в шестнадцать битов означает, что максимальная длина пакета может равняться 65535 битам. Поле смещения фрагмента должно уметь обозначить точку смещения на всей длине IP-пакета, следовательно, должно адресовать от 1 до 65535 байтов. Однако длина поля смещения фрагмента равна всего тринадцати битам и, следовательно, может принимать значения в интервале от 0 до 8191, позволяя адресовать максимум 8191 байт. Если значение поля смещения фрагмента кратно 8 байтам, проблема решается. Например, значение поля смещения 1 означает смещение в восемь байтов, 2 — в шестнадцать и т. д. Максимальное смещение при этом равно 65528 (8191x8). 115 
Глава 4. Сетевой протокол Интернет 'Ik. |-- Позиции битов о 15 16 Версия 1 Длина заголовка (VERS)4 бита! (HLEN)4 бита 31 Тип службы (TOS) 8 битов 20 байтов Идентификатор фрагмента 16 битов Время существования (TTL) 8 битов Протокол 8 битов Флаги \ Смещение фрагмента : 3 бита : 13 битов Контрольная сумма заголовка 16 битов IP-адрес источника 32 бита IP-адрес получателя 32 бита Опции (если есть) : Заполнение (если нужно) Данные Рис. 4.6. Поля длина пакета и смещение фрагмента в IP-заголовке Число 65528 указывает на последние восемь байтов пакета максимально возможной длины: 65528 плюс 7 равно 65535 (максимальная длина пакета). Процесс фрагментации: подводим итоги Максимальная длина передаваемого блока данных (MTU) обозначает наибольший размер пакета, который данная сеть в состоянии передать за один раз. Если длина пакета превосходит MTU, модуль IP делит его на блоки меньшего размера, называемые «фрагментами». Длина каждого фрагмента определяется значением MTU. Таким образом, IP образует фрагменты максимально допустимой для данного MTU длины. Модуль IP вычисляет начало (смещение) каждого фрагмента относительно начала датаграммы. Длина каждого фрагмента, созданного IP, кратна восьми байтам. Смещение фрагмента относительно начала датаграммы записывается в поле «смещение фрагмента» IP-заголовка датаграммы, содержащей данный фрагмент. Поле «фрагмент-продолжение» устанавливается в единицу для каждой вновь образованной датаграммы, кроме последней. Поле «фрагмент-продолжение» датаграммы, переносящей последний фрагмент, устанавливается модулем IP в ноль, поскольку фрагментов больше не будет. Сборка фрагментов Фрагментация необходима сети. Мы изучили причины, ее вызывающие, и сам процесс фрагментации не является более тайной. Теперь разберем процесс сборки фрагментированных данных, которым занимается принимающая сторона сетевого соединения, вернее, ее модуль IP. Как можно предположить, модуль 116 
IP исследует содержимое полей управления фрагментацией IP-заголовка пакета, то есть поля идентификации, флагов и смещения фрагмента. Как только принят пакет с установленным в единицу флагом «фрагмент-продолжение», запускается специальный таймер сборки фрагментов. Все фрагменты обязаны появиться у принимающей стороны до того, как истечет интервал времени, отпущенный на это таймером. Если время истекло, а все фрагменты так и не появились, модуль IP отбрасывает принятые фрагменты и не обрабатывает частично принятую датаграмму. Компьютер-приемник датаграммы располагает ее в буфере для сборки датаграмм. Фрагменты, принадлежащие одному пакету, определяются по адресу источника и полю идентификатора в IP-заголовке. Как только приходит датаграмма с битом «фрагмент-продолжение», равным нулю, модуль IP может подсчитать общую длину пакета. Поле «смещение фрагмента» задает смещение начала данного фрагмента от начала исходного пакета данных. Последний фрагмент в группе (с установленным в ноль битом «фрагмент-продолжение») позволяет модулю IP подсчитать длину исходного пакета путем сложения значений полей смещения фрагмента и длины пакета. Выяснив первоначальную длину датаграммы, модуль IP приступает к изучению полей смещения фрагмента и длины пакета в остальных фрагментах датаграммы. Теперь IP знает, что в его буфере находится полностью принятая датаграмма и теперь ее можно собрать целиком. Модуль IP собирает датаграмму, основываясь на известных значениях полей смещения каждого фрагмента. Получив в результате датаграмму в первозданном виде, сетевой уровень считает, что фрагментации как будто бы никогда и не было. Некоторые проблемы Как фрагментация, так и последующая сборка пакета происходит между сетевым уровнем и уровнем соединения. С точки зрения прикладного программиста оба процесса абсолютно прозрачны. То есть разрабатывая приложение Интернет, нет никакой необходимости учитывать эти проблемы в самом приложении. Всю работу за него сделают соответствующие уровни — сетевой и соединения. Они правильно фрагментируют и соберут данные, а приложение даже не заметит этого. Несмотря на то, что приложение может не учитывать фрагментацию, вы должны понимать некоторые проблемы, с которыми можно столкнуться. Например, вы знаете, что потеря хотя бы одного маленького фрагмента датаграммы приведет к отбрасыванию датаграммы целиком. Отсюда можно сделать следующий вывод: фрагментация данных увеличивает вероятность того, что датаграмма не дойдет до получателя. Если вышесказанное не слишком очевидно для вас, давайте рассмотрим, что мы знаем о датаграммах. Во-первых, датаграмма ненадежна и сеть не гарантирует 117 
Глава 4. Сетевой протокол Интернет ее доставку. Во-вторых, поскольку доставка не гарантируется, все протоколы и приложения, пользующиеся датаграммами, не должны рассчитывать и зависеть от безусловной доставки каждой датаграммы. То есть каждая датаграмма должна быть функциональна сама по себе и не зависеть от другой датаграммы. Теперь рассмотрим эффект фрагментации. Во-первых, ваша отдельная независимая датаграмма становится набором из нескольких датаграмм. Каждая из составляющих датаграмм оказывается зависимой от других в передаваемой последовательности фрагментов. Если сеть теряет один фрагмент, сетевой уровень отбрасывает все остальные фрагменты. Во-вторых, простая статистика показывает, что чем больше фрагментов, тем выше вероятность потери одного из них и, следовательно, исходной датаграммы целиком. Вывод из всего сказанного таков: если в сети происходит фрагментация, вероятность потери датаграммы увеличивается. Как видим, кажущиеся в начале несущественными детали могут сильно повлиять на разработку, казалось бы, независящего от них сетевого приложения. Этот пример иллюстрирует факт, что для успешного написания устойчиво и надежно работающего приложения Интернет необходимо хорошо знать все подробности функционирования набора протоколов TCP/IP. Узнавая все новые подробности о TCP/IP, иногда задавайте себе следующие вопросы: • Как эта информация относится ко мне и моим сетевым приложениям? * Каким образом эта порция новой информации встраивается в общую картину происходящего? • Каким образом новая информация может быть использована в моих целях при разработке приложений Интернет? Проблема не так страшна Вам наверное уже кажется, что проблема фрагментации данных — страшный враг, которого нужно избегать всеми доступными средствами. На самом деле, на вопрос, является ли избежание «всеми доступными средствами» действительно эффективной экономической политикой, отвечать придется только вам. Здесь мы заметим, что протокол управления транспортировкой (TCP) пользуется MTU, равным 576 байтам, для передачи данных компьютерам через несколько маршрутизаторов. Эта длина выбрана не случайно. Она позволяет разместить в пакете 512 байтов данных и оставить место для TCP- и IP-заголовков и опций. Большинство протоколов уровня соединения могут работать с таким MTU, следовательно, не фрагментируя данные до передачи по каналу связи. В зависимости от назначения и функций вашего приложения TCP/IP, может оказаться возможным, заранее проанализировав пути, по которым пойдут данные, выбрать MTU большего размера, не рискуя подвергнуть фрагментации драгоценные данные. 118 
Что такое IP-маршрутизация? . Что такое 1Р-маршрутизация? Читая данную главу до настоящего момента, вы все дальше и дальше углублялись в исследование набора протоколов TCP/IP. Изучив работу уровня соединения, сетевого уровня, формат адресов в Интернет, связанные с ними протоколы, датаграммы, вы добрались до самого сердца TCP/IP. Сейчас вы обладаете достаточным знанием для того, чтобы вернуться немного назад и созерцать великую картину, какой нам представляется стек протоколов TCP/IP. Вы знаете, что IP — это подсистема доставки данных в Интернет. Вы изучили массу информации, относящейся к IP-датаграммам. Вы знаете, что посредством доставки датаграмм данные приложений и информация различных протоколов перемещается по сети Интернет. Ключевым моментом в доставке IP-датаграмм являются таблицы 1Р-маршрутизации. Таблица IP-маршрутизации, или просто таблица маршрутизации, это то место, где хранятся адреса некоторых получателей в Интернет. Другими словами, программное обеспечение заглядывает в таблицы маршрутизации, чтобы найти наилучший маршрут для получателя сетевых данных. Содержимым и самими таблицами управляют протоколы маршрутизации. Протоколы маршрутизации не являются частью TCP/IP, поэтому в данной книге они не будут рассматриваться. Теория маршрутизации и разработка ее моделей на сегодняшний день является скорее искусством, нежели наукой. Прикладной программист обычно не имеет возможности управлять маршрутизацией данных своих приложений. Тем не менее прикладные программы широко используют IP-адреса в своей работе. А они, в свою очередь, имеют непосредственное отношение к IP-маршрутизации. В следующих абзацах вы познакомитесь с упрощенной концепцией маршрутизации пакетов Интернет, а также с таблицами маршрутизации. Номера сетей Перед тем как изучить устройство таблиц маршрутизации, вспомним один важный момент, касающийся IP-адресов. У каждого сетевого интерфейса, принадлежащего одной физической сети, должен быть уникальный собственный номер, а сетевому номеру следует быть одним и тем же для всех. Протоколу IP полагается иметь какое-нибудь средство, чтобы знать, в каком направлении передавать пакет, чтобы он попал к месту своего назначения в Интернет. Для этой цели используются таблицы маршрутизации. Таблицы маршрутизации основывают работу на том факте, что все хосты на одной и той же сети имеют один и тот же сетевой номер. Интернет обладает возможностью объединить свыше 3,7 миллиарда сетевых компьютеров или других сетевых устройств. Разумеется, никто не хочет, чтобы в таблице маршрутизации содержалась информация о таком количестве адресов. 119 
Глава 4. Сетевой протокол Поэтому IP маршрутизирует пакеты не между компьютерами, а между сетями — так проще. Следовательно, в таблицах маршрутизации хранятся только номера сетей. Записи в таблице маршрутизации Каждая запись в таблице маршрутизации состоит из трех следующих полей: сети (network), шлюза (gateway) и флагов (flags). Первые два — это сетевые номера, а поле флагов указывает на то, что эти сети напрямую соединены с сетью, которой принадлежит данная таблица. Поле «сеть» содержит список сетевых идентификаторов. Поле «шлюз» содержит информацию о маршрутизаторе. Это поле указывает маршрутизатор, служащий передатчиком пакетов в сеть, идентификатор которой указан в поле «сеть». Однако это не значит, что маршрутизатор напрямую связан с этой сетью назначения. В таблице просто указан тот маршрутизатор, который послужит следующим скачком при движении пакета к месту назначения. Что такое непосредственная доставка? Когда компьютер получает пакет, его IP-модуль исследует идентификатор сети назначения, размещенный в заголовке пакета. Далее IP-модуль запрашивает запись для сети с этим идентификатором у таблицы маршрутизации. Если запись найдена, исследуется содержимое поля флагов. Если в поле флагов указано прямое соединение, это значит, что пакет можно доставить, используя формат кадра данных низлежащего уровня соединения (например, в сетях технологии Ethernet или Token Ring). Далее процесс доставки будет непосредственно зависеть от применяемой сетевой технологии. В общих словах, непосредственная доставка значит, что сеть может преобразовать IP-адрес получателя в адрес формата уровня соединения (скажем, адрес Ethernet). Процесс преобразования обычно осуществляется протоколом преобразования адресов (ARP). Сеть инкапсулирует IP-датаграмму в кадр данных и передаст прямо в пункт назначения. Примечание: Любая датаграмма должна подвергнуться процедуре непосредственной доставки. Ведь откуда бы она ни шла, в конце концов она достигнет маршрутизатора, напрямую соединенного с сетью назначения. Что такое промежуточная доставка? Запись в таблице маршрутизации может говорить о том, что устройство с данным адресом не соединено напрямую с сетью, в которую нужно передать пакет. Значит, необходимо осуществить процесс промежуточной доставки. Таблица маршрутизации определенной сети содержит данные только о маршрутизаторах, связанных с ней напрямую. Это значит, что сеть может передать пакет данных, пользуясь непосредственной доставкой, любому из них. На рис. 4.7 изображен пример межсетевого объединения с четырьмя сетями и тремя маршрутизаторами. 120 
Что такое IP . , Маршрутизатор А 100.0.0.1 300.0.0.7 100.0.0.3 300.0.0.1 100.0.0.2 /' Сеть на Сеть 300.0.0.6 100.0.0.0 300.0.0.2 100.0.0.4 100.0.0.5 300.0.0.0 Ш 300.0.0.5 300.0.0.3 \ 300.0.0.4 Маршрутизатор В 200.0.0.1 \ / 200.0.0.3 Сеть 200.0.0.0 / 200.0.0.2 Маршрутизатор С 400.0.0.1 / %- v 400.0.0.4 Сеть 400.0.0.0 400.0.0.2 \ 400.0.0.3 Рис. 4.7. Простой пример межсетевого объединения с четырьмя сетями и тремя маршрутизаторами В табл. 4.6 приведен пример таблицы маршрутизатора В из рис. 4.7. Таблица 4.6. Записи в таблице маршрутизатора В на рис. 4.7 Сеть назначения Маршрут 100.0.0.0 прямая доставка 200.0.0.0 прямая доставка 300.0.0.0 через 100.0.0.2 400.0.0.0 через 100.0.0.2 Из табл. 4.6 видно, что если маршрутизатор В принимает пакет с местом назначения в сети 100.0.0.0, запись в таблице гласит, что данная сеть соединена напрямую. В этом случае маршрутизатор В непосредственно доставит пакет до места назначения. 121 
Глава 4. Сетевой протокол Интернет Приняв пакет для сети 400.0.0.0, маршрутизатор обнаружит, что должен передать его маршрутизатору по адресу 100.0.0.2, несмотря на то, что сеть 100.0. 0.0 не связана напрямую с сетью 400.0.0.0. Устройство с адресом 100.0. 0.2 — просто следующий скачок пакета на пути к месту назначения сети 400.0. 0.0. Добравшись до места с номером 100.0.0.2, пакет направляется к устройству 400.0. 0.1. Маршрутизатор 400.0.0.1 может доставить пакет непосредственно на сеть 400.0.0.0. Для маршрутизатора В это значит, что он осуществил промежуточную доставку пакета к месту назначения. Подводя итоги В этой главе вы узнали, что TCP/IP доставляет свои данные, пользуясь протоколом Интернет (IP). Вы выяснили, каким образом IP выполняет эту задачу. Вы также узнали, что распределением IP-адресов в Интернет занимается специальная организация «Информационный центр Интернет» (InterNIC). Далее, вы исследовали роль идентификаторов сетей и их классов в расширении адресного пространства Интернет. Сетевой уровень и протокол Интернет передают данные между сетевыми компьютерами. В следующей главе вы узнаете, каким образом транспортные протоколы TCP/IP осуществляют дальнейшую обработку данных, доставляя их соответствующим приложениям сетевого компьютера. Перед тем как перейти к пятой главе, проверьте, хорошо ли вы усвоили следующие вопросы: S Сетевой уровень TCP/IP управляет доставкой данных между сетевыми компьютерами. S 32-битный IP-адрес состоит из сетевого идентификатора (номера сети) и уникального номера сетевого интерфейса. S Класс адреса определяется старшими битами старших байтов IP-адреса. Разные классы адресов используют различное количество байтов в адресе для хранения идентификатора сети. S Протоколы преобразования адреса TCP/IP транслируют адрес из формата сетевого уровня в формат адреса уровня соединения и наоборот. S IP-датаграммы состоят из IP-заголовка, заголовка транспортного протокола (TCP или UDP) и прикладных данных. S IP-заголовок состоит из полей, определяющих версию протокола IP, создавшего датаграмму, местонахождение данных в пакете, информацию о фрагментации, контрольную сумму и информацию о маршрутизации. S В сети TCP/IP фрагментация происходит, когда IP пытается передать пакет с большей, чем MTU низ лежащего уровня, длиной. S Таблицы маршрутизации используются протоколом IP для доставки данных в Интернет. Они содержат информацию об адресах некоторых пунктов назначения в Интернет. S Таблицы маршрутизации определяют адрес следующего скачка пакета на пути к любому месту назначения в Интернет. 
Глав Транспортные протоколы Приложение, работающее с Интернет, как правило, общается с одним из протоколов транспортного уровня TCP/IP: протоколом управления транспортировкой (TCP) или протоколом пользовательских датаграмм (UDP). Приложение строит свою работу на взаимодействии с одним из этих протоколов. Приложения Интернет, например программа ftp, передающая файлы по сети, обычно использует TCP, так как он предлагает надежную потокоориентированную службу доставки. Приложения типа электронной почты часто пользуются TCP по той же самой причине. Не требующие особой надежности приложения типа tftp (протокол простой передачи файлов, trivial file transfer protocol) используют UDP. Приложения на основе протокола времени (time protocol), связывающиеся с серверами времени Интернет, могут пользоваться как тем, так и другим протоколом. Прочтя эту главу, вы будете точно знать, в каком случае может потребоваться TCP, а в каком — UDP. Конечная цель сетевого взаимодействия заключается в передаче информации между приложением-клиентом и приложением-сервером. Понимание транспортных протоколов и транспортного уровня совершенно необходимо для эффектив- 123 
Глава 5р Транспортные протоколы ного проектирования приложений типа клиент-сервер. Закончив чтение данной главы, вы овладеете следующими понятиями и концепциями: ♦ Как транспортный протокол использует порт протокола для связи с программой-приложением . ♦ Назначение полей данных в заголовке UDP. ♦ Как TCP обеспечивает надежную доставку данных. ♦ Как TCP использует скользящее окно для увеличения пропускной способности сети. ♦ Как модули TCP устанавливают и заканчивают соединение. ♦ Как TCP использует сообщения-подтверждения. ♦ Назначение полей данных в заголовке TCP. Что такое транспортный уровень? На первый взгляд кажется, что IP, служба доставки Интернет, и транспортные протоколы выполняют одинаковые функции. На самом деле IP модуль доставляет данные только между двумя компьютерами. Транспортный уровень и его протоколы передают данные между приложениями. Во многих случаях ответственность транспортных протоколов за передаваемые данные такая же, как и у протокола Интернет. И вообще, большинство из того, что вы узнали относительно IP-датаграммы и IP-заголовка, в полной мере применимо и к транспортным протоколам. Вы знаете, что TCP/IP включает два транспортных протокола: протокол управления транспортировкой (собственно, транспортный протокол, TCP) и протокол пользовательских датаграмм (UDP). Ориентированный на соединение протокол управления транспортировкой для приема и передачи данных использует надежный поточно-байтовый способ доставки. Сетевое соединение устанавливается в виде виртуальной цепи. Протокол пользовательских датаграмм ненадежен, не ориентирован на соединение, передает и принимает данные при помощи датаграмм. Что такое порт транспортного уровня? Понятие «порт» в терминологии TCP/IP очень похоже на IP-адрес компьютера. Только порт обозначает приложение, а IP-адрес — определенный компьютер (вернее, его сетевой интерфейс). Так же как IP-датаграммы содержат адреса источника и получателя данных, транспортные протоколы хранят номера портов источника и получателя. Если вышесказанное кажется вам несколько странным, давайте рассмотрим, что нам известно об аппаратных портах нашего персонального компьютера. Может быть, вам приходилось писать программу, посылающую данные на порт компьютера. Если нет, то вспомним процесс печати через параллельный или последовательный порт. Если вам приходилось пользоваться 
■ '' ; Что такое порт транспортного уровня? модемом, концепция портов TCP/IP должна показаться вам еще более зна-. комой. Примечание: В случае модема, компьютер принимает и посылает данные через последовательный порт. В случае принтера, данные обыкновенно только посылаются. Порты персональных компьютеров имеют название и номер. Параллельные порты компьютера называются LPT1 и LPT2. Последовательные — СОМ1 и COM2. В сети Интернет номера портов протоколов только нумеруются. Параллельный порт LPT1 персональных компьютеров годами использовался для печати данных. Тысячи программных продуктов знают, что печатать надо через LPT1. Точно так же порт протокола Интернет ассоциируется со вполне определенным приложением или функцией. Что такое порт протокола Интернет? Мы уже упоминали, что Интернет включает протоколы для некоторых часто используемых приложений, например ftp, telnet или электронной почты. Данным приложениям Интернет присвоены так называемые официальные (well-known) номера портов. «Официальность» порта состоит в том, что его номер широко известен и применяется всеми компьютерами в Интернет, работающими с данным сетевым приложением или функцией. Так же как программисты персональных компьютеров пользуются портом LPT1 для печати, программисты Интернет пользуются набором номеров портов для выполнения конкретных сетевых приложений. Например, официальный (общеизвестный) номер порта для простого протокола передачи файлов (tftp) — 69. Официальный номер порта telnet — 23. В табл. 5.1 приведен список широко используемых портов протоколов Интернет. Таблица 5.1. Некоторые официальные номера портов протоколов Интернет Протокол (сетевая служба) Номер порта Служба эхо (Echo Protocol) 7 Время суток (Daytime Protocol) 13 Протокол передачи файлов (File Transfer Protocol) 21 Протокол удаленного терминала (Telnet Protocol) 23 Простой протокол передачи почты 25 (Simple Mail Transfer Protocol) Точное время (Time Protocol) 37 Кто-есть-кто (Whois Protocol) 43 Протокол простой передачи файлов 69 (Trivial File Transfer Protocol) Информация о пользователях (Finger Protocol) 79 125 
Как используется порт UDP? Протокол, не ориентированный на соединение (IP или UDP), можно сравнить с почтовой службой доставки. Если вы забыли эту аналогию, перечитайте раздел «Словарь терминов расширяется» в третьей главе. Аналогия позволяет увидеть отношение между UDP, портами и приложениями. Здесь почтовое отделение превращается в сетевой компьютер, абонентские ящики — в порты, а люди, их арендующие, — в прикладные программы. Протокол Интернет — это сетевая служба доставки. Прежде вы думали, что IP похож на почтового работника. Теперь же IP больше походит на грузовик, развозящий письма между почтовыми отделениями, а транспортный протокол — на почтового работника, сортирующего и раскладывающего письма по абонентским ящикам. Грузовик (IP) развозит письма (данные) по почтовым отделениям (сетевым компьютерам). Почтовые работники (UDP) сортируют почту по номерам абонентских ящиков (портам). Рассортировав письма, почтовые работники (UDP) кладут письма (данные) в абонентские ящики (порты). Клиенты почтового отделения (приложения) периодически проверяют свои ящики и забирают почту. Почтовые работники (UDP) не уведомляют клиентов (приложения) о приходе свежей корреспонденции (данных), а просто размещают ее в абонентском ящике (порту). Поскольку TCP является надежным и ориентированным на соединение протоколом, его способ использования портов несколько отличается от способа UDP. Например, будучи не ориентированным на соединение, UDP просто доставляет данные до определенного порта и не обеспечивает никакого соединения между передатчиком и получателем. TCP ориентирован на соединение, поэтому доставка данных для него — не просто передача в порт, но в первую очередь соединение. Например, приложение TCP, пожелавшее открыть несколько соединений одновременно на одном и том же порту, может без проблем сделать это — данные не потеряются. Мы уже говорили о том, что TCP больше похож на телефонные переговоры, чем на почтовую службу. Сейчас мы немного изменим телефонную аналогию и представим ее в следующем виде. Офис станет сетевым компьютером, номер телефона — портом, а телефонный звонок — сетевым соединением. Служащие в офисе будут представлять прикладные протоколы, а их телефонные переговоры — обмен данными. Как и прежде, IP представляет телефонную компанию. Служащие (прикладные протоколы), работающие в офисе (сетевом компьютере), пользуются услугами телефонной компании (IP). Каждому служащему присвоен определенный телефонный номер (порт). Несколько телефонных Как используется порт TCP? 126 
линий (портов) остаются все время свободными, то есть ими может воспользоваться любой желающий. Каждое рабочее место оборудовано телефонным аппаратом, с которого можно звонить (устанавливать соединение), пользуясь любой не занятой в данный момент телефонной линией (портом) в офисе (сетевом компьютере). Телефонная компания (IP) передает все входящие звонки в офис (сетевой компьютер), заставляя телефонные аппараты звонить. Определенный номер (порт) соответствует определенному служащему (приложению), то есть тому, кто отвечает на звонок (устанавливает соединение). В начале служащий (прикладной протокол) с определенным номером (портом) всегда отвечает на звонок (устанавливает соединение). Далее, если служащий решает продолжить переговоры со звонящим абонентом, он некоторое время беседует с ним по телефону (производит обмен данными). Служащий (прикладной протокол) отвечает на звонки различным образом. Например, он может пригласить другого служащего принять участие в переговорах. При этом другой служащий (прикладной протокол) поднимает трубку параллельного аппарата (порта) и участвует в переговорах (устанавливает еще одно соединение по тому же самому порту). В некоторых случаях служащий не хочет занимать служебную линию связи (официальный порт) и переключает собеседника на одну из редко используемых линий (другой порт), чтобы никто не прерывал беседу (обмен данными). Как номер порта используется в программе? Поскольку транспортный уровень перемещает пакеты данных к прикладным программам и от них, он должен каким-то образом распознавать те программы, с которыми имеет дело. Тут на сцену и выступают номера портов. Любое приложение, независимо от того, сервер оно или клиент, имеет уникальный номер порта. Когда программа устанавливает соединение с сетью, ей присваивается определенный номер порта. Разрабатывая приложение-клиент, обычно не нужно беспокоиться по поводу номера его порта. Клиент может заранее и не знать его. Но совсем другое дело — приложение-сервер. Каждый раз, когда клиент посылает сообщение, транспортный уровень автоматически присваивает ему правильный номер порта в поле порта источника сообщения. В главе 19 описан процесс создания приложения-сервера, когда вы можете запросить у сети назначить ему определенный, заранее известный номер порта. Приложение-сервер, таким образом, может обслуживать запросы, поступающие с официальных номеров портов, перечисленных, например, в табл. 5.1. 127 
, , : ■ ' Глава 5. Транспортные протоколы . ... В - ' •• : Что такое протокол пользовательских датаграмм? UDP весьма похож на IP в том смысле, что они оба ненадежные, не ориентированные на соединение протоколы, пользующиеся датаграммами. Они оба переносят данные между компьютерами, однако только UDP умеет распознать то приложение среди многих, работающих внутри компьютера, которому предназначены данные. Как правило, сеть назначает таким приложениям определенный номер порта. Итак, UDP пользуется датаграммами для доставки данных. Точно так же, как IP прицепляет к данным IP-заголовок, UDP прицепляет к ним UDP-заголовок. Структура UDP-заголовка намного проще. На рис. 5.1 изображена структура UDP-датаграммы. UDP-заголовок содержит четыре поля: «порт-источник», «порт-получатель», «длина сообщения» и «контрольная сумма». Позиции битов ; 0 15 Порт-получатель UDP 16 31 | Порт-источник UDP | Длина сообщения UDP Контрольная сумма UDP | Область данных UDP Рис. 5.1. Структура UDP-датаграммы Длина UDP-заголовка — восемь байтов. Поля портов состоят из 16-битных целых чисел, представляющих номера портов протоколов. Поле «порт-источник» содержит номер порта, которым пользуется приложение-источник данных. Поле «порт-получатель» соответственно указывает на номер порта приложенияполучателя данных. Поле «длина сообщения» определяет длину (в байтах) UDP-датаграммы, включая UDP-заголовок. Наконец, поле «контрольная сумма», в отличие от контрольной суммы IP-заголовка, содержит результат суммирования всей UDP-датаграммы, включая ее данные, область которых начинается сразу после заголовка. Примечание: Несмотря на то, что контрольная сумма UDP включает область данных, подсчитывать и помещать ее в заголовок не обязательно. Такое поведение не характерно, например, для протоколов IP или TCP. Последние обязаны подсчитать и включить в свой заголовок контрольную сумму. Модуль UDP отслеживает появление вновь прибывших датаграмм, сортирует их и распределяет (демультиплексирует) в соответствии с портами назначения. На рис. 5.2 показан поток данных, следующий сквозь сетевой уровень и модуль UDP к прикладным программам. 128 
, Что такое транспортный протокол? i^OP^Miyta Щ ^Программа 12:| Щрр|щммаЩ Программный модуль UDP |дрмультиплексирует данные по номерам портов) т UDP-датаграммы Р~ Программный модуль: IP j| Прикладной уровень Транспортный j уровень ! Сетевой уровень Рис. 5.2. Поток данных через модуль UDP Что такое транспортный протокол? Транспортный протокол (Transport Control Protocol, TCP) наряду с протоколом IP — один из наиболее распространенных в Интернет. Так же, как и UDP, TCP служит для передачи данных между сетевым и прикладным уровнями сетевой модели. По сравнению с UDP TCP устроен гораздо сложнее. И это понятно — ведь ему приходится обеспечивать надежную, потоковую, ориентированную на соединение службу доставки данных. Другими словами, TCP сам следит за доставкой данных, а также за правильной последовательностью передаваемых пакетов. В отличие от TCP протокол пользовательских датаграмм (UDP) доставки данных не гарантирует. Не обеспечивает он и правильной последовательности прихода датаграмм. TCP пытается оптимизировать пропускную способность сети, то есть увеличивает производительность доставки пакетов в Интернет, насколько это вообще возможно. Для этого он динамически управляет потоком данных в соединении. Если буфер приемника данных на принимающем конце переполняется, TCP просит передающую сторону снизить скорость передачи. Рассмотрим, каким образом протокол управления транспортировкой пользуется услугами IP для передачи данных между двумя компьютерами. Может показаться странным, что TCP умудряется пользоваться ненадежным IP и при этом оставаться надежным. Также может казаться удивительным, что TCP, пользуясь не ориентированным на соединение IP, остается ориентированным на соединение. Наконец, как получается, что TCP доставляет данные в виде потока байтов, тогда как IP пересылает датаграммы? Последующие абзацы ответят на все эти вопросы и устранят возможные недоразумения. Постигая секреты TCP, вы должны помнить о том, что его данные всегда переносит IP. То есть данные TCP всегда упаковываются в 1Р-датаграммы. 5 Зак. № 1949 129 
Обеспечение надежности Для обеспечения надежной доставки и правильной последовательности данных в потоке TCP пользуется подтверждениями. Как только пункт назначения принимает блок данных, он передает подтверждение о приеме источнику данных. Источнику данных это сообщение говорит: «Все в порядке. Я принял твои данные». Каждый раз при передаче сообщения модуль TCP запускает специальный таймер. По истечении заданного в нем времени и не получении подтверждения TCP повторяет попытку передать свое сообщение. На рис. 5.3 схематично показана работа такой системы. Передатчик Приемник Сообщение jj ► [rC666u®|:f| > 1 1:.ЛОДТВерТОеНИ0:;2;: 1 [ Сообщение 3 ^ ► i [;-Пр^ёЩдёние.^ Рис. 5.3. Передача данных с простым подтверждением о доставке К несчастью, простое подтверждение о доставке, изображенное на рис. 5.3, работает исключительно неэффективно. Одна из сторон соединения вынуждена все время ждать появления подтверждения о доставке от другой стороны. Вскоре вы узнаете, что на самом деле TCP не использует такую простейшую схему подтверждения, при которой пакеты и подтверждения следуют по очереди друг за другом. Что такое скользящее окно? TCP не посылает один пакет, ожидая прихода подтверждения, чтобы послать следующий. Вместо этого он использует принцип «скользящего окна». Этот принцип позволяет послать несколько сообщений и только потом ожидать подтверждения. «Скользящее окно» проиллюстрировано на рис. 5.4. Окно в исходной позиции Окно скользит Рис. 5.4. Скользящее окно TCP 130 
__ * ' •• - „ : * л Что такое транспортным протокол? Выражаясь образно, TCP как бы накладывает окно на поток данных, ожидающих передачи, и передает все данные, попавшие в окно. Приняв подтверждение о доставке всех данных, TCP перемещает окно дальше по потоку и передает следующие попавшие в него сообщения. Работая сразу с несколькими сообщениями, TCP может одновременно «выставить» их на сетевой канал и только потом ожидать прихода подтверждения. Метод скользящего окна значительно увеличивает производительность соединения, а также эффективность циклов обмена сообщениями и подтверждениями об их доставке. Рис. 5.5 иллюстрирует цикл обмена сообщение-подтверждение TCP. •Компьютер-приемник Компьютер-яередатчи1< Поток сообщений СрббЩ^ние-1 Прием сообщения-' Передача подтверждения-! Г*""' :■ Прием сообщения-^ Передача подтверждений 2 СкдобЩейие-^ Прием подтвержденщ-1 г | Прием подтверждения^ Прием подтверждения-? k Рис. 5.5. Передача сообщений и подтверждений о доставке по схеме скользящего окна Передатчик и приемник на рис. 5.5 используют скользящее окно шириной в три пакета. То есть передатчик сначала высылает три пакета и только после ждет прихода подтверждения. Приняв подтверждение о доставке третьего последнего пакета, передатчик может посылать следующие три. TCP регулирует полосу пропускания сети, договариваясь с другой стороной о некоторых параметрах потока данных. Причем процесс регулировки происходит на протяжении всего соединения TCP. В частности, регулировка заключается в изменении размеров скользящего окна. Если сеть загружена не сильно и вероятность столкновения данных минимальна, TCP может увеличить размер скользящего окна. При этом скорость выдачи данных на канал увеличивается и соединение становится более эффективным, поскольку через сеть проходит больше данных за одно и то же время. Если, наоборот, вероятность столкновения данных велика, TCP уменьшает размер скользящего окна. Если размер скользящего окна, изображенного на рис. 5.4, принять равным восьми пакетам при обычном сетевом трафике, то в худших условиях, когда Интернет сильно загружен, его размер может уменьшиться до пяти. Наоборот, когда данных в сети немного, размер окна может увеличиться, например, до 10 — 20 пакетов. 131 
~ Транспортные протоколы Имейте в виду, что представленная на рис. 5.4 и описанная в предыдущих абзацах схема несколько упрощена. На самом деле TCP задает размер окна в байтах. То есть размер окна по умолчанию может равняться нескольким тысячам байтов, а не восьми, десяти и двенадцати байтам, как в предыдущем примере. Как правило, модуль TCP передает несколько сегментов, прежде чем скользящее окно заполнится целиком. Большинство систем в Интернет устанавливают окно равным по умолчанию 4096 байтам. Иногда размер окна равен 8192 или 16384 байтам. Сообщение TCP Блок данных TCP принято называть сообщением или сегментом. Оба эти термина вполне корректны и широко употребляются в литературе, посвященной Интернет. Мы, однако, по причинам, которые обсудим ниже, на протяжении всей книги будем употреблять термин «сегмент». TCP рассматривает свои данные в качестве однородного, неделимого потока. Тем не менее для доставки данных он вынужден использовать IP-датаграммы. Прикладной программе, однако, совершенно не обязательно знать, что ее поток данных переносят датаграммы. TCP делает этот процесс прозрачным для всех приложений, работающих через него. Где бы вы не встретили термин «сообщение», относящийся к TCP, можете смело подставлять туда термин «сегмент». Почему? Потому, что каждое сообщение TCP, доставленное датаграммой протокола Интернет, является на самом деле TCP-сегментом. Сегмент TCP состоит из ТСР-заголовка, TCP-опций и данных, переносимых сегментом. На рис. 5.6 изображена структура сегмента TCP. Несмотря на то, что заголовок показан состоящим как бы из нескольких уровней, на самом деле он является последовательным потоком данных, длиной как минимум в 20 байтов. Позиции битов ◄ о 32 бита 15 16 31 Порт-передатчик (16 битов) Порт-приемник (16 битов) 32-битный номер последовательности 32-битный номер подтверждения и А р R S|l R С1 S S Y 1 G к; |Н Т N, 1 Длина заголовка (4 бита) Зарезервировано (6 битов) Контрольная сумма TCP (16 битов) Размер окна (16 битов) Указатель на данные для неотложной обработки (16 битов) Опции (если есть) Заполнение (если нужно) Область данных (необязательна) Рис. 5.6. Структура сегмента (сообщения) TCP 132 
Что такое транспортный протокол? " ' • ' -■ ' В табл. 5.2 кратко описано назначение каждого поля заголовка TCP. Способы применения каждого поля будут обсуждены в следующих абзацах. Таблица 5.2. Назначение полей заголовка TCP Поле заголовка Назначение Порт-передатчик Обозначает порт протокола приложения-источника данных. Порт-приемник Обозначает порт протокола приложения-получателя данных. Номер последовательности Определяет первый байт данных в области данных сегмента TCP. Номер подтверждения Определяет следующий байт данных, который приемник рассчитывает получить из входного потока. Длина заголовка Длина TCP-заголовка, измеренная в 32-разрядных словах. Флаг URG Если установлен, извещает принимающий модуль TCP о том, что в сегменте находятся данные для неотложной обработки. Флаг АСК Указание принимающему модулю TCP на то, что поле номер подтверждения содержит соответствующие данные. Флаг PSH Требование принимающему модулю TCP передать данные приложению-получателю немедленно. Флаг RST Запрос принимающему модулю TCP сбросить соединение. Флаг SYN Запрос принимающему модулю TCP синхронизировать номера последовательности. Флаг FIN Сообщение принимающему модулю TCP об окончании передачи. Размер окна Сообщение принимающему модулю TCP о количестве байтов, которое способен принять модульпередатчик. Контрольная сумма TCP Служит для обнаружения поврежденных при передаче данных. Указатель на неотложные Указывает на последний байт данных, требую¬ данные щих неотложной обработки, находящихся в области данных сегмента TCP. Опции Обычно используются совместно с опцией максимальная длина сегмента (MSS). 133 
Установление TCP-соединения Чтобы обеспечить надежную передачу данных, а также правильный порядок следования сегментов, TCP использует сообщения-подтверждения о доставке. Для выполнения этих задач требуется какой-либо способ идентифицировать передаваемые данные. Также сеть должна уметь синхронизировать передаваемые между обоими сторонами данные. Другими словами, каждая сторона должна знать, когда можно начинать передачу. Сторонам также следует иметь представление, как обозначить тот или иной сегмент. Предположим, что модуль TCP принял поврежденный пакет. Он должен иметь возможность известить модуль-передатчик о том, какой именно пакет данных нужно повторить. Перед установлением соединения обе стороны должны договориться о таком, понятном им обеим, способе отметить каждый пакет. Определенная система обмена сообщениями-подтверждениями соединения TCP является частью более общего процесса синхронизации обмена данными. Не будет системы — соединение может не состояться. В следующих абзацах описываются поля TCP-заголовка, служащие для обмена подтверждениями. Для установления и прекращения соединения, а также для отправки и получения подтверждений TCP заголовок имеет поля «номер последовательности», «номер подтверждения» и поля флагов. Каждый раз, желая что-нибудь передать по протоколу TCP, программа-приложение запрашивает модуль TCP установить соединение. Модуль TCP в свою очередь шлет сообщение TCP с установленным флагом SYN (синхронизации) удаленному порту, с которым программа-клиент хочет установить соединение. Флаг синхронизации указывает принимающей стороне (серверу, например), что программа-клиент желает установить соединение. Вместе с флагом SYN, сообщение TCP несет в себе 32-битный номер последовательности, размещенный модулем TCP в поле «номер последовательности». TCP-модуль сервера отвечает сегментом TCP с установленными флагом подтверждения (АСК) и номером подтверждения. Для того чтобы разобраться в процедуре установления соединения TCP, необходимо понимать смысл номеров последовательности и подтверждения. В следующем разделе процедура установления соединения и смысл соответствующих номеров изучаются более подробно. Что такое начальный номер последовательности? Мы уже отмечали выше, что обе стороны соединения TCP должны идентифицировать информацию в потоке данных для того, чтобы корректно слать и принимать подтверждения о доставке. Номер последовательности — это то, как TCP обозначает данные. Сетевые компьютеры пользуются разнообразными методами для выбора начального номера последовательности (для наших целей, однако, эти методы несущественны). Вы можете рассматривать начальный номер последовательности как случайное число. Значение начального номера последовательности не играет никакой роли. 134 
Начальный номер последовательности представляет собой число, пересылаемое модулем-передатчиком TCP модулю-приемнику TCP. Передавая начальный номер, TCP-модуль как бы сообщает корреспонденту: «Эй, я хочу установить с тобой соединение. Нумерация данных в потоке от меня начинается с этого числа». Сервер-приемник сообщения, получив запрос на установление соединения, тоже не сидит сложа руки. Он посылает обратно сообщение, содержащее его собственный начальный номер. Модуль TCP генерирует какое-либо число, совершенно не зависящее от числа, посланного TCP-клиентом. Другими словами, сервер как бы сообщает: «Привет! Я получил твой запрос на TCP-соединение и высылаю номер, с которого начнется нумерация моих собственных данных». Все TCP соединения являются дуплексными. Данные следуют в обоих направлениях одновременно. Поток данных в одном направлении совершенно не зависит от потока данных в противоположном. В силу дуплексной природы соединения оба модуля TCP должны учитывать и нумеровать данные в каждом направлении по-разному, а значит, обрабатывать два начальных номера последовательности. Подтверждение доставки данных В TCP-заголовке самого первого, начального, сообщения-ответа сервера модуль TCP устанавливает два флага: синхронизации (SYN), чтобы известить модуль TCP клиента о том, что в сообщении содержится начальный номер последовательности сервера, и подтверждения (АСК), чтобы заставить клиента изучить содержимое поля «подтверждение». TCP-модуль сервера использует номер последовательности, принятый от клиента, чтобы сконструировать на его основе собственный номер подтверждения. Номер подтверждения всегда указывает на номер сообщения, которое сервер рассчитывает получить следующим. Таким образом, в начальном сообщении-ответе сервера содержится номер последовательности клиента, увеличенный на единицу. Предположим, например, что модуль TCP клиента, запрашивающего соединение, шлет номер последовательности, равный 1000. В ответ от сервера он получит начальное сообщение с числом 1001, установленным в поле подтверждения. Для модуля-клиента это выглядит, как будто сервер сказал: «Кстати, следующее сообщение, которое я от тебя ожидаю, должно иметь номер 1001». Соединение установлено! До передачи любых данных модуль TCP клиента, запросивший соединение, должен подтвердить начальное сообщение-ответ, пришедшее от модуля TCP сервера. То есть, когда модуль TCP клиента получает начальное сообщение-ответ от сервера, он должен посылать «подтверждение подтверждения». (На самом деле клиент подтверждает запрос сервера на синхронизацию обмена.) 135 
Глава 5, Транспортные протоколы Сообщение, посланное TCP-модулем клиента, тоже будет содержать установленный флаг «подтверждение». В поле «номер подтверждения» ТСР-модуль клиента размещает начальный номер последовательности, принятый от сервера, увеличенный на единицу. (Теперь TCP-модуль клиента не устанавливает флаг синхронизации, так как обе стороны соединения уже синхронизировались, то есть договорились о начальных номерах своих последовательностей.) Другими словами, между ТСР-модулями происходит обмен данными, состоящий из трех стадий: 1. ТСР-модуль клиента пытается установить TCP-соединение, посылая запрос на синхронизацию, содержащий среди прочего начальный номер последовательности. 2. ТСР-модуль сервера подтверждает прием запроса на установление соединения и в свою очередь шлет клиенту запрос на синхронизацию с собственным начальным номером последовательности. 3. ТСР-модуль клиента подтверждает прием запроса сервера на синхронизацию. Что такое номер последовательности? Номер последовательности однозначно идентифицирует байт данных в потоке данных TCP. Как следует из названия, номера последовательности последовательны, то есть следуют один за другим. Однако номера последовательности разных соединений TCP необязательно начинаются с одного и того же числа. Номер последовательности, устанавливаемый в каждом сегменте TCP, идентифицирует первый байт в сообщении. То есть является смещением относительно начала потока данных. Номер последовательности — то же самое, что счетчик переданных байтов. Он как бы говорит: «Первый байт этого пакета — это номер байта (номер последовательности) в потоке данных». Предположим, что программа-клиент желает передать 2000 байтов данных посредством TCP. Предположим, что произведена синхронизация и следующий номер последовательности будет равен 1251. Предположим так же, что длина данных в сегменте равна 500 байтам. Произойдут следующие события: 1. Модуль TCP клиента передает сегмент TCP, содержащий байты с 1 по 500. Номер последовательности, записанный в поле «номер последовательности» сегмента, равен 1251. 2. Модуль TCP клиента передает сегмент TCP, содержащий байты с 501 по 1000. Номер последовательности равен 1751. 3. Следующий сегмент, посланный модулем TCP клиента, содержит байты с 1001 по 1500 с номером последовательности, равным 2251. 4. Наконец, модуль TCP клиента передает данные с 1501 байта по 2000 и устанавливает номер последовательности равным 2751. 136 
TCP-модуль сервера в нашем примере шлет следующие сообщения: 1. Приняв первый сегмент от клиента, TCP-модуль сервера отвечает пакетомподтверждением с номером подтверждения 1751. Сервер как бы говорит: «Эй, я принял твои данные, и следующий сегмент, который я надеюсь получить, должен содержать номер последовательности 1751». 2. Приняв второй сегмент, TCP-модуль сервера шлет номер подтверждения, установленный в 2251. 3. Приняв третий сегмент, TCP-модуль сервера шлет номер подтверждения, равный 2751. 4. Приняв четвертый сегмент, TCP-модуль сервера шлет номер подтверждения, равный 3251. (В данный момент TCP-модуль сервера не знает, что передача данных окончена — TCP-модуль клиента еще не известил об этом.) Дуплексные сетевые службы Как уже отмечалось, соединение TCP является дуплексным. Это значит, что данные следуют одновременно в обоих направлениях. Данные, следующие в одном направлении, совершенно не зависят от данных, следующих в противоположном. Поскольку обмен данными TCP является дуплексным, ТСР-модулям необходимо подсчитывать получаемые и передаваемые данные раздельно, и номера последовательности для обоих потоков будут разными. Если вышесказанное не произвело на вас впечатления, давайте рассмотрим поток данных с точки зрения одной из сторон соединения. Предположим, что мы рассматриваем поток данных со стороны TCP-модуля клиента. С точки зрения TCP-моду ля клиента номер последовательности отсчитывает или идентифицирует данные, посылаемые клиентом в сторону TCP-модуля сервера. Номер подтверждения в сегментах, посланных TCP-модулем клиента, с его точки зрения идентифицирует данные, принятые от TCP-моду ля сервера. На рис. 5.7 изображен поток данных и номера последовательности так, как они выглядят со стороны TCP-модуля клиента. Сёш&вт 1 Клиент Сешент4 СешентТ —— (SYN) 1ппп си 1ьная длина сегментаТо24" /СУКП 2000 — [дек) Ю01 Максимальная длина сегмента £!!Ц°£ЧАСК)2001 jACKt 1002. mriy*™ '&сю10°- (АСК) 2002 Рис. 5.7. Идентификация данных и их поток с точки зрения TCP-модуля клиента 137 
Окончание соединения TCP Соединение TCP заканчивается обменом пакетами, состоящего из двух стадий. Каждая из сторон, как сервер, так и клиент, может предложить другой закончить соединение. Для этого сторона-инициатор обмена высылает пакет с установленным флагом «окончание обмена» (FIN). В силу дуплексной природы протокола TCP оба потока данных независимы, и должны быть завершены по отдельности. Если вам приходилось программировать в UNIX, последнее утверждение предыдущего абзаца может показаться подозрительным. Когда в UNIX закрывается соединение, дальнейший обмен по нему становится невозможен. Соединение TCP работает по-другому. Даже после закрытия соединения (завершения передачи данных) одной из сторон соединения она в состоянии продолжать прием данных от другой стороны соединения. Мысль принимать данные после закрытия соединения кажется странной на первый взгляд. На самом деле, установленный в пакете флаг FIN является сигналом, означающим, что одна сторона прекратила передачу данных. Приход сообщения-подтверждения от другой стороны означает, что обе стороны договорились прекратить обмен данными в одном направлении. Начиная с этого момента одна из сторон соединения будет молчаливо принимать данные, не делая никаких замечаний по их поводу, а другая молчаливо посылать, не ожидая никаких комментариев. Окончание (закрытие) TCP-соединения — двухступенчатый процесс. Одна сторона выполняет активное закрытие, а другая — пассивное. Закрытие активно, если вызвано по инициативе данной стороны, и, наоборот, пассивно, если вызвано противоположной стороной соединения. Сторона, первой высылающая пакет с установленным флагом «окончание соединения», является активной. Как правило, модуль TCP, принявший пакет с установленным флагом «окончание соединения», инициирует пассивное окончание соединения. Это просто значит, что пассивная сторона также посылает сообщение с установленным флагом окончания. Другими словами, сторона, принявшая сообщение об окончании первой, отвечает: «Хорошо, если тебе нечего больше послать, то и мне тоже нечего послать тебе». После того как обе стороны выслали друг другу сообщения об окончании и получили подтверждения о доставке этих сообщений, соединение TCP считается действительно законченным (закрытым). Что такое закрытие «наполовину»? Вы уже знаете, что в силу дуплексной природы TCP-соединения, в то время как поток данных в одну из сторон закончен, он может сохраняться в обратном направлении. Такое окончание лишь одного потока называется закрытием «наполовину» (half-close). Лишь очень немногие приложения TCP нуждаются или используют закрытие «наполовину». Однако если в ваши планы входит разработка приложений Интернет, эта возможность может когда-нибудь и пригодиться. 138 
Что такое транспортный протокол? Что такое заголовок TCP? I Из рис. 5.8 видно, что структура TCP-заголовка намного сложнее, нежели у UDP. В следующих абзацах изучается назначение полей, составляющих ТСРзаго ловок. Позиции ^ битов О 32 бита ► 15 16 31 Порт-передатчик (16 битов) Порт-приемник (16 битов) 32-битный номер последовательности 32-битный номер подтверждения Длина заголовка (4 бита) Зарезервировано (6 битов) U|A R;C g|k P(R SS HT NN Размер окна (16 битов) Контрольная сумма TCP (16 битов) Указатель на данные для неотложной обработки (16 битов) Опции (если есть) Заполнение (если нужно) i \ Область данных (необязательна) ] Рис. 5.8. Структура сегмента (сообщения) TCP Порт источника и порт получателя 16-битные поля источника и получателя однозначно определяют посылающие и принимающие данные приложения или прикладные протоколы. Номера портов источника и получателя в совокупности с IP-адресами сетевых компьютеров (в IP-заголовке) однозначно идентифицируют любое TCP-соединение. Каждая из сторон TCP-соединения называется сокетом (socket). Номер последовательности 32-битное поле номера последовательности обозначает первый байт данных из области данных сегмента TCP. Оно соответствует смещению этого байта относительно начала потока данных. Каждый байт в потоке данных может быть идентифицирован при помощи номера последовательности. Номер подтверждения 32-битное поле номера подтверждения обозначает байт данных, который принимающая сторона рассчитывает получить следующим в потоке данных. Например, если последний принятый байт имел номер 500, модуль TCP установит номер подтверждения равным 501. 139 
Глава 5. Транспортные протоколы | Щ 1 Ш|| И Длина заголовка Как и в заголовке IP, поле длины заголовка TCP состоит из четырех битов, обозначающих длину заголовка, измеренную в 32-битных словах. Как и заголовок IP, заголовок TCP обычно имеет длину в 20 байтов. Область данных начинается сразу после заголовка TCP. Таким образом, модуль TCP определяет место, где начинаются данные и кончается заголовок, просто прибавляя поле «длина заголовка», умноженное на четыре байта (32 бита), к первому байту сегмента данных. Флаги Заголовок TCP содержит шесть однобитных полей флагов. С тремя из них мы уже встречались. Это флаги синхронизации (SYN), подтверждения (АСК) и флаг окончания соединения (FIN). Следующие абзацы описывают оставшиеся три. Флаг URG Данный флаг сообщает принимающему модулю TCP о том, что указатель на данные, требующие немедленной обработки, в поле «неотложные данные» установлен, то есть указывает на них. (Модуль TCP должен обработать неотложные данные до того, как обрабатывать что-либо еще.) Флаг АСК Установленный флаг сообщает принимающему модулю TCP, что поле «номер подтверждения» содержит правильный номер подтверждения. Вы знаете, что данный флаг служит обеспечению надежной передачи данных. Флаг PSH Установленный флаг PUSH требует от принимающего модуля TCP вытолкнуть (push), то есть немедленно выслать принятый сегмент данных приложению-получателю. Как правило, модуль TCP буферизует принимаемые данные. То есть он не доставляет каждый сегмент по отдельности, а ждет, пока его буфер наполнится, а затем доставляет все принятые сегменты за один раз. Флаг PSH запрещает размещать сегменты данных в буфере. Telnet, например, устанавливает этот флаг. Нажатия на клавиши пользователем незамедлительно попадают на сервер Telnet, с которым он работает. Такое поведение устраняет возможные задержки в выдаче эха от сервера — большинство пользователей Telnet желают сразу видеть на экране то, что они печатают. Флаг RST Данный флаг запрашивает у принимающего модуля TCP сброс соединения. TCP устанавливает флаг RST, если с соединением случилась какая-либо проблема. Большинство приложений просто прекращает работу, приняв этот флаг. Флаг RST может применяться в сложных разработках для контроля повреждений в сети, сбоев в работе оборудования и сетевых программ. 140 
Флаг SYN Флаг SYN просит принимающий модуль TCP синхронизировать номера последовательности. Вы уже знаете, что этот флаг используется на этапе установления соединения, чтобы сообщить приемнику TCP о том, что источник готовится передать новый поток данных. Флаг FIN Флаг сообщает принимающему модулю TCP о том, что источник закончил передавать данные. Вы знаете, что этот флаг заканчивает соединение только в том направлении, в каком был послан. Чтобы закончить соединение полностью, принимающий модуль TCP должен также послать сообщение с установленным флагом FIN. Размер окна 16-битное поле «размер окна» сообщает принимающему модулю TCP количество байтов, которое собирается принять передатчик. Вы уже знаете, что TCP использует скользящие окна переменной длины для увеличения производительности и оптимизации пропускной способности сети. Значение данного поля определяет размер этого скользящего окна. Как правило, оно равняется нескольким тысячам байтов. Контрольная сумма TCP Как и в случае UDP, 16-битное поле контрольной суммы TCP содержит сумму, вычисленную по области данных. Протокол требует от передатчика, чтобы он включил вычисленную контрольную сумму в поле, а от приемника — чтобы он вычислил ее повторно и сравнил результаты. Примечание: Контрольные суммы UDP и TCP вычисляются похожим образом. Однако в случае UDP включать контрольную сумму в датаграмму не обязательно. Напротив, протокол TCP обязывает вставлять контрольную сумму в каждый переданный сегмент данных. Указатель неотложных данных 16-битное поле указателя определяет положение байта данных в области данных сегмента TCP. Указатель и флаг неотложных данных извещают принимающий модуль TCP о том, что некоторые, требующие немедленной обработки данные находятся в сегменте и указывают модулю на них. Никто, однако, так и не дал исчерпывающего ответа на вопрос, что же такое неотложные данные. Никто не определил ответственность модуля TCP за их обработку. Даже вопрос, на что же, собственно, обращает внимание указатель на неотложные данные, требует более основательного обсуждения. Дуглас Камер во втором издании классического труда «Межсетевое взаимодействие сетей на базе TCP/IP» (Internetworking with TCP/IP, Volume 1, 141 
Prentice Hall, 1991) обсуждает поле «неотложные данные» в разделе 12.12, «Данные вне основной полосы пропускания» (Out of Band Data). С другой стороны, Ричард Стивенс в разделе 20.8 своей замечательной книги «TCP/IP в иллюстрациях» (TCP/IP Illustrated, Volume 1, Prentice Hall, 1994) пишет следующее: 1 «...во многих сетевых приложениях данные для неотложной обработки TCP неправильно называются «данными вне основной полосы пропускания». Стивенс полагает, что эти приложения совершенно неоправданно смешивают разные понятия: данные вне полосы пропускания и данные для неотложной обработки. И он пытается объяснить причину возникновения такой ситуации: «Путаница в связи с неотложными данными TCP и данными вне основной полосы пропускания возникает из-за того, что предпочитаемый большинством интерфейс прикладного программирования (API) сам по себе относит данные «для неотложной обработки» к категории данных «вне основного диапазона». Относительно точного местоположения данных для неотложной обработки Стивенс делает следующий комментарий: «Идет продолжительный спор о том, должен ли указатель неотложных данных указывать на последний байт этих данных или на байт, следующий за последним. Первоначальный стандарт TCP позволял толковать это двояким образом, однако RFC, посвященный обязательным рекомендациям для сетевых компьютеров, вносит ясность в это дело, утверждая, что указатель все-таки указывает на последний байт данных для немедленной обработки. Проблема, однако, в том, что большинство реализаций сетевых операционных систем (т. н. производные операционной системы Беркли) продолжают использовать неверное представление. Получается, что вполне лояльное по отношению к стандарту TCP сетевое приложение не сможет работать с большинством остальных сетевых компьютеров». Стивенс и Камер согласны друг с другом в том, что указатель неотложных данных указывает на их последний байт. Также Стивенс подчеркивает, что не существует способа, позволяющего определить местонахождение начала неотложных данных. Практически единогласно утверждается, что приложению Telnet необходимо передавать неотложные данные, поскольку ему приходится обрабатывать разного рода управляющие последовательности. В настоящее время очевидно, что от употребления режима неотложных данных TCP следует воздерживаться. Нужно либо быть уверенным, что все программы, работающие с вашим приложением, ведут себя корректно, либо вообще не использовать этот режим при работе в Интернет. 142 
Опции Так же как и у IP, заголовок TCP содержит необязательное поле «опции» (options). В ходе установления соединения модули TCP договариваются о максимальной длине сегмента (MSS) и устанавливают соответствующую опцию. Смысл максимальной длины сегмента тот же, что и у максимальной длины передаваемого блока (MTU) физического уровня сети. Максимальная длина сегмента определяет максимальный размер сегмента, который может быть передан по соединению TCP. TCP оптимизирует пропускную способность сети, увеличивая ее производительность. Опция максимальной длины сегмента позволяет воспользоваться самым большим размером блока данных, который еще можно передать. Опция MSS устанавливается только в тех сообщениях, в которых уже установлен флаг SYN. Однако опция MSS не является предметом обсуждения между обоими модулями. Каждый из модулей TCP просто сообщает другому тот MSS, который он в состоянии принять. Если модуль TCP по каким-либо причинам не передает MSS, его партнер считает, что нужно пользоваться MSS, равным по умолчанию 536 байтам. Что такое инкапсуляция? Как уже замечалось, разработка программного обеспечения для Интернет в целом ненамного отличается от разработки обычного программного обеспечения. Многоуровневая структура сети и наличие протоколов TCP/IP позволяет скрыть от разработчика ненужные детали функционирования сетевых программ. Сетевые протоколы берут большинство рутинной работы на себя. Вся сложность процесса доставки данных в Интернет заключена в достаточно простой сетевой интерфейс. Прикладные данные просто передаются из программы в стек протоколов, этот протокол передает их следующему и т. д. Вы знаете, что весьма полезно представлять, как происходит весь этот процесс. Однако для того, чтобы программировать приложения, достаточно знать детали, касающиеся только взаимодействия программы с верхними протоколами стека TCP/IP, переносящими данные, то есть интерфейс сетевого программирования. Эта и две предыдущие главы познакомили вас с различными уровнями сети на базе TCP/IP. В этих главах также описываются интерфейсы между сетевыми уровнями и протоколами TCP/IP. Процесс перемещения данных сквозь стек протоколов на самом деле представляет собой их инкапсуляцию. Инкапсуляция данных — это их форматирование таким образом, чтобы они удовлетворяли тому или иному протоколу. По мере прохождения сквозь стек протоколов данные инкапсулируются в тот формат, с которым умеет работать очередной сетевой уровень. На рис. 5.9 процесс прохождения данных сквозь стек протоколов изображен целиком. Разработка структуры и функций приложения Интернет практически не отличается от разработки любого другого приложения. Решив передать информацию по Интернет, вы сначала решаете, каким протоколом удобнее воспользоваться. Данные будут инкапсулированы в выбранный протокол, отвечающий вашим 143 
Глава 5. Транспортные протоколы -NW Данные пользователя NNN Заголовок прикладных данных —ш NJNN— Данные пользователя ч//У— Прикладное сообщение Заголовок TCP -NNV Прикладные данные *444 Сегмент TCP Заголовок IP Заголовок TCP -ANV- 1Р-датаграмма - Прикладные данные *44+ Заголовок Ethernet Заголовок IP Заголовок TCP -ANV- Сетевой уровень Прикладные данные ш 1 Окончание] кадра Ethernet! I Уровень 14 байтов: 20 байтов 20 байтов Переменная длина < ! Кадр Ethernet ———— 4 байта -4- от 46 до 1500 байтов Драйвер I Ethernet | "^^^гоедйнения Кабель передачи данных Ethernet Рис. 5.9. Инкапсуляция данных по мере их прохождения сквозь стек протоколов требованиям. Для правильного выбора протокола необходимо знать, какие из них есть в вашем распоряжении и какими особенностями они обладают. Чтобы правильно инкапсулировать данные, необходимо представлять структуру данных выбранного протокола. Надеемся, что три последние прочитанные главы помогут вам понять этот процесс и все детали, необходимые для выполнения поставленных задач. Что такое прикладной уровень? Вам наверное уже понятно, что именно происходит на прикладном сетевом уровне. Он включает в себя все, касающееся непосредственно решаемой прикладной задачи. Другими словами, в качестве программиста приложений Интернет вы, разрабатывая программу, вместе с тем конструируете и прикладной уровень. Являясь прикладным программистом, вы, по определению, занимаетесь разработкой прикладных программ. Конструирование программы тесно связано с выполняемыми функциями. Например, коль скоро вам необходимо написать сетевую программу, вам требуется обменяться данными с другим приложением Интернет. Для успешной разработки приложения Интернет необходимо знать, 144 
как принимать и посылать сетевые данные. Приступая к написанию, задайте себе вопрос: «Каким образом моя программа будет обмениваться данными с Интернет?» И вы уже знаете ответ: просто посылая информацию вниз по стеку протоколов. Ваша ответственность за доставку данных заканчивается, как только прикладная программа передаст их низ лежащему протоколу. Далее каждый последующий уровень в стеке протоколов, сквозь который пойдут данные, будет выполнять свою собственную функцию: определять адрес, маршрут и транспортировать данные по Интернет. Чтобы задействовать определенный протокол, необходимо для начала знать, какие из них доступны в вашей системе. Также необходимо знать, где именно в стеке они находятся и понимать выполняемые ими функции. Как правило, прикладные программы взаимодействуют с протоколами UDP и TCP. Подводя итоги В этой главе вы узнали, как транспортные протоколы TCP/IP доставляют данные к прикладным программам через порты протоколов. Вы узнали, что в большинстве случаев требуется пользоваться TCP, а не UDP, так как первый обеспечивает надежную связь между приложениями. В добавок ко всему вы узнали, каким образом скользящее окно позволяет TCP увеличивать производительность соединения и управлять потоком данных между приложениями. В шестой главе вы познакомитесь с двумя протоколами специального назначения: для работы по последовательным линиям (SLIP) и для соединения «точка-точка» (РРР). Вы узнаете, как Интернет использует эти два протокола уровня соединения для того, чтобы преобразовать данные в набор кадров, то есть в вид, пригодный для переноса по последовательному соединению, каким является, например, модем, соединенный с обыкновенными телефонными проводами. До того как перейти к шестой главе, убедитесь, что вы хорошо усвоили следующие понятия: S Порт протокола TCP является адресом приложения в сетевом компьютере. S Транспортные протоколы общаются с прикладными программами при помощи портов. S TCP обеспечивает надежную доставку данных, включая контрольную сумму в сегмент TCP и обмениваясь сообщениями-подтверждениями о доставке. S Скользящее окно позволяет модулю TCP не ждать прихода подтверждения на каждый сегмент, а передать их сразу несколько. S Скользящее окно переменного размера позволяет TCP увеличить пропускную способность сети и управлять потоком данных между приложениями. S Процесс установления соединения TCP состоит из трех стадий, а окончания — из двух. S Соединение TCP является дуплексным. Поэтому для полного окончания соединения требуется завершить обмен данными в обоих направлениях. S Когда TCP закрывает соединение «наполовину», поток данных продолжает идти только в одном направлении. 
Глав Протоколы SLIP и РРР Пользователи Интернет, работающие через поставщиков услуг Интернет (Internet Service Providers, ISP), представляют самый быстрорастущий сегмент сети. Прием и передача данных ведется при помощи модема, подключенного через последовательный порт компьютера к обычной телефонной линии. При работе в среде Windows используется один из двух существующих протоколов для работы по последовательным линиям связи: РРР или SLIP. Для того чтобы писать сетевые приложения, необходимо хорошо представлять себе ключевые моменты и различия между ними. Необходимо также иметь представление о производном от SLIP протоколе, который называется SLIP с компрессией (обеспечивающий сжатие данных). Прочитав эту главу, вы овладеете следующими ключевыми понятиями: ♦ Каким образом SLIP переносит (инкапсулирует) 1Р-датаграммы. ♦ Недостатки SLIP и почему они существуют. ♦ Каким образом SLIP с компрессией увеличивает производительность последовательного канала. 146 
♦ Как РРР переносит (инкапсулирует) данные. ♦ Каким способом РРР устанавливает ТСР-соединение. ♦ Как работает протокол управления соединением в РРР. ♦ Какие существуют опции в протоколе управления соединением в РРР. ♦ Как РРР использует протокол управления сетью для конфигурирования сетевых уровней, таких как IP-уровень сети TCP/IP. Широко используемый стандартный интерфейс RS-232 предназначен для использования на последовательных линиях связи. Стандарт RS-232 описывает применение интерфейса для последовательной передачи данных между компьютерами и периферийными устройствами, например модемами, принтерами и мышками. Интерфейс RS-232 использует разъемы с 25 контактами (DB-25) или 9 контактами (DB-9). Физические характеристики интерфейса позволяют иметь максимальную длину кабеля 50 футов (около 15 метров). На самом деле кабель хорошего качества может иметь длину в несколько сотен футов. Стандарт описывает назначение и временные параметры всех 25 линий кабеля. Однако вряд ли какая-нибудь реализация RS-232 использует более дюжины линий. Реализация RS-232 в персональных компьютерах требует обязательного наличия всего девяти из них. Нулевой бит в RS-232 передается положительным напряжением, а единичный — отрицательным. Как правило, RS-232 соединяет компьютер с модемом, а уж модем по телефонному кабелю соединяется с Интернет. Обзор протоколов обмена Для установления соединения по протоколу SLIP обычно используется модем, работающий по телефонной линии и подключенный к асинхронному, последовательному порту. Два компьютера, установившие такое соединение, обмениваются данными с паузами переменной длины. К сожалению, в телефонной линии всегда присутствуют помехи, иначе называемые шумом, поэтому устройства, подключаемые к телефонной сети, отличают данные от возможных помех, пользуясь различными параметрами связи. Когда вы используете модем и программное обеспечение для обмена данными, вы настраиваете определенные параметры связи, такие как скорость, размер данных, контроль четности и т. д. Для успешной работы двух модемов по последовательной линии (такой, как телефонная сеть) оба они должны быть одинаково настроены. Параметры связи часто указываются как скорость, за которой следуют прочие параметры, например, в таком формате: 8-N-1. Если вы имеете опыт работы с модемом, то наверное знаете, что цифра 8 здесь означает длину слова в битах, которым обмениваются модемы. В процессе обмена каждый модем передает или принимает одно такое слово в момент времени. 147 
Протоколы SLIP и Р Четность Контроль четности применяется компьютерами, модемами и другими устройствами для проверки целостности данных. В следующих абзацах объясняется, каким образом компьютер устанавливает бит четности и как передающие устройства используют его, чтобы проверить, не повредились ли данные при пересылке. Как известно, данные по последовательному каналу передаются в виде нулей и единиц (бит данных). Данные делятся на слова фиксированной и заранее известной длины. Обычно это восемь бит. Однако некоторые протоколы последовательного соединения требуют, чтобы к такому слову добавлялся еще один служебный бит, называющийся битом четности. При этом соблюдается одно из двух следующих правил. Правило нечетности: добавлять такой бит, чтобы количество единиц в любом слове всегда было нечетно. Правило четности: добавлять такой бит, чтобы количество единиц в словах всегда было четно. Например, представим себе модем, которому нужно передать, используя «нечетный» протокол, двоичное слово 10001010 в качестве байта данных. Очевидно, что 10001010 содержит три единицы, то есть количество единиц нечетно. В таком случае в качестве бита четности модем поставит ноль, сохранив общее количество единиц нечетным. Если следующим байтом данных для передачи будет 10001011, содержащий, как видим, четное количество единиц, то в качестве бита четности модем поставит единицу, сделав таким образом общее количество единиц в слове (пять) опять нечетным. Чтобы удостовериться, что данные не повреждены, модем на принимающей стороне просто подсчитывает количество единиц в каждом слове (пакете). Предположим, что модемы используют «нечетный» протокол для обмена данными. Тогда если принимающий модем обнаружит в каком-то слове четное количество бит, он будет знать, что в принятом слове ошибка. Знание этого позволит модему предпринять определенные действия. Он может просто выбросить пакет, либо попросить передающий модем повторить передачу поврежденного пакета. В параметрах связи типа 8-N-1 буква N означает отсутствие контроля четности. Работа без контроля четности значит, что к пакету данных бит четности не добавляется. Как было написано выше, при использовании асинхронных последовательных линий связи модемы передают данные пакетами с паузами переменной длины между ними. Следовательно, принимающий модем должен уметь определить начало и конец пакета. Множество протоколов связи используют так называемые стартовые и стоповые биты для того, чтобы модем правильно понимал поступающие данные. Стартовый бит, всегда равный единице, говорит компьютеру, что последующие биты представляют данные. Стоповый бит, всегда равный нулю, обозначает соответственно конец пакета данных. Стартовые и стоповые биты 148 
Стартовые и стоповые биты применяются, чтобы принимающая сторона могла отличить пассивное состояние линии от состояния передачи данных. Ведь когда линия не занята, ее состояние может быть расценено как длинная последовательность нулей. Перед тем как передать данные, модем посылает стартовый бит, говорящий: «Эй, приготовься принимать мои данные». После передачи собственно пакета данных модем посылает столовый бит для перевода линии связи опять в состояние «выключено». Пакет данных, посланный по протоколу 8-N-1, будет иметь длину в десять бит; один стартовый бит, восемь бит данных, ни одного бита четности и один столовый бит. Боды и биты в секунду Довольно часто встречается мнение, что термин «бод» равен скорости, измеренной в битах в секунду. Другими словами, люди считают, что скорость 1200 бод равна скорости 1200 бит в секунду (bits-per-second, bps). Однако это неправильно. Как мы уже писали, модемы и другие передающие устройства посылают данные пакетами по восемь бит, вложенные между одним стартовым битом, одним стоповым битом и часто снабжаемые битом четности. Таким образом, каждый пакет данных имеет длину в десять или одиннадцать бит. Например, линия связи со скоростью 1200 бод передает в действительности от 110 до 120 байт в секунду. Аналогично, модем на 9600 бод передает от 850 до 960 байт в секунду. Используя технологии сжатия данных, новые модели модемов могут достигать очень высоких скоростей на тех же линиях связи. Повстречавшись с термином «бод», вы можете с большой долей точности заменить его на «бит в секунду». Однако не забывайте, что при этом речь ведется не только о битах данных, но также и обо всех служебных битах, которые мы рассмотрели, не несущих полезной информации, а только облегчающих передачу данных по линии связи. Если модемы используют сжатие данных, действительная скорость передачи может превысить скорость линии, измеренную в битах в секунду, на 200 процентов! Соединение по протоколу SLIP Протокол SLIP пользуется наибольшей популярностью для входа в Интернет начинающих пользователей с их домашних или рабочих компьютеров. Чтобы использовать SLIP, вы должны иметь соответствующее программное обеспечение, способное установить соединение по этому протоколу между вашим компьютером и хостом Интернет. Программное обеспечение такого рода (оно часто называется TCP-manager) выполняет функции управления сетевым устройством, то есть является драйвером сетевого устройства, такого, как модем. Вы загружаете и выгружаете программу управления SLIP по мере надобности. Примечание: Большинство программ управления SLIP имеют возможность набирать телефонный номер вашего поставщика услуг Интернет. 149 
ШЕФ» шп ■f.TW, те ш<ттпыт№ * РРр ■Л%аШй>1 . Ill || :;-. ’ Установление SLIP-соединения Соединение по притикилу SLIP — это наиболее экономичный и простой способ подключи ib ваш компьютер к Интернет. SLIP можно использовать, если ваша локальная сеть не имеет прямого доступа к Интернет или вы хотите соединить отдельный компьютер. Для работы SLIP необходимо, чтобы ваш поставщик услуг Интернет также обеспечил протокол SLIP на своем узловом компьютере (хосте Интернет). В настоящее время практически все коммерческие поставщики услуг Интернет обеспечивают такую возможность. Просто взгляните в соответствующий раздел телефонной книги, и вы наверняка найдете телефоны нескольких таких поставщиков услуг в вашем городе. Некоторые книги, посвященные Интернет, также имеют приложения, где приведены списки поставщиков услуг Интернет по городам. Для начала позвоните одному из них, ближайшему к вам, и спросите, дает ли он возможность работать по протоколу SLIP. Скорее всего дает. Тогда попросите установить соединение SUP для вашего компьютера. После заключения договора поставщик услуг Интернет даст вам номера телефонов, где установлены его модемы, пароль и имя для входа в систему, а также 1Р-адрес.1 2Также вам подробно расскажут, как правильно запустить SLIP-программу для установления связи с хостом поставщика услуг Интернет. После того как вы позвонили на компьютер поставщика услуг и установили SLIP-соединение, можно использовать имеющиеся у вас Windows-приложения для работы с Интернет. Находящиеся в США поставщики услуг Интернет делятся на две категории — местные и междугородные. Соответственно и плата за соединение, кроме той части, которую взимает поставщик, может также включать и плату за междугородный звонок. Плата за SLIPсоединение (впрочем, это относится и к РРР-соединению, о котором мы расскажем позже) взимается на почасовой основе, либо она фиксируется и оплачивается за месяц работы. Внимательно изучите все варианты оплаты соединения и выберите наиболее подходящий для вас. Установив SLIP-соединение, можно попробовать запустить какую-либо программу для работы с Интернет, например ftp. Все Интернет-программы для Windows при этом должны находиться на вашем жестком диске. Если вы хотите передавать файлы, на вашем компьютере должна быть соответствующая программа ftp для Windows. Если вам хочется читать и посылать почту или новости, на вашем компьютере должны быть установлены соответствующие программы для обработки почты и новостей. Чтобы понять, для чего это требуется, необходимо уяснить, что же происходит внутри компьютера, когда установлено SLIP-соединение с Интернет. Возможно, вы знаете, что кроме SLIP-соединения для работы в Интернет используется также соединение в режиме терминала. При этом сетевой компьютер поставщика услуг используется в качестве вашего компьютера, подключенного к Интернет. Службы Интернет становятся доступны вам через этот хост, и вы работаете с ними через командный интерпретатор UNIX, либо оболочку, 1 Впрочем, в зависимости от применяемой технологии некоторые поставщики услуг не назначают IP-адреса, а используют так называемое динамическое присвоение адреса. — Примеч. перев. 2 В России на данный момент практика междугородных звонков почти полностью отсутствует, ввиду ее крайней дороговизны. Поэтому практически все пользователи звонят внутри города. — Примеч. перев. 150 
1рОТОКОЛу SLIP 4'.,..,j содержащую меню для выполнения программ Интернет. Например, для запуска программы Telnet (доступ в режиме терминала к удаленному хосту) вы выбираете опцию меню под названием Telnet, либо печатаете на клавиатуре telnet. Запустив таким образом Telnet, вы работаете с программой, которая на самом деле находится на сетевом компьютере вашего поставщика услуг. Другими словами, жесткий диск компьютера поставщика услуг должен содержать все TCP/IP программы, которыми вам вздумается воспользоваться, работая в Интернет. Ваш компьютер просто работает как терминал, передавая нажатия клавиш и принимая результаты выполнения с хоста поставщика услуг Интернет. Напротив, установив SLIP-соединение, вы превращаете собственный компьютер в узел Интернет, делая его полноправным членом сети с собственным IP-адресом и именем. При этом становится возможным выполнять на нем все TCP/IP приложения! Разумеется, для этого приложения должны быть установлены прямо на вашем компьютере. SLIP — это больше, чем просто доступ к сети Для начала вы захотите попробовать поработать с программами-клиентами, такими как Telnet, Finger или Ftp. На самом деле, обладая SLIP-соединением и, следовательно, IP-адресом, вы можете предоставить сервис Интернет другим пользователям сети. Представьте, что на домашнем или рабочем компьютере запущена программа Ftp-сервер. При этом любой другой пользователь Интернет получает возможность перекачивать файлы с вашего компьютера на собственный. Если он также работает по протоколу SLIP, а не в режиме терминала, то файл передается с вашего компьютера прямо на его собственный. Пользователь Интернет в Австралии принимает файл с компьютера, находящегося в Соединенных Штатах за плату, равную стоимости местного телефонного звонка к его поставщику услуг Интернет! Другими словами, соединившись по SLIP с Интернет, ваш компьютер может выполнять все функции настоящего (большого) узла Интернет. И все это реально без затрат на дополнительное оборудование. Вам нужен лишь компьютер и модем. Чтобы компьютер оставался подключенным к сети постоянно, вам нужно оплатить постоянное соединение по SLIP и получить постоянный IP-адрес. Такого рода соединение обычно оплачивается ежемесячно, кроме того, оплачивается стоимость первоначальной установки. Как видим, все это довольно просто. Кроме расходов на установку постоянного SLIP-соединения и приобретения компьютера с модемом, в дальнейшем вам остается только оплачивать ежемесячные счета. Все, о чем вам потребуется беспокоиться в дальнейшем — это программное обеспечение. Доступно огромное разнообразие коммерческих и некоммерческих программ для работы с Интернет. Если вам не годится что-либо или вы хотели бы улучшить уже кем-то написанное, второй раздел этой книги — для вас. Он научит основным понятиям и приемам программирования для Интернет. 151 
: , Глава 6. Протоколы SHIP и РРР Инкапсуляция данных на уровне соединения Как было описано в главе 4, каждый уровень набора протоколов TCP/IP инкапсулирует (вставляет) данные в том формате, в котором они требуются для передачи окружающим уровням. При путешествии данных через стек протоколов TCP/IP они последовательно окружаются дополнительной информацией (инкапсулируются) для следующего на пути уровня. Для того чтобы послать IP-датаграмму через сетевой уровень, вышележащий уровень соединения должен соответствующим образом инкапсулировать данные, оформить их в кадр, как того требует стандарт сетевого уровня протокола TCP/IP. Например, уровень соединения для сети Ethernet инкапсулирует данные в кадр Ethernet. Для сети token-ring соответственно это будут кадры стандарта token-ring. Стандарты передачи данных по последовательному каналу связи, SLIP и CSLIP (SLIP со сжатием данных) просто определяют другой способ инкапсуляции. SLIP и CSLIP подготавливают данные для передачи по последовательному каналу (обычно это интерфейс RS-232) в Интернет. Протокол РРР также инкапсулирует данные для этой же цели. Как вы узнаете позже, РРР использует более сложный метод инкапсуляции и интерфейс с Интернет, нежели SLIP. Однако канал передачи для них всех по-прежнему последовательный и двухточечный. Логически, SLIP и РРР находятся между последовательным портом компьютера и его программным стеком TCP/IP. Что такое SLIP? Вы уже знаете, что протоколы семейства TCP/IP могут работать, пользуясь широким спектром разнообразных сетевых технологий. Большинство сетевых технологий требуют применения четко определенной структуры кадра данных. Институт электрической и электронной инженерии (IEEE), основанный в 1963 году и имеющий в своем составе более 300 000 членов, описал набор различных стандартов, облегчающих производителям ПО и оборудования разработку и применение совместимых друг с другом стандартов по передаче данных, в том числе и в локальных компьютерных сетях. Как и большинство создающих стандарты организаций, IEEE нумерует исходящие документы. Группа стандартов IEEE 802 посвящена локальным компьютерным сетям. Например, стандарт IEEE 802.1 посвящен методам управления сетью. IEEE 802.3 и IEEE 802.5 описывают физические уровни для сетей Ethernet и token-ring. IEEE 802.2 содержит спецификацию уровня соединения для сетей типа Ehternet, token ring и ряда других технологий. Преобразование (инкапсуляция) данных для передачи по последовательным каналам связи описано в документе под названием RFC 1055, «Л Nonstandard for Transmission of IP Datagrams Over Serial Lines: SUP», Romkey, 1988. RFC 1055 не является официальным стандартом Интернет. Он описывает стандарт де-факто. Это значит, что, хотя сообщество Интернет и не рассматривает RFC 1055 в качестве стандарта, любой желающий, чтобы его программное обеспече¬ 152 
ние обладало совместимостью с уже существующими методами передачи, должен воспользоваться рекомендациями документа в своей работе. SLIP — это протокол инкапсуляции IP-пакетов в кадры, пригодные для передачи по последовательному каналу связи. SLIP не предоставляет возможности адресовать данные, обозначать типы кадров, корректировать или определять повреждение данных, а также сжимать пакеты. Отсутствие этих возможностей делает протокол чрезвычайно простым в реализации и, следовательно, популярным. Несмотря на популярность, фирмы и производители программного обеспечения редко используют SLIP в качестве стандартного протокола, так как он не является официальным стандартом Интернет. Как правило, в качестве такого стандарта применяется РРР. Протокол РРР — это действительно стандарт Интернет, обладающий теми же свойствами по передаче данных в последовательном двухточечном канале, что и SLIP. Инкапсуляция данных SLIP Каждый протокол обладает свойством инкапсулировать данные. SLIP здесь не является исключением. Он использует специальные символы для ограничения кадра данных в последовательном канале. SLIP определяет следующие два символа, служащие для этой цели: End и Esc. Символом End служит символ с кодом ASCII 192 (ОхСО), символом Esc — символ с кодом 219 (OxDB). Компьютер с протоколом SLIP передает символ End в конце каждого пакета данных. Символ Esc используется для обозначения данных, имеющих тот же номер, что и символы Esc и End внутри пакета данных. В том, что для Esc и End выбрали именно указанные коды, нет особого скрытого смысла. Просто они были выбраны, и все. Поэтому почти наверняка в потоке данных пользователя будут встречаться как символы Esc, так и End. Когда это происходит, SLIP использует Esc, чтобы сообщить приемнику, что следующий символ с кодом End на самом деле не является концом кадра. Например, когда в пакете данных попадается байт с номером ОхСО (код символа End), SLIP подставляет двухбайтную Esc-последовательность Esc OxDC. Если байт имеет код самого символа Esc, SLIP вставляет двухбайтную Esc-последовательность Esc OxDD. Реализация SLIP на принимающей стороне совершает противоположные действия, чтобы правильно разобрать поступающий пакет данных. Если в последовательности встречается символ Esc, SLIP сразу же смотрит на следующий за Esc символ и в зависимости от его номера так или иначе интерпретирует принятую последовательность. Например, если следующий за Esc символ имеет код OxDC, SLIP заменяет два символа на один с кодом ОхСО. Если принято сочетание Esc OxDD, оно заменяется на байт с кодом OxDB. Когда SLIP видит, что пришедший байт имеет код End и перед ним нет байта с кодом Esc, это значит, что достигнут конец кадра. Далее, SLIP передает все полученные до этого данные вышележащему сетевому уровню в качестве IP-пакета. Представим себе IP-пакет, состоящий из двух байт. Пусть первый байт имеет код Esc, а второй — код End. На рис. 6.1 показано, каким образом из такого пакета получится кадр данных протокола SLIP. SLIP заменит два байта описанным выше образом на две Esc-последовательности. 153 
■s Глава 0, Протоколы SUP и РРР JS& W i 1Р-пакет Данные Данные Esc-последовательность Esc-последовательность < Кадр SLIP Рис. 6.1. IP-пакет с символами Esc и End внутри кадра данных V J Большинство реализаций SLIP посылают байт с кодом End также и впереди кадра данных. Строго говоря, протокол SLIP не требует этого. Однако, поступая таким образом, SLIP позволяет принимающей стороне эффективно отбросить любой мусор, принятый до передачи действительного кадра и расценивающийся как кадр. Реализация SLIP, действующая описанным образом, позволяет отбросить кадр данных нулевой длины, когда принимающая сторона получает два следующих друг за другом символа End. На рис. 6.2 показано, как оформляются два IP-пакета в реализации SLIP, применяющей вставку символа End в начало кадра данных. \ IP-пакет ► { IP-пакет ► I; . ■■■•J jf Кадр SLIP >; [ fP Конец Конец Конец Конец _ Все данные в этом интервале будут отброшены Рис. 6.2. Реализация SLIP, вставляющая символ End в начало кадра данных Коррекция ошибок в протоколе SLIP Как мы уже отметили выше, вставка символа End перед началом кадра позволяет принимающей стороне избавиться от любого шума на линии связи. Однако такими мерами все способности SLIP определить и тем более исправить ошибки данных исчерпываются. SLIP возлагает задачу по определению и исправлению пакетов данных и сообщений полностью на вышележащие протоколы, то есть на сетевой и транспортный уровни TCP/IP. В главе 3 отмечалось, что протокол IP требует присутствия и проверки контрольной суммы в заголовке пакета, поэтому SLIP вполне может не обращать внимание на возможное повреждение данных — эту работу за него сделает протокол IP, который проверит пакет и отбросит его в случае повреждения. TCP протокол таким же образом проверит контрольную сумму своего заголовка и сегмента данных и в случае повреждения 154 
■ . '• ■ mu ш ■ ft ft 18Ш поступит с пакетом надлежащим образом. Итак, мы видим, что, поскольку вышележащие протоколы и так проверяют состояние и целостность данных, нет никакой необходимости вводить дополнительный контроль данных на уровне протокола SLIP. SLIP плохо переносит UDP В предыдущем абзаце мы повторили тот факт, что IP и TCP обнаруживают и корректируют ошибки, могущие возникнуть при передаче данных к ним с нижележащих уровней, например от протокола SLIP, который сам не обнаруживает ошибок. Поскольку в UDP не применяется контрольных сумм, никто не может гарантировать, что пакет UDP дойдет до получателя по SLIP неповрежденным, коль скоро SLIP тоже не обнаруживает ошибки. Вообразите, что вы передаете пакеты UDP по шумной телефонной линии, пользуясь SLIP. Ни один из этих протоколов не обратит внимания на возможный сбой и повреждение данных. Компьютер, получивший поврежденный пакет, с чистой совестью будет считать его нормальным, и такое поведение сможет привести к непредсказуемым последствиям. Запомните, что нельзя передавать датаграммы UDP по протоколу SLIP, если только вы не используете UDP с контрольной суммой. Недостатки SLIP Кроме отсутствия обнаружения и коррекции ошибок, в протоколе SLIP отсутствуют еще некоторые достаточно важные для профессиональных сетевых программистов функции. Например, SLIP не в состоянии адресовать пакеты, обозначать пакеты различными типами, а также сжимать данные внутри пакета. В RFC 1055 прямо указано, что создатели SLIP разрабатывали его, когда наличие таких функций не было существенно. Отсутствие возможности адресации Каждый раз после установления SLIP-соединения компьютер превращается в полноправный хост Интернет со своим собственным IP-адресом. Таким образом, становится возможным обслуживать и других пользователей Интернет. Поскольку ваш поставщик услуг Интернет может применять динамическое присвоение адреса (из диапазона, имеющегося у него), при каждом новом соединении ваш компьютер будет получать новый IP-адрес. Следовательно, другие компьютеры в сети будут вынуждены искать вас каждый раз под неизвестно каким адресом. В дополнение ко всем неприятностям отметим, что не существует метода прямо указать ваш новый IP-адрес при установлении SLIP-соединения. Каждый раз вы вынуждены вручную вводить изменившийся адрес в компьютер, поскольку один компьютер не может автоматически передать IP-адрес другому, пользуясь SLIP. Из такого положения есть только один выход: получить у поставщика услуг один, принадлежащий только вам IP-адрес компьютера. Как правило, 155 
SLIP и PPP РКШ: л ,л иметь такой адрес обойдется вам дороже, чем иметь динамически присваиваемый. Примечание: Некоторые реализации протокола SLIP обладают способностью определять IP-адрес вашего компьютера автоматически. Однако это происходит перед установлением SLIP-соединения, когда вы только дозвонились до компьютера поставщика услуг Интернет и находитесь в режиме терминала. Программное обеспечение поставщика услуг печатает присваиваемый вам IP-адрес на ваш экран, а SLIP-программа перехватывает его и передает дальше в стек TCP/IP компьютера. В любом случае такая возможность не является заслугой протокола SLIP. SLIP не различает пакеты разных типов На свете есть много компьютеров, в которых в одно и то же время может исполняться несколько различных сетевых протоколов. Например, компьютеры фирмы DEC могут совмещать TCP/IP и DECnet. Разумеется, работая с двумя протоколами сразу, вы захотите, чтобы они жили вместе на одном и том же проводе, соединяющем вас с внешним миром. Такая задача проста, пока вы применяете Ethernet в качестве сетевой среды. Фреймы Ethernet имеют соответствующие поля, где указывается тип передающегося пакета, однако как только вы попытаетесь перейти на SLIP, обнаружится, что у кадра SLIP такое поле отсутствует, а, следовательно, он может передать данные только для одного IP протокола. SLIP не сжимает данные Сети Ethernet передают информацию со скоростью до 10 миллионов бит в секунду. Соединение SLIP может работать на скоростном модеме, но даже при этом обеспечивать скорость только 19200 бит в секунду. Другими словами, Ethernet быстрее SLIP более, чем в пятьсот раз. Для увеличения производительности SLIP-соединения вы можете сжимать передаваемые по модему данные, что уменьшает необходимый трафик сети и позволяет передать больше информации за меньшее время. Предположим, требуется передать файл размером в 100 Кбайт (100x1024 байт) по модему на скорости 1200 бод. Для этого потребуется около 14 минут: 100 х 1024 = 102400 байт 102400 байт /120 байт в секунду = 853 секунды 853 секунды / 60 секунд в минуте =14 минут Если передаваемые данные предварительно сжать в соотношении 1:4, объем уменьшится до 25 Кбайт. Время, нужное для передачи, сократится до четырех минут. Новые модемы используют встроенную технологию сжатия данных. Некоторые программные протоколы также используют сжатие данных при работе. Информация в заголовках пакетов TCP и IP, которая меняется редко, может быть эффективно устранена с применением простейших алгоритмов 156 
- ' (CSLIP) сжатия (компрессии) данных, когда передаются только изменяющиеся части заголовков. RFC 1055, описывающий протокол SLIP, не описывает, однако, никакого алгоритма компрессии. В следующем разделе вы познакомитесь с реализацией протокола CSLIP, обладающего возможностью сжимать заголовки TCP/IP для увеличения производительности. Протокол SLIP со сжатием (CSLIP) Алгоритм SLIP со сжатием заголовков данных, увеличивающий производительность сети, рассматривается в документе под названием RFC 1144, «Сжатие заголовков TCP/IP на низкоскоростных последовательных соединениях» (Compressing TCP/IP Headers for Low-Speed Serial Links, Jacobson, 1990). Примечание: В некоторой литературе протокол SLIP со сжатием данных называется еще и Van Jacobson CSLIP compression (компрессия Ван-Джекобсона), по имени создателя алгоритма. В описании протокола РРР используется название «Van Jacobson TCP/IP header compression» («компрессия заголовков TCP/IP Ван-Джекобсона»). Протокол CSLIP сжимает только заголовки пакетов. Сами данные пакета остаются неизменными. Точнее, CSLIP сжимает исключительно заголовки TCP и IP для сегментов данных TCP. CSLIP не затрагивает ни заголовки пакетов UDP, ни заголовки IP для них. Разработано достаточно много различных реализаций протокола CSLIP, поэтому вам скорее всего не понадобится изобретать новую. Вероятнее, вам придется конструировать алгоритмы передачи сетевой информации по последовательному каналу, а здесь не обойтись без знания эффективной методики по устранению избыточной информации из пакетов данных. CSLIP хорошо иллюстрирует применение одного из таких алгоритмов, пригодных для передачи информации не только по последовательному каналу. Примечание: Многие реализации SLIP поддерживают также и CSLIP. Если ваш вариант программы тоже позволяет устанавливать CSLIP-соединение, лучше всего будет использовать именно его. В худшем случае, соединение не будет работать, и вам останется перейти обратно к простому SLIP-соединению. Предпосылки к появлению CSLIP Чтобы понять, почему сжатие заголовков пакетов столь эффективно, давайте рассмотрим некоторые типичные сетевые задачи. • Интерактивный вход в удаленный компьютер (Telnet). • Интерактивная передача файлов (FTP). • Электронная почта с использованием Simple Mail Transfer Protocol (SMTP). • Чтение и передача новостей с использованием Network News Transfer Protocol (NNTP). 157 
Глава 6. Протоколы SUP и РРР Как и любая другая линия связи, последовательная линия переносит пакеты данных пользователя, снабженные заголовками. Для увеличения пропускной способности линии не мешало бы сжимать заголовки пакетов. Способы передачи пакетов по сети делятся на две большие категории: интерактивные и неинтерактивные. Мы покажем позже, что эффективность канала связи зависит от типа передачи пакетов. Прекрасными примерами неинтерактивной передачи пакетов служат два протокола: Ftp и NNTP. Разумеется, начальная стадия работы обоих процессов включает их ручную настройку и передачу параметров. Однако все, что происходит потом — это перекачка информации с одного сетевого хоста на другой, не требующая вашего вмешательства. При запуске ftp с вашего компьютера вы указываете имя файла для передачи, а потом сидите и ждете, пока поток байтов, составляющих этот файл, попадет с другого хоста на ваш собственный. Точно так же вы выбираете группу новостей в приложении NNTP и ждете, пока все новости с сервера передадутся на ваш компьютер. Все это примеры неинтерактивной передачи пакетов. Типичным примером интерактивной передачи информации служит Telnet. Каждое нажатие на клавиатуре пользователя обычно приводит к посылке пакета, содержащего код введенного символа, на удаленный сетевой хост. Несмотря на то, что многие реализации Telnet умеют передавать сразу всю введенную строку символов, обычно эта возможность не используется, ибо пользователь хочет получить незамедлительную реакцию удаленного компьютера на введенный символ. Кроме того, удаленный компьютер посылает пакет-подтверждение с копией введенного символа обратно пользователю. В общем, Telnet создает двунаправленный поток данных, состоящий из маленьких пакетов. Из главы 4 вы узнали, что обыкновенно IP-заголовки имеют длину в 20 байт. Из главы 5 — что заголовки TCP имеют длину также в 20 байт. Отсюда следует, что сеанс Telnet создает пакеты данных длиной в 40 байт заголовков для каждого переданного символа в один байт. Для понимания принципа работы CSLIP нужно усвоить два различных, но тесно связанных понятия: эффективность линии и интерактивная реакция системы. Эффективность линии — это коэффициент, равный длине заголовка TCP/IP пакета, деленной на длину заголовка плюс длину данных пользователя в этом пакете. Мы сейчас вычислим эффективность линии для сеанса Telnet. 1. Предположим, что программа Telnet передает один пакет на одно нажатие клавиши, которое, в свою очередь, состоит из одного символа длиной в байт. 2. Пакет данных, содержащий символ длиной в байт и снабженный TCP/IP заголовками (еще 40 байт), будет иметь длину в 41 байт. 3. Приемник пакета должен послать обратно подтверждение о доставке, и это будет пакет такой же длины в 41 байт. Таким образом, одно нажатие на клавишу приводит к передаче по сети двух пакетов общей длиной по 41 байт. (Мы помним, что пакет-подтверждение тоже содержит TCP/IP заголовки и переданный символ в качестве эха.) 158 
4. Теперь сосчитаем эффективность линии по приведенной выше формуле. (Она составит менее трех процентов.) Эффективность линии = Данные / (Размер_заголовка + Данные) Эффективность линии = 1 байт / (40 байтов + 1 байт) Эффективность линии = 1 байт / 41 байт Эффективность линии = 0.0244 Эффективность линии = 2.44 процента Примечание: Процесс передачи по TCP/IP дуплексный, так как пакеты данных следуют независимо друг от друга в обоих направлениях, поэтому эффективность линии считается независимо для обоих направлений. Впрочем, в нашем случае результат одинаков как для одного, так и для другого направления. Как следует из формулы, для увеличения эффективности линии надо либо увеличить количество данных в пакете, либо уменьшить размер заголовков. Алгоритм CSLIP концентрирует внимание на уменьшении размеров заголовков пакетов. Кроме того, CSLIP соблюдает требования интерактивной реакции системы. Интерактивность реакции системы — это просто ее свойство убедить пользователя в том, что все работает. Например, когда пользователь нажимает клавишу, он, вполне понятно, хочет увидеть, как введенный символ отобразится на его мониторе. Если работа сети приводит к ощутимым задержкам при передаче пакета, пользователь расценит интерактивность сети как неудовлетворительную. Человеческий фактор — еще одно понятие, которое следует иметь в виду при проектировании интерактивных систем. Для программиста важно, как человек отнесется к тому или иному поведению системы, с которой он работает. Алгоритмы CSLIP, безусловно, принимают в расчет и человеческий фактор тоже. Человеческий фактор В дополнение к такому понятию, как эффективность линии, CSLIP рассматривает человеческий фактор. Понятие человеческого восприятия тоже относится к человеческому фактору. В своей книге «Проектируя интерфейс пользователя: Стратегия эффективного взаимодействия человек-компьютер» (Designing the User Interface: Strategies for Effective Human-Computer Interaction, AddisonWesley, 1987) Бен Шнейдерман (Ben Shneiderman) приводит много замечательной, полученной экспериментальным путем информации о поведении пользователя за экраном компьютера. Например, выяснили, что нормальной задержкой в получении эха от нажатия клавиши можно считать интервал до 100 — 200 миллисекунд. По прошествии этого времени интерактивность системы считается пользователем неудовлетворительной. В RFC 1144 рассматривается, каким образом особенности передачи заголовков пакетов сетевых данных могут влиять на восприятие ситуации пользователем. Предположив, что каждый введенный символ приводит к появлению двух пакетов длиной в 41 байт, получим, что для обеспечения задержки эха не более 159 
чем на 200 миллисекунд, необходимо, чтобы скорость обмена составляла по меньшей мере 4000 бит в секунду. Другими словами, медленная последовательная линия заставляет пользователя думать, будто скорость работы программы мала, даже если программа вполне эффективна в действительности. Неинтерактивная передача пакетов также может влиять на интерактивную реакцию системы. Например, чтобы передача неинтерактивных пакетов обладала эффективностью более 90 процентов при длине TCP/IP заголовков в 40 байт, необходимо сохранять максимальную длину пакета (MTU) в диапазоне от 500 до 1000 байт. Предположим далее, что ваше соединение SLIP имеет MTU 1024 байт при скорости модема 9600 бод. При этом один пакет в одну сторону будет передаваться приблизительно в течение секунды. Любой интерактивный сеанс будет при этом ждать окончания передачи неинтерактивного пакета. Влияние аппаратных средств Кроме рассмотренного нами человеческого фактора, на проектирование протокола также влияют и некоторые особенности аппаратных средств. Производители модемов используют различные способы увеличения эффективности работы этих устройств. При программировании приложений нет необходимости знать досконально, что происходит внутри модема, однако нужно обратить внимание на некоторые вещи. Примечание: Теория связи оговаривает фактическую полосу пропускания между двумя устройствами. Эффективная полоса пропускания в зависимости от используемой техники позволяет увеличить (и превысить) фактическую полосу пропускания при сжатии передаваемых данных. Сжатие данных позволяет передать их больше за одно и то же время. В некоторых случаях достигаемая при этом скорость передачи превышает теоретический предел скорости канала связи. В дуплексном протоколе, характерном для модема, данные следуют в обоих направлениях одновременно. Однако для обоих направлений редко применяется одна и та же полоса пропускания, так как одна из сторон, участвующих в соединение, скорее всего передает больше данных, чем другая. Именно для нее отводится большая полоса пропускания за счет противоположной стороны. Распределение полосы пропускания происходит прозрачно для пользователя и управляется самим модемом. Чтобы определить, какая из сторон в соединении требует большей полосы, производители модемов считают, что одной из сторон всегда является человек, и именно она требует наибольшей полосы. Модем, однако, должен самостоятельно догадаться об этом. За отправную точку берется скорость в 300 бит в секунду. Большинство людей не могут печатать со скоростью, превышающей указанную. К сожалению, ситуация меняется, как только мы начинаем передавать пакеты TCP/IP с заголовками из сорока байт на каждый введенный символ. Скорость увеличивается в соотношении 40:1 и заставляет модем часто менять полосы в противоположных направлениях. IP-пакет размером в 41 байт состоит из 328 бит, что выходит за пределы, предписанные для узнавания человека 160 
i • Протокол SUP со сжатием |CSUP> модемом. При покупке модема следует обращать внимание на такие тонкости, как поддерживаемые типы сжатия данных и другие возможности по передаче данных. Покупка хорошего в этих отношениях модема позволит вам значительно увеличить производительность сетевого соединения. Помните, что ваш поставщик услуг Интернет иногда может дать вам хороший совет, объяснив, какая модель модема подойдет вам лучше всего и почему. Цели проектирования Современная архитектура модемов позволяет сократить потребность в скорости передачи нажатий клавиш до 300 бит в секунду и даже меньше. Если мы рассматриваем десятибитовую последовательность на один символ (восемь бит данных плюс старт- и стоп-биты), 300 бит в секунду образуют полосу пропускания в 30 байт данных в секунду. Обычная скорость печати на клавиатуре составляет 5 символов в секунду. Таким образом, для передачи заголовков остается 25 байт (30 - 5) при условии сохранения выбранной максимальной полосы пропускания в 300 бит в секунду. Другими словами, на один передаваемый символ допустимо передать еще и пятибайтовый заголовок. Кроме того, такая передача сохраняет хорошую интерактивность системы, так как пауза между нажатием и получением эха у нас не превысит 200 миллисекунд при скорости 4096 бит в секунду. На рис. 8 в RFC 1144 Джекобсон привел график соотношения между эффективностью линии и используемым MTU для скоростей в 2400, 9600 и 19200 бит в секунду. На нем видно, что увеличение MTU свыше 200 байт приводит лишь к незначительному увеличению эффективности соединения. Например, увеличив MTU с 200 до 576 байт, мы достигнем только трехпроцентного улучшения эффективности, увеличив при этом задержку интерактивного ответа для пользователя на 188 процентов. Джекобсон показывает, что оптимальным значением MTU является 200 байт. Работая с таким MTU на скорости 9600 бит в секунду, вы передадите символ и получите обратно эхо-пакет за время, не превышающее 400 миллисекунд. При этом соблюдается необходимое окно в 200 миллисекунд при передаче интерактивных пакетов. Реализация SLIP В RFC 1144 Джекобсон обсуждает методы, служащие для сокращения необходимой длины передаваемых заголовков с 40 байт на пакет до всего лишь трех-пяти. Джекобсон показывает, что на протяжении TCP-соединения около половины информации заголовка остается неизменной. Протокол CSLIP требует, чтобы после установки TCP-соединения хостами, они хранили у себя копии последнего принятого и переданного пакетов, и в дальнейшем, храня у себя номер текущего соединения, просто передает изменения в заголовках, позволяя собирать реальный заголовок на основе имеющейся неизменной части и принятого изменения. 6 Зак. № 1949 161 
, i. iij ■ < l idea 6. Протоколы SLIP и 11№ШИИЮШш Как только появляется новый CSLIP-пакет, сетевое ПО по идентификатору устанавливает, к какому соединению он относится и восстанавливает его в нормальном виде. Как видим, по CSLIP не передаются настоящие заголовки пакетов, что сокращает размер пакета сразу на 20 байт. Далее, CSLIP не передает поле IP заголовка «Общая длина пакета» (Total Length), получая его вместо этого от сетевого уровня соединения и сокращая длину еще как минимум на два байта. В заголовке IP-пакета остается только поле контрольной суммы заголовка, однако, как отмечает Джекобсон, нет никакой необходимости передавать контрольную сумму отсутствующих данных. Вместо передачи контрольной суммы заголовка CSLIP вычисляет ее на месте, в отличие от SLIP, который по-прежнему вынужден передавать контрольную сумму по каналу связи. Мы убираем контрольную сумму заголовка — и получаем еще два байта экономии. В результате остается еще 16 байт в заголовке пакета, которые могут изменяться на протяжении сеанса TCP/IP. Разумеется, они изменяются не постоянно, а лишь иногда. В RFC 1144 отмечается, что, скажем, протокол Ftp изменяет только идентификаторы пакетов (ID), номер последовательности и контрольную сумму в направлении от передатчика к приемнику. Идентификатор пакета, пакет-подтверждение, контрольная сумма и, возможно, окно передачи — вот что обычно изменяется по направлению от приемника к передатчику. Передатчик CSLIP всегда хранит копию последнего посланного пакета у себя. Таким образом, он знает, какие именно изменения произошли в следующем по счету пакете для передачи. Если передатчик шлет только изменившиеся байты, средний размер заголовка пакета становится равным примерно десяти байтам. Дальше Джекобсон показывает, что, зная, каким образом изменяются поля в заголовке, можно достичь еще большего сокращения его размера. Идентификатор пакета изменяется, как правило, на единицу при передаче каждого нового пакета. Что это значит? Что разность двух идентификаторов можно закодировать небольшим положительным целым числом, меньшим, чем 256 (один байт). Как правило, это число равно единице. Далее, для передатчика номер последовательности текущего пакета будет числом, полученным от сложения этого номера у предыдущего пакета с длиной предыдущего пакета. Максимальная длина IP-пакета равна 64000 байт, значит, изменение номера последовательности между двумя пакетами никогда не превысит двух байт. На этом этапе, посылая вместо реальных полей только их изменения, CSLIP экономит нам еще от трех до четырех байт дополнительно. Итак, мы сумели сократить размер TCP/IP заголовка с 40 байт до пяти, что и являлось поставленной целью. В разделе 3.2 RFC 1144 Джекобсон сообщает все детали для успешной реализации описанного протокола. Также он утверждает, что, приняв во внимание все особенности интерактивной и неинтерактивной передачи данных, можно уменьшить длину заголовков вообще до трех байт на пакет. Детали реализации в разделе 3.2 в общем случае не существенны, если только вам не хочется написать собственную реализацию CSLIP. Однако общие прин- 162 
.x-" ■ ~яЯГ.-.У-ЛДй У:-. У.» -УЛ.:*-:. ". У-: г:'"ЖГ; 1л\ х S х^ЧЛ l.> Ъ.\ у<- £" :Y, • Л V :< | ; •, js*< В .. « : ■ «• , , ципы сжатия пакетов годятся для любой сети с пакетами любого типа и могут сослужить вам большую службу, если перед вами встанет задача реализовать нестандартный протокол передачи сетевых данных. Общие принципы таковы: ваше приложение должно хранить у себя копии переданных и принятых заголовков пакетов. Оно должно передавать только изменения в заголовках, из которых другая сторона должна восстанавливать пакет в первоначальном виде. Примечание: Вышеописанный принцип передачи изменений заголовков еще часто называется более общим термином «дифференциальное кодирование». Протокол Point-to-Point (РРР) Все описанные недостатки SLIP (адресация, установка типа пакета и сжатие) устраняются в протоколе РРР, с которым мы сейчас и познакомимся. В отличие от SLIP, РРР является официальным стандартом Интернет. Поэтому производители программного обеспечения, так же как и другие коммерческие фирмы, применяют РРР в качестве базового стандарта для своих коммуникационных нужд. Протокол РРР состоит из трех основных частей: • Метод инкапсуляции данных, позволяющий на одном и том же канале связи использовать различные сетевые протоколы. • Протокол управления соединением (Link Control Protocol, LCP), которым пользуется программное обеспечение с целью установки, конфигурации и тестирования соединения. Обе участвующие в обмене стороны пользуются LCP для выбора приемлемых для них обоих свойств конкретного РРР-соединения. • Семейство протоколов управления сетью (Network Control Protocols, NCPs), позволяющее РРР-соединению использовать протоколы различных сетевых уровней. На сегодняшний день РРР применяется, пожалуй, реже, чем его собрат SLIP. Однако ситуация должна вскоре измениться. РРР предлагает пользователю значительные преимущества по сравнению с SLIP, поэтому в ближайшее время тенденция переменится в сторону РРР, тем более что все большее количество поставщиков ПО встраивает РРР в свою продукцию. Чтобы успешно писать сетевые приложения, использующие последовательные каналы связи, вы должны иметь хотя бы общее представление об его внутреннем устройстве и принципах работы. Настоящая глава и ставит целью дать вам такое представление. Более подробная информация о РРР содержится в трех документах: • Общая информация о РРР. RFC 1547, Requirements for an Internet Standard Point-to-Point Protocol, Perkins, 1993. • Описание РРР и LCP. RFC 1661, The Point-to-Point Protocol, Simpson, 1994. • Описание NCP для IP. RFC 1332, The PPP Internet Protocol Control Protocol (IPCP), McGregor, 1992. 163 
Инкапсуляция данных РРР Одной из целей при создании РРР было обеспечить работу нескольких сетевых протоколов по одному каналу связи независимо друг от друга. В RFC 1661 описана инкапсуляция РРР, используемая для этой цели, однако там не упомянута структура кадров, в которые упаковываются данные, поступившие из сетевого уровня соединения для передачи РРР. Кадр данных РРР При разработке кадра данных РРР был использован имеющий статус стандарта документ ISO 3309 под названием «Передача данных — Процедуры управления соединением высокого уровня — Структура кадра» («Data Communications — High-Level Data Link Control Procedures — Frame Structure»), выпущенный в свет в 1979 году и описывающий простой протокол уровня соединения под названием HDLC (High-Level Data Link Control). В HDLC определен тот же, что и в кадре SLIP, принцип маркировки начала и конца кадра специальными флагами. В HDLC, кроме того, применяется контрольная сумма CRC (Cyclic Redundancy Check) для обнаружения возможных ошибок. На рис. 6.3 приведен формат кадра РРР. Флаг Адрес fQx7]5ij OxFF Управ-! • лейие 0x03 | инкапсулированные РРР С/7Е J „ , „ 1 Байты 1 1 1 до 1500 байтов 2 1 Рис 6.3. Формат кадра протокола РРР Примечание: В документе ISO вместо термина Cyclic Redundancy Check, CRC (что значит «контроль при помощи циклического избыточного кода»), используется более специфический: «контрольная последовательность кадра» (Frame Check Sequence, FCS). Как видно из рисунка, в начале и конце каждого РРР-кадра помещается специальный флаг, имеющий значение 0х7Е. Поле адреса и управления всегда заполнены одними и теми же значениями: OxFF и 0x03 соответственно. Уровень соединения РРР использует символ 0x7D в качестве Esc-символа внутри кадра для обозначения символа 0х7Е (байт флага) или 0x7D (Esc-символ), которые могут иногда появиться среди данных пользователя. При появление внутри кадра символа 0х7Е, с тем же, что и у флага, значением, уровень соединения РРР подставляет вместо него двухбайтную Esc-последовательность 0x7D, 0х5Е. Вместо того чтобы передать 0х7Е, РРР передает Esc-символ (0x7D) и следующий за ним байт, получающийся как результат инвертирования шестого бита 0х7Е. В двоичном виде 0х7Е выглядит как 01111110, а 0х5Е равен 01011110 (обратите внимание на шестой бит). Таким же образом, если в потоке данных попадается байт 0x7D (Esc-символ), уровень соединения РРР 164 
. Протокол Point-to-Point (РРР) ШШЯШШя подставит вместо него Esc-символ и результат инвертирования шестого бита Esc-символа, то есть 0x5D. В двоичном виде 0x7D равен 01111101, a 0x5D равен 01011101 (и здесь обратите внимание на шестой бит символа). Множество модемов используют при работе так называемые служебные ASCIIсимволы (это те, коды которых меньше 0x20) для специальных целей. По умолчанию РРР кодирует такие символы точно таким же образом, что был описан выше. Кодирование управляющих ASCII-символов позволяет избежать возможных неприятностей, возникающих при неправильной их интерпретации оборудованием и программным обеспечением сетевых компьютеров. Например, некоторые модемы используют символ 0x1 В как собственный Escсимвол. Уровень соединения РРР заменит такой символ на Esc-последовательность 0х7Е), ОхЗВ. 0x1 В равен 00011011 в двоичном виде, ОхЗВ — 00111011. Как и прежде, подставленный символ получился путем инвертирования шестого бита 0x1 В. В данном случае из нуля получилась единица. В предыдущих примерах инвертирование приводило к замене единичного бита на нулевой. Тип кадра данных в РРР На рис. 6.4 показана структура пакета, вложенного в кадр данных РРР. Мы видим, что поле «Протокол» имеет ключевое значение в определении типа вложенных (инкапсулированных) данных. Оно образовано двумя байтами, следующими сразу в начале инкапсулированных данных, и определяет тип данных до конца кадра. Его значение 0x0021 означает, что инкапсулирована IP-датаграмма, то есть содержимое кадра предназначено для TCP/IP сети. Номер протокола 0хС021 обозначает, что кадр содержит данные протокола управления соединением (LCP). Вы помните, что LCP заведует установлением, конфигурацией и тестированием соединения РРР. Это значит, что информация, полученная РРР из такого кадра, будет использоваться им самим, а не переда- Кадр РРР Флаг Адрес Управление Протокол /w Информация ЛУ CRC Флаг 2 байта Протокол I 0x0021 IP-датаграмма Протокол I 0хС021 Данные протокола управления соединением | 11 Протокол I 0x8021 Данные протокола управления сетью Данные, инкапсулированные РРР > Рис. 6.4. Инкапсуляция протокола РРР 165 
~ г, Г., «г* Глава 6. Протоколы SLIP и РРР ваться куда-либо дальше по сети. Значение поля «Протокол», равное 0x8021, означает присутствие данных для протокола NCP — управления сетью, который РРР использует для управления некоторыми сетевыми уровнями, например IP. Инкапсуляция РРР по сравнению со SLIP Поле «Протокол» кадра РРР служит для передачи типа кадра: 1Р-датаграмма, LCP или NCP. На передачу поля «Протокол» РРР может отводить один или два байта данных. Чтобы оставить только один байт для передачи типа кадра, РРР в процессе конфигурации соединения могут договориться об этом между собой, используя LCP. Еще четыре байта выигрываются, если процессы РРР, опять-таки используя LCP, договариваются не посылать поля флага, адреса и управления — они все имеют одно и то же значение на протяжении соединения. В результате остаются три лишних, по сравнению со SLIP, байта, с наличием которых приходится мириться. Это поле протокола и два байта для контрольной суммы CRC. В дополнение ко всему, используя NCP, РРР может установить режим сжатия Ван-Джекобсона (RFC 1144). Вкратце, преимущества РРР таковы: • РРР позволяет мирно сосуществовать нескольким протоколам на одном и том же последовательном канале. При этом используется поле «Протокол». • РРР производит коррекцию ошибок, используя для этого поле контрольной суммы, CRC. • РРР умеет сжимать заголовки пакетов, пользуясь услугами протокола NCP. Этот же протокол используется для установки IP-адресов обеих сторон в соединении. • Протокол LCP служит для управления уровнем соединения, позволяя легко наращивать и расширять его возможности. Функции по управлению соединением До того как два компьютера начнут обмениваться данными по РРР, им необходимо правильно настроить канал связи и проверить его состояние. Для этих целей используется LCP, протокол управления соединением. Программы используют LCP для проверки состояния линии связи. После этого в ход пускается NCP, протокол управления сетью, для установки некоторых параметров на сетевом уровне, например IP. Наконец, уровень соединения становится способен передавать данные до тех пор, пока NCP и LCP последовательно не договорятся закончить соединения. На рис. 6.5 каждая фаза описанного процесса изображена схематически. Примечание: Такая же диаграмма приводится в RFC 1661. В документе она называется фазовой диаграммой (phase diagram). У нас использованы те же обозначения, что и в RFC 1661. 166 
Рис. 6.5. Процесс установления соединения РРР Фаза неактивности Фаза передачи данных по РРР-соединению всегда начинается и заканчивается фазой неактивности. В фазе неактивности физический уровень сети не готов к передаче данных. Протокол РРР переходит к фазе передачи данных только после того, как, находясь в фазе неактивности, он получит подтверждение о готовности физического уровня принимать и передавать данные. Например, если ваш канал связи представляет собой телефонную линию с модемом, РРР может следить за состоянием сигнала «наличие несущей» от модема, чтобы определить, когда физический уровень будет готов к работе. Как только сигнал устанавливается, РРР считает, что модем дозвонился и соединился с удаленным собратом, что позволяет приступить к следующей фазе — установлению соединения. Фаза установления соединения Получив определенный сигнал от физического уровня, РРР приступает к следующей фазе. А именно, используя LCP, оба процесса РРР соединения договариваются о его параметрах. В любом случае РРР начинает с параметров по умолчанию. Если партнер отказывается принять их или просит поменять на другие, РРР рассматривает предложение и, если оно его тоже устраивает, принимает его. Далее начинается фаза протокола сетевого уровня и в работу вступает протокол управления сетевыми параметрами (NCP). В RFC 1661 отмечено, что, выполняя фазу установления соединения, РРР должен полностью игнорировать все пакеты данных, не являющиеся пакетами LCP. (Напомним, что у пакета LCP поле «Протокол» содержит число 0хС021.) Получив «чужой» пакет, РРР не передает его дальше и не обрабатывает сам. RFC 1661 рекомендует, но не обязывает выдавать при этом сообщение о факте 167 
_ . ___ чава 6. Протоколы SLIP и РРР получения и содержимое чужого пакета на монитор или в системный файл. То, как должны выглядеть сообщения об ошибках, оставлено на усмотрение разработчиков протокола. Фаза проверки полномочий Фаза проверки полномочий следует за фазой установления соединения. Ее назначение — проверить, имеет ли право удаленный компьютер передавать данные. Фаза проверки полномочий может ограничивать права других компьютеров или отдельных пользователей по установлению РРР-соединения. Некоторые компьютерные сети предназначены для передачи конфиденциальной информации. Разумеется, если в какой-нибудь фирме устанавливается компьютер, работающий файл-сервером для ее сотрудников, руководству не захочется, чтобы к той же информации получил доступ кто-либо другой. Скорее всего руководство попросит своего программиста установить протокол для проверки полномочий пользователей. Некоторые сетевые протоколы имеют в составе подобные средства защиты данных. Однако TCP/IP к ним не относится. Именно это приводит к тому, что фаза проверки полномочий может иногда отсутствовать.1 Фаза управления сетью На протяжении фазы управления сетью РРР устанавливает некоторые параметры сетевого уровня. По окончании процесса РРР полностью готов к передаче данных вышележащим уровням и* может начинать передачу данных. Как уже было сказано, РРР может обслуживать несколько различных сетевых протоколов одновременно. Используя NCP, РРР может «на ходу» открыть новый или завершить обмен по старому сетевому протоколу. Представьте, например, что РРР ведет обмен пакетами IP. В любой момент, не прекращая обмена пакетами IP, вы можете начать обмен по протоколу DECnet, пользуясь тем же последовательным соединением. Когда обмен пакетами DECnet станет ненужным, вы можете завершить его, не прекращая соединения по протоколу IP. Другими словами, находясь в фазе управления сетью, РРР при помощи NCP открывает, настраивает и завершает сетевые соединения для нескольких сетевых протоколов. Для определенного сетевого протокола используется соответствующий протокол управления сетью. Например, NCP для IP отличается от NCP для DECnet. Каждый протокол управления сетью разрабатывается с учетом тонкостей в механизмах собственного сетевого протокола. Если вы работаете с поставщиком услуг Интернет по РРР, скорее всего фаза проверки полномочий отсутствует. Вместо нее вы просто вводите свое имя и пароль перед установлением РРР-соединения. С другой стороны, там, где это необходимо, даже простейшие реализации РРР предоставляют набор из нескольких, различной степени сложности схем проверки полномочий. — Примеч. перев. 168 
(РРР) Фаза прекращения соединения Как следует из названия, фаза прекращения соединения служит для завершения обмена данными. На рис 6.5 изображено, как РРР переходит к этой фазе при отсутствии прав доступа у удаленного компьютера. Соединение прекращается также при потере несущей в модеме. Обычно фаза прекращения соединения выполняется протоколом управления соединением (LCP). При этом процессы РРР договариваются друг с другом об окончании обмена. РРР обязан сообщить соответствующему сетевому уровню о начале фазы прекращения соединения. Это производится с участием NCP. Как вы уже знаете, NCP позволяет открывать и завершать обмен по нескольким сетевым протоколам во время работы. В RFC 1661 указано, что закрытие всех сетевых протоколов в РРР не должно приводить к разрыву соединения. Другими словами, отсутствие данных на линии не является достаточной причиной, чтобы прервать соединение. Сетевое программное обеспечение должно само явно указать РРР на необходимость этого. Протокол управления соединением РРР использует протокол управления соединением, чтобы договориться о некоторых возможностях конфигурации с партнером. Два взаимодействующих протокола РРР могут работать в различных операционных средах и могут быть сконфигурированы системными администраторами по-разному. Однако это не должно быть помехой в установлении соединения. Протокол управления соединением (LCP) как раз и призван решить эти проблемы. LCP умеет договариваться о формате инкапсуляции данных. При этом отмечаются поля, удаляемые из кадра данных при последующих транзакциях, что уменьшает объем передаваемой информации и, следовательно, увеличивает производительность. LCP меняет конфигурацию соединения «на лету», основывая свое решение на анализе трафика и совершенно незаметно для пользователя. Другими словами, неискушенный пользователь даже не заметит, что LCP выбрал оптимальную конфигурацию канала для данного сеанса связи. В следующих абзацах мы рассмотрим формат пакетов LCP и параметры, которые LCP может изменить при установлении соединения. Пакеты LCP В составе РРР различают три типа пакетов: конфигурации соединения, окончания сеанса и управления соединением. Пакеты конфигурации предназначены для установления и настройки линии связи. Пакеты окончания сеанса, как следует из названия, служат для завершения сеанса связи. Пакеты управления соединением используются LCP для обслуживания и отладки установленного соединения РРР. Значение 0хС021 в поле «Протокол», как было уже показано, означает, что в кадре РРР размещены данные LCP. На рис. 6.6 приведен формат пакета LCP. Однобайтовое поле «Код» обозначает тип пакета LCP, помещенного в кадр РРР. Однобайтовое поле «Идентификатор» протокола LCP обозначает порядковый 169 
|~Флаг | Адрес [Управление Протокол (0хС021) гЩ > 4 - 1 байт 1 байт 2 байта необязательно ' Пр Рис. 6.6. Формат пакета протокола управления соединением (LCP) номер пакета среди совокупности пакетов-запросов и пакетов-ответов, проходящих сквозь различные сетевые уровни, обслуживаемые РРР. Поле «Идентификатор» похоже на поле «порядковый номер» протокола TCP. Двухбайтовое поле «Длина» протокола LCP указывает общую длину пакета LCP, включая поля «Код», «Идентификатор», «Длина» и собственно данных. Поле данных LCP может быть пустым (ноль байтов). Тип пакета LCP, задающийся полем «Код», определяет формат и содержимое поля данных. В табл. 6.1 приведены возможные значения кодов LCP, определенные на июль 1994 года. Таблица 6.1. Значения поля Код протокола управления соединением (LCP) Код LCP Имя пакета Тип (класс) пакета 1 Configure-Request Конфигурация соединения (configuration) 2 Configure-Ack Конфигурация соединения 3 Configure-Nak Конфигурация соединения 4 Conf igure-Re j ect Конфигурация соединения 5 T erminate- Request Окончание сеанса (termination) 6 Terminate-Ack Окончание сеанса 7 Code-Reject Управление соединением (maintenance) 8 Protocol-Reject Управление соединением 9 Echo-Request Управление соединением 10 Echo-Reply Управление соединением И Discard-Request Управление соединением Примечание: Значения кодов LCP, а также любых других применяемых в Интернет констант (assigned numbers) всегда можно найти в последней версии документа RFC «Assigned numbers». Этот стандарт обновляется каждый раз, как только возникает необходимость. Структура пакетов конфигурации соединения LCP Пакеты конфигурации соединения LCP предназначены для инициализации и установления соединения по протоколу РРР. Существуют четыре разновидности пакетов: «конфигурация-запрос» (Configure-Request), «конфигурация-подтвер- 170 
ждение» (Configure-Аск), «конфигурация-неподтверждено» (Configure-Nak) и «конфигурация-отказ» (Configure-Reject). Для открытия соединения РРР требуется послать пакет «конфигурация-запрос». Это требование является обязательным для любой реализации протокола РРР. Поле данных пакета «конфигурация-запрос» содержит список желательных вариантов настройки соединения. Компьютер, вернее протокол РРР, получивший пакет «конфигурация-запрос», обязан ответить на него подобающим образом. Если список желательных вариантов настройки соединения подходит РРР по всем статьям, в ответ высылается пакет «конфигурация-подтверждение». Поле данных ответного пакета также содержит список вариантов конфигурации — точную копию принятого. Другими словами, ответный пакет как бы говорит: «Хорошо, все посланные тобой варианты настройки в поле данных пакета »конфигурация-запрос« принимаются». Имейте в виду, что этот пакет высылается, только когда все перечисленные варианты настройки подходят. Если компьютер не способен выполнить какой-либо пункт желательной настройки соединения, он должен ответить пакетом «конфигурация-неподтверждено». Поле данных этого пакета содержит список неподдерживаемых вариантов настройки. Другими словами, протокол РРР отфильтровывает список, помещая в поле данных только неудачные, с его точки зрения, параметры. Протокол LCP также разрешает помещать в поле данных пакета «конфигурация-неподтверждено» список дополнительных вариантов настройки соединения. Компьютер, получивший пакет «конфигурация-неподтверждено», должен ответить на запрос любых дополнительных вариантов настройки. Протокол РРР продолжает обмениваться пакетами до тех пор, пока оба участника соединения не придут к единому мнению по поводу конфигурации соединения. Модуль РРР, обнаруживший неизвестный ему вариант настройки в пакете-запросе, обязан ответить пакетом «конфигурация-отказ». Поле данных пакета «конфигурация-отказ» содержит список только неизвестных вариантов. Разница между вариантами, помещенными в поля данных пакетов «конфигурация-отказ» и «конфигурация-неподтверждено», состоит в том, что варианты«отказники» не подлежат дальнейшему обсуждению. Другими словами, РРР может изменить свое мнение о ранее неподтвержденном варианте конфигурации в процессе связи. В то же время варианты, перечисленные в пакете «конфигурация-отказ», больше никогда не будут запрашиваться. Структура пакетов окончания сеанса LCP Для окончания сеанса РРР использует две разновидности пакетов: «окончаниезапрос» (Terminate-Request) и «окончание-подтверждено» (Terminate-Аск). Обе разновидности пакетов полностью игнорируют поле данных. Протокол РРР требует, чтобы компьютер, получивший пакет «окончание-запрос», всегда отвечал передачей пакета «окончание-подтверждено». В RFC 1661 указано, что реализация РРР, которая хочет прекратить соединение, должна передавать пакет «окончание-запрос». Однако не обязательно, что все реализации РРР ведут себя именно так. Окончание сеанса приложения, работа- 171 
; ЩГлааа 6. Протоколы SUP и РРР ющего непосредственно с протоколом РРР, не должно зависеть только от приема соответствующего пакета. Например, модемное соединение может внезапно разорваться, и РРР вообще не получит никакого пакета. В RFC 1661 указано, что реализация РРР должна постоянно посылать пакеты «окончание-запрос» до тех пор, пока не произойдет один из следующих трех случаев: • Компьютер примет ответный пакет «окончание-подтверждено». • Низ лежащий сетевой уровень определит, что дальнейшая передача данных невозможна. (Например, модем потеряет несущую.) • Компьютер передаст достаточное количество неподтвержденных запросов на окончание соединения, чтобы быть уверенным, что компьютер на другом конце прекратил сеанс. Примечание: Если модуль РРР вдруг неожиданно получит непрошенный пакет «окончание-подтверждено», он должен либо вновь договориться о соединении, либо считать, что удаленный компьютер завершил сеанс. Структура пакетов управления соединением LCP Пакеты управления соединением используются для управления и отладки во время сеанса связи. Определено пять разновидностей пакетов: «код-отказ» (Code-Reject), «протокол-отказ» (Protocol-Reject), «эхо-запрос» (Echo-Request), «эхо-ответ» (Echo-Reply) и «игнорировать-запрос» (Discard-Request). Пакет «код-отказ» передается модулем РРР, принявшем пакет с неизвестным полем «код». Поле данных этого пакета (оно называется «Rejected-Packet field») содержит копию поля данных принятого пакета с неизвестным полем «код». Однако эта копия включает только данные из поля «информация». Ни заголовки уровня соединения, ни контрольная сумма CRC кадра РРР туда не попадают. На рис. 6.7 приведен формат пакета «код-отказ». Флаг Адрес Управление Протокол i Информация Код ’Идентификзто} - ' - - ■■■'■■ шенвы* пат —т Поле "отброшенный пакет" содержит только информацию из отброшенного кадра РРР Рис. 6.7. Формат пакета «код-отказ» LCP Пакет «протокол-отказ» передается по тому же самому поводу, что и «кодотказ» — в ответ на неопознанное значение поля «протокол» пакета РРР. Из рис. 6.8 видно, что пакет «протокол-отказ» содержит двухбайтовое поле, идентифицирующее неопознанный протокол, а также его «неопознанные данные» (Rejected-Information). 172 
Ъ mi Протокол Point-to-Point (РРР} ИННИЙК:£??Э®5?!Я!1^^НИЯЯИИ1!1^^ИИВ | Флаг Адрес Управление Протокол " :А /Информация И • • • ИМ ШшШШШШШШШЩщ Отброшенный ! ; протокол икс Рис. 6.8. Формат пакета «протокол-отказ» LCP Поле «неопознанный протокол» содержит значение неопознанного поля «протокол» в принятом пакете. Так же как и в предыдущем случае, поле «неопознанные данные» содержит данные только из информационного поля принятого пакета. Чтобы протестировать состояние канала связи, РРР шлет пакеты «эхо-запрос» и «эхо-ответ». Модуль РРР, получивший пакет «эхо-запрос», должен ответить пакетом «эхо-ответ». Пакет «игнорировать-запрос» предназначен для тестирования канала в одном направлении, от локального до удаленного компьютера. Модуль РРР, принимающий пакеты «игнорировать-запрос», не обращает на них никакого внимания. Пакеты всех трех только что перечисленных типов содержат поле «магическое число» в поле данных. «Магическое число» устанавливается при конфигурации соединения и необходимо для опознавания петли на канале связи. Мы подробно рассмотрим его применение, вместе с другими вариантами конфигурации LCP, в следующем разделе. Варианты конфигурации соединения LCP Варианты конфигурации соединения представляют собой набор характеристик канала связи «точка-точка». Каждый вариант имеет значение, принимаемое по умолчанию. Протокол РРР устанавливает значение по умолчанию, если оно отсутствует в принятом пакете «конфигурация-запрос». Значения, принимаемые по умолчанию, позволяют РРР не проводить переговоры о некоторых вариантах конфигурации. Однако в большинстве случаев выбор значения по умолчанию приводит к тому, что установленное соединение оказывается хуже по характеристикам, чем могло бы быть. На рис. 6.9 показан общий формат вариантов конфигурации LCP. Поле длины варианта конфигурации (1 байт) содержит длину варианта, включая поля типа, собственно длины и данных. Формат и содержимое поля данных для каждого варианта свое собственное. Однобайтовое поле типа служит для Тип Длина Данные : 1 байт 1 байт Рис. 6.9. Общий формат вариантов конфигурации LCP 173 
...... шш bi SUP иРРР KNT идентификации типа, о котором идет речь в пакете. В табл. 6.2 перечислены возможные значения типа по состоянию на июль 1994 г. Таблица 6.2. Значения поля типа для протокола конфигурации LCP Значение Описание варианта конфигурации поля типа 0 Не используется (зарезервировано) 1 Максимальная длина принимаемого блока (Maximum-ReceiveUnit) 3 Протокол авторизации доступа (Authentication-Protocol) 4 Протокол управления качеством (Quality-Protocol) 5 Магическое число (Magic-Number) 7 Сжатие данных поля протокола (Protocol-Field-Compression) 8 Сжатие полей адреса и управления (Address-and-ControlField-Compression) Максимальная длина принимаемого блока Вариант конфигурации «максимальная длина принимаемого блока» (MRU) — не что иное, как максимальный размер пакета данных, который модуль РРР в состоянии принять, равный по умолчанию 1500 байт. Модуль РРР может установить MRU как больше, так и меньше значения по умолчанию. Предположим, компьютер хочет передавать блоки данных размером 2048 байтов. Если удаленный компьютер согласится работать с блоками такой длины (указывается в пакете «конфигурация-запрос»), оба они будут передавать пакеты длиной по 2048 байтов. Нужно иметь в виду, что реализация РРР необязательно должна посылать пакеты длиной, в точности равной MRU. Вариант конфигурации MRU определяет именно максимальную длину пакета, превышать которую нельзя. РРР не обязан дополнять пакет меньшей длины пустой последовательностью данных. Он всегда может послать пакет меньшей, чем MRU, длины. Если компьютер по какой-либо причине не может работать с MRU, равным 1500 байтов, в процессе конфигурации соединения он может запросить MRU меньшего размера. На рис. 6.10 приведен формат варианта конфигурации MRU. 1 ТИП;; Максимальная 1 длина пакета (MRU) 1 1 байт 1 байт 2 байта 174 Рис. 6.10. Формат варианта конфигурации LCP «максимальная длина принимаемого блока» (MRU) 
' ШГ\ Протокол ', , . ■ • Значение поля длины варианта конфигурации MRU всегда равно четырем. Поле «максимальная длина принимаемого блока», длиной в два байта, имеет значение, равное максимальному количеству байтов в информационном поле пакета данных РРР. Другими словами, размер MRU не включает служебную информацию кадра РРР, полей протокола и контрольной суммы. Конфигурация протокола авторизации доступа При установлении соединения модуль РРР может воспользоваться тем или иным протоколом авторизации доступа. Данная возможность РРР реализуется на сетях, допускающих проведение авторизации доступа на сетевом уровне. Для выбора конкретного способа авторизации используются методы конфигурации LCP. Поскольку некоторые сети не позволяют проводить авторизацию на сетевом уровне, РРР не требует ее по умолчанию. Семейство протоколов TCP/IP не имеет в своем составе протоколов авторизации на сетевом уровне. Для запроса того или иного протокола авторизации модуль РРР, как и раньше, шлет пакет «конфигурация-запрос» с установленным типом пакета («запрос авторизации доступа») и определенным типом авторизации и ждет ответа от удаленного компьютера. Если удаленный компьютер согласен установить связь с авторизацией доступа, он, как и прежде, посылает ответный пакет «конфигурация-подтверждение». Таким образом, оба компьютера начинают сеанс с авторизацией доступа. Интересно, что РРР не требует авторизации в дуплексном режиме. То есть компьютеры могут применять различные типы авторизации на разных направлениях. Формат варианта конфигурации протокола авторизации доступа такой же, как и у MRU, и показан на рис. 6.10. Только вместо двухбайтового поля MRU там находится двухбайтовое поле типа протокола авторизации доступа. Кроме того, в поле данных может помещаться дополнительная информация в зависимости от конкретного типа протокола авторизации доступа. Конфигурация протокола управления качеством Протокол РРР предоставляет возможность следить за качеством соединения. В общем, качество линии выражается в количестве неверно принятых пакетов по сравнению с их общим числом. То есть выясняется, когда и насколько часто линия связи теряет данные. Вариант конфигурации протокола управления качеством позволяет модулю РРР выбрать подходящий метод наблюдения за количеством испорченных или потерянных данных. Так же как и в случае с протоколом авторизации доступа, РРР не требует дуплексной работы выбранного метода. Модуль РРР может выбрать один метод в одном направлении и другой — в обратном. Формат пакета конфигурации протокола управления качеством подобен по структуре формату пакета выбора протокола авторизации доступа. Вместо двухбайтового поля протокола авторизации там размещено двухбайтовое поле протокола управления качеством. Поле данных может содержать дополнительную информацию, относящуюся к конкретному указанном}’ методу управления. 175 
Магическое число Представьте ситуацию, когда ваша программа клиент шлет сообщения серверу, но последний располагается не на удаленном, а на вашем собственном компьютере. Вместо того чтобы сообщение вышло наружу и пошло по Интернет, а затем вернулось обратно на сервер, модуль РРР организует так называемую «петлю» (loop-back), о которой вскользь уже упоминалось. Соединение-петля позволяет приложениям типа клиент-сервер, находящимся на одном и том же компьютере, взаимодействовать, не передавая никаких данных по сети. Другими словами, программа делает вид, что посылает сетевые данные, а на самом деле просто оставляет их себе. Сетевое программное обеспечение должно уметь выяснить, что канал является петлей, и затем использовать его подобающим образом. Вариант конфигурации магического числа служит для распознавания канала-петли. В общем, магическое число — это уникальное в рамках сеанса число, используемое модулем РРР для распознавания канала-петли, а также некоторых других специальных случаев конфигурации уровня соединения. По умолчанию магическое число не устанавливается, а его полю присваивается значение 0. Примечание: Магическое число и его использование подробно рассматривается в документе RFC 1661. Схема конфигурации магического числа предполагает, что вероятность того, что оба ведущих сеанс связи компьютера выберут одно и то же число, пренебрежимо мала. Как правило, для выбора магического числа запрашивается функция генерации случайных величин. Схема конфигурации выглядит так: когда модуль РРР получает пакет «конфигурация-запрос» с вариантом конфигурации магического числа, он сравнивает его с предыдущим собственным пакетом «конфигурация-запрос». Если магические числа различны, это значит, что канал-петля отсутствует. Если магические числа совпадают, то, вероятно, присутствует канал-петля. Совпадение происходит потому, что, работая на канале-петле, РРР принимает обратно свои собственные пакеты-запросы. Приняв пакет с «подозрительным» магическим числом, модуль РРР должен послать пакет «конфигурация-неподтверждено» с новым магическим числом. Приняв пакет «конфигурация-неподтверждено» и проанализировав магическое число в нем, модуль может сделать окончательный вывод о наличии или отсутствии канала-петли. Другими словами, если это новое число будет отличаться от посланного, то канал-петля точно отсутствует. Если, наоборот, и это магическое число совпадет, вероятность присутствия канала-петли многократно возрастает. Установив наличие канала-петли, модуль РРР продолжает описанный выше процесс снова и снова. Протокол РРР обеспечивает метод определения каналапетли, но ничего не говорит о том, как следует обрабатывать такую ситуацию. Таким образом, обработка ситуации наличия канала-петли целиком зависит от особенностей конкретной реализации протокола. 176 
Протокол г PoirrI-to-Point (РРР). Конфигурация сжатия данных поля протокола Мы уже знаем, что РРР позволяет размещать обычно двухбайтовое поле протокола в одном байте. Вариант конфигурации сжатия данных поля протокола (Protocol Field Compression, PFC) позволяет РРР договориться с удаленным модулем о таком сжатии. Послав пакет «конфигурация-запрос» на установление сжатия данных поля протокола и получив положительный ответ, то есть пакет «конфигурация-подтверждено», оба модуля продолжают сеанс, размещая поле протокола в одном байте вместо двух. Однако любая реализация РРР обязана уметь работать и без такого сжатия, то есть используя нормальное двухбайтовое поле. Протокол РРР никогда не сжимает поле протокола в пакетах LCP. Пакеты LCP всегда имеют двухбайтовое поле протокола. Это необходимо, так как на стадии обмена пакетами LCP один модуль РРР может еще не знать, выбран ли тот или иной метод сжатия его партнером. Имейте также в виду, что, подсчитывая контрольную сумму CRC пакета и сжимая данные в поле протокола, модуль РРР исходит из того, что кадр РРР тоже сжат. Конфигурация сжатия полей адреса и управления Поскольку поля адреса и управления сетевого уровня соединения суть постоянные величины, они — первые кандидаты на удаление из кадра РРР. Вариант конфигурации сжатия полей адреса и управления (ACFC) позволяет модулю РРР договориться с партнером об удалении этой лишней информации из кадров последующего сеанса связи. Удаление дает выигрыш в размере двух байтов на кадр, увеличивая, таким образом, производительность сеанса. Так же как и в случае конфигурации сжатия поля протокола, сжатие полей адреса и управления в пакетах LCP невозможно. Таким образом, формат пакетов LCP всегда остается однозначным. Также для подсчета контрольной суммы CRC модуль РРР использует размер сжатого кадра. Что такое протокол управления сетью IP? Мы уже знаем, что РРР включает в себя семейство протоколов для управления сетью, позволяющих ему взаимодействовать с различными протоколами уровня соединения. Протокол управления сетью IP описан в документе RFC 1332 (The РРР Internet Protocol Control Protocol (IPCP), McGregor, 1992). Говоря общими словами, протокол управления сетью IP (IPCP) служит для конфигурации, включения и отключения модулей IP обоих сторон сеанса в соединении «точка-точка». Чем IPCP отличается от LCP? За некоторыми исключениями протокол управления сетью IP весьма похож на протокол управления соединением. Однако модуль РРР передает данные IPCP, упаковывая по одному пакету в информационное поле одного кадра РРР. Значение поля «протокол» для IPCP равно 0x8021. Протокол IPCP использует только первые семь кодов («конфигурация-запрос», «конфигурация-подтверждение», «конфигурация-неподтверждено», «конфигурация-отказ», «окончание- 177 
Протоколы SLIP и РРР ■ ; * ' запрос», «окончание-подтверждено» и «код-отказ»), определенных для LCP. Стандарт требует от РРР, чтобы все пакеты с иными значениями поля «код» отбрасывались, то есть чтобы в ответ выдавались пакеты «код-отказ». Вы наверное догадываетесь, что варианты конфигурации IPCP и LCP совершенно различны. В следующих абзацах мы познакомимся с вариантами конфигурации IPCP. Варианты конфигурации протокола 1РСРБ В рамках протокола управления сетью IP определены три варианта конфигурации. Однако один из них (установка IP-адресов) на сегодняшний день устарел и даже не упоминается в современном стандарте RFC 1332. В данный момент существенными вариантами остаются два: протокол сжатия IP и установка IP-адреса. Примечание: Будьте внимательны: устаревший вариант конфигурации использует слово «адрес» во множественном числе; современный вариант — в единственном. Конфигурация протокола сжатия IP Протокол сжатия заголовка TCP/IP Ван-Джекобсона уменьшает размер заголовков на три байта. Вариант конфигурации протокола сжатия IP позволяет модулю РРР договориться об использовании конкретного протокола сжатия, например CSLIP. По умолчанию сжатие не производится. Вариант конфигурации протокола сжатия IP включает двухбайтовое поле «протокол сжатия 1Р». В настоящий момент определен только один метод сжатия — Ван-Джекобсона (CSLIP). Значение поля «протокол сжатия 1Р» для сжатия Ван-Джекобсона равно 0x002D. То же значение в поле «протокол» РРР может использовать при начальной инициализации соединения. Конфигурация IP-адреса Если поставщик услуг Интернет не назначил вам постоянного IP-адреса, вы должны вводить его вручную каждый раз при установке соединения SLIP. Одно из самых значительных преимуществ РРР по сравнению со SLIP в том, что пользователям сетей TCP/IP не нужно заботиться об установке IP-адреса вручную. РРР делает это самостоятельно. Вариант конфигурации IP-адреса IPCP позволяет РРР договориться с партнером об IP-адресах обеих сторон соединения. Модуль РРР может либо запросить определенный IP-адрес, либо потребовать у удаленного компьютера назначить его. На рис. 6.11 приведен формат варианта конфигурации IP-адреса. 1 s I Тип Яmm i L.„— -L \ 1 байт ; 1 байт | | шимши ■ Ш IP-адрес ' .«и .in 4 байта Рис. 6.11. Формат варианта конфигурации IP-adpeca 178 
итоги I ■HR 1 Поле IP-адреса равно четырем байтам. Оно может содержать 32-битный адрес формата Интернет. Процесс конфигурации IP-адреса заключается в следующем. Сперва передается пакет «конфигурация-запрос» с установленным вариантом конфигурации IP-адреса. Поле IP-адреса содержит запрашиваемый адрес. Если модуль РРР желает, чтобы ему назначили адрес, он также передает пакет «конфигурация-запрос». Однако в этом случае поле адреса заполнено нулями. Чтобы назначить IP-адрес, удаленный компьютер шлет пакет «конфигурациянеподтверждено», содержащий назначаемый IP-адрес, в ответ на который локальный компьютер шлет пакет «конфигурация-подтверждено». В этой главе вы познакомились с тем, как простой протокол передачи кадров, такой как SLIP, позволяет эффективно передавать IP-датаграммы по последовательному каналу связи. Далее вы узнали, как протокол РРР преодолевает недостатки SLIP, касающиеся идентификации пакетов и присвоения IP-адресов. Наконец, вы узнали в общих чертах, как устроен протокол управления сетью IP. В следующей главе вы познакомитесь с интерфейсом программирования сокетов Беркли (Berkley sockets), с тем, как он упрощает сетевое программирование в операционной системе UNIX. Изучив интерфейс сокетов Беркли в седьмой главе, вы приступите к изучению интерфейса сокетов Windows в восьмой, последней в первой части книги. Во второй части вы будете разрабатывать простые программы TCP/IP, пользуясь интерфейсом программирования сокетов Windows. Перед тем как начать чтение седьмой главы, проверьте, хорошо ли вы усвоили следующие понятия: ✓ Протокол SLIP использует специальный символ ОхСО для инкапсуляции IP-датаграмм. S Протокол CSLIP сжимает информацию в заголовке IP, тем самым увеличивая производительность передачи на последовательном соединении. S РРР использует CSLIP в качестве варианта конфигурации сжатия заголовков IP. ✓ Протокол РРР пользуется протоколом управления соединением (LCP) для выбора некоторых вариантов конфигурации, например максимальной длины принимаемого блока (MRU) или сжатия заголовков IP (CSLIP). S Протокол РРР использует специальный протокол управления соединением (LCP) для работы TCP/IP на последовательном канале связи. S Для каждого типа сети протокол РРР, выступая в качестве уровня соединения, использует свой собственный протокол управления сетью (NCP). Подводя итоги 
а в Интерфейс сокетов Интерфейс прикладной программы (API) представляет собой просто набор функций (интерфейс), использующийся программистами для разработки прикладных программ в определенных компьютерных средах. Программист Windows, например, желающий написать программу, может воспользоваться широким набором функций, предоставленных в их распоряжение фирмой Microsoft — интерфейсом прикладных программ Windows или Windows API. В восьмидесятых годах американское правительственное агентство по поддержке исследовательских проектов (ARPA), финансировало реализацию протоколов TCP/IP для UNIX в Калифорнийском университете в г. Беркли. В ходе этого проекта группа исследователей-программистов разработала интерфейс прикладного программирования для сетевых приложений TCP/IP (TCP/IP API). По причинам, изложенным ниже, этот интерфейс был назван сокетами TCP/IP (TCP/IP sockets). Примечание: Так как изначально разработчиками интерфейса сокетов были исследователи университета в г. Беркли, этот интерфейс часто называется сокетами Беркли (Berkeley sockets) или сокетами в стиле Беркли. Интерфейс сокетов — это API для сетей TCP/IP. Другими словами, он описывает набор программных функций или процедур, позволяющий разраба- 180 
, * ; ' Ввод-вывод сетевых данных и данных в файловой системе тывать приложения для использования в сетях TCP/IP. В наши дни интерфейс сокетов наиболее широко используется в сетях на базе TCP/IP. То есть чтобы разрабатывать сетевые приложения, вам необходимо изучить его. Прочтя данную главу, вы овладеете следующими понятиями: ♦ Как создать сокет. ♦ Как настроить (сконфигурировать) сокет. ♦ Как передать данные через сокет. ♦ Как принять данные из сокета. ♦ Как сокеты используются в программах-серверах. Различные реализации сокетов Изначально, сокеты были встроены в операционную систему UNIX. Однако как вы узнаете в восьмой главе, многие другие операционные системы, например Microsoft Windows, также реализовали интерфейс сокетов в виде библиотек программ. Другими словами, для того чтобы выполнить сетевую функцию в операционной системе, отличной от UNIX, вам необходимо вызвать соответствующую процедуру из библиотеки сокетов. Несмотря на различия в операционных системах, исходные тексты программ, написанных в них, немногим отличаются друг от друга. В любой программе, для того чтобы выполнить определенные действия, вызывается соответствующая функция. Ввод-вывод сетевых данных и данных в файловой системе Интерфейс сокетов Беркли проще понять, если вы имеете хотя бы общее представление о системе ввода-вывода в UNIX. Как уже было сказано, разработчики сокетов Беркли стремились реализовать протоколы TCP/IP в рамках операционной среды UNIX. При этом они располагали уже имеющимся в UNIX набором вызовов системных функций. В результате, интерфейс сокетов использовал те же системные вызовы, что и остальные программы. Он оказался встроенным в саму операционную систему. Если вы не знакомы с UNIX, мысль встроить интерфейс сокетов в саму операционную систему может показаться странной. Однако если рассмотреть систему ввода-вывода в UNIX повнимательней, оказывается, что интерфейс, размещенный внутри системы, смотрится вполне естественно и натурально. Системные вызовы ввода-вывода в UNIX выглядят как последовательный цикл, состоящий из операций типа открътъ-считатъ-записатъ-закрытъ. Файл, перед тем как читать из него, должен быть открыт. Далее над ним выполняются операции по считыванию/записи. По окончании операций файл закрывается. 181 
Глава 7. Интерфейс сокетов Программисты в UNIX могут не знать различий между файлами и внешними устройствами. Одни и те же системные вызовы используются по отношению к принтеру, модему, дисплею и файлам. Внешние устройства и файлы отображены в UNIX на одну файловую систему. Чтобы открыть файл или получить доступ к устройству, например стриммеру, программист должен вызвать одну и ту же системную функцию. В ответ на системный вызов UNIX возвращает так называемый дескриптор файла (file descriptor); его иногда называют указателем (file handler). Дескриптор файла указывает на внутрисистемную таблицу, описывающую открытый файл или устройство. Для файла в таблице могут приводиться его имя, размер, дата создания. Дескриптор файла в UNIX может указывать на файл, внешнее устройство или любой другой объект, позволяющий выполнять над ним системные функции ввода или вывода. На первых порах разработки интерфейса сокетов исследователи пытались заставить сетевой ввод-вывод функционировать так же, как и любой другой ввод-вывод UNIX. Другими словами, они хотели, чтобы интерфейс сокетов считывал и записывал данные в уже известном нам цикле открыть-считатьзаписать-закрыть. То есть устанавливая сетевое соединение, программа бы открывала его, затем считывала и записывала в него данные. Наконец, соединение закрывалось бы. С точки зрения UNIX такой подход сделал бы сетевой ввод-вывод точно таким же, как и все остальные операции по вводу-выводу данных, значительно облегчив интеграцию сокетов в операционную систему. Возникают некоторые проблемы В процессе разработки исследователи выяснили, что сетевой ввод-вывод значительно сложнее, чем ввод-вывод для любых других устройств. Проектируя обычные системы ввода-вывода, они столкнулись с проблемами, с которыми никогда до этого не встречались. В следующих абзацах кратко рассматриваются две проблемы сетевого ввода-вывода, с которыми столкнулись разработчики интерфейса сокетов. Вы знаете, что сетевые программы, как правило, создаются по модели клиентсервер. Разработчики сокетов могли запросто реализовать такой API для системы ввода-вывода UNIX, в котором программист мог бы создать программу-клиент, активно устанавливающую сетевое соединение. Однако этот же API был бы должен позволить создавать и программы-серверы, пассивно ждущие, пока к ним кто-нибудь не обратится. Поскольку обычная система ввода-вывода UNIX попросту не умеет пассивно вводить и выводить данные, разработчики были вынуждены создать набор новых функций для обработки пассивных операций ввода-вывода. Было обнаружено, что стандартные функции ввода-вывода UNIX также плохо умеют устанавливать соединения. Как правило, они пользуются фиксированным адресом файла и устройства для обращения к нему. То есть адрес файла или устройства для каждого компьютера — постоянная величина. Соединение (или 182 
путь) к файлу или устройству доступно на протяжении всего цикла запись-считывание — то есть до тех пор, пока программа не закроет соединение. Фиксированный адрес — превосходная мысль, если протокол передачи данных ориентирован на соединение. Для не ориентированных на соединение протоколов фиксированный адрес представляет проблему. Например, в датаграмме, переданной IP, присутствует адрес компьютера-получателя (IP-адрес), однако протокол не устанавливает предварительно никакого соединения. В системе ввода-вывода UNIX отсутствуют какие-либо возможности для этого. Одним словом, когда разработчики сокетов приступили к работе, система ввода-вывода UNIX предоставляла возможность только считывать-записывать данные протоколам, передающим потоки байтов. Две вышеперечисленные и некоторые другие, менее очевидные проблемы привели к тому, что разработчики отказались от внесения модификаций в существующую систему ввода-вывода UNIX, а вместо этого создали новый API (набор функций). Интерфейс сокетов, получившийся в конечном итоге, унаследовал дух операционной системы UNIX. Например, дескриптор сокетов в интерфейсе по-прежнему называется дескриптором файла, и информация о сокете хранится в той же таблице, что и дескрипторы файлов. Изучая интерфейс сокетов в этой главе и интерфейс сокетов Windows в следующей, помните, что впервые сокеты появились именно в UNIX, поэтому их дальнейшие реализации унаследовали характерные черты, свойственные коду UNIX. Характеристики интерфейса сокетов могут казаться странными до тех пор, пока вы не рассмотрите поближе, откуда они происходят. Сокеты ведут свою родословную из UNIX. Абстракция сокетов Сокет можно рассматривать, как конечный пункт передачи данных по сети. Сетевое соединение — это процесс передачи данных по сети между двумя компьютерами или процессами. Сокет — конечный пункт передачи данных. Другими словами, когда программы используют сокет, для них он является абстракцией, представляющий одно из окончаний сетевого соединения. Для установления соединения в абстрактной модели сокетов необходимо, чтобы каждая из сетевых программ имела свой собственный сокет. Связь между двумя сокетами может быть ориентирована на соединение, а может быть и нет. Несмотря на то, что разработчики модифицировали системный код UNIX, интерфейс сокетов по-прежнему использует концепцию ввода-вывода данных UNIX. То есть сетевая модель интерфейса сокетов до сих пор использует цикл открыть-считать-записать-закрыть. Чтобы открыть или создать файл в UNIX (и в большинстве других ОС), вы должны указать его описание (например, имя файла и то, как вы будете его использовать: записывать или считывать). Затем вы запрашиваете у операционной системы дескриптор файла, соответствующий вашему описанию. Не существует каких-либо ограничений на то, когда запрашивать дескриптор. Как 183 
Глава 7. Интерфейс сокетов - только вам нужен файл, запрашивайте его дескриптор. В один и тот же момент времени может быть открыто несколько устройств или файлов. В любом случае операционная система возвращает дескриптор (обычно это целое число), однозначно соответствующий указанному файлу или устройству. Интерфейс сокетов работает точно так же. Когда программе нужен сокет, она формирует его характеристики и обращается к API, запрашивая у сетевого программного обеспечения его дескриптор. Структура таблицы с описанием параметров сокета весьма незначительно отличается от структуры таблицы с описанием параметров файла. Однако это отличие важно. Отличие дескриптора сокета от дескриптора файла Вы знаете, что процессы получения дескриптора файла и сокета от операционной системы отличаются незначительно. Однако таблицы, на которые указывают дескрипторы, отличаются между собой. Тогда как дескриптор файла указывает на определенный файл (уже существующий или только что созданный) или устройство, дескриптор сокета не содержит каких-либо определенных адресов или пунктов назначения сетевого соединения. Тот факт, что дескриптор сокета не представляет определенную сетевую точку входа (endpoint), существенно отличает его от любого другого дескриптора стандартной системы ввода-вывода. В большинстве операционных систем дескриптор файла указывает на определенный файл, находящийся на жестком диске. Программы, работающие с сокетами, сначала образуют сокет и только потом соединяют его с точкой назначения на другом конце сетевого соединения. Если бы файловый ввод-вывод состоял из этих же шагов, приложение сначала получало бы дескриптор файла, а затем привязывало бы его к имени определенного файла на жестком диске. Рассмотрим потребности сетевой программы TCP/IP, передающей информацию датаграммами по не ориентированному на соединение протоколу. Программа указывает адрес назначения датаграмм, но не устанавливает предварительного соединения с компьютером-получателем данных. Вместо этого программа передает датаграммы по адресу назначения. Сетевое программное обеспечение (уровень IP) обслуживает процесс доставки. Чтобы интегрировать TCP/IP в среду UNIX, разработчики сокетов должны были добавить к существующей системе ввода-вывода новые возможности. API сети TCP/IP был необходим способ получать дескриптор ввода-вывода, не устанавливая предварительно соединения с удаленным компьютером. Вместо того чтобы модифицировать существующую систему ввода-вывода UNIX, разработчики сокетов создали новую функцию, которая и получила название «сокет» (socket). В следующем разделе вы узнаете, как функция socket позволяет программе получить дескриптор сокета, не указывая адрес получателя сетевых данных. 184 
Создание т Создание сокета Создавая программу TCP/IP, необходимо иметь возможность пользоваться как ориентированными, так и не ориентированными на соединение протоколами. Интерфейс сокетов позволяет программам использовать оба этих типа протоколов. Однако процессы создания сокета и соединения сокета с компьютером-получателем происходят раздельно. Чтобы создать сокет, программа вызывает функцию socket. Она, в свою очередь, возвращает дескриптор сокета, подобный дескриптору файла. Другими словами, дескриптор сокета указывает на таблицу, содержащую описание свойств и структуры сокета. Следующий пример показывает возможную форму вызова функции socket: socket_handle = socket (protocol__f ami ly, socket__type, protocol); Создавая сокет, вы указываете три параметра: группу, к которой принадлежит протокол, тип сокета и сам протокол. Первый параметр задает группу или семейство, к которому принадлежит протокол, например семейство TCP/IP. Второй параметр, тип сокета, задает режим соединения: датаграммный или ориентированный на поток байтов. Параметр «протокол» определяет протокол, с которым будет работать сокет, например TCP. В следующих разделах мы подробно обсудим различные параметры функции socket. Параметры сокета Протоколы TCP/IP были не единственными, интегрированными разработчиками сокетов в систему UNIX. Кроме TCP/IP, встроенный API обслуживает и некоторые другие протоколы. Конечно, разработка API изначально ориентировалась на TCP/IP, однако и другие сети не были забыты. Различные семейства протоколов появились благодаря концепции универсальности API сокетов. Семейства протоколов и адресов Первый параметр, указываемый в вызове функции socket, определяет группу или семейство, к которому принадлежит протокол. Такой группой может быть, например, семейство TCP/IP. Так как могут быть выбраны различные семейства протоколов, интерфейс сокетов может обслуживать несколько различных типов сетей одновременно. Фактически, интерфейс сокетов Беркли обслуживает семейства протоколов TCP/IP и семейство протоколов сетевых служб фирмы Ксерокс (XNS). Примечание: XNS является многоуровневой системой протоколов, похожей на TCP/IP. Разработанная в исследовательском центре фирмы Ксерокс в Пало-Альто, система XNS послужила прототипом для некоторых популярных сетевых протоколов, таких как Novell Netware, Banyan Vines и 3+ фирмы 3Com. Для указания группы протоколов в интерфейсе сокетов определены символьные константы (макроопределения). Символьная константа PF_INET, например, определяет семейство протоколов TCP/IP. Константы, определяющие другие 185 
Глава % Интерфейс сокетов семейства, также начинаются с префикса «PF_». Константа PF_UNIX определяет семейство внутренних протоколов ОС UNIX, a PF_NS — семейство протоколов фирмы Ксерокс. Семейства адресов тесно связаны с семействами протоколов. Форматы адресов различных сетей не одинаковы. Разработчики сокетов в полном соответствии с этим соображением еще больше обобщили интерфейс сокетов, реализовав для работы с различными сетями возможность обращения к различным семействам сетевых адресов. Символьные константы, указывающие на семейство адресов, начинаются с префикса «AF__». Константа, обозначающая семейство адресов Интернет (TCP/IP), называется AF_INET. Константа AF_NS обозначает семейство адресов фирмы Ксерокс, a AF_UNIX — файловой системы UNIX. К сожалению, ввиду тесной взаимосвязи между семействами протоколов и семействами адресов, существует ошибочное мнение, что это одно и то же. К примеру, в интерфейсе сокетов TCP/IP значения констант семейства протоколов (PF_INET) и семейства адресов (AF_INET) равны. В результате, некоторые, замечательные в других отношениях справочные руководства, часто рекомендуют программистам использовать одно и то же значение в качестве обоих параметров вызова функции socket. Как Стивенс и Камер указывают в книге «Межсетевое взаимодействие сетей на базе TCP/IP» (Internetworking with TCP/IP, Volume 3, Prentice Hall, 1993), разница между семейством протоколов и семейством адресов позволяет программисту пользоваться несколькими семействами адресов внутри одного из семейства протоколов. Другими словами, интерфейс сокетов не накладывает ограничений на использование множества различных форматов адресов в рамках одного и того же семейства протоколов. Такая гибкость на самом деле не представляет особенного интереса для программистов Интернет, поскольку их «родной» протокол, TCP/IP, умеет пользоваться адресами только собственного формата. Несмотря на это досадное ограничение, сама гибкость подхода разработчиков демонстрирует планирование и предвидение, проявленные на стадии разработки интерфейса сокетов. На сегодняшний день константы PF_INET и AF_INET имеют одинаковые значения, поэтому все равно, как их применять. Но если планировать на будущее, ситуация может измениться, и семейство адресов перестанет быть эквивалентным семейству протоколов. Если вы — программист Интернет, то лучше всего ставить константы там, где им положено находиться: PF_INET — для указания на семейство протоколов, a AF__INET — для указания на семейство адресов. Такой подход упростит понимание исходного текста программы и снимет потенциальные неприятности, могущие возникнуть при переносе программы в другую операционную систему. Тип соединения Соединения TCP/IP бывают двух режимов: ориентированные и не ориентированные на соединение. В ориентированных на соединение протоколах данные перемещаются как единый, последовательный поток байтов без какого-либо деления на блоки. В не ориентированных на соединение протоколах сетевые 186 
: данные перемещаются в виде отдельных пакетов, называемых датаграммами. Как уже отмечалось, сокеты могут работать как с не ориентированными, так и с ориентированными на соединение протоколами. Второй параметр вызова функции socket обозначает тип соединения, который вы желаете использовать. Символьная константа SOCK_DGRAM обозначает датаграммы, a SOCK_STREАМ — поток байтов. Интерфейс сокетов также определяет третий тип соединения, называемый «простой сокет» (raw socket). Простой сокет позволяет программе использовать напрямую те низкоуровневые протоколы, которые обычно используются сетевыми протоколами более высокого уровня. В главе 16 вы узнаете, что программа Ping создает простой сокет, чтобы напрямую использовать протокол управляющих сообщений Интернет (ICMP). Обычно ICMP служит для передачи сообщений о различных сетевых ошибках. Примечание: В интерфейсе сокетов определены еще два типа протоколов. Однако в данной книге они не будут рассматриваться из-за того, что на момент написания не существовало ни одной программы, работающей с ними. Как правило, прикладные программы не используют ICMP. Они предоставляют самой сети решать проблемы, связанные с ошибками. Транспортные протоколы Интернет самостоятельно доставят сообщение об ошибке, адресованное прикладной программе. Однако прикладная программа может напрямую обратиться к уровню IP или ICMP. Для этого ей придется создать простой сокет. Выбор протокола Семейство TCP/IP состоит из нескольких протоколов, например IP, ICMP, TCP и UDP. Любое семейство состоит из набора протоколов, которыми пользуются сетевые программисты. Третий параметр функции socket позволяет выбрать тот протокол, который будет использоваться вместе с сокетом. Как и в случае остальных параметров, протокол задается символьной константой. В сетях TCP/IP все константы начинаются с префикса IPPROTO_. Например, протокол TCP обозначается константой IPPROTO_TCP. Символьная константа IPPROTO_UDP обозначает протокол UDP. Следующий оператор демонстрирует, как может выглядеть вызов функции socket: socket_handle = socket (PF_INET, SOCK__STREAM, IPPROTO__TCP) ; Данный вызов сообщает интерфейсу сокетов о том, что программа желает использовать семейство протоколов Интернет (PF_INET), протокол TCP (IPPROTO_TCP) для соединения, ориентированного на поток байтов (SOCKJSTREAM). Процесс В предыдущем разделе вы узнали, что сокет представляет конечную точку сетевого соединения. Также вы узнали, что сокет определяется членом в таблице 187 
Глава 7. Ишерфейс сокетов дескрипторов, похожим на дескриптор файла в таблице дескрипторов файлов. Вы узнали, что для создания сокета вызывается системная функция socket. Адреса бывают разными Адреса всегда представляли критический компонент во всех сетевых соединениях. В нескольких предыдущих главах вы прочли об адресах буферов, сетевых адресах Интернет и адресах портов. Для того чтобы систематизировать эту информацию, мы сейчас рассмотрим каждый из этих типов адресов. Адрес буфера указывает на местонахождение массива данных, расположенного в оперативной памяти (RAM) компьютера. В некоторых случаях сетевое программное обеспечение, например интерфейс сокетов, отводит память для хранения буфера данных. В других случаях пользовательские программы отводят буферы для хранения данных сетевых программ. В языках С и C++ для того, чтобы получить доступ к данным, расположенным в сетевом буфере, ему необходимо назначить адрес, присвоив его переменной-указателю на этот буфер. Адрес Интернет (IP-адрес) определяет компьютер, подключенный к сети TCP/IP. Строго говоря, адрес Интернет присваивается не компьютеру, а его сетевому интерфейсу, то есть сетевой карте, подключенной к Интернет. Сетевой уровень IP использует IP-адрес, чтобы доставить данные от одного компьютера к другому. Из пятой главы вы узнали, что транспортный уровень TCP/IP использует порты протоколов для того, чтобы доставить данные определенному сетевому приложению внутри сетевого компьютера. С точки зрения сети, порт протокола — не что иное, как адрес приложения или процесса. Порт протокола подобен дескриптору задачи для сетевого программного обеспечения. Когда модуль сетевого программного обеспечения транспортного уровня, например TCP, желает установить соединение с программой или процессом, он использует порт протокола. Порт протокола обозначает процесс сетевого компьютера. Сокет представляет собой адрес в том же смысле, в каком его представляет дескриптор файла или процесса. Он —■ член системной таблицы дескрипторов. Формат системной таблицы дескрипторов и то, как именно сетевое программное обеспечение отводит память для ее членов, зависит от операционной среды, которой она принадлежит. Для продолжения обсуждения интерфейса сокетов, дескрипторов сокетов и их адресов необходимо четко представлять разницу между всеми описанными форматами. Должно быть, вы обнаружили, что в обсуждении сокетов еще нигде не появлялся сетевой адрес. Ранее мы явно отметили, что для открытия сокета указывать сетевой адрес не требуется. Следующий системный вызов, например, определяет семейство протоколов, тип сокета и протокол, однако в нем не упоминается никакого сетевого адреса: socket_handle = socket(protocol_family, socket__type, protocol); Вам, может быть, непонятно, как сокет служит конечным пунктом соединения, когда в нем отсутствует сетевой адрес. В следующих разделах описывается, что же происходит на самом деле, когда создается сокет, и как сокет выполняет операции сетевого ввода-вывода. Что такое дескриптор сокета? Вы знаете, что при создании сокета сетевой адрес не указывается. Функция socket создает сокет и возвращает значение дескриптора, присвоенного системой этому сокету. Дескриптор указывает на член системной таблицы дескрипторов, соответствующий данному сокету. 188 
11811! * * \ : ; Создание сокета Системная таблица дескрипторов управляется реализацией сокетов. Вам, как прикладному программисту, приходится общаться с этой таблицей посредством дескрипторов сокетов. На самом деле «создание сокета» — это просто процесс отведения памяти системой для размещения в ней структуры данных, описывающей данный сокет. В операционной системе UNIX каждый процесс владеет одной таблицей дескрипторов файлов. (Вы помните, что разработчики интерфейса сокетов использовали ту же концепцию и для сетевого ввода-вывода.) Функция-socket в UNIX получает дескриптор из таблицы дескрипторов файлов. Дескриптор является указателем на внутреннюю структуру данных. Структура данных сокета в упрощенном виде показана на рис. 7.1. Таблица индексных дескрипторов Структура данных сокета 1 Семейство протоколое • фокальный IP-адрес • Удаленный JP Локальнмй гюг~ Удаленный порт протокола Рис. 7.1 Упрощенная структура данных сокета Как видно из рисунка, структура данных сокета включает элементы для хранения аргументов, с которыми была вызвана функция socket. Кроме того, в структуре размещены четыре адреса: локальный IP-адрес, удаленный 1Р-адрес, адреса локального и удаленного портов. Каждый раз, когда программа вызывает функцию-socket, реализация сокетов отводит машинную память для новой структуры данных, а затем размещает в ней семейство адресов, тип сокета и протокола. В таблице дескрипторов размещается указатель на эту структуру. Дескриптор, полученный вашей программой от функции socket, является индексом (порядковым номером) в таблице дескрипторов. Интерфейс сокетов не определяет никаких способов управления дескриптором сокета. Сам UNIX обращается с дескрипторами сокетов точно так же, как с дескрипторами файлов. Другие приложения могут обращаться с дескрипторами так, как это им заблагорассудится. Другими словами, то, что происходит с данными, на которые указывает дескриптор, зависит от конкретной реализации системы, с которой вы работаете. Вам, как прикладному программисту, не обязательно знать подробности, касающиеся таблиц дескрипторов, структуры данных в них и отведения памяти. Мы рассматриваем все это, только чтобы показать, каким образом сокету присваивается сетевой адрес. Функция socket образует структуру данных сокета, не заполняя при этом поля адресов. Чтобы связать сокет с определенным сетевым адресом, необходимо вызвать другие функции, входящие в состав API, так, как это будет показано в следующих разделах. 189 
Глава 7. Интерфейс сокетов ж»: чд 9* i Модель интерфейса сокетов Парадигма сокетов, или модель интерфейса сокетов, рассматривает сетевые компьютеры в качестве конечных точек сетевого соединения. Каждое сетевое соединение включает две конечные точки: локальный компьютер и удаленный компьютер. В рамках интерфейса сокетов каждая конечная точка сети представлена сокетом. Вы знаете, что большинство сетевых программ пользуются моделью клиент-сервер. Сетевые соединения в рамках этой модели также включают две конечные точки соединения. Модель клиент/сервер делает эти две точки неравноправными. Одна должна выполнять функции сервера, а другая — клиента. Конечная точка-клиент инициирует запрос к сетевым службам и представлена программой-клиентом или процессом-клиентом. Конечная точка, отвечающая на запрос, представлена программой или процессом-сервером. Из четвертой главы вы узнали, что сетевой уровень IP идентифицирует сетевые компьютеры при помощи сетевого адреса. Это значит, что каждый компьютер, подключенный к Интернет, должен иметь уникальный сетевой адрес. В главе 5 объясняется, как транспортный уровень использует адреса портов для обозначения определенных приложений (процессов) в сетевом компьютере. Каждый сетевой процесс, таким образом, использует номер порта сетевого компьютера в качестве собственного адреса. Наконец, вы знаете, что программы Интернет должны использовать семейство протоколов TCP/IP для передачи своих данных по сети. Таким образом, соединение между двумя сетевыми программами несет в себе следующую информацию: • Местный (локальный) порт протокола, обозначающий программу или процесс, посылающий сообщения или датаграммы. • Адрес локального компьютера, обозначающий сетевой компьютер, принимающий пакеты данных. • Удаленный порт протокола, обозначающий программу или процесс-получатель данных. • Протокол, обозначающий, каким образом программа собирается передавать данные по сети. Структура данных сокета, как видно на рис. 7.1, соответствующая дескриптору сокета, содержит информацию об этих же пяти пунктах. Таким образом, сокет является реализацией абстрактной модели конечной точки сетевого соединения. Структура данных сокета содержит все элементы, необходимые конечной точке сетевого соединения. Структура данных сокета значительно упрощает процесс сетевого соединения. Когда одна программа желает установить связь с другой, программа-передатчик просто отдает свою информацию сокету, а интерфейс сокетов в свою очередь передает ее дальше стеку сетевых протоколов TCP/IP. Перед этим программа должна создать сокет, вызвав системную функцию-сокет, а затем сконфигурировать его, пользуясь функциями, также входящими в интерфейс сокетов. В следующих разделах будет показано, каким образом сокет конфигурируется. 190 
Использование сокета в программе Перед тем как начать использовать сокет, программа должна сконфигурировать его. Структура его данных должна быть подготовлена к приему как номера порта, так и сетевого адреса компьютера. Термин «адрес сокета» относится не к самому сокету, а к адресам портов и компьютеров, размещенных внутри его структуры данных. Вызывая функцию socket, вы не указываете ни номер порта, ни сетевой адрес. Вместо этого вы передаете некоторые параметры. Вместе с протоколом указывается тип сетевой службы (ориентированный или не ориентированный на соединение). Также указывается, как будет работать программа: клиентом или сервером. (Назначение программы — выполнять функции клиента или сервера. Несмотря на то, что одна программа может быть и тем и другим, в один момент времени она может выполнять функцию либо только клиента, либо только сервера.) Параметры, передаваемые сокету, зависят от назначения программы, так же, как и от типа сетевой службы по досташ данных. Настройка сокета Каждая сетевая программа вначале создает сокет, вызывая функцию socket. При помощи других функций сокет конфигурируется или настраивается так, как это нужно сетевой программе. Для того чтобы передавать данные через сокет, можно воспользоваться одним из двух типов сетевых служб: датаграммной или ориентированной на поток байтов. Также сокет можно настроить на один из двух типов поведения программы: клиент или сервер. В табл. 7.1 перечислены функции API, используемые для конфигурирования сокета. Вы знаете, что любой сокет должен содержать пять блоков информации, описывающей соединение: протокол, местный и удаленный IP-адреса, номера местного и удаленного портов. В следующем разделе обсуждается, как и когда используются данные функции. Таблица 7.1. Функции интерфейса сокетов, используемые при конфигурации сокета для сетевого соединения Использование Местный процесс Удаленный процесс сокета Ориентированный Вызов функции connectO записывает в структуру на соединение клиент данных сокета информацию как о местном, так и об удаленном участниках соединения Ориентированный на соединение сервер bindO listenO и acceptO Не ориентированный на соединение клиент bind() sendtoO Не ориентированный на соединение сервер bindO recvfromO 191 
Соединение сокета Сокет представляет абстракцию, пользуясь которой вы можете настраивать и программировать конечные точки сетевого соединения. В предыдущей главе объяснялось, что ориентированные на соединение протоколы организуют между конечными точками виртуальную цепь. Другими словами, соединение между конечными точками в этом случае теоретически не отличается от выделенного двухточечного соединения. Транспортный уровень TCP/IP, TCP (ориентированный на соединение) обслуживает виртуальную цепь (держит соединение открытым), обмениваясь сообщениями-подтверждениями о доставке данных между двумя конечными точками. В результате, ориентированной на соединение программе-клиенту в сети TCP/IP нет дела до локального номера порта, с которого передаются ее данные. Программа-клиент может принимать данные на любом порту протокола. Поэтому в большинстве случаев ориентированные на соединение программы-клиенты не указывают номер локального порта протокола. Ориентированная на соединение программа-клиент вызывает функцию connect, чтобы настроить сокет на сетевое соединение. Функция connect размещает информацию о локальной и удаленной конечных точках соединения в структуре данных сокета. Функция connect требует, чтобы были указаны: дескриптор сокета (указывающий на информацию об удаленном компьютере) и длина структуры адресных данных сокета. Следующий оператор — пример обращения к функции connect: result = connect (socket_handle, remote__socket_address, address_length); Первый параметр функции connect, дескриптор сокета, получен ранее от функции socket. Функция socket всегда вызывается до того, как устанавливается соединение. Дескриптор сокета указывает программному обеспечению, какая именно структура данных в таблице дескрипторов имеется в виду. Дескриптор также сообщает о том, куда нужно записать информацию об адресах удаленного участника соединения. Второй параметр функции connect — адрес удаленного сокета, является указателем на структуру данных адреса сокета специального вида. Информация об адресе, хранящаяся в структуре, зависит от конкретной сети, то есть от семейства протоколов, которое мы используем. Во второй части книги вы узнаете больше об этой структуре. На данный момент нам важно знать, что структура данных сокета содержит семейство адресов, порт протокола и адрес сетевого компьютера. Функция connect записывает эту информацию в таблицу дескрипторов сокетов, на которую указывает соответствующий дескриптор сокета (первый параметр функции connect). До того как вызвать функцию connect, информацию об адресах удаленного компьютера нужно занести в структуру данных сокета. Другими словами, функция connect должна знать сетевой адрес и номер порта удаленного компьютера. Местный IP-адрес, однако, можно не указывать. Реализация сокетов 192 
тшШ К вашей операционной системы самостоятельно разместит адрес вашего компьютера и номер локального порта протокола. В добавок ко всему интерфейс сокетов проследит за тем, получило ли приложение данные, доставленные в локальный порт протокола транспортным уровнем сети. Другими словами, интерфейс сокетов сам выбирает локальный порт протокола для приложения и уведомляет его о получении данных — прикладная программа может не заботиться о том, какой именно порт она использует. Третий параметр функции connect, длина адреса, сообщает интерфейсу длину структуры данных адресов удаленного сокета (второй параметр), измеренную в байтах. Содержимое (и длина) этой структуры зависит от конкретной сети. Зная длину структуры, интерфейс сокетов представляет, сколько памяти отведено для хранения этой структуры. Когда реализация сокетов выполняет функцию connect, она извлекает количество байтов, указанное третьим параметром из буфера данных, на который указывает параметр «адрес удаленного сокета». Функция connect устанавливает прямое соединение с удаленным сетевым компьютером. Это необходимо, только если используется ориентированный на соединение протокол. Если протокол не ориентирован на соединение, он никогда не устанавливает его напрямую. Не ориентированный на соединение протокол передает данные в датаграммах — он никогда не передает их потоком байтов. Точно так же программа-сервер никогда не начинает соединение первой. Вы можете создать программу-сервер, работающую по ориентированному на соединение протоколу, однако и в этом случае она будет пассивно прослушивать порт протокола, ожидая появления запроса от клиента. Другими словами, прямое соединение инициируется клиентом, а не сервером. Ключевое сходство между любой не ориентированной на соединение программой и программой-сервером, ориентированной на соединение, состоит в том, что обе они прослушивают порт протокола. Например, ориентированные и не ориентированные на соединение программы-серверы должны ждать появления запроса клиента на порту протокола. Точно так же, поскольку не ориентированные на соединение клиенты не устанавливают соединения напрямую с удаленным компьютером, они должны ожидать появления датаграммы-ответа на собственный запрос на порту протокола. Функция bind интерфейса сокетов позволяет программам связать локальный адрес (совокупность адресов локального компьютера и номера порта) с сокетом. Следующий оператор иллюстрирует вызов функции bind: result = bind(socket__handle, local_socket_address, address_length); Создавая программу-сервер, вы закладываете в нее способность ожидать появления запроса от клиента. Вы знаете, что транспортный уровень TCP/IP общается с приложениями (клиентами и серверами) через порт протокола. Указание локального адреса (порта протокола) 7 Зак. № 1949 193 
Другими словами, чтобы принять запрос клиента, сервер должен ожидать, что транспортный уровень доставит его на определенный номер порта протокола. Программа-сервер должна использовать функцию bind, чтобы зарегистрировать собственный порт протокола в рамках интерфейса сокетов. Программа должна сообщить интерфейсу, на какой из портов нужно доставлять данные, предназначенные именно этому серверу. Реализация сокетов, в свою очередь, сообщает транспортному уровню, что определенный порт протокола занят приложением и что он должен доставлять все данные, адресованные этому порту, интерфейсу сокетов. Как было замечено, не ориентированный на соединение клиент также должен прослушивать порт протокола. Программы, пользующиеся не ориентированными на соединение протоколами, не устанавливают прямого сетевого соединения. Из этого следует, что не ориентированный на соединение клиент также должен прослушивать порт протокола на предмет появления ответных датаграмм. Как и программы-серверы, клиенты, не ориентированные на соединение, используют функцию bind, чтобы зарегистрировать порт протокола в интерфейсе сокетов. Другими словами, не ориентированный на соединение клиент, так же, как и сервер, указывает интерфейсу сокетов тот порт, который он будет использовать для доставки данных. Реализация сокетов организует в свою очередь интерфейс между таким клиентом и программным модулем UDP транспортного уровня. В последующих разделах этой главы обсуждаются функции интерфейса сокетов (такие, как listen, accept, recvfrom и recv), используемые прикладными программами для получения данных из порта протокола. На настоящий момент вы должны понимать, что для того, чтобы сконфигурировать сокет на определенный порт протокола, необходимо вызвать функцию bind. Передача данных через сокет После того как сокет сконфигурирован, через него можно установить сетевое соединение. Процесс сетевого соединения подразумевает посылку и прием информации. Интерфейс сокетов включает несколько функций для выполнения этих обеих задач. В этом разделе описывается, как программа посылает данные через сокет. Позднее мы обсудим, каким образом программы принимают данные. Интерфейс сокетов Беркли обеспечивает пять функций для передачи данных через сокет. Эти функции разделены на две группы. Трем из них требуется указывать адрес назначения в качестве аргумента, а двум остальным — нет. Основное различие между двумя группами состоит в их ориентированности на соединение. Все пять функций из интерфейса сокетов описаны в табл. 7.2. 194 
Таблица 7.2. Функции передачи данных из интерфейса сокетов, доступные прикладным программам Функция интерфейс сокетов Описание send Передает данные через соединенный сокет. Использует некоторые флаги для управления поведением сокета. write Передает данные через соединенный сокет. Для передачи используется буфер данных. writev Передает данные через соединенный сокет. В качестве буфера используется раздельно расположенные блоки памяти. sendto Передает данные через не соединенный сокет. Использует буфер данных. sendmsg Передает данные через не соединенный сокет. В качестве буфера используется гибкая структура сообщения. Передача данных через соединенный сокет Виртуальные цепи образуются ориентированными на соединение протоколами. При этом соединение между конечными точками сети выглядит, как выделенное двухточечное соединение. После того как программное обеспечение установило соединение, прикладная программа может обмениваться данными в простом последовательном потоке байтов. Функции интерфейса сокетов, осуществляющие ориентированную на соединение передачу данных, не требуют от прикладных программ указывать адрес назначения в качестве аргумента. Вся информация об адресах назначения (сетевой адрес и номер порта) заносится в структуру дескриптора сокета на стадии его конфигурации. В течение сеанса связи через ориентированный на соединение сокет, все заботы, связанные с адресами удаленного сокета и управления интерфейсом с транспортным уровнем, берет на себя реализация сокетов. Функции send, write и writev предназначены только для соединенных сокетов — они не позволяют программе указать адрес назначения. Все три функции требуют, чтобы в качестве первого аргумента при вызове был задан дескриптор сокета. Функция writev, в отличие от write, не требует, чтобы данные занимали непрерывную область памяти. В качестве аргумента функции writev может передаваться массив адресов, по которым расположены данные. Следующий оператор демонстрирует типичный вызов функции write: result = write (socket__handle, message_buffer, bufferJLength) ; 195 
Нам уже знаком первый параметр, дескриптор сокета. Вы знаете, что он обозначает структуру в таблице дескрипторов, содержащую информацию о данном сокете. Второй параметр функции write, буфер сообщения, указывает на буфер, то есть область памяти, в которой расположены предназначенные для передачи данные. Прикладная программа должна предварительно отвести память для этого буфера, а затем заполнить его данными. Третий параметр вызова функции обозначает длину буфера, то есть количество данных для передачи. Функция writev вызывается так, как показано ниже: result = writev(socket_handle, io_vector, vector_length); Так же, как и в случае write, функция writev требует, чтобы первым параметром указывался дескриптор сокета. Ее второй параметр, вектор ввода-вывода, указывает на массив указателей. Предположим, что данные для передачи располагаются в различных областях памяти. В этом случае каждый член массива указателей представляет собой указатель на одну из областей памяти, содержащей данные для передачи. Когда функция writev передает данные, она находит их по указанным прикладной программой в массиве указателей адресам. Данные высылаются в том порядке, в каком их адреса указаны в массиве указателей. Третий параметр функции writev определяет количество указателей в массиве указателей, заданном вектором ввода-вывода. Как было отмечено, второй параметр функции writev является вектором вводавывода, то есть задает адрес массива, состоящего из последовательности указателей на блоки данных для передачи. С каждым указателем в последовательности интерфейс сокетов связывает определенную длину блока данных, то есть сколько байтов данных присутствует по каждому адресу в последовательности. На рис. 7.2 изображен образец формата вектора ввода-вывода. Позиции битов 32 бита ■ Указатель на блок данных 1 (32-разрядный адрес памяти) 31 Длина блока данных 1 _____ (32-разрядное целое) ё»/10к 2 , 1(32-разрядныйадэо,пшятиК Длина блока данных 2 ...... Рис. 7.2. Формат массива указателей, на который указывает аргумент функции writev Поскольку функция write использует непрерывную область памяти в качестве буфера данных, то и выполняется она быстрее, чем writev. Однако в ситуации, когда структура передаваемых данных сложна либо нет возможности получить у системы достаточно большой непрерывный блок памяти, выручает функция writev. Функция send — последняя из рассматриваемых функций, позволяю- 196 
Передача данных через еокет| щих отправлять данные через соединенный сокет. Следующий оператор является образцом вызова функции send: result = send(socket_handle, message_buffer, buffer_length, special_flags); Основное преимущество send состоит в том, что приложение может задать некоторые флаги для управления передачей данных. Вы знаете, что, например, TCP/IP имеет режим передачи данных вне основной полосы пропускания (данных для неотложной обработки). Эти данные имеют приоритет выше, чем у остальных. Один из возможных флагов в вызове send может сообщить, что приложение передает данные, требующие немедленной обработки. Примечание: Несмотря на то, что функция send позволяет передавать данные для неотложной обработки, вы не должны использовать этот режим, если четко не представляете, как они будут обработаны приемником. Теоретически, передача неотложных данных — мощное средство, но его конкретные реализации сложны и часто не совместимы друг с другом. Чтобы узнать больше о данных для неотложной обработки, прочитайте главу 16 книги «Межсетевое взаимодействие сетей на базе TCP/IP», том 2 (Internetworking with TCP/IP, Volume 2: Design, Implementation, and Internals, Douglas E. Comer and David L. Stevens, Prentice Hall, 1994). Три вышеописанные функции (write, writev и send) возвращают целое число в качестве результата. Если не произошла ошибка, результат будет равен количеству переданных байтов. В случае ошибки, возвращаемое значение результата будет равно -1. Сокеты Беркли устанавливают стандартную переменную еггпо языка С для того, чтобы сообщить дополнительную информацию об ошибке. Однако не все компиляторы С для персональных компьютеров устанавливают эти значения. А иногда они делают это по-разному. В следующей главе вы узнаете, что Windows API использует совершенно другую систему информирования об ошибках. Передача данных через не соединенный сокет Чтобы послать данные через соединенный сокет, то есть сокет протокола, ориентированного на соединение, прикладная программа может использовать одну из трех описанных в предыдущем разделе функций. Однако ни одна из них не позволяет указывать адрес получателя данных. Для того чтобы послать данные через не соединенный сокет, требуется вызвать одну из двух следующих функций: sendto или sendmsg. Они обеспечиваются интерфейсом сокетов именно для этих целей. Функция sendto требует шесть параметров в качестве аргументов. Первые четыре те же, что и в функции send. Пятый параметр, структура адреса сокета, определяет адрес назначения. Шестой параметр, длина структуры адреса сокета, — размер этой структуры в байтах. Следующий оператор демонстрирует вызов функции sendto: 197 
result = sendto (socket_handle, message..buffer, buffer..length, special_flags, socket^address_structure, address_structure_length) ; Функция sendmsg позволяет использовать гибкую структуру данных вместо буфера, расположенного в непрерывной области памяти. Следующий оператор демонстрирует вызов sendmsg. В качестве аргументов указываются дескриптор сокета, указатель на структуру данных и дополнительные флаги: result = sendmsg(socket_handle, message_structure, special_flags) ; Структура сообщения позволяет программе гибко размещать длинные списки параметров сообщения в единой структуре данных. Функция sendmsg похожа на writev в том, что прикладная программа может разместить свои данные в нескольких раздельно расположенных блоках памяти. Другими словами, как и в функции writev, структура сообщения содержит указатель на массив адресов памяти. На рис. 7.3 показан пример формата структуры сообщения, используемый sendmsg. 4 32 бита ► Позиции битов j о 31 Указатель на структуру данных сокета Длина структуры данных сокета Указатель на список векторов ввода/вывода Длина списка векторов ввода/вывода Указатель на список прав доступа Длина списка прав доступа Рис. 7.3. Формат структуры сообщения, используемый функцией sendmsg В следующем разделе вы познакомитесь с функцией recvmsg, которая принимает данные в структуру сообщения точно такого же типа, какой использует функция sendmsg. Также вы познакомитесь с другими функциями, предназначенными для приема данных. Прием данных через сокет В интерфейсе сокетов есть пять функций, предназначенных для приема информации. Они называются: read, readv, recv, recvfrom, recvmsg и соответствуют функциям, использующимся для передачи данных. Например, функции recv и send обладают одинаковым набором параметров. Функция recv принимает данные, a send — посылает. Точно так же одинаков набор параметров и у функций writev и readv. Функция writev передает данные, a readv — принимает. 198 
И та и другая позволяют задать массив адресов памяти, где располагаются данные. Функции recvfrom и recvmsg соответствуют функциям sendto и sendmsg. В табл. 7.3 приведен список всех соответствующих функций: Таблица 7.3. Соответствующие друг другу функции передачи и приема данных интерфейса сокетов Функция передачи данных Соответствующая функция приема данных send recv write read writev readv sendto recvfrom sendmsg recvmsg Несмотря на то, что интерфейс сокетов имеет соответствующие друг другу функции приема и передачи данных, никто не обязывает соблюдать это соответствие. Предположим, удаленный сетевой компьютер желает передать данные вашему приложению. Чтобы передать программе пришедшие из сокета данные, вовсе не обязательно вызывать строго соответствующую функцию. Так или иначе, данные в сокете все равно представлены единым потоком байтов. Поэтому считать их можно любой из имеющихся функций: recv, read или readv. Интерфейс сокетов позволяет использовать наиболее удобную в данный момент функцию. Например, если программа не хочет запрашивать у системы большие непрерывные блоки памяти, она может пользоваться функцией readv. В любом случае необходимо учитывать тот факт, что исходный текст программы легче всего читается, если в нем для решения одинаковых задач всегда вызываются однотипные функции. Процесс целиком На рис. 7.4 изображены системные вызовы интерфейса сокетов, как правило, используемые программами, ориентированными на соединение. Левая часть диаграммы показывает вызовы на стороне сервера, а правая — на стороне клиента. Линии и стрелки между модулями сервера и клиента изображают поток сетевых сообщений между двумя программами. Программа-сервер создает сокет, вызывая функцию socket. Строго говоря, программа сервер запрашивает у интерфейса сокетов отвести структуру данных сокета и возвратить дескриптор сокета, который будет использован для дальнейших вызовов сетевых функций. Далее, сервер привязывает сокет к локальному номеру порта протокола. 199 
Сервер, ориентированный на соединение socket() listen() accept() Блокирование до получения запроса со стороны клиента ! 4 Обработка з? I write() Клиент, ориентированный на соединение socket() connect() write() Клиент! Рис. 7.4. Сокеты в ориентированном на соединение протоколе В следующем разделе будут описаны системные вызовы listen и accept. На данный момент достаточно знать, что вызов listen требует, чтобы сокет прослушивал порт на предмет входящих соединений и подтверждений о доставке. Другими словами, вызов listen переводит сокет в режим пассивного ожидания соединения. Ожидающий соединения сокет высылает каждому передатчику сообщение-подтверждение о том, что сетевой компьютер принял запрос на установление соединения. Однако на самом деле это не означает, что пассивный сокет принял запрос. Чтобы действительно принять запрос и установить соединение, программа должна вызвать функцию accept. Как показано на рис. 7.4, программа-клиент также создает сокет, вызывая функцию socket. Однако в отличие от сервера, ориентированной на соединение, например TCP, программе-клиенту нет дела до номера порта протокола, который она получит. То есть ей незачем вызывать функцию bind. Вместо этого программа-клиент, ориентированная на соединение, устанавливает соединение, вызывая функцию connect. После установления соединения передача данных происходит при помощи функций write и read. Кроме них клиент и сервер могут использовать send и recv, а также любую другую функцию, предназначенную для работы с ориентированным на соединение протоколом. Рис. 7.5 иллюстрирует систем- 200 
Не ориентированный на соединения сервер socket() щ recvform() Блокирование до получения запроса j со стороны кл иен ш v ^ sendto() Не ориентированный на соединение клиент socket() sendto() recvform() I: ;■ Клиент Данные (запрос) > Данные (ответ) Рис. 7.5. Сокеты в не ориентированном на соединение протоколе ные вызовы функций, предназначенных для не ориентированных на соединение протоколов. Не ориентированный на соединение сервер на рис. 7.5 вызывает функции socket и bind так же, как и сервер, ориентированный на соединение. Но поскольку образованный сокет не соединен, для чтения данных программа-клиент использует функцию recvfrom вместо обычных recv или read. Обратите внимание на то, что программа-клиент, изображенная на рис. 7.5, вызывает функцию bind, но не вызывает connect. Вы помните, что не ориентированные на соединение протоколы не устанавливают никакого предварительного соединения между конечными точками сети. Вместо этого для передачи данных используется функция sendto, требующая от программы указать адрес назначения сообщения в качестве одного из аргументов. Функция recvfrom также не ожидает соединения. Вместо этого она обрабатывает любые данные, появившиеся на соответствующем (связанном с ней) порту протокола. Получив датаграмму из сокета, функция recvfrom записывает как ее содержимое, так и сетевой адрес, с которого она получена. Программы, серверы и клиенты используют сетевой адрес для идентификации процесса передатчика или приемника датаграммы. Как и положено, сервер посылает ответную датаграмму по адресу, ранее извлеченному функцией recvfrom из пришедшей датаграммы. 201 
Сокеты и серверы Как показано на рис. 7.4, обыкновенная, ориентированная на соединение программа-сервер вызывает функции listen и accept. Первая переводит сервер в режим пассивного ожидания запроса. Функция accept, наоборот, заставляет сокет программы установить соединение. В следующих абзацах описано, как и почему серверы используют эти две функции специального назначения. Потоки и процессы Большинство операционных систем являются многопоточными. Поток (thread)1 — это цепочка инструкций, из которых составлена программа. Каждое отдельное приложение в системе представлено набором потоков, которые можно считать процессами. Многопоточная программа (процесс) может состоять из множества одновременно и независимо исполняющихся потоков. Если многопоточная программа исполняется на многопроцессорном компьютере, каждый поток может исполняться на собственном процессоре. Очевидно, что многопоточная программа в этом случае исполняется значительно быстрее по сравнению с программой из одного потока. Вдобавок ко всему, многопоточная программа на компьютере с одним процессором оказывается быстрее программы из одного потока, который исполняется в одном процессе. Переключение процессов (контекста) на многозадачных компьютерах занимает больше времени, нежели переключение потоков. Конструкция сервера параллельной обработки предполагает, что для каждого нового запроса от клиента создается новая копия процессасервера. Если сервер параллельной обработки многопоточный, его производительность значительно увеличивается. Вместо создания нового процесса или их переключения, многопоточный сервер параллельной обработки просто образует новый поток, выполняющийся гораздо быстрее. Если сетевой компьютер оборудован несколькими процессорами, сервер будет работать еще быстрее, поскольку каждый поток сможет выполняться на отдельном процессоре. Функция listen Как вам известно из второй главы, серверы бывают последовательной и параллельной обработки. Процесс-сервер, обрабатывающий каждый запрос клиента индивидуально, является последовательным. Последовательный сервер обрабатывает запросы поочередно, выстраивая их в очередь, если необходимо. Чтобы быть эффективным, такой сервер должен затрачивать ограниченное и заранее предсказуемое время на обработку поступающих запросов. Если сервер должен одновременно обработать несколько поступивших запросов, когда заранее неизвестно, сколько времени будет затрачено на обработку каждого, он конструируется параллельным. Параллельный сервер создает отдельный процесс (или поток, если это позволяет операционная система) для обработки каждого запроса. Другими словами, он обрабатывает запросы параллельно. Потоки также называются легковесными процессами (lightweight processes) и иногда нитями (threads). — Примеч. перев. 202 
Теперь рассмотрим, что происходит, когда появляется очередной запрос, а сервер еще не закончил обработку предыдущего. То есть когда программа-клиент пытается послать еще один запрос, в то время как последовательный сервер еще не закончил обработку предыдущего, или когда клиент посылает запрос до того момента, как параллельный сервер закончил создание нового процесса — обработчика предыдущего запроса. В этом случае сервер может отвергнуть или игнорировать поступивший запрос. Для того чтобы обработка была эффективнее, существует функция listen. Она, действуя как можно быстрее, располагает все поступившие запросы в очередь. Функция listen не только переводит сокет в пассивный режим ожидания, но и подготавливает его к обработке множества одновременно поступающих запросов. Другими словами, в системе организуется очередь поступивших запросов, и все запросы, ожидающие обработки сервером, помещаются в нее, пока освободившийся сервер не выберет его. При вызове функции listen указываются два параметра: дескриптор сокета и длина очереди. Длина очереди обозначает максимальное количество запросов, которое может поместиться в ней. Следующий оператор — образец вызова функции listen: result = listen(socket_handle, queue_length); В настоящее время максимальная длина очереди равна пяти. Если вы попытаетесь указать большее число, то получите сообщение об ошибке. Если очередь при поступлении нового запроса окажется переполненной, сокет отвергнет соединение и программа-клиент получит сообщение об ошибке. В случае последовательного сервера, задавать длину очереди, равную одному или двум, тоже полезно. Это позволит серверу, если он не справился с обработкой за минимальное назначенное время, все-таки не отвергнуть новый запрос,.а выбрать его из входной очереди. Функция accept Как уже отмечалось, функция bind привязывает сетевой адрес и номер локального порта протокола к определенному сокету. Программы-клиенты устанавливают соединение, привязывая сокет к удаленному порту протокола, расположенному на удаленном компьютере. Клиент знает, какой именно сервер ему нужен, поэтому располагает всей требуемой информацией. С другой стороны, сервер не имеет представления о том, от какого клиента может поступить запрос. Поэтому, вызвав bind, он не может определить заранее ни его адреса, ни номера порта. Вместо этого сервер вызывает bind, задав в качестве аргумента символ, совпадающий с любым адресом (wildcard). Константа, определяющая любой адрес, называется INADDR_ANY. Другими словами, такой сервер принимает запросы от любого сетевого компьютера, то есть от любой программы-клиента. Примечание: Как вы узнаете из главы 19, для того чтобы указать «любой» адрес, используется символьная константа INADDR_ANY, описанная в файлезаголовке winsock.h. 203 
m ДУГ; Глава 7. Интерфейс сокетов Как следует из названия, функция accept позволяет серверу принять запрос на соединение, поступивший от клиента. После того как установлена входная очередь, программа-сервер вызывает функцию accept и переходит в режим ожидания (паузы), ожидая запросов. Для того чтобы понять смысл всех операций сервера, мы должны подробно рассмотреть, как функционирует accept. При вызове accept требуется указывать три параметра: дескриптор сокета, его адрес и длину адреса. Дескриптор сокета описывает сокет, который будет прослушиваться сервером. В момент появления запроса реализация сокетов заполняет структуру адреса (на которую указывает второй параметр) адресом клиента, от которого поступил запрос. Реализация сокетов заполняет также третий параметр, размещая в нем длину адреса. Следующий оператор демонстрирует, как можно вызывать функцию accept: result = accept(socketjiandle, socket_address, address_length); После того как реализация сокетов поместит информацию об адресе клиента в область памяти, заданную параметром функции, она выполнит операции, необходимые для того, чтобы сервер заработал. Первым делом, получив запрос соединения на сокет, обслуживаемый функцией accept, реализация образует новый сокет. Новый сокет связывается с адресом процесса-передатчика запроса. На рис. 7.6 изображена ситуация, когда два клиента обращаются к серверу параллельной обработки запросов. Стадия 1: Установление соединения клиент-сервер < ► Стадия 2: Сервер параллельной обработки передает управление дочернему процессу Клиент 1 |+ ^ Дочерний процесс 1 Стадия 3: Если в момент обработки запроса от первого клиента поступает запрос от второго, сервер параллельной обработки передает управление второму дочернему процессу Рис. 7.6. К серверу параллельной обработки обращаются программы-клиенты 204 
Коротко весь процесс можно описать следующим образом: когда на сокете, контролируемом функцией accept, появляется очередной запрос клиента, программное обеспечение-реализация сокетов автоматически создает новый сокет и немедленно соединяет его с процессом-клиентом. Сокет, на который поступил запрос, освобождается и продолжает работу в режиме ожидания запросов от любого сетевого компьютера. Процесс-сервер Как только на сокете, обслуживаемом функцией accept, появляется запрос, функция возвращает серверу дескриптор только что созданного нового сокета. Что сервер будет делать с этим дескриптором, зависит от реализации сервера. Сервер может обрабатывать запросы параллельно или последовательно. Предположим, что вы создали последовательный сервер. Следовательно, он будет последовательно обрабатывать, а затем закрывать все переданные ему функцией accept дескрипторы сокетов. По окончании обработки конкретного запроса последовательный сервер вновь вызывает accept, а она возвращает ему дескриптор сокета для следующего запроса, если он имеется, и т. д. Если до этого вызывалась функция listen, запрос может быть выбран из входной очереди, если нет — сервер прослушивает сетевые запросы напрямую через сокет. Теперь предположим, что вы написали сервер параллельной обработки. После того как выполнится функция accept, параллельный сервер создаст новый (дочерний) процесс и передаст ему задачу по обслуживанию нового запроса. Каким образом создается дочерний процесс, зависит от операционной системы. В UNIX, например, для этого вызывается функция fork. Независимо от того, как создается дочерний процесс, процесс-родитель передает ему копию нового сокета. Далее родительский процесс закрывает собственную копию сокета и вновь вызывает функцию accept. Можно видеть, что и последовательный и параллельный серверы действуют практически одинаково, обслуживая сетевые запросы. Между ними существует лишь одно различие: параллельный сервер не ждет, пока закончится обработка запроса, а вызывает функцию accept сразу же. Последовательный сервер не может вызвать accept до тех пор, пока не обработает запрос. Программа-сервер с параллельной обработкой запросов создает дочерний процесс для обработки поступающих запросов и сразу после этого вызывает функцию accept. За исключением случаев, когда обработка запроса почти не занимает времени, сервер успевает вызвать accept еще до того, как дочерний процесс ее закончит. Другими словами, сервер параллельной обработки запросов способен отвечать на множество запросов одновременно. Принципы проектирования параллельного сервера Вам, может быть, непонятно, каким образом параллельный сервер одновременно выполняет запросы, поступившие на один и тот же порт протокола. Вы помните, что для сетевого соединения сокету необходимо иметь две конечные точки. 205 
Конечные точки сетевого соединения определяют адреса взаимодействующих процессов. До тех пор пока сетевые процессы соединены с различными точками сетевого соединения, они могут работать на одном и том же порту протокола, не мешая друг другу. Сервер параллельной обработки создает новый процесс для каждого соединения. Другими словами, на сетевом компьютере постоянно работает одиночный главный сервер, ожидая запроса от любого процесса в сети. В другой момент времени, на том же компьютере по-прежнему работает главный сервер, а вместе с ним — множество подчиненных. Каждый подчиненный сервер работает с уникальным адресом конкретного, соединенного с ним процесса. Например, сегмент TCP идентифицируется своим уникальным адресом. Когда сервер принимает сегмент TCP, он отправляет его на сокет, связанный именно с адресом сегмента. Если такого сокета не существует (что означает поступление запроса на новую сетевую службу), сервер передаст сегмент сокету, соединенному с любым адресом (wildcard address). Мы помним, что сокет, соединенный с любым адресом, принимает запросы на соединения, поступающие с любого сетевого адреса. Начиная с этого момента весь процесс повторяется. Другими словами, сокет главного процесса-сервера (прослушивающий запросы с любого сетевого адреса) порождает дочерний процесс-сервер, который, в свою очередь, и обрабатывает запрос. Необходимо понимать, что сокет, прослушивающий любой сетевой адрес, не может иметь открытого соединения. Мы помним, что для установления сетевого соединения необходимо иметь пару конечных точек, каждая из которых должна обладать определенным адресом. Поскольку сокет главного процесса-сервера всегда прослушивает запросы с любого адреса, он в состоянии обработать только вновь поступивший запрос. Функция select Существует другая функция, не показанная на рис. 7.4 и 7.5, — она довольно часто используется серверами и называется «select». Сложные программы-клиенты также могут использовать ее. Функция select позволяет одиночному процессу следить за состоянием сразу нескольких сокетов. При вызове select указываются пять параметров. Первый параметр, количество сокетов (number of sockets), задает общее количество сокетов для наблюдения. Параметры readable-sockets, writeable-sockets и error-sockets являются битовыми масками, задающими тип сокетов. Приведенный ниже оператор демонстрирует, как можно вызвать функцию select: result = select(number_of_j30ckets# readable_sockets, writeable__sockets, error_sockets, max_time) ; Сокет для чтения (readable socket) содержит принятые данные, которые извлекаются программой при помощи стандартных вызовов recv или recvfrom. Сокет для записи (writable socket) — это сокет, установивший соединение. Через него программа может передавать данные, используя стандартные функции типа send или sendto. В случае сетевой ошибки, функция select обозначает сокет, в котором это произошло, как ошибочный (exception). Ситуация ошибки требует дальнейшей программной обработки. В любом случае select определяет состояние только тех сокетов, которые были отмечены в каждой битовой маске. Интерфейс 206 
сокетов использует битовые маски для определения набора сокетов, за которыми будет установлено наблюдение. При выходе в вызывающую программу select возвращает количество сокетов, готовых к операциям ввода-вывода. Для заданных дескрипторов файлов функция select также изменяет их битовые маски. Другими словами, вы указываете функции select за какими сокетами следить и какого рода информация о каждом из сокетов вас интересует. Функция select, в свою очередь, информирует вас о количестве сокетов, готовых для приема-передачи данных (чтения, записи и сообщений об ошибках). В дополнение select модифицирует битовые маски таким образом, что каждый сокет оказывается принадлежащим определенной категории. До того как вызвать функцию select, программа должна установить биты в маске так, чтобы они указывали на сокеты, информацию о которых необходимо получить. Функция select сбросит биты, указывающие на любой сокет, не готовый к определенной операции ввода-вывода. После завершения функции select прикладная программа может проанализировать содержимое битовой маски. Если бит, идентифицирующий определенный сокет, окажется установленным, это значит, что сокет готов к операции ввода-вывода (чтению, записи или сообщению об ошибке). Можно написать программу так, что она будет изменять свое выполнение в зависимости от результатов вызова функции select. Предположим, мы спроектировали программу, создающую три сокета. Предположим, у нас есть одна процедура, обслуживающая операции чтения, другая процедура, осуществляющая запись, и третья, обрабатывающая ошибочные ситуации. Функция select может вызываться, чтобы одновременно получить информацию о состоянии всех трех сокетов. Предположим, что в результате вызова select программа узнает, что один сокет может считывать данные, второй может записывать, а третий содержит информацию об ошибке. В этом случае разумнее всего будет вызвать процедуру обработки ошибок. На самом деле, получив статус всех контролируемых сокетов, программа может выбрать то действие, которое наиболее разумно предпринять. Подводя итоги В этой главе вы узнали, откуда появился и как развивался интерфейс сокетов, созданный, чтобы перенести семейство протоколов TCP/IP на операционную систему UNIX. В главе рассматривалось, каким образом сетевая абстракция — конечные точки соединения — служит для описания сокетов. Вы узнали, как создавать и настраивать сокеты, а также как передавать и принимать данные через них. В восьмой главе вы прочитаете об интерфейсе прикладного программирования сокетов Windows (Winsock API). Разработчики Winsock API основывались на интерфейсе сокетов Беркли, описанном в настоящей главе. Чтобы правильно усвоить концепции Winsock API, необходимо хорошо представлять основные принципы интерфейса сокетов вообще. Перед тем, как приступить к чтению восьмой главы, проверьте, до конца ли вы усвоили следующие ключевые понятия: S Сокет — это абстрактное представление конечной точки сетевого соединения. 207 
Глава 7. Интерфейс сокетов : : : ■ • ■ Ш S Разработчики интерфейса сокетов приспособили его для работы не только с сетями на базе TCP/IP. S Интерфейс сокетов пользуется понятиями группы (семейства) протоколов и сетевых адресов для того, чтобы иметь возможность работать с различными сетями. S С интерфейсом сокетов могут работать как ориентированные, так и не ориентированные на соединение протоколы. Однако каждый тип протокола обслуживается различными наборами функций. S При помощи интерфейса сокетов можно разрабатывать как программы-серверы, так и программы-клиенты, однако каждый тип приложения обслуживается различными функциями. S До того как настроить сокет на сетевое соединение, программа должна создать его, вызвав функцию socket. S Для соединения сокета с партнером на другом конце недостаточно только создать его. Кроме функции socket должна вызываться функция connect. S То, какие функции вызываются для настройки сокета, зависит от того, серверу или клиенту предназначен этот сокет, а также от того, ориентирован или не ориентирован на соединение выбранный протокол. S Для передачи данных в интерфейсе сокетов предназначены пять различных функций. S На каждую функцию по передаче данных в интерфейсе сокетов приходится соответствующая функция для приема данных. S При помощи интерфейса сокетов можно разрабатывать серверы как с последовательной, так и с параллельной обработкой данных. 
Интерфейс сокетов Windows В течение нескольких последних лет операционная среда Microsoft Windows развивалась наиболее быстрыми темпами. На сегодняшний день более 50 миллионов пользователей работают в этой среде, и ожидается скорейший переход на следующую ступень развития Windows — Windows 95. Очевидно, что популярность этой операционной системы в ближайшем будущем не собирается уменьшаться. Самый быстрорастущий сегмент Интернет состоит как раз из пользователей Windows, установленной на их персональных компьютерах. Совокупность двух этих факторов приводит нас к выводу, что потребность в программистах-разработчиках приложений Интернет для Windowsкомпьютеров будет расти все больше и больше, так как рынок приложений еще далек от насыщения. Эта глава целиком посвящена написанию приложений Интернет для операционной системы Windows. Для многих из вас эта глава как раз то, из-за чего была куплена вся книга. В седьмой главе вы узнали, какие системные вызовы нужно выполнять приложению Интернет, чтобы устанавливать сетевые соединения через сокеты, рабо- 209 
Глава 8. Интерфейс сокетов Windows тающие как две конечные точки сети между двумя компьютерами. В данной главе мы познакомимся с сокетами Windows и специальным программным обеспечением (оно называется Winsock), предназначенным для доступа к ним. Прочитав главу, вы овладеете следующими ключевыми понятиями: ♦ Каким образом Winsock вписывается в операционную систему Windows. ♦ Чем сокеты Беркли отличаются от аналогичных в Winsock. ♦ Что такое операции с блокировкой в Winsock. ♦ Каким образом используются специфические для Windows функции Winsock. Происхождение Winsock Вы знаете, что в процессе сетевого соединения два компьютера или процесса обмениваются данными. Сетевые профессионалы называют каждую сторону такого соединения конечной точкой (endpoint). Сокет является выражением абстракции конечной точки сетевого соединения. В 80-х годах разработчики Калифорнийского университета в г. Беркли использовали парадигму (модель) сокетов, чтобы реализовать интерфейс прикладного программирования (API) сетей на базе TCP/IP. (Из седьмой главы вам известно, что интерфейс прикладного программирования API представляет собой набор функций, используемых программистами для написания приложений в определенной сетевой операционной среде.) Интерфейс Беркли — всего лишь одна (хотя и чаще всего используемая) реализация интерфейса прикладного программирования, основанная на модели сокетов. Сокеты Windows (часто называемые «Winsock») — также интерфейс прикладного программирования, разработанный на основе сокетов Беркли. Тогда как сокеты Беркли используются на разных операционных системах, Winsock предназначен исключительно для семейства Windows, то есть для Microsoft Windows, Windows NT и Windows 95. В состав Winsock входит множество функций из интерфейса Беркли, изначально разработанных для Unix — операционной системы, для которой и предназначались сокеты Беркли. Кроме того, имеется набор специфических для Windows функций, позволяющих программистам пользоваться преимуществами интерфейса Windows, основанного на передаче сообщений. В данной главе описываются как специфические по сравнению с сокетами Беркли функции, так и сетевое программирование в Windows само по себе. Спецификация Winsock, приведенная в данной главе, соответствует версии 1.1. Примечание: Чтобы разобраться в Winsock API, вы должны хорошо представлять себе концепция интерфейса сокетов. Winsock сильно напоминает сокеты Беркли, поэтому рекомендуется хотя бы ознакомиться с содержанием седьмой главы, если вы еще этого не сделали. Успех в понимании материала данной главы сильно зависит от того, насколько хорошо усвоен материал предыдущей. 210 
Реализация Winsock Где еще можно найти информацию о сокетах Windows? Если вы намереваетесь разрабатывать сетевые приложения в среде Windows, вам может понадобиться спецификация Winsock. В каталоге /pub/micro/pc-stuff/ms-windows/winsock анонимного ftp-архива sunsite.unc.edu находится масса полезной информации, касающейся Winsock. Один из наиболее полезных файлов там — спецификация Winsock в формате help-файла Windows (.HLP). Он находится в каталоге ./winsock1.1. Реализация Winsock Сеть Интернет основывается на протоколах TCP/IP. Windows — самая популярная среда программирования на сегодняшний день. Спецификация Windows Sockets описывает стандарт, по которому программы Windows обязаны общаться с сетями на базе TCP/IP. Таким образом, Windows Sockets API представляет колоссальные возможности в развитии старых и написании новых сетевых приложений. Корпорация Microsoft, создатель и владелец Windows, не имеет имущественных прав на стандарт Winsock. Усилия, необходимые для создания спецификации Windows Sockets, были затрачены большим количеством людей, работающих в различных корпорациях. Цель разработки — создание единого интерфейса прикладного программирования (API), который бы использовался в равной мере как разработчиками, так и продавцами сетевого программного обеспечения и служил бы стандартом работы с TCP/IP для Windows. Современный стандарт Windows Sockets (версии 1.1) обеспечивает работу исключительно сетей на базе TCP/IP, хотя положение вещей может и измениться в будущем. Мы уже говорили о том, что первоначально интерфейс сокетов был встроен в UNIX. Другими словами, API оказался частью операционной системы. Интерфейс сокетов Windows не входит в состав Windows, а реализован в виде динамически загружаемой библиотеки (DLL). Что такое динамическая библиотека (DLL) На протяжении многих лет программисты писали так называемые резидентные программы (TSR) для DOS. Будучи однажды запущенной, резидентная программа остается в памяти компьютера. В дальнейшем она реагирует на наступление определенных событий в системе, например прерывания от клавиатуры, и выполняет некоторые действия. DOS, например, не умеет работать с мышью. Чтобы дать программе для DOS возможность воспользоваться мышью, необходимо загрузить специальную программу-драйвер мыши, которая является резидентной. Компания Microsoft никогда не объявляла резидентные программы официально сопровождаемой частью DOS. В случае Windows, однако, Microsoft предусмотрела создание механизмов, обеспечивающих новую функциональность для программ этой операционной среды. Этим стандартным для Windows механизмом являются так называемые динамические библиотеки (Dynamic Link Library, DLL). DLL представляет собой модуль исполняемого кода, который умеет загружаться по требованию прикладного процесса, то есть как только процессу понадобилась функция, входящая в состав DLL. Когда необходимость в функциях DLL исчезает, Windows автоматически выгружает DLL из памяти. Архитектура DLL построена таким образом, что позволяет нескольким прикладным программам одновременно пользоваться одним и тем же модулем. 211 
• • 1 Г М I Глава 8- Интерфейс токётов Window ^ • wt * V 'лл„: l^s£'*т М'/ < \ :Т* Краткое обозрение библиотеки функций Winsock Интерфейс прикладного программирования Winsock содержит набор (библиотеку) функций общего пользования, требуемых для решения определенного класса задач. Спецификация Winsock разделяет всю библиотеку на три группы: • Функции сокетов в стиле Беркли, включенные в состав Winsock API. • Функции для работы с базами данных, позволяющие программам получать информацию об именах доменов, коммуникационных службах и протоколах. • Специфические функции Windows, расширяющие набор функций интерфейса сокетов Беркли. В следующих разделах мы коротко обсудим все три группы функций. В нашей книге мы также разделим функции библиотеки на два вида: блокирующие и не блокирующие. Блокирующая функция заставляет вызвавшую ее программу ждать окончания сетевой операции ввода-вывода. Не блокирующая функция либо завершается сразу, либо возвращает сообщение об ошибке. Другими словами, не блокирующие функции не ждут окончания операции. Блокирование — один из наиболее важных для понимания аспектов сетевого программирования. Сетевая литература слишком часто употребляет этот термин, не затрудняясь объяснить его значение. Мы обсудим блокирование более подробно немного позже в последующих разделах. Функции интерфейса сокетов Блокирующая операция задерживает выполнение программы до окончания своей работы. Как правило, все функции сетевого ввода-вывода сокетов в стиле Беркли — блокирующие. На деле, блокирование проявляется задержкой в выполнении программы до окончания передачи-приема сетевых данных. В табл. 8.1 приведены функции сокетов в стиле Беркли, которые могут блокировать выполнение операций в Winsock API. Таблица 8.1. Функции в стиле Беркли, которые могут блокировать операции Winsock API Функция Описание accept Подтверждает запрос на установление соединения. Образует новый сокет и соединяет его с удаленным сетевым компьютером, запрашивающим соединение. Исходный сокет возвращается в состояние приема входящих запросов. closesocket Закрывает одну сторону в соединении сокетов. connect Устанавливает соединение на указанном сокете. 212 
Реализация Winsock Таблица 8.1 (окончание) Функция Описание recv Принимает данные из соединенного сокета. recvfrom Принимает данные из соединенного или не соединенного сокета. select Выполняет синхронные мультиплексные операции ввода-вывода путем наблюдения за состоянием нескольких сокетов. send Передает данные через соединенный сокет. sendto Передает данные через не соединенный или соединенный сокет. Изучив описания функций сокетов, приведенные в табл. 8.1, можно заметить, что все они либо производят операции ввода-вывода, либо ждут окончания сетевого ввода-вывода до того, как завершить выполнение. Отсюда можно сделать вывод, что любая функция, так или иначе связанная с операциями ввода-вывода, может блокировать выполнение остальных функция Winsock API. С другой стороны, функции, приведенные в табл. 8.2, не выполняют операций ввода-вывода в процессе работы. Они либо преобразуют информацию, либо имеют дело с локальным сокетом сетевого компьютера. Другими словами, их деятельность не связана с удаленными сетевыми устройствами. Поэтому, несмотря на то, что они также являются функциями в стиле Беркли, ни одна из них не блокирует операции прикладной программы. Таблица 8.2. Функции в стиле Беркли, не блокирующие работу Winsock API Функция bind getpeername getsockname getsockopt htonl htons inet_addr inet_ntoa ioctlsocket Описание Присваивает имя неинициализированному (новому) сокету. Получает имя удаленного процесса, связанного с указанным сокетом. (В дальнейшем вы узнаете, что Winsock API хранит эту информацию в локальной структуре данных, поэтому вызов данной функции не связан с операциями сетевого ввода-вывода.) Возвращает имя указанного местного (локального) сокета. Возвращает статус (опции) указанного сокета. Преобразует порядок байтов в 32-разрядном числе из машиннозависимого в сетевой. Преобразует порядок байтов в 16-разрядном числе из машиннозависимого в сетевой. Преобразует строку с IP-адресом в формате десятичное с точкой в 32-разрядное двоичное число (с сетевым порядком байтов). Преобразует IP-адрес в формат десятичное с точкой. Управляет параметрами сокета, относящимися к обработке операций сетевого ввода-вывода. 213 
Глава 8. Интерфейс сокетов Windows г. ...X :'У V- Таблица 8.2 (окончание) Функция listen ntohl ntohs setsockopt shutdown socket Описание Переводит указанный сокет в состояние прослушивания запросов на входное соединение. (Функция переводит сокет в режим прослушивания, однако сама по себе не производит никаких операций сетевого ввода-вывода.) Преобразует порядок байтов 32-разрядного числа из сетевого в машиннозависимый (порядок хоста). Преобразует порядок байтов 16-разрядного числа из сетевого в машиннозависимый (порядок хоста). Устанавливает режим (опции) работы сокета. Закрывает одну сторону дуплексного соединения (только для местного компьютера). Образует точку сетевого соединения и возвращает дескриптор сокета. Функции для работы с базами данных Программы Интернет должны понимать различные виды адресации. Например, пользователь может указывать имя, например jamsa.com, а может — адрес в виде десятичного с точкой, например 168.158.20.102. До того как использовать эти виды адресации, программа должна преобразовать их к единому виду — структуре данных сокета. Функции Winsock API умеют работать лишь с такими структурами. Когда программа выводит данные, она должна совершить обратное преобразование: из структуры данных сокета в вид, понятный пользователю. К счастью, функции для работы с базами данных, список которых приведен в табл. 8.3, кроме всего прочего, успешно решают задачи прямого и обратного преобразования адресов. Они позволяют прикладной программе получать информацию об именах доменов, коммуникационных службах и протоколах. Для выполнения своих задач этим функциям приходится обращаться к разнообразным, как местным, так и удаленным, источникам информации. Спецификация Winsock определяет интерфейс, а также формат данных, который возвращается функциями для работы с базами данных. Вопросы хранения и выборки данных зависят от конкретной реализации Winsock и не оговариваются в стандарте. Таблица 8.3. Функции Winsock API для работы с базами данных Функция Описание gethostbyaddr Возвращает имя (имена) и IP-адрес, соответствующие указанному сетевому адресу. 214 
Таблица 8.3 (окончание) Функция Описание gethostbyname gethostname getprotobyname getprotobynumber getservbyname getservbyport Возвращает имя (имена) и IP-адрес, соответствующие указанному сетевому имени. Возвращает имя локального сетевого компьютера. Возвращает официальное имя и номер протокола по указанному имени (например, TCP). (В семействе протоколов TCP/IP каждому протоколу соответствует уникальное целое число.) Возвращает имя и номер протокола по указанному номеру. Возвращает имя сетевой службы (например, time) и номер порта протокола, соответствующие указанному имени. Возвращает имя сетевой службы и номер порта протокола, соответствующие указанному номеру порта. Некоторые из вышеописанных функций возвращают указатели на значения, хранящиеся в системной области памяти (volatile). Реализация Winsock может использовать такие области повторно при вызове следующей функции. Это значит, что, если программа желает использовать полученное значение переменной, его необходимо переписать в переменную, принадлежащую программе, до следующего вызова функций, иначе оно может оказаться утерянным. Данные, находящиеся в системных буферах, действительны только до следующего вызова функции Winsock. Интерфейс прикладного программирования Winsock включает так называемые асинхронные (специфические для Windows) аналоги всех функций для работы с базами данных, за исключением gethostname. В дальнейшем мы еще обсудим асинхронные функции Windows. На данный момент сообщим только, что асинхронные функции представляют собой Windows-расширения интерфейса сокетов Беркли. Они появились в результате стремления разработчиков использовать встроенный в Windows механизм обмена сообщениями. В табл. 8.4 перечислены семь функций в стиле Беркли вместе с асинхронными эквивалентами. Все они работают с базами данных и входят в состав Winsock API. Таблица 8.4. Асинхронные функции для работы с базами данных в составе Winsock API Функция в стиле Беркли Асинхронный эквивалент gethostbyaddr W S AAsyncGetHostBy Addr gethostbyname W S A AsyncGetHostByName gethostname (He имеет эквивалента) getprotobyname W S A AsyncGetProtoByName 215 
Глава 8. Интерфейс сокетов Windows Таблица 8.4 (окончание) Функция в стиле Беркли Асинхронный эквивалент getprotobynumber W S A AsyncGetProtoByNumber getservbyname W S AAsyncGetServByName getservbyport W S AAsyncGetServBy Port Функции-расширения, специфические для Windows Как мы только что отметили, разработчики Winsock реализовали специальные асинхронные версии некоторых функций для работы с сокетами, для того чтобы программисты могли пользоваться преимуществами механизма передачи сообщений в Windows. Чем больше вам придется заниматься написанием сетевых программ для Windows, тем чаще и эффективнее вы будете употреблять асинхронные функции. В табл. 8.5 приведены все Windows-расширения, входящие в состав Winsock API, за исключением функций для работы с базами данных, так как они уже приведены в табл. 8.4. Таблица 8.5. Описание функций-расширений Windows, за исключением асинхронных функций для работы с базами данных Функция Описание W S A AsyncSelect W S ACancel AsyncRequest W S ACancelBlockingCall WSACleanup W S AGetLastError WSAIsBlocking W S ASetBlockingHook W S ASetLastError WSAStartup Асинхронный вариант функции select. Прекращает некорректное выполнение функции W S A AsyncGetXByY. Прекращает некорректное выполнение блокирующей функции API. Уведомляет Windows Sockets о том, что программа закончила работу с DLL. Возвращает сообщение о последней ошибке при вызове функции. Определяет, является ли низ лежащий уровень Winsock DLL блокирующим. Устанавливает ловушку блокирующего вызова. (См. соответствующий раздел.) Фиксирует сообщение об ошибке для последующего вызова WSAGetLastError. Инициализирует низлежащий уровень Winsock DLL. WSAUnhookBlockingHook Восстанавливает первоначальную блокирующую функцию. 216 
L ' К:- ' ' s Общая картина Общая картина Чтобы осознать картину программирования в Windows целиком, нужно понимать, какое место в ней занимает Winsock и какие именно функции системы им выполняются. Множество фирм продают свои приложения, полезные утилиты и программы. Для того кто не знаком со структурой Winsock, бывает сложно определить, где заканчивается разработка и начинается Winsock. В главе 6 вы выяснили, что для передачи сетевой информации по модему и телефонным линиям в Интернет существуют протоколы последовательной передачи данных — SLIP и РРР. Производители продукции на базе Winsock часто разрабатывают свои собственные версии этих протоколов. Как правило, одним из компонентов бывает программа для набора телефонного номера для Windows. Она позволяет связать компьютер под Windows с Интернет посредством SLIP или РРР. Очень часто производители сетевого программного обеспечения разрабатывают собственный стек протоколов TCP/IP, включая его в состав продукции. Несмотря на очевидную полезность (и даже, возможно, необходимость) всех этих разработок для написания собственных приложений TCP/IP, необходимо понимать, что все это само по себе не является частью интерфейса Winsock. На рис. 8.1 показано, как интерфейс Winsock вписывается в общую схему разработки TCP/IP программ для Windows. Сетевая карта Модем Сетевой кабель I 5 .... .. V* ./ Аппаратное обеспечение Приложение Windows || - - • | RPC или другой ретееои протокол высокого уровня | Протокол SLIP или РРР [ последовательного порта | i —_—^ Рис. 8.1. Место, занимаемое Winsock в операционной среде Windows 217 
Модуль WINSOCK.DLL находится между стеком протоколов TCP/IP и клиентскими приложениями. Другими словами, он управляет интерфейсом к стеку TCP/IP. В предыдущей части книги вы подробно ознакомились как с протоколами TCP/IP, так и с протоколами последовательной передачи данных SLIP и РРР. Но некоторые компоненты, изображенные на рис 8.2, вам, наверное, еще незнакомы. В следующих разделах мы обсудим эти моду ли-драйверы программного обеспечения вместе с протоколами связи высокого уровня. Протоколы высокого уровня Как правило, интерфейс протоколов высокого уровня, показанный на рис. 8.1, разрабатывается поставщиком программного продукта. Этот уровень располагается между программами Windows и модулем WINSOCK.DLL. Протоколом высокого уровня может являться продукт вызова удаленных процедур (Remote Procedure Call, RPC) или что-нибудь еще, обеспечивающее подобные услуги. RPC — это интерфейс, позволяющий программе вызывать функции, которые на самом деле выполняются на удаленном компьютере. Известно, что программы на процедурных языках программирования очень часто вызывают различные функции или процедуры. Когда вызывается функция, ей, в общем случае, передаются параметры. Инструкции процессора, которые составляют суть функции, обычно находятся либо в самой программе, либо в Windows DLL. В случае вызова удаленных процедур инструкции находятся где-то на другом сетевом компьютере. Предположим, вы разрабатываете программы для локальной сети на базе TCP/IP. В рамках локальной сети одному из компьютеров может понадобиться функция, находящаяся в WINSOCK.DLL. В этом случае, поскольку модуль находится на удаленном компьютере, программа должна вызвать удаленную процедуру. Интерфейс RPC позволяет вызывать процедуры, которых нет на местном компьютере — их код находится где-то еще на сети. Например, модуль WINSOCK.DLL может стоять только на центральном сетевом сервере. Интерфейс RPC является стандартной частью операционных систем Windows NT и Windows 95. В любом случае факт вызова удаленной, а не локальной функции полностью замаскирован как от пользователя, так и от программиста. То есть обращение к удаленной функции происходит точно так же, как если бы она была обыкновенной. Интерфейс RPC позволяет незаметно преобразовать вызов, обменяться сетевыми данными, передать аргумент, получить результат и вернуть его вызывавшей программе так, что она ничего не заподозрит. Отметим, что RPC еще не получил широкого распространения, так что его роль будет все более возрастать, как с каждым днем возрастает роль электронных компьютерных сетей в нашей жизни. Вам, однако, не потребуется RPC при программировании простых примеров этой книги. Последовательные соединения Сетевые приложения, использующие Winsock, можно писать и на отдельно стоящем, не подключенном к сети компьютере. Очевидно, что для тестирования 218 
Ч>бЩая картина т&&*\ и проверок иногда необходимо получать доступ к сети TCP/IP. Отдельно стоящий компьютер не имеет протоколов высокого уровня, о которых вы только что читали. Ему нет необходимости вызывать удаленные процедуры, поскольку на нем уже установлено все нужное для работы. Тем не менее у компьютера должен быть какой-нибудь способ соединиться с Интернет. Предположим, что вы, как и большинство, имеете отдельно стоящий персональный компьютер и при этом хотите программировать сетевые приложения. В шестой главе мы писали, что такой персональный компьютер можно соединить с Интернет посредством SLIP или РРР. Для этого достаточно иметь всего лишь модем и обычную телефонную линию. SLIP и РРР — протоколы уровня соединения, инкапсулирующие данные в кадры перед тем, как передать их по последовательной линии связи. То есть SLIP и РРР инкапсулируют IP-пакеты в кадры, а затем передают хост-компьютеру в Интернет. На рис. 8.1 показано, что оба протокола размещаются между стеком TCP/IP и драйвером последовательного порта. Драйвер — это просто программный модуль, обеспечивающий интерфейс к определенному устройству, в нашем случае — к последовательному порту. Конечно, вы можете купить замену, однако стандартным драйвером последовательного порта в Windows является COMM.DRV. Он автоматически появляется на жестком диске при установке Windows. Драйвер COMM.DRV обслуживает низкоуровневую рутину, связанную с передачей данных между операционной системой Windows и последовательными портами (обычно СОМ1 и COM2). Функции драйвера для нас не интересны, и для успешной работы достаточно просто знать, что драйвер существует в природе. Как повысить производительность последовательного соединения? Если вы соединяетесь с Интернет по последовательному каналу, вам будет интересно узнать способ, как повысить его производительность и добиться устойчивой работы даже на высоких скоростях современных модемов. Эти способы бывают разными. Можно, например, приобрести усовершенствованный вариант драйвера, взамен старого COMM.DRV. Более быстрый модем — тоже хорошая вещь. Если у вас внешний модем, проверьте, какая микросхема последовательного приема-передачи (Universal Asinchronous Receiver-Transmitter, UART) установлена в компьютере и обслуживает модем. Примечание: Большинство внутренних модемов уже имеют усовершенствованную микросхему UART, установленную на собственной плате. Покупая внутренний модем, об этой проблеме не нужно беспокоиться. Микросхема UART предназначена для управления последовательными портами персонального компьютера. Она преобразует приходящие от процессора байты в биты, которые необходимо передать по последовательному каналу. Входящие по последовательному каналу, например от модема, данные также представлены в виде отдельных битов. Перед отправкой процессору биты необходимо собрать в байты. Во всех современных PC устанавливаются усовершенствованные модели микросхемы UART, называемые 16550. Микросхема 16550 значительно быстрее своих более старых сородичей — моделей 8250А или 16450. 219 
Если у вас PC старой модели и на нем установлен старый UART, вы можете купить карту последовательных портов, и на ней будет новый UART. Такие карты обычно не дороги. Имейте в виду, что производители постоянно выпускают все новые и более быстрые варианты UART. К тому времени, как вы читаете эту книгу, модель 16550 уже, возможно, и не является «усовершенствованной». Другими словами, найдите немного времени, чтобы ознакомиться с последними достижениями в области коммуникаций по последовательным каналам — очень может быть, что вы значительно ускорите связь с Интернет, потратив совсем немного денег. Разработка приложений Winsock на локальной сети Если вы разрабатываете программы на локальной сети, в вашем компьютере скорее всего вместо драйвера последовательного порта установлена сетевая интерфейсная карта. У нее, как известно, свой собственный пакетный драйвер, который является, как уже отмечалось, просто программным модулем, находящимся между стеком TCP/IP и собственно сетевой картой. Это продемонстрировано на рис. 8.1. В большинстве случаев нет нужды изучать подробности о работе драйвера сетевой карты. Как и в случае драйвера последовательного порта, достаточно знать, что он существует. Факт, что существуют драйверы, важен для нас. Он находится между программой Winsock и шиной, переносящей данные. Любая неприятность, случившаяся с драйвером, повлечет за собой сбои в работе прикладной программы. Предположим, что мы разрабатываем сетевую программу и вдруг обнаруживаем, что она себя неправильно ведет по непонятным для нас причинам. В этом случае технический персонал, ответственный за состояние сети, должен вначале удостовериться, что пакетные драйверы вашей сетевой карты не являются источником проблем. Несколько протоколов с одной сетевой картой В зависимости от сети один сетевой компьютер может работать одновременно по двум различным протоколам (например, TCP/IP и DECNet). Для работы с двумя различными протоколами производители сетевых карт создают отдельные драйверы для каждого. Джон Ромки (John Romkey) из FTP Software (North Andover, Massachusetts) разработал спецификацию на пакетный драйвер, позволяющую одной и той же интерфейсной сетевой карте одновременно обрабатывать TCP/IP и другие сетевые протоколы. Открытый интерфейс соединения на уровне данных (Open Data-Link Interface, ODI), разработанный Novell и Apple, также позволяет множеству протоколов работать на одной сетевой карте. Спецификация интерфейса сетевого драйвера (NDIS), разработанная Microsoft и 3Com, обеспечивает такие же возможности. Однако стратегический подход у NDIS иной. Драйвер NDIS мультиплексирует протоколы так, что на компьютере одновременно уживаются несколько различных стеков. Другими словами, производители сетевых карт могут теперь разрабатывать драйверы, совместимые с NDIS. Это значит, что такой драйвер также совместим с любым другим драйвером NDIS. 220 
Концепция программирования сокетов Написание программ Winsock Для работы программ Winsock требуется, чтобы в системе присутствовал файл динамической библиотеки функций WINSOCK.DLL, реализующий интерфейс Winsock API. Компилятор C/C++ должен знать о том, что некоторые вызываемые программой функции находятся в динамической библиотеке. Для этого служат так называемые библиотеки импорта (import library), указываемые при вызове компилятора. Прочитайте инструкцию к компилятору — там должно быть указано, как это сделать. На дискете, приложенной к книге, находится файл WINSOCK.DLL, разработанный Питером Таттамом (Peter Tattam) из университета Тасмании в Австралии. Он распространяется на условиях shareware (условно-бесплатно). Информация относительно WINSOCK.DLL находится в файле READ.ME. Концепция программирования сокетов В седьмой главе мы обсуждали сокет как конечную точку сетевого соединения. Мы исследовали интерфейс сокетов Беркли, построенный на абстракции сокетов. Как выяснилось позже, интерфейс Winsock также основан на концепциях сокетов Беркли. В следующих разделах приведено краткое обозрение концепции программирования сокетов. Если по прочтении что-то останется неясным, обратитесь еще раз к седьмой главе, где сокеты обсуждаются подробнее. В следующих главах этой книги Winsock API обсуждается не иначе, как в контексте концепции программирования сокетов. Для того чтобы использовать интерфейс сокетов в сетевом соединении, программа должна выполнить четыре простых действия: 1. Во-первых, программа создает сокет. 2. Во-вторых, сокет настраивается на определенный режим работы. Другими словами, он либо соединяется с удаленным компьютером, либо привязывается к локальному порту протокола. 3. В-третьих, программа передает и принимает данные через сокет так, как это требуется приложению. 4. Наконец, по окончании работы сокет закрывается. Создание нового сокета Перед тем как использовать сокет, его нужно создать. Для этого предназначена функция socket. Следующий оператор демонстрирует, как создается новый сокет: socket_handle = socket (prot ocol_f ami ly, socket_type, protocol); Дескриптор сокета Winsock не соответствует дескриптору сокета Беркли. (Вопрос о структуре дескриптора сокета Winsock обсуждается позже в этой же главе.) Все аргументы, однако, в точности соответствуют аргументам функции в стиле Беркли. То есть семейство протоколов обозначает семейство или группу 221 
■ва 8* связанных между собой сетевых протоколов, например стек TCP/IP. Тип сокета обозначает, будет ли передача ориентирована на поток байтов или на датаграммы. А параметр protocol обозначает конкретный протокол, с которым будет иметь дело приложение, работая через этот сокет, например TCP или UDP. В седьмой главе мы обсуждали вопрос о том, что возможность задавать семейства протоколов и адресов, предусмотренная разработчиками сокетов Беркли, нужна для обеспечения работы нескольких сетевых сред. Поскольку программист может выбрать, с какими протоколами ему работать, производители программ могут использовать в своих продуктах один и тот же интерфейс для различных сетевых протоколов и форматов адресов. Имена семейств протоколов и адресов в Winsock в точности такие же, как и в сокетах Беркли: PF_INET обозначает протоколы TCP/IP, AF_INET — семейство адресов формата Интернет. Winsock умеет работать как с ориентированными, так и с не ориентированными на соединение протоколами. Когда программа создает новый сокет, она должна указать требуемый тип протокола. Известно, что ориентированные на соединения работают с потоком байтов, а не ориентированные на соединение — с датаграммами. Чтобы указать тип сокета, используется второй аргумент функции socket. Для датаграмм он равен символьной константе SOCK_DGRAM, а для потока байтов — константе SOCK_STREAM. Примечание: Некоторые реализации Winsock имеют в составе третий тип сокетов — простой (raw) сокет, обозначаемый SOCKET_RAW. Тем не менее этот тип сокетов не описан в спецификации Winsock 1.1, а значит, ваш WINSOCK.DLL не обязан уметь работать с таким сокетом. Простые сокеты применяются, когда программа желает получить доступ к низкоуровневым сетевым протоколам, например IP или ICMP. Третий параметр функции позволяет программе указать вид протокола (например, TCP или UDP) для сокета. Протоколы обозначаются символьной константой, начинающейся с префикса IPPROTO_. Например, чтобы сокет работал по протоколу TCP, функции socket передается константа IPPROTO__TCP в качестве третьего параметра. Символьная константа IPPROTO_UDP определяет вид протокола UDP. Следующие операторы показывают пример вызова функции socket: socket_handle- = socket (PF^INET, SOCK_STREAM, IPPROTO__TCP) ; Полученный сокет будет работать по протоколу TCP (IPPROTO_TCP) из семейства протоколов Интернет (PF_INET) с данными в виде потока байтов (SOCK_STREAM). При вызове функции socket Winsock отводит специальную область памяти для хранения информации о новом сокете. Примечание: Внутренняя структура данных Winsock, в которой хранится информация о сокете, весьма напоминает структуру сокетов Беркли. Структура сокетов была подробно рассмотрена нами в седьмой главе. 222 
Настройка сокета Для настройки сокета используются различные функции, входящие в состав Winsock API. То, как сокет настраивается, зависит, как вы помните из седьмой главы, от типа сетевого взаимодействия (ориентированное или не ориентированное на соединение) и от функций, выполняемых программой (клиент или сервер). В седьмой главе также объясняется, что каждый сокет (как конечная точка соединения) должен обладать следующей информацией: IP-адресом локального и удаленного компьютеров, портами протоколов локального и удаленного процессов и типом сетевого протокола. В табл. 8.6 приведены функции Winsock API, вызываемые для настройки сокета. Таблица 8.6. Функции Winsock API, вызываемые для настройки сетевого соединения через сокет Способ использования сокета Локальная информация Удаленная информация Ориентированный на соединение клиент Однократный вызов функции connect записывает локальную и удаленную информацию в структуру данных сокета Ориентированный на соединение сервер bind listen и accept Не ориентированный на соединение клиент bind sendto Не ориентированный на соединение сервер bind recvfrom В дальнейшем вы узнаете, что функции Winsock, перечисленные в табл. 8.1, устанавливают информацию об IP-адресах и портах протоколов местного (локального) и удаленного компьютеров. Протокол для сетевого соединения определяется при создании самого сокета. Соединение через сокет Ориентированный на соединение протокол устанавливает виртуальное соединение между конечными точками сети. Ориентированные на соединение протоколы, например TCP, держат соединение установленным, обмениваясь специальными сообщениями-подтверждениями. Программа-клиент, ориентированная на соединение, не обращает внимания на локальный номер порта протокола, передающего данные. После того как соединение установлено, доставка данных программе-клиенту полностью контролируется транспортным протоколом. То есть ориентированная на соединение программа-клиент не должна указывать номер порта протокола. Единственной информацией, необходимой сокету, является информация об удаленном сетевом компьютере — его IP-адрес и порт 223 
Глава 8. Интерфейс сокетов Windows протокола. Winsock автоматически выбирает подходящий номер местного порта протокола и заносит его в структуру данных сокета наряду с IP-адресом местного сетевого компьютера. Далее Winsock API обеспечивает доставку данных в целости и сохранности от транспортного уровня до порта протокола и программы-клиента. Другими словами, порт выбирается автоматически самим Winsock API, и программа-клиент извещается о появлении в нем новой порции сетевых данных. Ориентированная на соединение программа-клиент использует функцию connect для настройки сокета на сетевую работу. При вызове connect указывается три аргумента: дескриптор сокета, адрес удаленного сокета и длина адреса. Следующий оператор демонстрирует образец вызова connect: result = connect(socket_handle, remote__socket_address, address_length); Первый параметр функции connect, дескриптор сокета, представляет собой результат вызова функции socket. Это значит, что до того, как соединить сокет, его необходимо создать при помощи функции socket. Второй параметр функции connect, адрес удаленного сокета, является указателем на специальную структуру адреса сокета. В структуре адреса указаны: семейство адресов, порт протокола и адрес сетевого интерфейса сокета. До того как вызвать функцию connect, программа должна занести в эту структуру IP-адрес удаленного компьютера и номер удаленного порта протокола. Примечание: В девятой главе объясняется, каким образом программа может выяснить IP-адрес удаленного сетевого компьютера, пользуясь стандартной услугой Интернет. При вызове функции connect Winsock записывает адрес и порт удаленного компьютера во внутреннюю структуру данных, на которую ссылается дескриптор сокета (первый аргумент в вызове connect). Другими словами, после того как сокет создан и соединен с удаленным компьютером, все остальные функции Winsock пользуются информацией из его структуры данных. После того как сокет создан и через него установлено соединение, внутренняя структура данных дескриптора сокета содержит всю необходимую для обмена сетевыми данными информацию: • Протокол сетевого соединения (он указывается при вызове функции socket). • IP-адрес локального сетевого компьютера (протокол TCP получает его от Winsock). • Номер локального порта протокола (как правило, TCP сам присваивает его программе). • IP-адрес удаленного компьютера (программа указывает его при вызове функции connect). • Номер удаленного порта протокола (он указывается при вызове функции connect). 224 
Концепция программирования сокетов При вызове функции socket указывается необходимый программе протокол (например, TCP). Winsock автоматически заносит локальный IP-адрес и выбирает порт протокола для программы. Программа определяет адрес и порт протокола удаленного компьютера и записывает их в структуру, на которую указывает второй аргумент функции connect. Вся информация о сокете хранится в отведенной Winsock области памяти, на которую указывает дескриптор сокета. Прослушивание запросов на входное соединение Как отмечалось в седьмой главе, только ориентированные на соединение клиенты инициируют прямое соединение с сокетом удаленного компьютера. Не ориентированные на соединение протоколы никогда не устанавливают прямого соединения. Вместо этого они передают по сети датаграммы (а не потоки байтов). Точно так же, несмотря на то, что программа-сервер часто пользуется ориентированными на соединение протоколами, она никогда не начинает соединение первой. Сервер пассивно прослушивает порт на появление запроса от клиента. Другими словами, ориентированный на соединение клиент всегда инициирует соединение. Сервер же только отвечает на запросы клиента. Выходит, что ориентированные на соединение серверы и все не ориентированные на соединение программы (и клиенты и серверы) обладают общими чертами: они прослушивают порт протокола. Точно так же не ориентированная на соединение программа должна прослушивать порт протокола на наличие ответа от сервера. Запомните, что не ориентированный на соединение протокол не устанавливает прямого сетевого соединения с удаленным компьютером. В результате, программа-клиент, не ориентированная на соединение, должна сначала передать датаграмму-запрос, а затем ждать датаграмму-ответ от сервера. Другими словами, программа-клиент, не ориентированная на соединение, обязана прослушивать порт протокола на наличие ответа от сервера. Функция bind из набора Winsock API связывает локальный IP-адрес и локальный порт протокола с сокетом. Следующий оператор демонстрирует способ применения bind: result = bind(socket_handle, local_socket_address, address__length) ; Вы знаете, что программа-сервер находится в состоянии ожидания запросов клиента. Транспортный уровень TCP/IP пользуется портами протоколов для передачи данных между клиентом и сервером. Другими словами, для получения запроса клиента программа-сервер прослушивает определенный порт, данные на который доставляет протокол транспортного уровня. Функция bind служит для того, чтобы зарегистрировать порт протокола в Winsock. Программа указывает Winsock, с какого порта поступают данные, a Winsock в свою очередь указывает транспортному уровню, что данные из этого порта предназначены для Winsock API. Не ориентированные на соединение программы-клиенты пользуются функцией bind аналогичным образом. Так же как и сервер, такой клиент указывает Winsock порт, на который приходят данные. Транспортный уровень затем 8 Зак. № 1949 225 
Глава 8. Интерфейс сокетов Windows Л?гГ. •• - ^ \77 У л.:.' .'•••'•••:i информируется о режиме и настройке данного порта. Winsock обслуживает интерфейс между клиентом и транспортным протоколом (UDP). В дальнейшем вы узнаете, что, передавая запрос на установление соединения, клиент указывает номер локального порта, на который должен приходить ответ от сервера. Как только появляется ответная датаграмма сервера, транспортный уровень информирует об этом Winsock. В последующих разделах вы узнаете, как именно используется Winsock для извлечения данных из порта протокола. Сейчас нам важно знать, что функция bind настраивает сокет на прослушивание датаграмм или запросов на соединение от определенного порта протокола. Как пользоваться сокетом? Через правильно настроенный сокет и при помощи Winsock API прикладная программа передает и принимает данные. Для сетевого ввода-вывода в интерфейсе сокетов Беркли используются функции ввода-вывода ОС Unix. Поскольку Winsock не является частью Windows, его функции ввода-вывода немного отличаются от аналогичных в сокетах Беркли. Сокеты Беркли имеют пять функций для приема данных и пять — для их передачи. В Winsock определены четыре функции: две для приема и две для передачи. Все они приведены в табл. 8.7 вместе с кратким описанием. Таблица 8.7. Функции сетевого ввода-вывода Winsock API Функция Winsock API Описание send Передает данные через соединенный сокет. Для управления сокетом предусмотрено несколько специальных флагов. sendto Передает данные по указанному сетевому адресу. Адрес записан в структуре адреса сокета. Данные хранятся в буфере памяти (простом массиве). recv Принимает данные от соединенного сокета. Для управления сокетом существует несколько специальных флагов. recvfrom Принимает данные из сокета вместе (если нужно) с сетевым адресом компьютера-источника. Для хранения информации предусмотрен буфер памяти (простой массив). Сразу бросается в глаза, что функции send и recv могут применяться только на соединенных сокетах. В функциях recvfrom и sendto допускается указывать сетевой адрес. Другими словами, их можно использовать на не ориентированных на соединение сокетах. Дополнительно функции recvfrom и sendto можно использовать с соединенными сокетами — при этом информация об адресах попросту игнорируется. Тем не менее если у вас ориентированный на соединение обмен данными, нужно всегда использовать предназначенные для этого функции recv и send. Тогда какой-нибудь программист, исследуя исходный текст вашей 226 
Концепция программирования сокетов программы, сразу поймет, что речь идет об ориентированном на соединение протоколе. Иногда приходится конструировать программные модули общего назначения, когда заранее неизвестно, какой тип соединения будет использоваться. В этом случае применяйте функции recvfrom и sendto. Поскольку им все равно, ориентирован сокет на соединение или нет, вы с легкостью сможете встроить такой модуль в программу для работы с любым типом протокола. Как пользоваться ориентированным на соединение сокетом? Ниже приведен типичный образец вызова функции send: result = send(socket_handle, message_buffer, buffer_length, special_flags); Поскольку в функции send отсутствует возможность указывать адрес компьютера-получателя, ее можно использовать только вместе с соединенным сокетом. То есть до того, как вызвать send, программа должна создать сокет (вызвав функцию socket), а затем соединить его (вызвав функцию connect). Функция connect заносит адрес удаленного компьютера во внутреннюю структуру данных сокета. Когда вызывается send, Winsock извлекает эту информацию (IP-адрес и порт протокола) из структуры, на которую указывает дескриптор сокета. Далее функция send передает данные из буфера, на который указывает второй параметр в сети. Сетевой адрес назначения данных, как мы уже указывали, берется из структуры данных сокета. Партнером функции send является функция recv. Оператор ниже демонстрирует, как выглядит вызов recv: result = recv(socket_handle, message_buffer, buffer__length, special_flags); Функция recv извлекает данные из локального порта протокола и копирует их в буфер, на который указывает ее второй аргумент. Когда программа вызывает recv, дескриптор сокета, первый аргумент функции, указывает, откуда нужно брать номер порта протокола. Несмотря на то, что Winsock и транспортный уровень управляют очередью поступающих в порт данных, программа не может воспользоваться этим системным буфером для хранения данных. Мы помним, что транспортный уровень обслуживает не один протокол, поэтому при вызове recv необходимо указать буфер самой программы, куда данные и перепишутся из порта протокола. Указатель на буфер — второй параметр функции recv. Параметр, задающий длину буфера, необходим для того, чтобы Winsock знал максимальное количество байтов, которое вмещается в пользовательский буфер. Существуют два специальных флага, которые можно задать в качестве последнего параметра recv. Первый флаг — символьная константа MSG_OOB. Употребление MSG_OOB в функции recv указывает на то, что программа запрашивает данные «вне диапазона» (out-of-band), называемые иначе данными для неотложной обработки (urgent data). Если указан этот флаг и данные вне 227 
диапазона присутствуют, recv немедленно вернет их вызывающей программе. Если таких данных нет, устанавливается код ошибки EINVAL. Второй флаг — символьная константа MSG_PEEK, позволяет программе анализировать запросы, находящиеся во входной очереди транспортного уровня. В нормальной ситуации, как только данные из входной очереди считаны, они уничтожаются. Когда указан флаг MSG_PEEK, транспортный уровень не удаляет данные из входной очереди после считывания. То есть у программы появляется возможность решить, как поступить с ожидающими считывания данными. То ли считать их целиком, то ли по кусочкам меньшего размера. Образец программы FTP, описанный в третьей части книги, демонстрирует образец применения этого флага при вызове recv. Если специальные флаги отсутствуют, вместо них нужно задавать 0. При таком вызове recv просто скопирует содержимое входной очереди транспортного уровня в пользовательский буфер. Как уже говорилось, программа может вызывать recvfrom и sendto вместо вышеописанных send и recv. В случае ориентированного на соединение сокета обе группы ведут себя совершенно одинаково. Однако sendto и recvfrom лучше применять вместе с не ориентированным на соединение сокетом. Как пользоваться не ориентированным на соединение сокетом? Как показано в предыдущем разделе, функции send и recv не позволяют задавать адрес удаленного компьютера. Сокет должен быть соединен с удаленным компьютером до вызова этих функций. Функции revfrom и sendto, напротив, дают возможность передавать данные через не ориентированные на соединение сокеты. Так же, как и в сокетах Беркли, функция sendto из Winsock требует указывать шесть параметров. Первые четыре в точности совпадают с параметрами функции send. Пятый параметр, структура адреса сокета, определяет адрес назначения. Шестой параметр, длина структуры адреса, определяет длину адреса назначения в байтах. Ниже показано, как вызывается функция sendto: result = sendto(socket_handle, message_buffer, buf f er__length, special_flags, socket_address_structure, address_structure_length); Информация о локальном адресе (IP-адресе и порте протокола) не ориентированного на соединение сокета заносится в структуру при вызове функции bind. Как было показано в табл. 8.7, функция sendto размещает информацию об удаленном компьютере во внутренней структуре Winsock. До того как вызвать sendto, нужно занести информацию об удаленном компьютере в структуру данных сокета. Пятый параметр sendto является указателем на эту структуру. Когда Winsock запрашивает передачу данных у транспортного уровня, информация о параметрах соединения передается ему из внутренней структуры данных Winsock. Транспортный уровень в свою очередь строит заголовки UDP-датаграмм на основании этой информации и передает датаграммы в сеть. 228 
с сокетами Wii Сокеты Беркли по сравнению у>;у.'£Шуу1 ХУх^Щк:ч*.•;., V>0гУУ.::::':../:/л^.у.:*:.?:: *£;' >у> • у J:*: *:‘V:Я Примечание: Функцию sendto допустимо использовать и на ориентированных на соединение сокетах. При этом реквизиты удаленного компьютера, указанные в параметрах sendto, игнорируются. Разумеется, транспортный уровень в этом случае формирует не UDP-датаграммы, а сегменты TCP. Дополнением к функции sendto является функция recvfrom. Полная аналогия с парой send и recv. Вот как можно вызвать функцию recvfrom: result = recvfrom (socket_handle, message_buffer, buf fer_length, special_flags, socket_address_structure, address_structure_length) ; Функция recvfrom извлекает данные из порта протокола точно так же, как это делает recv. Кроме того, в случае не соединенного сокета (SOCK_DGRAM) recvfrom извлекает адрес компьютера-источника из заголовка датаграммы. Программа-сервер, обслуживающая не соединенный сокет, обязана считывать адрес компьютера-источника сообщения, поскольку сервер должен знать, кому послать ответ. В случае программы-клиента адрес источника сообщения необходим, если клиент желает продолжить обмен сообщениями. Адрес компьютераисточника необходим и затем, чтобы выяснить, оттуда ли, откуда ожидается, пришло сообщение. Однако во многих случаях клиенту не обязательно знать адрес компьютера, приславшего сообщение. Другими словами, функция recvfrom записывает адрес, с которого пришла датаграмма, в структуру адреса сокета, на которую указывает пятый параметр. Причем данные извлекаются, если только этот указатель действителен (не равен NULL). Если указатель на структуру данных сокета равен NULL, адрес компьютера-источника сообщения в заголовке UDP-датаграммы не извлекается и никуда не копируется. Сокеты Беркли по сравнению с сокетами Winsock Модель программирования Winsock, описанная в предыдущих разделах, тесно связана с моделью сокетов Беркли, которую мы рассмотрели в седьмой главе. И это не случайно, поскольку разработка Winsock базировалась на модели интерфейса Беркли. Интерфейс сокетов Беркли наложил отпечаток на структуру сетевого программного обеспечения TCP/IP в мире UNIX. Концепция сокетов дает программистам понятную и удобную модель для разработки сетевых приложений. В главе 7 мы писали, что интерфейс Беркли дает инструмент для создания как весьма сложных серверных приложений, так и устойчиво работающих приложений-клиентов. В результате, одной из заявленных при разработке спецификации Winsock целей была следующая: «... необходимо обеспечить безболезненный переход любого, знакомого с программированием сокетов в Unix и других ОС программиста в 229 
среду Windows, а также легкость переноса туда исходных текстов уже существующего сетевого программного обеспечения.» Первая часть декларации с успехом выполнена. Если вам приходилось разрабатывать программы с сокетами в других средах, вы без особого труда перейдете к сокетам Windows. Однако вторую часть декларации оказалось не так легко выполнить. Хотя Winsock и облегчает задачу переноса исходного текста из других сред программирования, все оказывается не так просто, как хотелось бы. Вы легко начнете программировать Winsock, что называется «с нуля», особенно если вы знакомы с сокетами Беркли. Однако занявшись переносом уже готовой программы под Windows, можно встретить массу неприятностей. Первым делом, наверное, вы захотите использовать функции Winsock в стиле Беркли, избегая специфических для Windows расширений. Через некоторое время станет ясно, что без расширенных функций Windows не обойтись. При этом имейте в виду, что вследствие разницы между сокетами Беркли и сокетами Windows очень может быть, что программу придется просто переписать заново. Возможно, ее придется переписать до того, как вы начнете встраивать специфические функции Windows. Пишите ли вы программу «с нуля» или переносите уже существующую, вам всегда поможет следующее краткое обозрение различий между сокетами Windows и сокетами Беркли. Конечно, мы предполагаем, что вы знакомы с сокетами Беркли, описанными в седьмой главе. Файлы заголовков В любой сетевой программе Unix необходимо указывать массу файлов-заголовков, описывающих различные сетевые функции. В программах Winsock указывается только один файл-заголовок winsock.h. Он указывается всегда (#include <winsock.h>), если приложение работает с сокетами или вызывает любую функцию, входящую в Winsock. В большинстве Windows-программ требуется также указывать файл-заголовок windows.h. Если winsock.h включен, то необходимости дополнительно включать windows.h нет — это делается автоматически в файле winsock.h, поскольку он не может обойтись без windows.h. Управление данными При разработке Windows-программ необходимо учитывать следующую особенность: любой объект памяти, например буфер или переменная, должен быть доступен для WINSOCK.DLL в любой момент времени при исполнении операций Winsock. В многопоточных версиях Windows программа обязана самостоятельно координировать доступ к объектам памяти. Об этом прямо сказано в спецификации Winsock, где указано, что реализации Winsock (WINSOCK.DLL) не отвечают за управление памятью программы. 230 
Сокеты Беркли по сравнению eh Обязательные функции Winsock В Winsock определены две обязательные для исполнения функции: WSAStartup и WSACleanup. Первая вызывается перед тем, как вызвать любую другую функцию Winsock. Вторая — по окончании работы с Winsock. Для каждой функции WSAStartup всегда должна вызываться соответствующая функция WSACleanup. В следующих разделах подробно описываются действия, производимые каждой функцией. Функция WSAStartup Функция WSAStartup позволяет программе указать требуемую версию Winsock. Она также позволяет программе получить информацию о конкретной реализации Winsock. Фактически, при вызове WSAStartup происходит диалог между программой и модулем WINSOCK.DLL. Возможность узнавать версию Winsock предусмотрена для того, чтобы обеспечить разработку программ, использующих более новые и не работающих со старыми версиями Winsock. Такая программа указывает минимальную версию, с которой она еще может работать, а Winsock — наиболее высокую версию из тех, что он может обеспечить. Далее программа решает, как ей лучше поступить в том или ином случае. Спецификация Winsock содержит рисунок, демонстрирующий варианты «переговоров» разных версий DLL при вызове WSAStartup. Функция WSACleanup Прикладная программа может вызывать WSAStartup несколько раз за время работы. Это может понадобиться, например, если в разных частях программы требуется узнать версию WINSOCK.DLL. И вместо того чтобы отводить глобальный массив для хранения версии, программа вызывает WSAStartup. Функция WSACleanup органично дополняет WSAStartup. На каждый вызов функции WSAStartup должен приходиться вызов WSACleanup. Winsock регистрирует каждый вызов WSAStartup при помощи внутреннего счетчика вызовов. Каждый раз при вызове WSAStartup значение счетчика увеличивается. При вызове WSACleanup, наоборот, уменьшается. Последняя, вызванная программой функция WSACleanup (устанавливающая счетчик в ноль) заставляет Winsock произвести операции по очистке буферов, переменных и т. д., имевших отношение к программе. Реализация Winsock (WINSOCK.DLL) отводит внутренние статические области памяти для зарегистрировавшейся в ней программы. Каждая программа получает собственные области памяти, к которым можно обращаться только через Winsock API. Области памяти являются внутренними для Winsock точно так же, как и структуры данных сокета. Последняя вызванная WSACleanup говорит Winsock, что он не нужен больше программе, следовательно, можно освободить все ресурсы, отведенные ранее для этой программы. Функция WSAStartup начинает работу программы с Winsock, a WSACleanup прекращает ее. 231 
Как только WSACleanup вызвана в последний раз, Winsock отсоединяет все оставшиеся сокеты, ориентированные на соединение. Тем не менее все данные, оставшиеся в выходных очередях, отправляются. Предположим, что закрытие сокета следует сразу же за командой send. Даже если сеть и не успела передать все данные до вызова WSACleanup, они все равно будут посланы Winsock. Как мы уже говорили, на любой вызов WSAStartup должен приходиться вызов WSACleanup. Это позволяет очистить отведенные для программы области памяти в Winsock. Вызывая WSACleanup, вы говорите Winsock, что не нуждаетесь больше в его услугах и в выделенной для программы памяти. Winsock в свою очередь может освободить любую память, ранее принадлежавшую программе. Многие реализации Winsock ведут себя корректно, даже если WSACleanup не вызывалась программой. В спецификации Winsock говорится о том, что такого поведения вполне можно ожидать и, следовательно, к нему нужно подготовиться. С другой стороны, Winsock не обязан выполнять какие бы то ни было действия при некорректном поведении программы. Другими словами, то что происходит при окончании программы, которая не вызвала WSACleanup, зависит от конкретной реализации Winsock. Таким образом, программе необходимо тщательно отслеживать последовательность вызовов WSAStartup и WSACleanup. Особенно это относится к аварийному завершению программы. Даже в этом случае следует предусмотреть корректное окончание работы с WINSOCK.DLL, чтобы сбой в программе не отразился на обслуживающих ее сетевых модулях. Примечание: Если программа разрабатывается для многопоточной среды Windows 95 или Windows NT, имейте в виду, что функция WSACleanup относится ко всем потокам завершаемого процесса. Это важно, поскольку многие функции Winsock относятся исключительно к тому потоку процесса, из которого были вызваны. WSACleanup к таковым не относится — она завершает сетевые операции всех потоков, связанных с завершаемой программой. Дескрипторы сокетов Как известно из седьмой главы, дескрипторы сокетов Беркли повторяют организацию дескрипторов файлов. В Winsock определяется новый тип данных, SOCKET, который является дескриптором сокета. Дескриптор сокета в среде UNIX (как и другие дескрипторы) является небольшим положительным целым числом. Поэтому многие программы, перенесенные в Windows из Unix, предполагают, что дескриптор является положительным целым числом. В общем случае, это не так. Дескриптор сокета Winsock не обязан иметь положительное значение. В спецификации Winsock тип SOCKET определен как беззнаковый (unsigned). Недействительные сокеты обозначаются символьной константой INVALID_SOCKET. В спецификации это значение приравнивается выражению (SOCKET)(~0) — отрицательное число (-1) в беззнаковом виде. Далее специ- 232 
iii| ft ж A ’ сравнению с совета фикация оговаривает, что действительный сокет может иметь дескриптор из диапазона от нуля до INVALIDJSOCKET-1. Таким образом, единственное число, которым можно обозначить недействительный сокет, — наибольшее возможное беззнаковое целое, OxFFFF. Некоторые сетевые приложения очень интенсивно используют сокеты. Если вы переносите программы из Unix в Windows, имейте в виду, что функции, проверяющие сокет на корректность просто по отрицательному значению дескриптора (что правильно в интерфейсе Беркли), могут здорово ошибиться в случае Winsock. Дескрипторы файлов и сокетов Вы знаете, что в интерфейсе Беркли дескрипторы сокетов эквивалентны дескрипторам файлов. Когда Unix-программа желает использовать файловую функцию для сетевого ввода-вывода, она передает функции дескриптор сокета вместо дескриптора файла. Дескриптор сокета Winsock, однако, отнюдь не эквивалентен дескриптору файла Windows. Вам не удастся осуществить файловую операцию ввода-вывода, пользуясь дескриптором сокета Winsock. Примечание: Хотя дескриптор файла и не является дескриптором сокета в Winsock, структура данных сокета Winsock весьма напоминает простейшую структуру, рассмотренную в седьмой главе. Особое внимание при переносе из Unix в Windows следует обратить на функции read и write. Они не входят в состав Winsock, поэтому нуждаются в замене соответственно на recv и send. Вместо функции close необходимо использовать closesocket, а вместо ioctl — ioctlsocket. Все вышесказанное систематизировано в табл. 8.8. Таблица 8.8. Функции Беркли, отсутствующие в Winsock, и их аналоги Функция Беркли Функция-аналог Winsock read recv write send close closesocket ioctl ioctlsocket Имейте в виду, что эти функции чаще всего встречаются в сетевых программах. Другие функции невозможно перенести напрямую. К ним относятся, например, readv, writev, recvmsg и sendmsg. Часто использующие их программы Беркли, как правило, нуждаются в полной переписке. Функции Winsock по обработке ошибок При возникновении ошибки в UNIX-программе устанавливается значение стандартной переменной еггпо. Эта переменная отсутствует в Winsock. Вместо этого для идентификации ошибки в нем существует символьная константа 233 
Глава 8. Интерфейс сокетов Windows Шж^-?:-лу- SOCKET_ERROR (определенная как -1). To есть если при выполнении функции Winsock возникает ошибка, то возвращается значение SOCKETJERROR. В ответ необходимо вызвать функцию WSAGetLastError. Если она не вызвана, а следующая функция также выработала значение ошибки, код ошибки изменится. В многопоточных операционных системах, Windows 95 и Windows NT, WSAGetLastError вызывает функцию Win32 GetLastError. Функция GetLastЕггог возвращает значение ошибки того потока, из которого была вызвана. Из систем Беркли в Winsock перекочевало большинство кодов ошибок. Однако константы называются немного иначе. Все константы Беркли в Winsock получили префикс WSA (чтобы обозначить принадлежность к Winsock). Например, константа ENETDOWN, обозначающая ошибку низлежащего сетевого уровня, превратилась в WSAENETDOWN. Функция select в Winsock Как известно из седьмой главы, функция select дает процессу возможность отслеживать состояния нескольких сокетов одновременно. Функция select в Winsock выполняет аналогичные действия. То есть она используется для контроля состояния определенного набора сокетов. Предположим, что программа желает послать сетевому хосту некоторые данные, но не может сделать этого до появления ответа от него. При помощи select можно заставить Winsock следить за появлением ответа. Как только Winsock доложит, что ответ пришел, программа может передать подготовленные данные. Вы знаете, что вариант Беркли функции select позволяет следить за состоянием чтения, записи и ошибок определенных сокетов. Для этого программа исследует содержимое битов в специальной маске дескрипторов сокетов, установленной до вызова select. Функция select в Winsock не нуждается в специальной маске. Вместо этого используется массив дескрипторов сокетов. При этом в описании операций select сохранился тот же самый термин set (массив, набор). В контексте Winsock он означает список дескрипторов сокетов, участвующих в определении статуса при помощи select. Перечисленные в табл. 8.9 макросы (они определены в файле winsock.h) используются при вызове select. Таблица 8.9. Макросы для управления списком сокетов функции select Имя Функция FDCLR Удаляет дескриптор из списка. FD_ISSET Возвращает ненулевое значение (true), если дескриптор сокета находится в списке, и ноль (false), если нет. FD_SET Добавляет дескриптор к списку. FD_ZERO Инициализирует список дескрипторов сокетов. Примечание: Функция select — полноправный член Winsock, однако в нем существует ее асинхронная версия WSAAsyncSelect. 234 
Сокеты Беркли по сравнению с сокета*#» Winsock Опасайтесь смешивать разные модели памяти Методы управления памятью в Unix значительно отличаются от методов Windows. Все 16-битные версии Windows, к которым относится и Windows 3.1 (и любые другие 16-битные программы на персональном компьютере), различают между ближними (near) 16-битными и дальними (far) 32-битными указателями. В персональных компьютерах на базе Intel ближний указатель содержит адрес в виде 16-битового смещения. Адрес смещения указывает на ячейку памяти в пределах сегмента размером в 64 Кб. В программе могут определяться сегменты кода, данных, стека и дополнительные сегменты. Дальний указатель наряду с 16-битным смещением содержит и 16-битный адрес сегмента. 32-разрядные версии Windows, например Windows NT, пользуются простой моделью памяти, где любой адрес задается 32-битным смещением. При компиляции программ на персональном компьютере используются разные модели памяти. Например, в модели small предполагается, что все указатели — ближние, если не указан модификатор типа FAR (char FAR *address). С другой стороны, в модели large предполагается, что все указатели дальние, если не указан модификатор типа NEAR (char NEAR *address). В табл. 8.10 приведены характеристики четырех основных моделей памяти персональных компьютеров. Таблица 8.10. Основные модели памяти персональных компьютеров на базе Intel Модель Сегмент кода Сегмент данных Small i 1 Medium много 1 Compact 1 много Large много много Примечание: Существует еще модель памяти huge (огромный). Она во всем аналогична модели large, за исключением одного момента: длина блока данных в модели huge может превышать размеры одного сегмента. В остальных моделях блок данных должен помещаться в пределах одного сегмента. Использование 16-разрядных ближних указателей ускоряет работу программ. Однако смешивая различные модели в одном проекте, вы рискуете столкнуться с различными проблемами, неожиданными результатами и трудностями, например при отладке. В добавок ко всему, смешивая ближние и дальние указатели в одной программе, вы усложняете восприятие ее текста другими программистами. В Unix понятие о моделях памяти отсутствует. Так же, как и Windows NT, Unix использует модель простой адресации. Поэтому, перейдя от Unix к Windows и будучи не знакомым с другими моделями памяти, вы скорее всего предпочтете 235 
пользоваться исключительно моделью large. Этим преодолеваются трудности 16-битной модели адресации, пришедшей из DOS. В конце концов, можно перейти к Windows 95 или Windows NT, где проблемы исчезают вовсе. К сожалению, как модели large, так и модели compact присущи некоторые недостатки, однако их обсуждение выходит за рамки нашей книги. Мы рекомендуем прочитать следующий, ставший классическим труд Чарльза Петцольда (Charles Petzold) «Программирование Windows 3.1» (Programming Windows 3.1, Third Edition, Microsoft Press, 1992). Соглашения о вызовах функций Переходя от Беркли к Winsock, обратите внимание на особенности вызовов функций. Вы наверное знаете, что функции Windows всегда снабжаются модификатором FAR PASCAL. Ключевое слово FAR указывает компилятору, что исполняемый модуль функции может находиться в другом сегменте относительно вызывающей программы. Ключевое слово PASCAL указывает компилятору, что вместо обыкновенных соглашений при вызовах процедур С следует использовать соглашения языка Pascal. По умолчанию компилятор С генерирует код, в котором параметры функции помещаются в стек справа налево начиная с последнего аргумента. Далее код вызывающей программы обязан самостоятельно скорректировать указатель стека после возврата из функции. Такое поведение нормально для программы на С. В языке Pascal все происходит наоборот: параметры помещаются в стек слева направо, и корректировать указатель стека при возврате обязана вызванная функция. Опять-таки, если вы не знакомы с Windows, все это может показаться странным. В случае чего, компилятор С предупредит вас о неправильном вызове функции, определенной в winsock.h. Если вы забыли включить winsock.h в текст программы, компилятор выдаст предупреждение об отсутствии прототипа функции. Что такое блокирование? Для того чтобы эффективно писать программы в многозадачной среде Windows, нужно понимать, что представляет собой блокирование и как оно отражается на работе процессов. Блокирующая функция не дает возможности исполнять следующие операторы до тех пор, пока она сама не закончит выполнение. Функция, считывающая с жесткого диска, например, не даст программе в том же модуле выполняться дальше до тех пор, пока все данные не будут считаны. В терминологии Winsock такое поведение называется блокированием. Напротив, не блокирующая операция завершается немедленно. Предположим, что ваша программа выводит графику на экран до тех пор, пока пользователь не нажмет клавишу. Если бы функция считывания с клавиатуры была блокирующей, она ждала бы нажатия, не давая выполняться остальным операторам. Прекратился бы даже вывод графики на экран. Лучшим решением является не блокирующая функция считывания с клавиатуры, быстро проверяющая, не была 236 
Щт такое Шокирование? ли нажата клавиша. Если нажатие было, она считывала бы его, а если нет — возвращала бы соответствующее значение, давая остальным функциям выполняться без задержки. Сценарии выполнения программы В операционной системе DOS каждый оператор программы должен закончиться, прежде чем будет вызван следующий. Вызов функции может повлечь за собой возникновение сразу множества различных эффектов, однако с точки зрения программы операторы все равно выполняются последовательно. В многозадачных средах, например Unix, задачи выполняются параллельно, поэтому несколько процессов могут существовать одновременно. Однако и в этом случае в рамках одной программы операторы выполняются последовательно. Похожая ситуация присутствует и в псевдо-многозадачной среде Windows 3.1. Такая разная многозадачность Пользователю кажется, что в многозадачных операционных системах типа Windows или Unix множество программ исполняются одновременно. Вы знаете, что это всего лишь иллюзия. На самом деле, в многозадачной среде один центральный процессор последовательно переключается между исполняющимися задачами. Например, Windows 3.1 позволяет одной программе выполнить несколько операторов, затем другой программе выполнить несколько операторов и т. д. Точно так же Unix-системы на компьютерах с одним CPU делят его между множеством одновременно выполняющихся процессов. Разделение процессорного времени между задачами происходит по-разному в Windows и в Unix. Windows необходимо, чтобы программа, захватившая CPU, взаимодействовала с операционной системой (cooperation). Мы назовем такое поведение кооперацией. Unix в отличие от Windows не требует кооперации со стороны программ, а самостоятельно прерывает (preempts) программу, чтобы получить ресурсы CPU. Другими словами, многозадачность Windows 3.1 зависит от того, вернет ли взявшая на себя ресурсы CPU программа их обратно или нет. Windows разрешает выполнение нескольких задач одновременно, предполагая, однако, что задачи будут взаимодействовать (кооперировать) с операционной системой, передавая ресурсы ей и другим задачам. Если задача не отдала ресурсы, система управления Windows оказывается бесполезной и вся система зависает. Поскольку разделение ресурсов между программами Windows требует кооперативное™ процессов, Windows и называется операционной системой с кооперативной многозадачностью. Чтобы лучше понять различия между Windows и Unix, вспомните, когда в последний раз передача новостей прервала ваш любимый сериал по телевизору. Это то, как Unix поступает с процессами. В Unix не нужно, чтобы процесс взаимодействовал (кооперировал) с системой. Если Unix понадобятся ресурсы, она просто приостановит выполнение подходящей задачи. В семействе Windows 237 
аналогичными свойствами обладают Windows 95 и Windows NT. В следующих разделах мы покажем, что блокирующие операции выполняются по-разному в системах с прерыванием и кооперацией процессов. Блокирующие потоки В многопоточных ОС, например Windows NT, программа может состоять из нескольких потоков (threads). Потоки представляют собой как бы мини-процессы в рамках одного процесса и выполняются одновременно. Даже в многопоточных ОС оператор потока не получит управления до тех пор, пока не закончится предыдущий оператор. Независимо от операционной системы, все операторы в потоках исполняются по очереди. В DOS только одна программа может исполняться на компьютере в данный момент времени. Во многих версиях Unix исполняется множество процессов, но каждый процесс имеет только один поток. Многопоточная ОС Windows NT позволяет одной исполняющейся программе иметь несколько потоков одновременно. В любом случае, операторы одного потока исполняются последовательно, друг за другом. Мы хотим ясно показать, что в любой операционной системе существует проблема блокирования, которую нужно как-то решать. В любой операционной системе возникает ситуация, когда процесс или поток вызывают оператор или функцию, требующую значительного времени на выполнение. Другими словами, эта функция блокирует поток. В случае DOS это приводит к прекращению любой активности компьютера. Во многих версиях Unix и в Windows 3.1 блокирование приводит к зависанию всего процесса, откуда была вызвана блокирующая функция. В Windows 95 и Windows NT блокирующая функция замораживает выполнение вызвавшего ее потока. Вызов операции сетевого ввода-вывода в программе Winsock также является блокирующим. (Далее объясняется, как этот вызов можно сделать не блокирующим, но по умолчанию он все равно блокирует.) Например, вызов функции connect для установления прямого соединения с удаленным хостом блокирует программу до тех пор, пока низ лежащий транспортный уровень не установит соединения и не информирует Winsock об этом. Когда вызывается функция recv и данные для чтения во входной очереди отсутствуют, recv блокирует дальнейшее выполнение программы до тех пор, пока они не появятся в сокете порта протокола. Другие блокирующие функции Winsock приведены в табл. 8.2 и 8.4. Не блокирующие вызовы функций Прикладные интерфейсы Беркли и Winsock позволяют вызывать функции, не выполняя при этом блокирующих операций. Для этого сокет, с которым работает функция, объявляется не блокирующим. Ни один из вышеперечисленных API не имеет отдельных функций для блокирующих и не блокирующих операций. Блокирование полностью зависит от типа сокета, используемого при вызове функции. 238 
.. V::® Что такое блокирование? Предположим, что нам нужно вызвать функцию recv для чтения данных из сокета и мы не хотим, чтобы функция блокировала программу. Пусть наша программа обслуживает TCP-соединение с удаленным хостом. Предположим, что наша программа и удаленный хост обмениваются сообщениями по одному и тому же соединению, при этом их сообщения не следуют поочередно. Другими словами, программа может передать несколько сообщений и только потом получить ответ. В этом сценарии есть тонкость: если программа выполнит функцию recv, а она заблокирует программу, не будет никакой возможности передать еще какое-либо сообщение. Вполне вероятно, что наша программа так и не дождется ответа от удаленного хоста, поскольку он в свою очередь будет ожидать дополнительного сообщения. Мы продемонстрировали, как вызов блокирующей функции может надежно «подвесить» выполнение неправильно разработанной программы — функция recv будет «вечно» ожидать ответа, тогда как удаленный хост — «вечно» ожидать сообщения от нашей программы. Примечание: Образец такого поведения на примере программы FTP рассмотрен в третьей части книги. Не блокирующий сокет создается и соединяется обычным образом при помощи функций socket и connect. Далее вызывается функция Winsock для изменения режима сокета с блокирующего на не блокирующий. (По умолчанию интерфейсы Беркли и Winsock создают блокирующие сокеты.) После того как сокет изменил режим на не блокирующий, и интерфейс Беркли и Winsock гарантируют, что ни одна функция, использующая такой сокет, не приведет к выполнению блокирующей операции. К сожалению, в литературе для новичков обычно не объясняется, каким образом работает не блокирующий сокет. Не существует волшебного способа превратить блокирующий сокет в не блокирующий. Все вышеописанные сценарии выполнения программы остаются в силе. По-прежнему оператор выполняется за оператором. Предположим, что мы назначили не блокирующий сокет%, выполняем на нем функцию recv, и данные для чтения еще не поступили. Если recv обязана ждать появления данных, она заблокирует программу. Не существует способа ждать данные и при этом не блокировать выполнение. Вам, наверное, интересно, что же происходит, если на не блокирующем сокете вызвана функция recv, а сокет не содержит данных. Ответ прост — ничего не происходит. Когда на не блокирующем сокете вызвана блокирующая функция, она возвращает ошибку. Если сокет назначен не блокирующим, операционная система (ядро Unix или WINSOCK.DLL) всегда проверяет возможность немедленного завершения операции до того, как ее начать. Если реализация сокетов не в состоянии завершить операцию, не заблокировав программу, она просто возвращает ошибку. Если вызванная функция не приводит к блокированию, она возвращает нормальный результат. Основная проблема, связанная с литературой, посвященной Winsock, и даже спецификацией Winsock, в том, что не блокирующие сокеты объявляются панацеей от всех бед. Да, это так, но никогда не переоценивайте действительные возможности таких сокетов. Другими словами, в литературе говорится, что не 239 
блокирующие сокеты помогут вам решить проблемы, но не говорится, каким образом. Вы знаете, что, если вызванная на не блокирующем сокете функция может заблокировать программу, она возвращает ошибку. Сперва вы можете подумать: зачем мне все это нужно? Потом — а как мне получить данные из не блокирующего сокета? Не блокирующие сокеты и асинхронные функции Новички в программировании Winsock часто предполагают, что только асинхронные, специфические для Windows функции являются не блокирующими. Более того, поскольку спецификация Windows рассматривает асинхронные и не блокирующие функции в качестве синонимов, это ошибочное мнение только укрепляется. Спецификация также утверждает, что только асинхронная Windows-функция из набора Winsock API способна выполнить не блокирующую операцию. К сожалению, такое представление не верно. Оно особенно запутывает тех, кто не знаком с интерфейсом сокетов Беркли. Вы уже знаете, что после того, как сокет объявлен не блокирующим, любая функция из интерфейса Беркли или Winsock не сможет заблокировать на нем программу. Другими словами, никто не заставляет вас пользоваться только асинхронными Windows-функциями, чтобы совершать не блокирующие операции. Разумеется, в интерфейсе Беркли отсутствуют специальные не блокирующие функции. Зато не блокирующим можно объявить сам сокет. По этой причине интерфейс Беркли не содержит специальных асинхронных функций — операции ввода-вывода в Unix, откуда и пошел интерфейс сокетов, не являются асинхронными. Синхронно или асинхронно? Интерфейс ввода-вывода в Unix синхронный. Это значит, что операции вводавывода происходят в момент вызова соответствующей функции. Функция чтения или записи не завершится до тех пор, пока не закончится вызванная ею операция. Асинхронная функция (в том числе и ввода-вывода) завершается немедленно после ее вызова. В этом она очень похожа на не блокирующие операции с сокетами, однако есть существенная разница. Если операция на не блокирующем сокете не может завершиться сразу, она ничего не делает и возвращает сообщение об ошибке. Если вызвана асинхронная функция, а возможность произвести требуемую операцию отсутствует, то этот факт не порождает сообщения об ошибке. Любая асинхронная функция отвечает только за начало операции — она не ждет ее завершения. Вместо этого за продолжением начатой операции наблюдает операционная система. В большинстве ОС принято посылать сообщение о результатах окончания асинхронной операции. Windows относится именно к 240 
Подробнее о функции WSAAsyncSelect таким ОС. Фактически, каждое событие в Windows происходит асинхронно. Когда происходит событие или заканчивается асинхронная операция, Windows посылает сообщение тому объекту (обычно, окну), которое было указано при вызове функции. Во многих случаях содержимое сообщения может быть задано самим программистом. ОС Windows — не единственная система, обрабатывающая асинхронные события. На это способна даже DOS. Помните, как при нажатии комбинации клавиш Ctrl-C происходит прерывание программы DOS? Эта комбинация — типичный асинхронный сигнал операционной системе, требующий прекратить выполнение программы. Большинство ОС имеют те или иные механизмы, способные реагировать на внешние асинхронные события. Однако основное отличие Windows в том, что она полностью является асинхронной. Другими словами, в основе Windows лежит большой набор асинхронных сообщений, высылаемых в ответ на события в системе, и запросы прикладных программ. Разрабатывая программы Windows, вы используете концепцию, основанную на сообщениях. Любая Windows-программа умеет генерировать собственные асинхронные сообщения и обрабатывать сообщения системы. То есть процесс разработки сложной программы для Windows заключается прежде всего в создании процедур для обработки сообщений. Также при разработке приложений Winsock вы вызываете не блокирующие операции при помощи асинхронных функций интерфейса Winsock. Необходимо запомнить две вещи. Асинхронные операции не являются блокирующими. В интерфейсе Winsock, однако, для не блокирующих операций с сокетами, применять асинхронные функции вовсе не обязательно. Еще раз о функции select Вы помните, что функция select предназначена для того, чтобы один процесс мог контролировать состояние сразу нескольких сокетов. Функция select позволяет контролировать сокеты по чтению, записи и ошибкам. В Winsock существует асинхронный аналог select, под названием WSAAsyncSelect. Функция WSAAsyncSelect является основной функцией Winsock, способной выполнять не блокирующие операции на сокетах. Подробнее о функции WSAAsyncSelect В сценарии блокирующего вызова (который мы рассматривали на примере recv) программа могла вызвать WSAAsyncSelect, чтобы перевести сокет в не блокирующий режим работы. WSAAsyncSelect — единственная в наборе Winsock, принимающая в качестве параметра дескриптор сокета. Основное различие между select и WSAAsyncSelect состоит не в том, что последняя асинхронна, а в том, что только первая способна следить сразу за несколькими сокетами. Да, WSAAsyncSelect способна работать с набором сокетов, однако только после того, как будет вызвана столько раз, сколько сокетов имеется в этом наборе. Другими 241 
Глаша 8, Интерфейс сокетов Windows словами, она должна вызываться с каждым сокетом, за состоянием которого программа желает наблюдать. WSAAsyncSelect требует, чтобы ее параметром являлся только один дескриптор сокета. Функция WSAAsyncSelect иллюстрируется своим прототипом: int PASCAL FAR WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg,long lEvent); Первый параметр WSAAsyncSelect — дескриптор сокета, за состоянием которого мы желаем наблюдать. Последний, четвертый параметр является битовой маской, определяющей те события, за которыми мы желаем наблюдать. В табл. 8.11 содержатся возможные варианты параметра lEvent. Таблица 8.11. Значения параметра функции WSAAsyncSelect, определяющего, за какими событиями в сокете мы желаем наблюдать Константа FDJREAD FD_WRITE FD_OOB FD_ACCEPT FD_CONNECT FD CLOSE Описание Извещение о готовности для чтения Извещение о готовности для записи Извещение о поступлении данных вне диапазона Извещение об установке входящего соединения Извещение об установившемся соединении Извещение о закрытии сокета Для наблюдения за комбинацией событий в сокете вышеуказанные значения можно совмещать при помощи операции побитового ИЛИ (OR). Предположим, что мы хотим, чтобы Windows посылала сообщение по прибытии данных в сокет и когда сокет готов для передачи данных (для записи). В этом случае в качестве параметра WSAAsyncSelect указывается следующая комбинация: (FD_READ | FD WRITE). Как только статус сокета станет истинным (true), например, в сокете появятся данные для считывания, Windows пошлет сообщение главной процедуре программы (той же самой, что обрабатывает класс сообщений WM_COMMAND). Второй и третий параметры функции WSAAsyncSelect такие же, как и в любой функции Windows. wMsg определяет посылаемое сообщение, a hWnd — дескриптор объекта (окна) получателя сообщения. Как только в сокете произойдет ожидаемое событие, Windows пошлет сообщение стандартного формата. Стандартный формат сообщения Windows включает дескриптор окна, идентификатор сообщения и два параметра сообщения (16- битный и 32-битный). 16-битный параметр сообщения описывает дескриптор сокета, в котором произошло событие, а первые шестнадцать битов 32-битного — само событие. Старшие 16 битов содержат возможное сообщение об ошибках. Вообще, в силу ориентированной на сообщения природы Windows, функция WSAAsyncSelect весьма важна. Возможность контролировать состояние одно- 242 
Проблемы блокирования т Windows а 1 временно нескольких сокетов требуется во многих случаях. Например, это нужно для сервера, который должен наблюдать за приходом новых запросов, обрабатывая текущий запрос клиента. Использование функции WSAAsyncSelect Вызов функции WSAAsyncSelect переводит сокет в не блокирующий режим работы. Предположим, например, что вы хотите вызвать функцию recv, не рискуя при этом заблокировать программу. Для этого вы создаете и соединяете сокет при помощи функций socket и connect. Далее, вместо того чтобы сразу вызвать recv, вы вызываете WSAAsyncSelect со следующими четырьмя параметрами: • Дескриптор сокета. • Окно для принятия сообщения. • Идентификатор посылаемого сообщения. • Сетевое событие, за которым нужно наблюдать (в нашем случае, FDJREAD). Другими словами, функция WSAAsyncSelect указывает Windows на необходимость информировать программу о готовности сокета к чтению данных. Как только сокет получит сетевые данные, Windows пошлет соответствующее сообщение, заданное при вызове WSAAsyncSelect. Как только обработчик сообщений получит его, у программы появится возможность вызвать recv для данного сокета. Поскольку данные уже получены сокетом, функция recv никак не может блокировать программу, скорее всего она сразу вернет ожидающие считывания данные. Справедливости ради стоит отметить, что и такая схема не свободна от ряда недостатков, и пользовательская программа должна справляться с ними. Как бы то ни было, применение WSAAsyncSelect позволяет сделать вызов recv не блокирующим. Проблемы блокирования в Windows 3.1 Вы знаете, что любая операционная система должна каким-то образом решать проблемы, связанные с блокированием. В операционных системах с прерыванием процессов, например в Unix, блокирующая функция не воздействует на остальные работающие программы. Блокированный процесс как бы «засыпает» на то время, пока блокирующий вызов не закончится. Все остальные процессы продолжают выполняться как ни в чем не бывало. В Windows 3.1, однако, блокирующий вызов создает серьезные проблемы. Все работающие процессы могут остановиться, поскольку для передачи управления блокированная программа должна сама освободить ресурсы процессора. На рис. 8.2 приведена типичная для ориентированных на соединение сокетов схема вызова функций. Данная схема практически повторяет схему, приведенную в главе 7 при обсуждении сокетов Беркли. 243 
Глава 8. Интерфейс сокетов Windows Сервер, ориентированный на соединение ■ 1 т~ | bind*) т~... IIfstenf) I .' Г ; accept? ; - | Блокирование до получения запроса со стороны клиента i recv() Обработка запроса i .. send?) | | Сетевой компьютер | Установление соединения Данные (запрос) Данные (ответ) Клиент, ориентированный на соединение ■■■'■ •• , socket() I >| connect() | | Сетевой компьютер \ Рис. 8.2. Ориентированный на соединение сокет Winsock Обратите внимание на то, что сервер блокируется после вызова функции accept. Как уже отмечалось, блокирование одной программы в Windows приводит к остановке остальных процессов. Чтобы предотвратить возникновение такой ситуации, Winsock никогда не разрешает совершать блокирующие операции в Windows. Блокирование в сокетах Windows Спецификация Winsock детально рассматривает вопросы блокирования. В то же время спецификация предполагает детальное знакомство программиста со всеми особенностями среды Windows. В следующих абзацах мы приводим краткое описание механизма передачи сообщений в Windows. Примечание: В спецификации Winsock версии 1.1 блокирование рассматривается в разделах 3.1.1 и 3.3.3. 244 
Блокирование в сокетах Windows Краткое обозрение методов обработки сообщений Windows Как правило, сообщения Windows обрабатываются в цикле, аналогичном нижеследующему: MSG msg; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } Цикл while знаком всем программистам. Он часто используется в задачах по получению, обработке и управлению сообщениями программы. Функция GetMessage принимает сообщения от Windows. Функция TranslateMessage, в свою очередь, преобразует сообщения от клавиатуры (например, WM_KEYDOWN или WMJSYSKEYDOWN) в символьные сообщения (например, WM_CHAR). Наконец, функция DispatchMessage высылает все сообщения Windows тому окну, которому они предназначены. В Windows для каждого окна, открытого программой, необходимо создавать процедуру-обработчик сообщений. Такие процедуры могут выглядеть следующим образом: long FAR PASCAL _export WndProc(HWND hwnd, UINT iMessage, UINT wParam, LONG lParam) { LRESULT IResult; switch (iMessage) { case WM_PAINT: // Рисовать окно IResult = 0; break; case WM_COMMAND: // Обработать команду IResult = 0; break; case WM_DESTROY : // Закончить выполнение программы PostQuitMessage(0); IResult = 0; break; default: IResult = DefWindowProc(hwnd, iMessage, wParam, lParam); 245 
Глава 8. Интерфейс сокетов Windows break; } return(IResult); } Windows отвечает за посылку всех сообщений. Сообщения генерируются в ответ на множество различных системных событий. Иногда к генерации сообщения приводит срабатывание внутреннего таймера, иногда — действие пользователя (например, изменение размера или закрытие окна). Пользователь может выбрать вариант в меню, что также приведет к генерации сообщения. Таким образом, Windows-программа должна уметь как-то реагировать на возможные сообщения в свой адрес. Предположим, что прикладная программа посылает электронную почту по запросу пользователя. Когда пользователь выбирает соответствующий пункт меню, Windows генерирует сообщение. Процедура-обработчик сообщения принимает его и запускает программу обработки электронной почты. В ответ на требование запустить электронную почту, программа скорее всего установит сетевое соединение. Кроме того, программа пошлет сообщение об установлении соединения по окончании процесса. Для того чтобы выяснить, установлено ли соединение, можно вызвать уже знакомую нам функцию WSAAsyncSelect — она проверит статус интересующего нас сокета. В общем, любая сетевая программа Windows строится по вышеописанной схеме. Необходимо помнить, что деятельность Windows-программ основана на обмене сообщениями. В этом их отличие от программ Unix. Unix представляет образец в основном синхронной операционной системы, поэтому разработчикам Winsock пришлось адаптировать изначально синхронный API к условиям асинхронной операционной среды. Обработка не блокирующих вызовов функций Winsock — один из шагов разработчиков, предпринятых с целью сблизить два различных интерфейса между собой. Ловушка блокировки Полностью блокирующие операции в Windows запрещены. Когда программа вызывает блокирующую функцию, на самом деле Windows входит в цикл, в котором периодически проверяет состояние дескриптора блокировки (blocking handler) или, как его еще называют, процедуру-ловушку (hook routine). Назначение дескриптора блокировки состоит в перехвате блокирующего вызова функции. Стандартный дескриптор блокировки Winsock содержит конструкцию, похожую на следующую: if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } 246 
Функция PeekMessage проверяет состояние очереди сообщений приложению на наличие сообщения. Если таковое присутствует, оно направляется в структуру MSG Windows, и функция возвращает ненулевое значение. Это, в свою очередь, позволяет TranslateMessage и DispatchMessage обработать новое сообщение. В отличие от GetMessage (стандартной функции Windows), PeekMessage не дожидается появления сообщения в очереди. Дескриптор блокировки позволяет остальным программам продолжать свою деятельность. В то же время он позволяет безболезненно выполнить блокирующий вызов. В чем проблема? Дескриптор блокировки предотвращает зависание Windows, связанное с блокированием процессов. Однако возникает иная проблема. Пока дескриптор работает, существует вероятность, что вновь появившееся сообщение Windows заставит процедуру-обработчик сообщений вызвать другую функцию Winsock. Спецификация Winsock говорит по этому поводу: «Так как такую ситуацию очень трудно обработать правильно, Winsock рассматривает такое поведение прикладной программы как некорректное». Другими словами, пока происходит блокирующий вызов, приложение не имеет права вызывать другие функции Winsock. Это ограничение относится как к блокирующим, так и к не блокирующим операциям. Таким образом, пользователь Winsock сталкивается с той же проблемой, что и пользователь интерфейса Беркли. В течение блокирующей операции ни тот ни другой не могут вызывать функции из сетевого интерфейса. Решение проблемы Для управления блокирующими операциями в Winsock существуют две функции: WSAIsBlocking и WSACancelBlockingCall. Они понадобятся, если вам захочется определить блокирующую операцию и прекратить ее. Вот их прототипы: BOOL PASCAL FAR WSAIsBlocking(void); int PASCAL FAR WSACancelBlockingCall(void); Обе функции не нуждаются в параметрах. Первая определяет, происходит ли в данный момент блокирующая операция. Она возвращает булеву переменную true, если да, и false, если нет. Если блокирующую операцию необходимо прервать, вызывается WSACancelBlockingCall. Блокирующая операция при этом аварийно завершается, а вызвавшая ее функция возвращает код ошибки WSAEINTR. Любая другая функция Winsock, вызванная, пока продолжается блокирующая операция, возвратит значение ошибки WSAINPROGRESS. Ваше собственное решение проблемы Для написания сложной Windows-программы иногда требуется более сложный, чем обычно, обработчик сообщений. Для таких программ предназначены две функции Winsock: WSASetBlockingHook и WSAUnhookBlockingHook. Обе они 247 
>ейс сокетов Windows ' 'щ. 1~ ' т. ' -I Глава 8. Интерф позволяют опытному программисту эффективно управлять блокирующими операциями. Первая позволяет установить свой собственный дескриптор блокировки, а вторая — восстановить системный. Если вы испытываете потребность в написании собственного дескриптора блокировки, рекомендуем внимательно изучить раздел 4.3.13 спецификации Winsock под названием WSASetBlockingHookO. В нем детально описывается структура дескриптора. Также в нем описывается (в псевдокоде) структура главного цикла Winsock (WINSOCK. DLL), в который попадает блокирующая функция. Примечание: При проектировании процедуры-обработчика блокирующих операций необходимо внимательно рассмотреть вопросы реентерабельности. Необходимо четко понимать все проблемы, связанные с ней. К сожалению, в данной книге мы не сможем рассмотреть этот интересный аспект подробнее. Блокирование в Windows NT и Windows 95 Как уже говорилось, блокирующие операции существуют в любых операционных средах. В ОС Windows NT и Windows 95 эти операции происходят на уровне нитей, а не процессов. Поскольку многопоточные версии Windows не являются кооперативными, как Windows 3.1, блокирование одного потока процесса не приводит к блокированию остальных. Winsock для многопоточных версий Winsock не имеет процедуры-обработчика блокировки в своем составе. Таким образом, блокированный поток полностью приостанавливает выполнение и ожидает окончания операции. В Windows 95 и Windows NT, однако, существует функция WSASetBlockingHook. Если нужно, вы можете написать и подключить собственный обработчик блокирующих, вызовов. Позволяя устанавливать собственный обработчик блокирующих вызовов, Winsock упрощает задачу переноса 16-разрядных приложений из Windows 3.1 в 32-разрядные версии Windows 95 и Windows NT. Предположим, что разработанная вами программа для Windows 3.1 использует собственный обработчик блокирующих вызовов. Позднее, решив перенести программу под Windows 95, вы могли бы столкнуться с различиями в реализации переключения процессов и отсутствием стандартного дескриптора блокировки в 32-разрядной Windows. Возможность установить собственный дескриптор блокировки под Windows 95 полностью снимает проблему несовместимости. Подводя итоги В этой главе вы изучили Winsock — модуль, обеспечивающий взаимодействие прикладной программы со стеком протоколов TCP/IP. Winsock занимает положение между программой и протоколами TCP/IP. Использование сокетов Winsock мало чем отличается от использования сокетов Беркли. Также вы 248 
познакомились с блокированием процессов в Windows и тем, как используется обработчик блокирующих вызовов — дескриптор блокировки. В этой главе было описано, чем функции сокетов Winsock отличаются от функции интерфейса Беркли. В частности, в Winsock добавляется файл заголовков winsock.h, изменяются дескрипторы сокетов и методы обработки ошибок. Также объяснено, как использовать специфические для Windows асинхронные функции для выполнения асинхронных операций и управления блокирующими сокетами. В следующей главе вы начнете разрабатывать простые программы, основанные на протоколах TCP/IP. Для этого вам потребуется знание интерфейса Winsock, описанного в данной главе. Перед тем как перейти к следующей главе, проверьте, хорошо ли вы усвоили следующие понятия: S Интерфейс прикладного программирования Winsock основывается на модуле динамической библиотеки WINSOCK.DLL. S Функции Winsock делятся на три категории: функции сокетов, функции для работы с базами данных Интернет и асинхронные функции Windows. S Winsock организует интерфейс между прикладными программами и стеком протоколов TCP/IP. S Winsock-программы можно разрабатывать как на сетевом компьютере, так и на компьютере, подключенном к Интернет при помощи последовательного соединения. S Winsock эмулирует блокирующую операцию, передавая управление ловушке блокировки. Ловушка блокировки позволяет не останавливать выполнение остальных Windows-приложений. S В Winsock не существует специальных блокирующих или не блокирующих функций — вместо этого сокет переводится в блокирующий или не блокирующий режим. 
Г л a Имена доменов в Интернет В четвертой главе вы узнали, что адрес Интернет представляется 32-битным числом. Пользователи и программисты обычно обозначают адрес в виде четырех десятичных чисел, разделенных точками (dotted-decimal). Однако, как показано ниже, адрес Интернет можно изобразить в двоичной, десятичной, шестнадцатиричной системах счисления, а также в специальном формате десятичное с точкой (dotted-decimal): IP-адрес в двоичном виде: 10000110000110000000100001000010 IP-адрес в десятичном виде: 2249721922 IP-адрес в шестнадцатиричном формате: 0x86180842 IP-адрес в форме десятичное с точкой: 134.24.8.66 IP-адрес в форме «десятичное с точкой» наиболее удачен, так как удобен для запоминания и прочтения. Так или иначе, но запоминание сетевого компьютера по его номеру похоже на запоминание ваших друзей не по именам, а по номерам их карточек социального страхования. Большинство людей находят, что 250 
Примериспользования PNS имена — все-таки более удачный выбор. По этой причине разработчики Интернет реализовали так называемую систему имен доменов Интернет (Domain Name System, DNS). Система имен доменов позволяет обращаться к сетевым компьютерам не только по их IP-адресам, но и по индивидуальным именам. То есть вместо того, чтобы связаться с компьютером с IP-адресом 192.102.249.3, вы называете его имя, например cerfnet.com. Прочитав эту главу, вы овладеете следующими ключевыми понятиями: ♦ Каким образом работает распределенная система имен доменов Интернет. ♦ В чем разница между иерархическим и простым пространствами имен. ♦ Каким образом DNS позволяет пользователям ссылаться на компьютеры не по IP-адресам, а по именам. ♦ Как серверы DNS используются в Интернет для преобразования имен в 32-битные адреса и обратно. ♦ Как написать программу-клиент, преобразующую имена DNS в 32-битные или десятичные с точками IP-адреса (и наоборот). Программистам часто приходится использовать цифровой IP-адрес в своих приложениях. Некоторые программы требуют ввести IP-адрес компьютера. Обычные пользователи, однако, предпочитают обходиться именами, указывая, например, cerfnet.com в качестве адреса вместо трудно запоминаемого IP-адреса 192.102.249.3. Когда программа выводит адрес компьютера, пользователь также ожидает увидеть имя, а не цифры. Во многих случаях пользователь должен знать как имя, так и IP-адрес интересующего его сетевого компьютера. Внутри приложений почти всегда используется 32-битный IP-адрес. Одну вещь нужно хорошо запомнить: как бы ни был представлен адрес Интернет — именем или цифрами, он всегда указывает на тот же самый адрес. Пример использования DNS Система имен доменов Интернет предоставляет программе возможность указать определенное имя, например jamsa.com, чтобы обратиться к определенному компьютеру. Вы узнаете, что в интерфейсе прикладного программирования Winsock API скрыты некоторые детали реализации DNS, так что в принципе писать программы, использующие DNS, можно, не вдаваясь в подробности ее работы. Однако рассказать вам о некоторых подробностях входит в наши планы. Чем больше вы узнаете о DNS, тем успешнее сможете разрабатывать программы. В наши дни все больше и больше коммерческих организаций присоединяются к Интернет. И большие и маленькие компании стремятся как можно эффективнее использовать потенциал распределенных баз данных, по принципу которых построена DNS. Овладение принципами построения распределенных баз данных дает возможность писать конкурентоспособные в корпоративном мире приложения. Система имен доменов Интернет (предмет изучения этой главы) являет собой превосходный образец распределенной базы данных. 251 
шерне* Глава &ш Имена домё* Распределенной база данных называется потому, что ее данные физически находятся на двух или более компьютерах. Для программ, использующих базу, географическое местоположение компьютеров несущественно. Другими словами, база данных может содержать данные, размещенные на компьютерах в Калифорнии и Нью-Йорке. Программное обеспечение распределенной базы данных обслуживает и управляет данными так, как если бы все они принадлежали единой базе данных. Скорее всего вам никогда не придется разрабатывать собственную систему имен доменов. Однако вы можете применить принципы, по которым она организована, в своих новых разработках. DNS иллюстрирует технику реализации огромной распределенной сетевой базы данных, подчиненную концепции программирования «клиент-сервер». Иерархическое и простое пространство имен Изначально в Интернет существовало простое пространство имен компьютеров. То есть разработчики никак не структурировали его, чтобы отразить отношения между различными сетями и компьютерами. Например, в небольшой фирме можно использовать имена типа payroll, accounting, timekeeping, production и research для компьютеров в модели простого пространства имен. Компьютеры payroll, accounting и timekeeping могут принадлежать отделу финансов и бухучета фирмы, однако в их именах это никак не отражается. Иерархическое пространство имен, наоборот, больше походит на структуру компании, отраженную в документации каждой достаточно крупной организации. Например, компанию возглавляет президент или председатель совета директоров. Следующий уровень — управляющие подразделениями. Каждое подразделение, в свою очередь, может состоять из нескольких отделов. Иерархическая структура управления компании похожа на иерархическое пространство имен. Во главе находится объект, несущий ответственность за все принимаемые решения. Ответственность за общее дело передается вниз по структуре организации на более низкие уровни управления. Иерархическое пространство имен Каждый сетевой компьютер в Интернет имеет уникальный адрес. В старые добрые времена, когда Интернет состоял из нескольких дюжин сетей, простое пространство имен не представляло проблемы. Присвоить уникальный сетевой адрес отдельному компьютеру не составляло особого труда. Однако вместе с расширением Интернет размер таблиц с адресами компьютеров также начал быстро разрастаться. В наше время Интернет объединяет более 20 000 компьютерных сетей. Каждая сеть состоит минимум из нескольких компьютеров, и каждому требуется собственный адрес. Поскольку задача присвоения и распределения адресов физически не может быть решена силами одной организации, 252 
Что такое система имен доменов? была разработана иерархическая система имен доменов Интернет. Она позволяет распределить и децентрализовать ответственность и управление присвоением имен. Другими словами, множество организаций несут ответственность за присвоение уникальных адресов и имен; каждая — на собственном уровне иерархии. И никто не несет общей или полной ответственности. Еще один пример иерархической системы — номера обыкновенных телефонов. В Соединенных Штатах телефонные номера состоят из десяти цифр, три из которых являются кодом региона, три — префиксом и четыре последних — собственно номером телефонного соединения. Код региона обозначает определенное географическое местоположение региона в стране. Префикс обозначает область меньших размеров, расположенную внутри региона. Иерархическая система телефонных номеров упрощает маршрутизацию вызовов в телефонной сети США. Нетрудно догадаться, что задача по назначению миллионов телефонных номеров — не из простых. С счастью, этим занимается не одна организация. Первым делом Федеральная комиссия по связи и телекоммуникациям (FCC) разделила страну на регионы, каждому из которых соответствует собственный код региона. Внутри каждого региона телефонные компании распределяют префиксы. Наконец, местные телефонные операторы присваивают абонентам уникальные номера, состоящие из четырех цифр. Другими словами, вся система целиком оказывается иерархичной и децентрализованной. Так же как и телефонная система США, имена в Интернет присваиваются по иерархической схеме. В отличие от телефонных номеров, имена Интернет не привязаны географически к какому-либо месту. Имена не распределяются исходя из физического местоположения компьютеров. То есть две компьютерных сети, находящиеся в том же городе или штате, не обязательно имеют похожие имена.1 Система имен доменов Интернет не устанавливает никакого соответствия между именами и физическими сетевыми соединениями компьютеров. Двум компьютерам, установленным в одной и той же сети, не обязательно иметь похожие названия. Что такое система имен доменов? Определенное имя DNS, например ftp.microsoft.comj обозначает определенный компьютер. Каждое из составляющих имени можно назвать меткой. Имя ftp.microsoft.com состоит из трех меток: ftpt microsoft и сот. Метки отделены Имена некоторых компьютеров, например в С.-Петербурге, содержат в названии наименование города, например spb. То есть иногда географическое местоположение компьютеров все-таки отражают в именах. В настоящее время наблюдается тенденция отказываться от такой схемы распределения имен. — Примеч. перев. 253 
,r Глава 9. Имена доменов в Интерг друг от друга точками. Имя читается следующим образом: f-t-p-T04Ka-microsoftточка-сош. Одно из определений термина «домен» гласит, что это есть сфера деятельности, отношений или выполнения каких-либо совместных функций. Метка ftp означает, что данный компьютер является хостом ftp, то есть на нем работает ftp-сервер. Эта метка описывает функцию компьютера по обслуживанию передачи файлов. Метка microsoft описывает организацию (сферу деятельности), которой принадлежит компьютер, — корпорацию Microsoft. Метка сот обозначает тот факт, что данный компьютер выполняет коммерческие функции. Таким образом, каждая метка в имени компьютера описывает домен, то есть сферу деятельности, принадлежности или выполняемых функций. Структура DNS Структура DNS похожа на структуру дерева каталогов вашего компьютера. На вершине иерархии находится корневой каталог, не имеющий имени, — ни один корневой каталог не имеет имени. Наоборот, каждый домен, так же, как и каталог компьютера, имеет собственное имя. Так же как каталог компьютера может иметь подкаталоги, каждый домен в DNS может разделяться на несколько поддоменов. На рис. 9.1 показана иерархическая структура системы имен доменов Интернет. Рис. 9.1. Иерархическая структура системы имен доменов Интернет 254 
Чт? такоесистема шеи доменов? Следующий сразу после корневого уровень состоит из трех групп доменов верхнего уровня: 1. Агра — это специальный домен, необходимый для преобразования IP-адресов в имена DNS, а не наоборот. 2. Группа организаций, к которой принадлежит владелец данной сети. Она обозначается трехбуквенной меткой, например com, edu или gov. 3. Группа по стране пребывания или по географическому местоположению. Обозначается двухбуквенной меткой (наименованием страны) в соответствии со списком, разработанным национальным институтом стандартов (ISO 3166). Если вам приходилось работать в Интернет и раньше, вы наверное встречались с доменами второй группы, которые обычно разделены на шесть основных категорий, показанных в табл. 9.1. Таблица 9.1. Трехбуквенные домены верхнего уровня Домен Описание сот Коммерческие организации. edu Образовательные учреждения, например колледжи и университеты, gov Правительственные учреждения США. int Международные организации, mil Военные организации США. net Сеть, не попадающая ни в одну из вышеперечисленных категорий. org Организация, не попадающая ни в одну из вышеперечисленных категорий. Распространено мнение, что трехбуквенные домены верхнего уровня присваиваются только организациям в пределах США. В общем случае, это не так. Из всех сочетаний только gov и mil обозначают исключительно учреждения США. Все остальные сочетания в равной степени могут использоваться организациями любой другой страны. Многие организации в пределах США вместо трехбуквенного имени домена используют двухбуквенное имя us, обозначающее страну пребывания (США). Кто за все это отвечает? Мы уже знаем, что быстрое разрастание простого пространства имен приводит в чрезмерной нагрузке на организацию, занимающуюся присвоением имен компьютерам. Устроенное иерархически, пространство имен позволяет распределить нагрузку на домены нижних уровней, передав им полномочия по присвоению адресов и организации поддоменов внутри себя. Информационный 255 
Г лава 9- Имена доменов » Интернет центр Интернет (InterNIC) при этом занимается только обслуживанием имен доменов верхнего уровня. InterNIC передает полномочия по присвоению имен различным организациям. Каждая несет ответственность за некоторую ветвь общей иерархии DNS. Ветвь в иерархии, за которую отвечает организация, называется зоной (zone). Другими словами, InterNIC передает полномочия по назначению имен в определенных зонах (частях иерархии DNS) конкретным организациям. Организация, ответственная за зону DNS, может поделить ее на части и передать полномочия по присвоению имен дальше. Например, коммерческая фирма может разделить свою зону по принципу принадлежности к различным подразделениям, а каждое подразделение — по принадлежности к разным отделам. Этот процесс продолжается до тех пор, пока одно ответственное лицо не сможет полностью управлять назначением имен в пределах одной четко отграниченной зоны. Это лицо является администратором DNS. Администратор управляет сервером DNS, обслуживающим его или ее зону. Концепция сервера DNS Как уже отмечалось, пользователи предпочитают обращаться к компьютерам не по адресам, а по именам. С другой стороны, сетевая программа, обращаясь к сокету, вынуждена указывать 32-разрядный IP-адрес. Очевидно, что нам необходим быстрый и надежный метод, чтобы каждому имени поставить в соответствие адрес. Проблема, возникающая в связи с преобразованием адресов, решилась разработчиками путем создания сервера DNS (сервера имен). Сервер DNS — это программа, преобразующая имена доменов в IP-адреса. Этот сервер работает на тысячах компьютеров в Интернет. Когда программе нужно соединиться с удаленным компьютером, первым делом она соединяется с сервером DNS и просит его найти IP-адрес по известному имени. Запрос к серверу имен обычно состоит из структуры данных, включающей имя хоста (сетевого компьютера), адрес в формате «десятичное с точкой» и 32-разрядный двоичный адрес. Во многих случаях для программы-сервера DNS отводится отдельный компьютер. Тогда сервером DNS можно называть весь компьютер целиком. Операция преобразования имени в адрес весьма важна. Ведь пока преобразования не произойдет, соединение не сможет установиться. В дальнейшем вы узнаете, что каждая зона имеет первичный (primary) сервер DNS и один или несколько вторичных (secondary) или запасных. Важно понимать, что серверы DNS организованы в иерархическую структуру. Ни один сервер DNS не может знать обо всех компьютерах в Интернет. Мы уже отмечали, что сама система имен доменов является распределенной базой данных — данные о конкретных компьютерах находятся на различных серверах DNS. Общаясь друг с другом, сервер с сервером, любой сервер DNS может преобразовать любой сетевой адрес в Интернет. На рис. 9.2 показана схема работы серверов DNS в Интернет. Корневой сервер (root) знает, какой из серверов может преобразовать адреса каждого из доменов верхнего уровня. Серверы следующего уровня знают о серверах своих подчиненных уровней и т. д. DNS пользуется моделью «клиент- 256 
Концепция сервера PNS Рис. 9.2. Взаимосвязь между серверами DNS в Интернет сервер». Программа-клиент в этом случае называется «преобразователь» (resolver). Прикладная программа, желающая преобразовать имя компьютера в его адрес, обращается (через преобразователь) к серверу DNS с запросом. Теоретически, преобразователь имен должен связаться с корневым сервером DNS и передать ему запрос на преобразование имени в IP-адрес. Корневой сервер в свою очередь должен определить, к какому из подчиненных серверов он должен обратиться, то есть к какому домену принадлежит имя компьютера, указанное в запросе. Сервер DNS второго уровня делает то же самое, и это продолжается до тех пор, пока запрос не достигнет зоны, в которой будет искомая информация. В конце концов выполненный запрос возвратится обратно к вызывавшей программе и IP-адрес будет найден. А как на самом деле? На самом деле, один сервер DNS может содержать большую часть иерархии имен доменов. Другими словами, дерево сервера имен широко и включает совсем немного уровней. То есть необходимость в обращении к большому количеству серверов для выполнения запроса возникает в реальности не очень часто. На рис. 9.3 показана картина взаимосвязи серверов DNS, более приближенная к реальной жизни. Каждый сервер обслуживает одну или несколько зон в дереве DNS. Администратор DNS специально настраивает сервер на работу со своей зоной ответствен- Рис. 9.3. Более реалистичная картина взаимодействия серверов DNS 9 Зак. № 1949 257 
Глава 9 Имена доменов а Интернат ности. В соответствии с правилами Интернет, каждой зоне соответствует один первичный (primary) и один или несколько вторичных (secondary) серверов. Вторичные серверы вступают в работу, когда первичный оказывается перегруженным или выходит из строя. Исходная информация для первичного сервера DNS хранится на его дисках, то есть загружается при запуске из файлов. Вторичные серверы получают информацию от первичного. Процесс получения информации вторичным сервером от первичного называется передачей зоны (zone transfer). Как правило, обновление информации вторичного сервера (передача зоны) происходит один раз за несколько часов. Первичные и вторичные серверы DNS независимы. Информация, которую они содержат, как правило, избыточна. Цель наличия нескольких серверов DNS в одной и той же зоне — обеспечить надежность функционирования. Сбой в работе одного из серверов DNS не повлечет за собой полной остановки работы этой службы. Преобразование имен в IP-адреса Идеальная картина работы преобразователя адресов значительно отличается от реального положения вещей. На самом деле, программа-клиент (преобразователь) связывается с сервером имен из своей зоны. Сервер рассматривает запрос, чтобы выяснить, в каком домене находится указанное там имя и кто его обслуживает. Если указанный домен входит в зону ответственности этого сервера, сервер преобразует имя в IP-адрес, пользуясь собственными таблицами, а затем отправляет ответ клиенту. Если сервер не в состоянии обслужить запрос, то есть домен находится вне его зоны, дальнейшее его поведение зависит от типа запроса клиента. Преобразователь устанавливает один из двух типов запросов. Сперва он просит сервер произвести полное преобразование. В официальной терминологии этот тип запроса называется «рекурсивное выполнение» (recursive resolution). Если клиент запрашивает рекурсивное выполнение и сервер не в состоянии выполнить преобразование, он должен обратиться к тому серверу, который сможет выполнить запрос, а затем возвращает ответ клиенту. Второй тип запроса — выполнение методом итераций (iterative resolution). Запрос на выполнение методом итераций просит сервер либо выполнить преобразование, либо, в случае неудачи, указать клиенту следующий сервер, к которому можно обратиться. Когда сервер получает запрос на выполнение методом итераций, он либо успешно преобразует имя в IP-адрес, либо его ответ содержит адрес следующего сервера, который, возможно, имеет больше информации. При этом, как видим, наш сервер не делает полного преобразования для клиента. 258 
Как устроен преобразователь адресов? Взаимодействие между серверами DNS Серверу DNS не обязательно знать имена или IP-адреса остальных серверов в Интернет. Достаточно располагать информацией о корневых серверах. Файлы конфигурации каждого первичного сервера содержат IP-адреса корневых серверов. Корневые серверы должны знать имена и IP-адреса каждого сервера DNS второго уровня. В табл. 9.2 приведен список корневых серверов DNS по состоянию на май 1994 г. Таблица 9.2. Корневые серверы системы имен доменов Интернет по состоянию на май 1994 г. Имя хоста Сетевой адрес Программа-сервер NS.NIC.DDN.MIL 192.112.36.4 bind (UNIX) AOS.BRL.MIL 128.63.4.82 26.3.0.29 192.5.25.82 bind (UNIX) C.NYSER.NET 192.33.4.12 bind (UNIX) TERP.UMD.EDU 128.8.10.90 bind (UNIX) NS.NASA.GOV 192.52.195.10 128.102.16.10 bind (UNIX) NIC.NORDU.NET 192.36.148.17 bind (UNIX) NS1.ISI.EDU 128.9.0.107 bind (UNIX) NS.ISC.ORG 192.5.5.241 bind (UNIX) NS.INTERNIC.NET 198.41.0.4 bind (UNIX) Примечание: Наиболее свежая информация о корневых серверах DNS находится в файле netinfo/root-servers.txt по адресу nic.ddn.mjl. Файл можно получить при помощи анонимного ftp. Как устроен преобразователь адресов? Специальное программное обеспечение, работающее на стороне клиента в составе DNS, называется «преобразователь» (resolver) или «преобразователь имен». Windows API обеспечивает выполнение двух функций преобразователя: gethostbyaddr и gethostbyname. Обе функции получают информацию о сетевом компьютере. Прикладные программы пользуются этими функциями, чтобы преобразовать IP-адрес или имя компьютера. В обоих случаях функции связываются с серверами DNS для выполнения этого преобразования. Другими словами, программа вызывает эти функции, чтобы преобразовать IP-адрес в имя или наоборот. Функция, в свою очередь, открывает коммуникационный канал 259 
Глава 9. и май а доменов в интернет и посылает запрос к серверу имен доменов. Получив необходимую информацию, она закрывает канал и возвращает полученное значение вызывавшей прикладной программе. Рассмотрим следующую небольшую программу, QLookup (Quick Lookup). Она демонстрирует основные шаги, которые нужно предпринять для обращения к серверу имен доменов. Из программы выброшено все, не относящееся к делу. Оставлены только детали, необходимые для работы с функциями gethostbyaddr и gethostbyname. Жестко заданные значения переменных и панели сообщений освобождают нас от необходимости программировать пользовательский интерфейс Windows. Исходный текст программы, QLOOKUP.СРР, находится на приложенной к книге дискете. Программа подробно обсуждается в этом разделе, и здесь же приведен ее исходный текст: #include "..\winsock.h" // Файл заголовков Winsock #define PROG_NAME "Simple DNS Lookup" #define HOST_NAME "CERFNET.COM" // Может быть любым (настоящим // именем компьютера #define WINS0CK_VERSION 0x0101 // Необходим Winsock // версии 1.1 #define PF_INET_LENGTH 4 // Длина адреса в протоколах #define HOST_ADDR "129.79.26.27"// winftp.cica.indiana.edu // Интернет всегда равна II 4 байтам WSADATA wsaData; LPHOSTENT lpHostEnt; LPSTR szIPAddr; DWORD dwIPAddr; // Сведения о реализации Winsock // Структура с информацией о // сетевом компьютере Интернет // IP-адрес в виде беззнакового // целого двойной длины // IP-адрес в виде "десятичное II с точкой" if (WSAStartup (WINSOCK__VERSION, fcwsaData) ) MessageBox(NULL, "Could not load Windows Sockets DLL. ", PROG_NAME, MB__OK I MB_ICONSTOP) ; else { // Преобразуем имя сетевого хоста lpHostEnt = gethostbyname(HOST_NAME); 260 
Как устроен преобразователь адресов?’ if (IlpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OK I MB_ICONSTOP) ; else // IP-адрес преобразуется в нотацию "десятичное II с точкой" { szIPAddr = inet_ntoa(* (LPIN__ADDR) * (lpHostEnt->h__addr_list) ) ; } MessageBox(NULL, szIPAddr, 1pHostEnt->h_name, MB_OK | MB_ICONINFORMATION) ; // Формат "десятичное с точкой" преобразуется в // 32-разрядный IP-адрес dwl PAddr = inet__addr (HOST__ADDR) ; if (dwlPAddr == INADDR_NONE) MessageBox(NULL, "Invalid Internet address!", HOST_ADDR, MB_OKIMB_ICONSTOP); else // Преобразуем IP-адрес { lpHostEnt = gethostbyaddr((LPSTR) &dwIPAddr, PF__INET_LENGTH, PF_INET) ; if (!lpHostEnt) MessageBox(NULL, "Could not get host name!", HOST_ADDR, MB_OKIMB_ICONSTOP); else MessageBox (NULL, lpHostEnt->h_name, HOST__ADDR, MB_OK I MB_ICONINFORMATION) ; } } WSACleanup(); // Программа освобождает занятые ресурсы // и завершается return(NULL); } 261 
Глава 9- Имена доменов в Интернет Пишем программу Quick Lookup Оператор в первой строчке листинга включает файл заголовков функций интерфейса Windows Sockets — WINSOCK.DLL. #include "..Winsock.h" Вы знаете, что файл winsock.h необходимо включать в каждую программу, использующую интерфейс программирования Windows Sockets. В нем заданы константы и определены прототипы функций для всего Winsock API. Подробнее о ..\winsock.h Практически все исходные тексты программ на дискете, приложенной к книге, содержат этот оператор: #include "..\winsock.h" Пара точек в начале оператора означает, что файл находится не в текущем каталоге, а в каталоге уровнем выше. Любая программа Winsock обязана содержать этот оператор. Файл, включаемый в текст программы (winsock.h), содержит необходимые для работы с Winsock определения (прототипы) функций и значения символьных констант. Если вам приходится в основном заниматься разработкой приложений Winsock, есть смысл переписать winsock.h в каталог, где размещаются все включаемые файлы вашего компилятора (каталог «include», например). Другая возможность — скопировать его в каталог, где размещаются файлы проектов, если по какой-то причине вам не хочется смешивать стандартные файлы заголовков с winsock.h. Если вы только начинаете программировать в среде Winsock, лучше всего хранить этот файл в доступном месте, откуда его будет легко прочитать в случае необходимости. Вместо того чтобы принимать решения за вас, программа установки на жесткий диск просто размещает winsock.h в общем для всех программ месте — в родительском каталоге. Оттуда его легко скопировать, например в каталог, содержащий стандартные файлы заголовков компилятора. В этом случае необходимо все операторы типа include заменить на следующие: #include <winsock.h> Если вам больше нравится хранить файл заголовков в рабочем (текущем) каталоге, оператор нужно заменить на следующий: #include "winsock.h" Операторы include в программах-примерах предполагают, что они будут компилироваться из тех каталогов, в которых были установлены изначально. То есть копируя программы оттуда в другое место, не забывайте соответственно менять операторы include. В противном случае компилятор выдаст сообщение о том, что он не может найти файл заголовков winsock.h. В большинстве Windows-программ необходимо указывать файл заголовков windows.h. Где-то в начале файла winsock.h можно найти следующее выражение: #ifndef _INC_WINDOWS # include <windows.h> #endif 262 
Выражение означает, что файл заголовков windows.h будет включен автоматически, если вы не сделали этого раньше. В файле windows.h находится выражение, предотвращающее его повторное включение в программу: #ifndef _INC_WINDOWS #define _INC_WINDOWS // Если файл windows.h включен, это // выражение определено Значения констант Сразу после оператора, включающего winsock.h, следуют пять операторов, задающих значения необходимых для работы констант: #define PROG_NAME "Simple DNS Lookup" #define HOST_NAME "CERFNET.COM" // Любое (настоящее) имя // компьютера #define WINS0CK_VERSION 0x0101 // Необходим Winsock // версии 1.1 #define PF_INET__LENGTH 4 // Длина адреса // в протоколах Интернет // всегда равна 4 байтам #define HOST_ADDR "129.79.26.27" // winftp.cica.indiana.edu WINSOCK_VERSION обозначает версию интерфейса Winsock (1.1). Константа AF INET LENGTH определяет длину (в байтах) адреса, указываемого в вызове функции gethostbyaddr. Эти константы используются во всех программах-примерах, приведенных в этой книге. Константа PROG_NAME определяет название (краткое описание) программы, которое можно использовать в дальнейшем. Если вы решите сменить имя программы, просто замените значение этой константы на другое. HOST_NAME и HOST_ADDR задают имя и адрес сетевого компьютера в Интернет. В их качестве можно использовать имя и адрес любого реально существующего сетевого компьютера. Значения переменных Следующие несколько строк листинга QLookup начинают определение функции WinMain (она нужна для всех программ под Windows) и определяют три локальные переменные: dwIPAddr, wsaData и lpHostEnt. int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow) { WSADATA wsaData; // Сведения о реализации // Winsock LPHOSTENT lpHostEnt; // Структура с информацией // о сетевом компьютере 263 
: П лава 9. Имена доменов "щИнтернет DWORD dwIPAddr; // IP-адрес в виде // "беззнаковое целое двойной // длины" LPSTR szIPAddr; // IP-адрес в виде "десятичное // с точкой" IP-адрес, как известно из третьей главы, 32-битная величина. Переменная dwIPAddr типа unsigned long (беззнаковое целое двойной длины) служит для хранения двоичного представления IP-адреса. Тип WSADATA определяет структуру данных, содержащую версию, описание, статус и информацию сокета о Winsock DLL. В нашей программе переменная wsaData сохраняет информацию о реализации Windows Sockets. Функции gethostbyaddr и gethostbyname требуются для доступа к службе DNS. Обе функции возвращают информацию о сетевом компьютере. Windows Sockets хранит эту информацию в специальной структуре данных, называемой host-entry. Тип данных PHOSTENT определяет переменную-указатель на эту структуру. Содержимое структуры описано в файле winsock.h: struct hostent { char FAR * h_name; char FAR * FAR * h_aliases; short h_addrtype; short h_length; char FAR * FAR * h_addr_list; idefine h_addr h_addr_list [0] // AaQan аёу niaiandeiinde n // lQaauaoueie aaQneyie }; В табл. 9.3 описано назначение каждого элемента структуры. Таблица 9.3. Элементы структуры данных host-entry Windows Sockets Элемент Назначение h_name h_aliases h_addrtype h_length h addr list Официальное имя хоста. Массив псевдонимов данного хоста, заканчивающийся нулем. Тип возвращенного функцией адреса; в случае Windows Sockets он всегда равен AF_INET. Длина адреса в байтах. Для типа AF_INET длина всегда 4 байта. В наших примерах для обозначения длины используется константа AF_INET_LENGTH. Список (массив) адресов хоста. Он заканчивается нулем, и в случае Windows Sockets порядок следования байтов сетевой. Переменная lpHostEnt имеет тип LPHOSTENT. Она нужна программе QLookup, чтобы размещать информацию о сетевом компьютере, возвращаемую функциями gethostbyname и gethostbyaddr. 264 
Ill . ... устроен Инициализация Winsock DLL До того как выполнить какую-либо функцию, входящую в состав Windows Sockets API, необходимо вызвать функцию WSAStartup. Следующие несколько строчек программы QLookup инициализируют Windows Sockets DLL, вызывая WSAStartup. Далее, QLookup проверяет результат выполнения этой функции. (WSAStartup возвращает ноль, если все в порядке, и код ошибки в случае неуспеха.) Для выдачи результата на монитор используется стандартная функция MessageBox: if (WSAStartup (WINS OCK__VERS ION, fcwsaData) ) MessageBox(NULL, "Could not load Windows Sockets DLL.", PROG_NAME, MB_OKIMB_ICONSTOP); else { // Программа продолжается } WSACleanup(); // Программа освобождает занятые ресурсы и // завершается return(NULL); QLookup прекратит выполнение, если WSAStartup вернет код ошибки. Функция WSACleanup стирает информацию о регистрации программы в Winsock DLL. Перед тем как закончить выполнение, программа всегда должна выполнять эту функцию. Она освобождает занятые программой системные ресурсы. Qlookup всегда возвращает NULL в качестве результата своей работы. Получение информации о компьютере по его имени Интерфейс Winsock Sockets содержит семь функций для работы с базами данных, позволяющих получить информацию о сетевых компьютерах, протоколах и коммуникационных службах. Функция gethostbyname, например, возвращает информацию о компьютере по его имени. Приведенный ниже оператор демонстрирует прототип функции gethostbyname: struct hostent FAR * PASCAL FAR gethostbyname (const char FAR * name); Чтобы воспользоваться функцией, ей необходимо передать единственный параметр — указатель на имя компьютера, например cerfnet.com. Функцию можно вызвать так: lpHostEnt = gethostbyname(HOST_NAME); В данном случае именем компьютера служит константа HOSTJMAME, равная cerfnet.com. Функция gethostbyname, в свою очередь, выполняет запрос на определение адреса и возвращает указатель на структуру PHOSTENT в переменной lpHostEnt. 265 
Глава SL Имена доменов в Интернет Подробнее о функции gethostbyname Переменная-результат выполнения gethostbyname указывает на структуру типа host-entry, специально отведенную для программы. В Windows Socket существует только одна такая структура, поэтому данные не могут храниться в ней постоянно. Реализация Windows Sockets гарантирует их сохранность только до следующего вызова функций из Windows Sockets API. Другими словами, спецификация Windows Sockets позволяет разработчикам использовать область данных повторно при следующих вызовах функций Windows Sockets. Для того чтобы сохранить полученные данные, необходимо скопировать нужную информацию из структуры в собственные переменные программы, до того, как вызвана какая-либо другая функция. Как управлять указателями переменных в Winsock? Поскольку большинство функций Windows Socketd API возвращают указатели на системные, а значит, переменные, области памяти, необходимо усвоить последовательность действий для того, чтобы сохранять нужные данные: 1. Вызвать необходимую функцию Windows Sockets API. 2. Если функция возвращает указатель, его необходимо сразу же использовать в программе, либо скопировать все значения, которые пригодятся позже, в отведенные и контролируемые вашей программой переменные. 3. Теперь вы можете снова вызывать функции из Windows Sockets API. Поскольку указатели возвращаются многими функциями Windows Sockets API, игнорируя эти требования, вы постоянно рискуете получить и использовать далее неправильные данные. Следующие операторы демонстрируют, как QLookup проверяет результат вызова gethostbyname и выдает его на монитор: lpHostEnt = gethostbyname(HOST_NAME); if (!lpHostEnt) MessageBox(NULL, "Could not get IP address'", HOST_NAME, MB_OK|MB_ICONSTOP) ; else // IP-адрес преобразуется в "десятичное с точкой" { szIPAddr = inet_ntoa(*(PIN_ADDR)*(lpHostEnt->h_addr_list)); MessageBox(NULL, szIPAddr, lpHostEnt->h_name, MB_OK | MB_ICONINFORMATION) ; } Если gethostbyname не в состоянии определить адрес хоста по его имени, она возвращает NULL. Если lpHostEnt не равен NULL, QLookup вызывает функцию MessageBox, чтобы выдать IP-адрес хоста на монитор (в формате «десятичное с точкой»). 266 
Как устроен преобразователь адресов? Подробнее о функции inet_ntoa Элемент структуры host-entry, h_addr_list, указывает на список IP-адресов сетевого компьютера. Их может быть несколько, по числу сетевых интерфейсных карт, установленных в компьютере. Функция inet_ntoa требуется для того, чтобы преобразовать первый адрес в списке, на который указывает h_addr_list, в символьное представление, то есть в адрес вида «десятичное с точкой». Прототип функции inet_ntoa следующий: char FAR * PASCAL FAR inet_ntoa(struct in_addr in); Параметром функции является структура типа in_addr. Структура состоит из объединения, в котором 32-битный IP-адрес представлен тремя способами: как четыре байта, два 16-битовых числа и как одно 32-битовое число. Описание структуры in_addr находится в файле winsock.h: struct in_addr { union { struct { u_char // IP-адрес по байтам // (Четыре байта) s_bl, // Байт 1 (старший) s_b2, // Байт 2 s_b3, // Байт 3 s_b4; // Байт 4 (младший) } S_un_b; struct // IP-адрес в виде двух 16-битовых величин { u_short s_wl, // Первые (старшие) 16 битов s_w2; // Вторые (младшие) 16 битов } S_un_w; u_long S_addr; // IP-адрес в виде 32-битной величины } S_un; } Примечание: Как видим, IP-адрес, записанный в структуре, хранится в любых мыслимых для прикладной программы форматах. Структура in_addr снимает с прикладной программы необходимость самостоятельно транслировать адреса из одной системы счисления в другую и тем самым рисковать совершить ошибку. Функция inet_ntoa возвращает указатель на строку, содержащую IP-адрес в формате «десятичное с точкой», извлеченный из структуры-параметра вызова. Выдавая номер компьютера на монитор, наша программа QLookup использует этот указатель в качестве второго аргумента в вызове функции MessageBox. Например, если gethostbyname, вызванная с аргументом cerfnet.com, возвращает действительный указатель на структуру host-entry, панель, выведенная QLookup на экран, будет выглядеть, как показано на рис. 9.4. 267 
Глава 9. Имена доменов в Интернет Рис. 9.4. Панель сообщения QLookup с адресом сетевого компьютера cerfnet.com Как видно из рис. 9.4, текст в панели сообщения — это IP-адрес компьютера cerfnet.com, равный 192.102.249.3. Поле h field в структуре host-entry является указателем на официальное имя хоста. QLookup помещает этот элемент структуры (lpHostEnt->h_name) в заголовок панели сообщения. Если указатель lpHostEnt равен NULL, QLookup вызывает функцию MessageBox, чтобы вывести сообщение «Could not get IP address» («преобразование имени в адрес осуществить не удалось»). В качестве заголовка панели используется параметр, переданный gethostbyname. На рис. 9.5 показана панель, возникающая, когда gethostbyname не смогла преобразовать имя cerfnet.com в 1Р-адрес. Рис. 9.5. Функции gethostbyname не удалось найти адрес Подробнее о функции inet_addr Независимо от того, удалось ли получить информацию о сетевом компьютере по имени, QLookup пробует получить информацию по его адресу. Для этого QLookup преобразует адрес из десятичного с точкой в 32-битный 1Р-адрес. Функция inet_addr находится в библиотеке функций Windows Sockets. Она не обращается к Интернет (или любой другой сети TCP/IP). Ее задача — преобразовать строку с IP-адресом «десятичное с точкой» в его 32-разрядное двоичное представление. Прототип inet_addr следующий: unsigned long PASCAL FAR inet_addr(const char FAR * cp) ; Функция возвращает значения типа unsigned long (беззнаковое длинное целое). Переменная этого типа представляет 32-разрядное число, равное IP-адресу аргумента. Программа QLookup преобразует строковую константу HOST_ADDR в переменную dwIPAddr, имеющую тип unsigned long: dwIPAddr = inet_addr (HOST__ADDR) ; 268 
Если аргумент inet_addr задан неверно, функция вернет значение INADDRJSJONE. QLookup проверяет результат inet_addr (присвоенный dwIPAddr), чтобы установить, было ли преобразование успешным: if (dwIPAddr == INADDR_NONE) MessageBox(NULL, "Invalid Internet address'", HOST_ADDR, MB_OK|MB_ICONSTOP) ; Если inet_addr вернула значение INADDR_NONE, QLookup выводит панель сообщения с соответствующей надписью: «Invalid Internet address!». Подробнее о функции gethostbyaddr Если dwIPAddr содержит правильный 32-битный IP-адрес, QLookup пробует получить информацию о сетевом компьютере по нему. Для этого используется функция gethostbyaddr. Прототип функции описан ниже: struct hostent FAR * PASCAL FAR gethostbyaddr (const char FAR * addr, int len, int type); У gethostbyaddr три параметра: указатель на IP-адрес (с сетевой последовательностью байтов, в том виде, в каком его возвращает функция inet_addr), длина адреса (в Интернет всегда равная четырем байтам) и тип адреса (всегда равный AF_INET). Первый параметр в вызове gethostbyaddr в нашей программе равен dwIPAddr. Это значение, полученное при вызове inet_addr. Для оставшихся двух параметров используются константы AF_INET_LENGTH и AF_INET. Первая определена в QLookup и равна четырем байтам. Вторая определена в файле winsock.h. QLookup использует переменную lpHostEnt — указатель на структуру host-entry повторно для сохранения результата функции gethostbyaddr: lpHostEnt = gethostbyaddr((LPSTR) fcdwIPAddr, AF_INET_LENGTH, Так же как и gethostbyname, gethostbyaddr возвращает указатель на структуру host-entry. Далее QLookup проверяет результаты вызова, присвоенные переменной lpHostEnt: if (!lpHostEnt) MessageBox(NULL, "Could not get host name I", HOST_ADDR, MB_OK | MB_ICONSTOP) ; else MessageBox (NULL; lpHostEnt-»h_name, HOST__ADDR, MB_OK | MB__ICONINFORMATION) ; Так же как и gethostbyname, gethostbyaddr возвращает NULL в случае неудачной попытки получить нужную информацию или ошибки. Если результат не NULL, QLookup выводит панель сообщения, на котором присутствует h_name (имя сетевого компьютера) как поле структуры host-entry. Константа HOST_ADDR находится в заголовке панели. Она показывает значение первого аргумента, переданного функции inet_addr. Определив константу HOST_ADDR как 129.79.26.27, например, получим сообщение, показанное на рис. 9.6. 269 
Глава 9. Имена доменов в Интернет 129.79.26.27 winftp.cica.indiana.edu Рис. 9.6. Программа QLookup выводит имя сетевого компьютера, определив его по IP-adpecy 129.79.26.27 Если указатель lpHostEnt равен NULL, QLookup выведет панель сообщения с текстом «Could not get host name» («Не могу получить имя хоста»), как показано на рис. 9.7. Рис. 9.7. Сообщение об ошибке при работе функции gethostbyaddr Подводя итоги В этой главе вы узнали, что система имен доменов Интернет (DNS) представляет собой превосходный образец широкомасштабной распределенной базы данных. Децентрализованная иерархическая система DNS позволяет с легкостью управлять назначением имен миллионов сетевых компьютеров в Интернет. В следующей главе мы воспользуемся техникой программирования, изученной в этой главе. Вы узнаете, как устроен и функционирует протокол информации о пользователях Finger. До того как перейти к главе 10, убедитесь, хорошо ли вы понимаете следующие ключевые моменты: S Система имен доменов Интернет (DNS) предназначена для пользователей, желающих обращаться к компьютерам по именам, например jamsa.com, а не по номерам, таким как 168.158.20.102. S Система имен доменов Интернет преобразует (ставит в соответствие) имя компьютера с его IP-адресом в формате «десятичное с точкой» или 32-битовым эквивалентом. S База данных DNS является распределенной. S Серверы DNS общаются друг с другом для выяснения, кто из них обладает информацией для преобразования имени домена в IP-адрес. 
а в Протокол Finger и информация о пользователях В девятой главе вы научились искать имена компьютеров и соответствующие им IP-адреса при помощи Winsock API. Точнее, вы научились вызывать функции API для работы с базой данных имен доменов Интернет (DNS). Учебная программа QLookup выполняет жизненно важную для любого приложения Интернет функцию и необходима практически каждому пользователю. Без нее прикладные программы Winsock могли бы работать только с IP-адресами компьютеров. В этой главе вы продвинетесь дальше, за рамки простого получения информации о сетевом компьютере. Программа Finger, рассмотренная ниже, позволяет получать информацию о пользователях, подключенных к удаленному компьютеру (например, jamsa.com). Более того, Finger позволяет получать общедоступную информацию о конкретном лице, работающем в системе (например, о 271 
i Глаза 10, Протокол Finger и информация о пользователях kcope@jamsa.com). В процессе разработки программы вы познакомитесь с сетевым протоколом Finger, предназначенным для получения конкретной информации о пользователях от удаленного компьютера. Для этого в программе создается сокет, затем он соединяется с удаленным компьютером. После этого программа производит обмен данными и получает информацию о работающих в системе пользователях. Усвоив механизм работы протокола Finger, вы получите представление о том, как связаться и воспользоваться услугами любой другой информационной службы Интернет. Разумеется, форматы сообщений и способы взаимодействия варьируются от протокола к протоколу, однако принцип остается тем же самым. Так что главное — уловить идею. А уловив идею, вы окажетесь на расстоянии вытянутой руки от того, чтобы начать разрабатывать собственные сетевые службы Интернет. Прочитав эту главу, вы овладеете следующими ключевыми понятиями: ♦ Каким образом протокол виртуального терминала делает не заметными различия между операционными системами. ♦ Как запросить и получить информацию из базы данных, описывающей сетевые службы. ♦ Как посылать запросы и получать ответы при помощи протокола Finger. ♦ Где искать и как воспользоваться самыми важными для Интернет информационными источниками — документами RFC (Request for Comments). Еще раз о сетевом уровне представления Вы знаете, что далеко не все компьютерные системы трактуют один и тот же набор символов (например, перевод каретки, перевод строки, символы табуляции и забоя) одинаково. В Unix, например, строчку текста принято заканчивать символом перевода каретки (CR), а в некоторых системах — переводом строки (LF). В DOS, как известно, строка оканчивается и переводом каретки, и переводом строки. Прервать выполняющуюся программу в некоторых системах можно нажатием комбинации Ctrl-C, а в некоторых — клавишей Esc. Другими словами, для одинаковых целей в различных ОС используются различные символы. Все компьютеры (точнее, операционные системы) выполняют похожие операции. Однако несовместимость управляющих символов может привести к тому, что корректно работающая на одном компьютере сетевая программа выдаст непредсказуемые результаты на другом. Хорошо спроектированная сеть обязана сама помогать программам устранять такие недоразумения. К задачам уровня представления относится устранение несовместимости между сетевыми компьютерами. Функции уровня представления, как правило, выпол- 272 
Что такое виртуальный сетевой терминал? няются протоколами виртуального сетевого терминала. При этом с точки зрения сетевой программы дисплеи и принтеры всех удаленных компьютеров выглядят и управляются одинаково. Предположим, что сетевая программа шлет закодированные виртуальным протоколом данные по Интернет. Символы окончания строки в этих данных соответствуют требованиям протокола виртуального терминала. Протокол, например, может кодировать окончание строки комбинацией символов перевода каретки и перевода строки. Компьютер-получатель данных преобразует их из протокола виртуального терминала в собственный формат. То есть комбинация «перевод каретки-перевод строки» преобразуется в локальное представление, позволяющее правильно выдать текст на экран. Стек протоколов TCP/IP не имеет отдельного уровня представления. Основные функции этого и сеансового уровней модели ISO/OSI берет на себя прикладной уровень. Тем не менее в TCP/IP существует протокол виртуального терминала. Что такое виртуальный сетевой терминал? Семейство протоколов TCP/IP имеет протокол виртуального терминала под названием TELNET. Вам, возможно, приходилось встречаться с ним, когда вы входили в удаленную систему. Однако не всем известно, что TELNET работает по специально спроектированному протоколу, описанному в RFC 854, «Спецификация протокола Telnet» (Telnet Protocol Specification, Postal and Reynolds, 1983). Основополагающая концепция TELNET — идея сетевого виртуального терминала (Network Virtual Terminal, NVT). Эта концепция используется не только в TELNET. Многие протоколы, например Finger, также работают по этому принципу. В спецификации TELNET виртуальный терминал описывается, как воображаемое устройство, выполняющее соглашения о работе обыкновенных компьютерных мониторов или терминалов, таких как VT100, например. Концепция NVT, как она описывается в спецификации TELNET, является исключительно протоколом работы виртуального терминала. Сам протокол TELNET, однако, не является протоколом виртуального терминала. Подробнее о TELNET вы узнаете, прочитав шестнадцатую главу. Все компьютерные системы выполняют сходные действия, выводя информацию на экран. Например, когда экран переполняется, почти все терминалы сдвигают текст вверх, освобождая внизу место для вновь поступающего текста. Большинство терминалов позволяют пользователю стирать ошибочно введенные данные и т. д. Каждый терминал пользуется концепцией перевода каретки, оставшейся с давних времен, когда еще существовали устройства, называемые «каретки», которые действительно можно было «переводить». Когда машинистка заканчивала ввод строчки, она нажимала «перевод каретки», и лист бумаги возвращался в исходную позицию, сместившись вниз на одну строку. На современных компьютерах для этой же цели используется клавиша «Return» или «Enter», сдвигающая курсор в крайнюю левую позицию следующей строки. Сама функ- 273 
10. протокол Finger и информаций Пользователям - ция называется по-прежнему: «перевод каретки» (CR). В спецификации виртуального терминала указывается, какими символами нужно кодировать те или иные действия. То есть какие символы представляют перевод каретки, забой, очистку экрана и прочие специальные действия. Определив единый для всех сетевых программ стандарт, NVT тем самым скрыл различия в реализации этих функций в разных сетевых компьютерах. На рис. 10.1 показано, как NVT вписывается в схему сетевого соединения между двумя компьютерами в Интернет. Программа-клиент | Данные в формате системы клиента Компьютер-клиент ТСР-соединение Данные в формате NVT Программа-сервер j Данные в формате системы сервера Компьютер-сервер Рис. 10.1. Концепция сетевого виртуального терминала (NVT) Перед тем как отправить данные, и клиент и сервер кодируют их в соответствии с требованиями NVT. Программы-получатели данных, наоборот, декодируют их в соответствии с требованиями собственной операционной системы. Формат NVT Формат NVT, заданный в спецификации TELNET, весьма прост. Вы знаете, что наименьшая частица информации, с которой обычно оперируют современные компьютеры, равна одному байту. Для кодирования сетевых данных NVT пользуется стандартной американской кодировкой ASCII (American Standard Code for Information Interchange). Для кодирования данных в стандартном (не расширенном) наборе ASCII на один символ отведено всего 7 битов. Для передачи командных последовательностей NVT использует восьмой (старший) бит. Примечание: Семибитный стандартный набор US-ASCII способен воспроизвести максимум 128 символов. Двоичное 1111111 (семь битов) равно десятичному 127. Прибавим к этому символ с кодом «ноль» и получим всего 128 символов. Вы наверное знаете, что в наборе US-ASCII есть 95 печатных символов и 33 управляющих. К первым относятся цифры, буквы, знаки пунктуации и другие. Некоторые из 33 управляющих широко применяются в устройствах вывода данных, а некоторые — нет. Хорошо известен, например, символ с кодом 274 
Что такие виртуальный сетевой терминал? ASCII 7 или, по-другому, BEL. Его появление в тексте приводит к генерации звукового сигнала. Некоторые из управляющих символов ASCII используются и в NVT. Они перечислены в табл. 10.1. Таблица 10.1. Управляющие ASCII-коды в представлении NVT Код управляющего символа Шестнадцатиричное значение Совершаемое действие NUL 0x00 Не производит никакой операции. BEL 0x07 Звуковой (звонок) или визуальный сигнал. BS 0x08 Сдвиг влево на одну позицию (backspace). нт 0x09 Сдвиг вправо на одну позицию горизонтальной табуляции. LF ОхОА Сдвиг вниз на одну строку (перевод строки, line-feed). VT 0x0В Сдвиг вниз на следующую позицию вертикальной табуляции. FF ОхОС Перемещение на начало следующей страницы (formfeed). CR 0x0D Сдвиг к левой границе текущей строки (возврат каретки, carriagereturn). Кроме того, в NVT определена стандартная комбинация символов для обозначения конца строки: CRLF (перевод каретки и перевод строки). Как только пользователь нажимает клавишу Enter (или Return), NVT преобразует нажатие в символы CRLF. Определения символов NVT называются также NVT ASCII — они полностью описаны в спецификации протокола TELNET. NVT ASCII используется многими сетевыми программами. Примечание: Кроме вышеописанных символов, NVT определяет и некоторые другие. Правда, они очень редко встречаются на практике. Виртуальный сетевой терминал В RFC 854, «Спецификация протокола TELNET», NVT описывается как часть протокола TELNET. NVT обеспечивает стандартный сетевой интерфейс, подобный виртуальному сетевому протоколу, призванный скрыть различия между компьютерами в интерпретации таких символов, как перевод каретки, строки, маркеров конца строки и т. п. Для кодирования цифр, букв и знаков пунктуации NVT использует 7-битную кодировку. Из 33 управляющих символов ASCII используются только восемь (они перечислены в табл. 10.1). Комбинация CRLF (перевод каретки и перевод строки) используется в качестве маркера конца строки. Набор ASCII, определенный в NVT, часто так и называется: NVT ASCII. Множество программ Интернет пользуются набором NVT ASCII при передаче сетевых данных. 275 
Глава 10. Протокол Finger и информация о пользователях Учебная программа Finger Программа, работающая по протоколу Finger, весьма проста по структуре. Протокол Finger имеет официальный номер порта 79. Вы знаете, что номер порта протокола относится к определенной сетевой программе. Для того чтобы запросить службу Finger у удаленного компьютера, программа должна установить TCP-соединение с его портом под номером 79. Все запросы Finger выполняются в формате NVT ASCII. Чтобы получить список работающих пользователей, программа передает пустую строку. Чтобы получить информацию о конкретном пользователе, строка-запрос должна содержать имя или идентификатор этого пользователя. Каждая строка запроса заканчивается маркером конца строки в формате NVT ASCII — комбинацией CRLF. В этом разделе мы напишем учебную программу под названием QFinger. Она похожа на QLookup из предыдущей главы. QFinger продемонстрирует несколько важных в программировании Интернет моментов в чистом виде. Как и в случае QLookup, все данные, в нормальной ситуации вводимые пользователем, находятся прямо в тексте программы. Также устранены все сложности в программировании пользовательского интерфейса и обработке сообщений Windows. Исходный текст QFinger есть на дискете, приложенной к книге, и полностью приводится ниже. Сейчас мы рассмотрим его в подробностях. #include "..\winsock.h" #define PROG_NAME "Simple Finger Query" #define HOST_NAME "cerfnet.com" (настоящим) именем компьютера #define WINSOCK_VERSION 0x0101 #define PF__INET_LENGTH 4 #define FINGER_QUERY "lonetech" #define DEFAULT_PROTOCOL 0 #define SEND_FLAGS 0 #define RECV^FLAGS 0 // Может быть любым // Необходим Winsock версии 1.1 // Длина адреса в протоколах // Интернет всегда равна // 4 байтам // Настоящее имя пользователя // или имя для входа в систему // Протокол не задан, поэтому // используем протокол "по // умолчанию" // Флаги для функции send() //не заданы // Флаги для функции recv() //не заданы int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow) { WSADATA wsaData; // Сведения о реализации // Winsock LPHOSTENT lpHostEnt; // Структура с информацией о // сетевом компьютере 276 
Учебная программа SOCKET nSocket; SOCKADDR_IN sockAddr; LPSERVENT IpServEnt; short iFingerPort; char szFingerlnfo[5000] ; char szFingerQuery[100]; int nCharSent; int nCharRecv; int nConnect; // Номер сокета, используемого // в данной программе // Структура адреса сокета // Структура с информацией о // сетевой службе // Официальный номер порта // протокола — 79 // Буфер для хранения // информации Finger // Буфер для хранения запроса // Finger // Количество переданных // символов // Количество принятых символов // Результат соединения // (дескриптор) сокета if (WSAStartup(WINSOCK_VERSION, &wsaData)) MessageBox(NULL, "Could not load Windows Sockets DLL.", PROG__NAME, MB__OK I MB_ICONSTOP) ; else // Преобразуем имя хоста { lpHostEnt = gethostbyname(HOST_NAME); if (!lpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST__NAME, MB_OK I MB_ICONSTOP) ; else // Создаем сокет { nSocket = socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL); if (nSocket == INVALID_SOCKET) MessageBox(NULL, "Invalid socket!!", PROG_NAME, MB_OK|MB_ICONSTOP); else // Настраиваем сокет { // Получаем информацию о службе Finger IpServEnt = getservbyname("Finger", NULL); if (IpServEnt == NULL) iFingerPort = IPPORT_FINGER; // Официальный // порт протокола else iFingerPort = lpServEnt->s_port; 277 
Глава IQ. Протокол Finger и // Определяем адрес сокета sockAddr.sin_family = AF_INET; // Семейство / / адресов Интернет sockAddr.sin_port = iFingerPort; s о с kAddr.s in_addr = * ( (LPIN_ADDR) *lpHostEnt->h_addr_list) ; // Соединяем сокет nConnect = connect(nSocket, (PSOCKADDR) &sockAddr, sizeof(sockAddr)); if (nConnect) MessageBox(NULL, "Error connecting socket!!", PROG_NAME, MB_OK | MB_I CONS TOP) ; else // Формируем и посылаем запрос Finger { wsprintf(szFingerQuery,"%s\n", FINGER_QUERY); nCharSent = send(nSocket, szFingerQuery, lstrlen(szFingerQuery), SEND_FLAGS); if (nCharSent == SOCKET_ERROR) MessageBox(NULL, "Error occurred during send () ! ", PROG_NAME, MB_OK |MB__ICONSTOP) ; else // Принимаем информацию Finger { do { nCharRecv = recv(nSocket, (LPSTR)kszFingerlnfо[nConnect], sizeof(szFingerlnfo) - nConnect, RECV_FLAGS); nConnect+=nCharRecv; } while (nCharRecv > 0); if (nCharRecv == SOCKET_ERROR) MessageBox(NULL, "Error occurred during recv()!", PROG_NAME, MB_OKIMB_ICONSTOP); else // Выводим информацию Finger { wsprintf(szFingerQuery,"%s@%s", FINGER_QUERY, HOST_NAME); 278 
■ Учебная программа Roger } } } } } WSACleanup(); return NULL; } MessageBox(NULL, szFingerlnfo, szFingerQuery, MB_OKIMB_ICONINFORMATION); } // Программа освобождает занятые ресурсы //и завершается Константы Так же как и другие программы Winsock API, QFinger включает в себя файл winsock.h. После оператора include следуют операторы, задающие константы: #include "..Winsock.h" #define PROG_NAME "Simple Finger Query" #define HOST_NAME "cerfnet.com" #define WINSOCK_VERSION 0x0101 #define PF__INET_LENGTH 4 #define FINGER_QUERY "lonetech" #define DEFAULT_PROTOCOL 0 #define SEND_FLAGS 0 #define RECV_FLAGS 0 // Может быть любым // (настоящим) именем // компьютера // Необходим Winsock версии 1.1 // Длина адреса в протоколах // Интернет всегда равна II 4 байтам // Настоящее имя // пользователя или имя для // входа в систему // Протокол не задан, // поэтому используем // протокол "по умолчанию" // Флаги для функции send() //не заданы // Флаги для функции recv() //не заданы Константы PROG_NAME, HOST_NAME и WINSOCK_VERSION здесь играют ту же роль, что и в QLookup. HOST_NAME должна являться именем реально существующего в Интернет компьютера. В качестве имени пользователя QFinger передает серверу константу FINGER_QUERY. В нашем случае HOST_NAME задает имя компьютера «cerfnet.com». Константа FINGER_QUERY задает пользователя по имени «lonetech». Вместо этих значе¬ 279 
Глава 10. Протокол Finger и информация о пользователям ний или идентификатора пользователя можно подставить любые другие — лишь бы они существовали в реальности. Константа DEFAULT_PROTOCOL, равная нулю, говорит о том, что нам нужен стандартный протокол Finger, то есть протокол по умолчанию. Работа некоторых функций Winsock контролируется при помощи специальных флагов, о которых вы узнаете позже. В программе QFinger они не используются, поэтому значения SEND FLAGS и RECV_FLAGS равны нулю. Переменные В следующих строках исходного текста объявляются некоторые переменные и описывается функция WinMain: int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow) WSADATA wsaData; LPHOSTENT lpHostEnt; SOCKET nSocket; SOCKADDR_IN sockAddr; LPSERVENT IpServEnt; short iFingerPort; char szFingerlnfo[5000]; char szFingerQuery[100]; int nCharSent; int nCharRecv; int nConnect; // Сведения о реализации Winsock // Структура с информацией о // сетевом компьютере // Номер сокета, используемого // в данной программе // Структура адреса сокета // Структура с информацией о // сетевой службе // Официальный номер порта // протокола — 79 // Буфер для хранения // информации Finger // Буфер для хранения запроса // Finger // Количество переданных // символов // Количество принятых символов // Результат соединения сокета Переменная wsaData, так же, как и в QLookup, содержит сведения о реализации Winsock, полученные при вызове WSAStartup. lpHostEnt также указывает на структуру данных с информацией об удаленном компьютере. Структура заполняется при вызове функции gethostbyname. В табл. 9.3 девятой главы приведены составляющие структуры данных об удаленном компьютере. Перед тем как создать и соединить сокет, из этой структуры необходимо извлечь IP-адрес компьютера. Переменная nSocket имеет тип SOCKET, определенный в winsock.h как беззнаковое целое. Назначение переменной типа SOCKET соответствует назначению дескриптора файла. Программа Winsock может создавать множество сокетов, 280 
Учебная программа Finger подобно тому, как программа DOS или Windows может одновременно открывать множество файлов. Переменная типа SOCKET указывает на сокет, созданный нашей программой для обмена данными с сервером. В переменной iFingerPort хранится номер порта службы Finger. Переменная szFingerlnfo является вместилищем данных, полученных от удаленного компьютера. Переменная szFingerQuery служит буфером запроса к службе Finger. Переменные целого типа, nCharSent и nCharRecv подсчитывают количество байтов, переданное или полученное через сокет нашей программой. В переменной nConnect хранится результат операции по соединению сокета. QFinger проверяет ее значение, чтобы выяснить, было ли соединение успешным. Назначение переменных sockAddr и IpServEnt будет объяснено в следующих разделах. С самого начала Несколько первых операторов в QFINGER. СРР в точности повторяют операторы из QLookup из девятой главы: if (WSAStartup(WINSOCK_VERSION, &wsaData)) MessageBox(NULL, "Could not load Windows Sockets DLL.", PROGJKTAME, MB_OK I MB_ICONSTOP) ; else // Преобразуем имя сетевого компьютера { lpHostEnt = gethostbyname(HOST_NAME); if (!lpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OK|MB_ICONSTOP) ; else { // Продолжаем выполнение QFinger } } WSACleanup(); // Программа освобождает занятые ресурсы и // завершается return NULL; В начале, как видим, вызывается WSAStartup. Ее задача — инициализировать модуль WINSOCK.DLL. Далее вызывается gethostbyname, чтобы получить из DNS информацию о сетевом компьютере, который нас интересует. Параметр gethostbyname — имя компьютера, а результатом при успешном выполнении является указатель на структуру (lpHostEnt), содержащую данные о компьютере. Если возвращаемый указатель равен NULL (запрос был неудачным), программа выведет соответствующее сообщение на дисплей. Поскольку остальные операторы программы заключены в фигурные скобки ({}) конструкции else, следующим после неудачного выполнения gethostbyname будет оператор WSACleanup, и программа корректно завершится. Вы помните, что WSACleanup 281 
Глава 10. Протокол Finder и информация о пользователе освобождает ресурсы Winsock, связанные с вызывающей программой. WSACleanup должен вызываться всегда перед завершением программы. Создание сокета Если переменная-указатель lpHostEnt не равна NULL, QFinger вызывает функцию socket и создает новый сокет для сетевого соединения: if (!lpHostEnt) MessageBox(NULL, "Could not get IP address!", H0ST_NAME, MB_0K|MB_IC0NST0P) ; else // Создаем сокет { nSocket = socket(PF__INET, SOCK_STREAM, DEFAULT_PR0T0C0L); if (nSocket == INVALID_SOCKET) MessageBox(NULL, "Invalid socket!!", PR0G_NAME, MB_0K|MB_IC0NST0P) ; Функция socket возвращает дескриптор сокета. Дескриптор, как мы уже писали, хранится в переменной nSocket типа SOCKET. Следующий оператор проверяет, действительно ли сокет существует, сравнивая значение дескриптора с константой INVALID__SOCKET. Если нет, то есть свободных сокетов не существует, на дисплей выводится предупреждающее сообщение, и программа завершается. У функции socket следующий прототип: SOCKET PASCAL FAR socket (int af, int type, int protocol); При вызове указываются три параметра. Первый — семейство адресов. В версии 1.1 Winsock существует только одно семейство адресов и протоколов, AF_INET и PF_INET соответственно. Второй параметр обозначает тип сетевой службы (потоковый или датаграммный) для использования вместе с сокетом. Winsock версии 1.1 обеспечивает два этих типа сетевых служб; они обозначаются константами SOCK_STREAM и SOCK_DGRAM соответственно. Примечание: В главе 14 вы узнаете, что существует третий тип сетевой службы — простой (raw) сокет, обозначаемый как S0CK_RAW. Спецификация Winsock версии 1.1 не требует поддержки этого типа сокетов, поэтому он не обязательно присутствует во всех реализациях Winsock. Сетевая служба SOCK_STREAM обеспечивает надежную, двухстороннюю, ориентированную на соединение передачу данных. Транспортный протокол для этой службы — TCP. Служба SOCK_DGRAM, наоборот, ненадежна, поскольку использует датаграммы и ориентирована на протокол UDP. Если вы не хотите определять протокол, четвертый параметр функции socket равен нулю. При этом используется стандартный протокол или протокол по умолчанию. В случае QFinger четвертый параметр функции, DEFAULT_PROTOCOL, как раз и равен нулю. 282 
Учебная программа Finger После того как сокет создан и программа получила его дескриптор, он настраивается на адрес удаленного компьютера. Первый шаг на этом пути — получение информации о сетевой службе из базы данных. В случае QFinger необходима информация о сетевой службе Finger. Что такое база данных по сетевым службам? В базе данных по сетевым службам хранится информация о службах типа Finger, Ftp, Mail, Telnet и многих других. Для каждой службы задается номер порта, доступные протоколы и псевдонимы (другие имена для той же самой службы). На персональных компьютерах эта информация, как правило, находится в текстовом файле формата ASCII под названием SERVICES. Файл SERVICES обычно находится в том же каталоге, что и WINSOCK.DLL. Переменная IpServEnt программы QFinger указывает на структуру с информацией о сетевой службе: LPSERVENT IpServEnt; // Структура с информацией о // сетевой службе Структура LPSERVENT определена в файле-заголовке winsock.h следующим образом: typedef struct servent FAR *LPSERVENT; // Расширенный тип // данных Windows struct servent { char FAR * s__name; char FAR * FAR * s_aliases; short s_port; char FAR * s_proto; }; Элементы структуры заполняются при вызове функции getservbyname. Вот ее прототип: struct servent FAR * PASCAL FAR getservbyname(const char FAR * name, const char FAR * proto); В табл. 10.2 приведены описания элементов структуры servent. Таблица 10.2. Элементы структуры, содержащей информацию о сетевой службе Элемент Описание s_name Официальное название службы, например Finger. s_aliases Список псевдонимов данной сетевой службы. s_port Номер порта протокола данной службы, равный 79 для Finger. s_proto Название протокола, работающего с этой службой, например TCP или UDP. 283 
Глава 10. Протокол Finger и информация о пользователях При вызове функции getservbyname указываются два параметра — указатель на имя службы и указатель на название протокола. Указатель на название протокола может равняться NULL. В этом случае будет выбран протокол по умолчанию. В случае QFinger первый параметр указывает на строку «finger», а второй равен NULL — программе нужен стандартный протокол. Функция getservbyname исследует базу данных с информацией о сетевых службах. На персональных компьютерах это обычно ASCII-файл под названием SERVICES. Его фрагмент приводится ниже. Файл состоит из трех колонок. Первая содержит наименование сетевой службы, вторая определяет номер порта и протокол, а в третьей приведены соответствующие службе псевдонимы: # Network services, Internet style # #name port/protocol aliases # echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users daytime 13/tcp day t ime 13/udp netstat 15/tcp qotd 17/tcp quote chargen 19/tcp ttytst source chargen 19/udp ttytst source ftp 21/tcp telnet 23/tcp smtp 25/tcp mail time 37/tcp timserver time 37/udp timserver rip 39/udp resource # resource location nameserver 42/tcp name # IEN 116 who is 43/tcp nicname domain 53/tcp nameserver # name-domain server - domain 53/udp nameserver mtp 57/tcp # deprecated tf tp 69/udp rje 77/tcp netrjs finger 79/tcp link 87/tcp ttylink supdup 95/tcp hostnames 101/tcp hostname # usually from sri-nic ns 105/tcp # ph name server 284 
Учебиая программа Finger рор2 109/tcp postoffice2 рорЗ 110/tcp postoffice sunrpc 111/tcp portmapper sunrpc 111/udp portmapper auth 113/tcp authentication sf tp 115/tcp uucp-path 117/tcp nntp 119/tcp readnews untp # USENET News Transfer Protocol Примечание: Спецификация Winsock не затрагивает вопросов, касающихся базы данных по сетевым службам. На персональных компьютерах, однако, база данных чаще всего хранится в виде текстового файла SERVICES, находящегося в том же каталоге, что и WINSOCK.DLL. Рассмотрим первые две записи файла SERVICES. Вы видите, что служба echo с официальным номером порта 7 может использоваться как с протоколом TCP, так и с протоколом UDP. Служба или сервер echo просто возвращает все выданные клиентом данные обратно в том же виде. Предположим, что мы вызвали getservbyname со следующими аргументами: getservbyname("echo", NULL); getservbyname обратится к файлу SERVICES и найдет в нем первую строчку с именем echo (с протоколом TCP). Структура информации о сетевой службе после вызова будет содержать следующие элементы: s_name[] = "echo"; s_aliases = NULL; s_port = 7; s_proto[] = "tcp"; Если вам понадобится протокол UDP, функцию getservbyname следует вызывать так: getservbyname("echo", "UDP"); В этом случае getservbyname заполнит структуру данными о службе echo с протоколом UDP: s__name [ ] = " echo" ; s_aliases = NULL; s_port = 7; s_protо[] = "udp"; He все сетевые службы позволяют использовать оба протокола. Ftp, например, может выполняться только с протоколом TCP. Точно так же протокол TFTP (Trivial FTP, «простой протокол передачи файлов») выполняется только вместе с протоколом UDP. 285 
Глава 10i Протокол Finger и информация о пользоватеяях Если вы внимательнее посмотрите на файл SERVICES, то увидите, что службы, работающие с обоими протоколами, сперва указаны с TCP, а затем с UDP. Поэтому, если вам не нужен именно UDP, в вызове getservbyname в качестве второго параметра можно указывать NULL. Функция, в свою очередь, всегда вернет данные о службе с протоколом TCP, если он, конечно, доступен для этой службы. Ниже приведены образцы псевдонимов сетевых служб. Например, SMTP (Simple Mail Transfer Protocol, «простой протокол передачи почты») имеет псевдоним mail, служба time — timserver, a whois — псевдоним nicname. #nameport/protocol aliases # smtp 25/tcp mail time 37/tcp timserver time 37/udp timserver whois 43/tcp nicname При вызове getservbyname все равно, что указывать — официальное имя или псевдоним. Следующие два вызова getservbyname эквивалентны: getservbyname("smtp", NULL); getservbyname("mail", NULL); При этом возвращается указатель на следующую структуру: s_name [ ] = " smtp" ; s_aliases[] = "mail"; s_port = 25; s_proto[] = "tcp"; Точно так же эквивалентны два этих вызова: IpServEnt = getservbyname("whois", NULL); IpServEnt = getservbyname("nicname", NULL); Оба они вернут следующие результаты: s_name [ ] = " who is"; s_aliases[] = "nicname"; s_port = 43; s_proto[] = "tcp"; База данных по сетевым службам База данных по сетевым службам содержит список часто встречающихся сетевых служб, таких как Ftp, Finger или Telnet. Для каждой службы задается официальный номер порта и ее транспортный протокол. Некоторые службы используют более одного протокола (TCP и UDP, например). Также в базе данных находятся псевдонимы сетевых служб. На персональных компьютерах, как правило, база данных находится в текстовом файле SERVICES в том же каталоге, что и WINSOCK.DLL. Для доступа к базе данных необходимо вызвать функцию getservbyname. 286 
Учебная программа Finger Получение информации о сетевой службе Как показано ниже, QFinger вначале проверяет переменную lpHostEnt, указатель на структуру с информацией об удаленном компьютере. Если информация действительно получена, программа продолжает выполнение и пробует получить информацию о сетевой службе Finger, вызывая функцию getservbyname. Указатель на структуру с информацией о службе Finger хранится в переменной IpServEnt. Далее QFinger проверяет содержимое IpServEnt: if (UpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OK|MB_ICONSTOP); else // Получаем информацию о службе Finger { IpServEnt = getservbyname("Finger", NULL); if (IpServEnt == NULL) iFingerPort = IPPORT_FINGER; // Используем // официальный номер порта else iFingerPort = lpServEnt->s_port; Если IpServEnt действителен (то есть не равен NULL), iFingerPort получает значение lpServEnt->s_port, то есть номера порта Finger из базы данных. Если IpServEnt равен NULL, номер порта (iFingerPort) принимается равным константе IPPORT_FINGER. В файле winsock.h определены номера портов для чаще всего встречающихся сетевых служб, в том числе и для Finger, номер порта которого равен 79: // Официальные номера портов различных сетевых служб #define IPPORT_ECHO 7 ttdefine IPPORT_DISCARD 9 #define IPPORT_SYSTAT 11 ttdefine IPPORT_DAYTIME 13 ttdefine IPPORT_NETSTAT 15 ttdefine IPPORT_FTP 21 ttdefine IPPORT_TELNET 23 ttdefine IPPORT_SMTP 25 ttdefine IPPORT_TIMESERVER 37 ttdefine IPPORT_NAMESERVER 42 ttdefine IPPORT_WHOIS 43 ttdefine IPPORT_MTP 57 ttdefine IPPORT_TFTP 69 ttdefine IPPORT_RJE 77 ttdefine IPPORT_FINGER 79 ttdefine IPPORT_TTYLINK 87 ttdefine IPPORT_SUPDUP 95 287 
Глава 10. Протокол Finger и информация о пользователях Другими словами, QFinger пытается получить номер порта Finger из базы данных при помощи getservbyname. Если это ему не удается и IpServEnt равен NULL, программа использует номер порта Finger по умолчанию, как он определен в winsock.h: IpServEnt = getservbyname("Finger", NULL); if (IpServEnt == NULL) iFingerPort = IPPORT_FINGER; // Используем официальный // номер порта else iFingerPort = lpServEnt->s_port; To есть успешная работа функции getservbyname для QFinger не очень важна. С другой стороны, для некоторых сетевых служб winsock.h не содержит никакой информации. Например, в winsock.h не определен протокол передачи новостей NNTP (Network News Transfer Protocol). Иногда вы можете не знать, каким транспортным протоколом пользуется та или иная сетевая служба. В обоих случаях вы обязаны запросить информацию из базы данных, и если ее там не окажется, программу придется прервать. Структура адреса сокета Переменная sockAddr типа SOCKADDR_IN объявлена следующим образом: SOCKADDR_IN sockAddr; // Структура адреса сокета Тип SOCKADDR_IN объявляется в winsock.h как структура адреса сокета: typedef struct sockaddr_in SOCKADDR_IN; // Расширенный тип // Windows struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin__zero [8] ; }; В табл. 10.3 перечислено назначение каждого элемента структуры. Таблица 10.3. Элементы структуры адреса сокета Элемент Назначение sin_family sin_port sin_addr sin zero Тип адреса сокета. В случае TCP/IP всегда равен AF_INET. Номер порта протокола. IP-адрес удаленного компьютера, записанный в структуре in_addr Winsock. В настоящее время не используется и равен нулю. 288 
Учебная Finger Адресная структура sockaddr_in содержит информацию об адресе в формате Windows Sockets. Адрес компьютера необходим всегда, когда вы обращаетесь к удаленному компьютеру. Он должен быть доступен всем программам Windows Sockets API, поскольку требуется при соединении сокета. IP-адрес Winsock содержится в элементе sin_addr адресной структуры сокета. Элемент sin_addr на самом деле находится в структуре in_addr. Если вы помните материал девятой главы, там сказано, что в структуре in_addr содержится объединение (union), служащее для размещения IP-адреса в трех разных форматах: как четыре 8-битных, как два 16-битных и как одно 32-битное значение. Элемент sin_port просто содержит номер порта протокола. Как известно из пятой главы, номер порта по своему назначению похож на 1Р-адрес. Только связан он не с номером компьютера, а с номером протокола. Большинство протоколов имеют официально назначенные номера портов. Как мы уже писали, официальный номер порта протокола Finger равен 79. Перед тем как вызвать функцию connect, QFinger присваивает значение порта протокола Finger полю sin_port структуры sock_addr. Что такое семейства адресов и протоколов? Элемент sin_family адресной структуры определяет тип адреса для сокета. Как указано в табл. 10.3, семейство адресов для TCP/IP всегда AF__INET. Сетевые протоколы могут использовать другое представление адресов. Концепция семейства адресов и протоколов позволяет программам просто манипулировать адресами, не вдаваясь в подробности реализации. Например, вы можете написать процедуру, которая выполняет различные действия, в зависимости от того, с каким семейством адресов имеет дело. Другими словами, как и в случае интерфейса Беркли, разработчики Winsock могут приспособить его для работы не только с сетевыми протоколами TCP/IP. Поскольку в качестве параметров функций могут задаваться различные семейства адресов и протоколов, можно с легкостью переходить от одного набора сетевых протоколов к другому. Иногда, чтобы перейти к другому типу сети, программисту достаточно заменить только значения констант. Примечание: В то время, когда писалась эта книга, реализация Windows Sockets умела работать только с TCP/IP. Тем не менее идея семейств адресов и протоколов позволяет в будущем включить поддержку и других сетей без существенного изменения набора функций Winsock. Функция gethostbyaddr — хороший образец процедуры, предназначенной для работы с различными семействами адресов или протоколов. Как вам известно из девятой главы, у нее следующий прототип: struct hostent FAR * PASCAL FAR gethostbyaddr(const char FAR * addr, int len, int type); Первый параметр gethostbyaddr должен указывать на действительный IP-адрес. Адрес должен быть в двоичном виде (и с сетевым порядком байтов). Обратите внимание на то, что IP-адрес 32-разрядный, а первым параметром gethostbyaddr является указатель на тип char. 10 Зак. № 1949 289 
Разработчики Winsock ориентировались на то, что Winsock будет обслуживать не только семейство TCP/IP. Поэтому вместе с адресом вы должны указать длину этого адреса и его семейство. Посмотрим, что это значит для вас, как разработчика. Предположим, что вы спроектировали приложение Winsock API для работы в Интернет. Прошло несколько лет, и вам понадобилось перенести его в другую сетевую среду, также поддерживаемую Windows Sockets. Наряду с другими изменениями, вам потребуется модифицировать вызов функции gethostbyaddr. В функции gethostbyaddr будет необходимо изменить третий параметр (новое семейство протоколов) и, возможно, второй параметр (длина адреса). Библиотека Winsock для новой сети рассмотрит переданные параметры и правильно заполнит структуру с информацией о сетевом компьютере. Теперь предположим, что gethostbyaddr принимает только 32-разрядные адреса, а в новой сети адреса 64-разрядные. В этом случае gethostbyaddr будет просто невозможно пользоваться, а в Winsock придется включать дополнительные функции для работы с другой размерностью адресов. Предположим, что адреса у новой сети все-таки 32-разрядные, но у них другой порядок байтов. В этом случае в Winsock придется иметь отдельные функции для каждой комбинации «размер адреса — порядок байтов». Представляете себе, что получиться в результате? Так что параметры семейств адресов и протоколов позволяют грамотно обойти трудности, связанные с дальнейшим развитием Winsock. Вы повстречаете константы AF_INET и PF_INET в большинстве программ, с которыми вам доведется встретиться. AF_INET представляет семейство адресов (AF) Интернет (INET), a PF_INET — семейство протоколов (PF) Интернет. В то же время в спецификации Winsock указано, что эти константы равны. Другими словами, в файле-заголовке Winsock есть следующая строчка: #define PF_INET AF_INET Адрес сокета QFinger использует переменную sockAddr типа SOCKADDRJN. SOCKADDRJN описывает структуру-адрес сокета Интернет. Следующие несколько операторов из QFINGER. СРР заносят необходимую для соединения сокета с удаленным компьютером информацию в переменную sockAddr: // Формируем адрес сокета sockAddr. sin_family = AF__INET; // Семейство адресов Интернет sockAddr.sin_port = iFingerPort; sockAddr. sin_addr = * ( (LPIN_ADDR) *lpHostEnt->h_addr__list) ; Семейство адресов AF_INET заносится в поле sin_family структуры sockAddr. Далее поле sin_port получает значение номера порта протокола Finger. Как вы помните, оно берется либо из сетевой базы данных (посредством getservbyname), 
либо из файла winsock.h, где определено как IPPORT_FINGER. Наконец, элементу sin_addr присваивается IP-адрес, извлеченный из структуры hostentry. Соединяем сокет Как только сокету присвоен адрес, он готов к соединению. Соединение происходит при участии функции connect. Вот ее прототип: int PASCAL FAR connect (SOCKET s, const struct sockaddr FAR * name, int namelen); Вы уже встречались с параметрами функции connect. Первый параметр — дескриптор сокета, полученный от функции socket. Второй параметр — указатель на действительную адресную структуру сокета. Мы уже рассматривали ее структуру подробно. Значения, присвоенные элементам адресной структуры sockAddr, также уже рассматривались. Переменная sockAddr передается функции connect в качестве параметра. Третий параметр функции connect — длина адресной структуры сокета. Чтобы вычислить длину структуры, QFinger вызывает стандартный оператор языка C/C++ sizeof, а результат передает функции. Ниже приведены операторы, при помощи которых QFinger соединяет сокет: // Соединяем сокет nConnect = connect(nSocket, (PSOCKADDR) &sockAddr, sizeof(sockAddr)); if (nConnect) MessageBox(NULL, "Error connecting socketII", PROG__NAME, MB_OK I MB_ICONSTOP) ; Если все прошло успешно, функция connect возвращает ноль. Результат connect отправляется в переменную nConnect, затем ее значение проверяется QFinger. Если значение nConnect свидетельствует о случившейся ошибке, программа завершается, выдав предупреждающее сообщение на экран. Вы знаете, что при создании сокета функцией socket, необходимо указывать тип протокола. Если программе нужно надежное, двухстороннее соединение, указывается SOCK__STREAM, если нужен датаграммный сервис — указывается SOCK DGRAM. Функция socket возвращает дескриптор сокета. Функция connect проверяет переданный ей дескриптор, выясняя, с каким типом протоколов она имеет дело. Если выбран потоковый протокол (SOCK_STREAM), connect соединяется с удаленным компьютером. Для датаграммного сокета (SOCKJDGRAM) это излишне, поэтому адрес пункта назначения не устанавливается. Если функция connect отработала успешно, сокет готов к передаче и приему данных от удаленного компьютера. 291 
Глава 10. Протокол Finger и информация о пользователях Что нужно, чтобы соединить сокет' Как уже неоднократно говорилось, чтобы получить доступ к различным сетевым службам, программе необходимо выполнить последовательность определенных действий. Причем эта последовательность одинакова для большинства программ. После того как вы усвоите, что нужно предпринять, чтобы получить доступ к сетевым службам, вам станет проще создавать свои собственные варианты протоколов и сетевого сервиса. Возможно, лучший способ узнать, что же все-таки требуется — пройти весь процесс задом наперед, начав с соединенного сокета. Другими словами, чтобы соединить сокет, ему нужно предоставить определенную информацию. Следовательно, нужно рассмотреть эту информацию подробнее и выяснить, откуда она берется. Ниже приведены шаги, которые нужно предпринять, чтобы соединить сокет с удаленным приложением. Шаги приведены в обратном порядке. Вы начинаете с соединенного сокета и следуете назад: 1. Чтобы соединить сокет, вызывается функция connect. Ее аргументы — дескриптор и адрес сокета. 2. Чтобы получить дескриптор сокета, вызывается функция socket. Для нее необходимо указать тип сетевого соединения — потоковый или датаграммный. Также можно указать протокол или ограничиться протоколом по умолчанию. 3. Чтобы создать адрес сокета, необходимо заполнить структуру данных. В ней, в частности, указывается порт протокола и реально существующий IP-адрес удаленного компьютера в формате структуры адреса Интернет. 4. Чтобы получить номер порта, вызывается функция getservbyname. В качестве аргумента указывается наименование сетевой службы. Дополнительно указывается протокол, либо принимается протокол по умолчанию. 5. Чтобы получить IP-адрес, вызываются функции для работы с DNS, например gethostbyname или gethostbyaddr. Посылаем запрос Finger После того как соединение установлено, мы можем посылать и принимать данные. Функции send для этого задается аргумент — дескриптор сокета, через который мы хотим передать данные. Так выглядит прототип функции send: int PASCAL FAR send(SOCKET s, const char FAR * buf, int len, int flags); Сокет, через который передаются данные, как уже говорилось, указывается при помощи дескриптора — первого параметра send. Остальные параметры зависят от вашего приложения. Второй является указателем на буфер, в котором хранятся данные для передачи. Третий параметр — длина этого буфера. Четвертый параметр может задаваться специальными флагами, задача которых — изменить поведение сокета при передаче данных. В спецификации Winsock версии 1.1 определено два таких флага: MSGJDONTROUTE и MSG_OOB. Флаг MSG_DONTROUTE указывает, что сообщение нельзя маршрутизировать. Другими словами, блок передаваемых с этим флагом данных не должен обрабатываться низ лежащим сетевым уровнем, ответственным за маршрутизацию обычных сообщений. В спецификации Winsock указано, что этот флаг может игнорироваться реализациями Winsock. Поэтому вам его лучше не употреблять в целях иных, нежели простое тестирование. 292 
Флаг MSG_OOB требует, чтобы данные передавались (и, соответственно, принимались) как данные «вне диапазона». Как вам известно из пятой главы, данные вне диапазона или данные для неотложной обработки представляют собой мощный инструмент сетевого управления. Но, поскольку пока так и не удалось прояснить ситуацию относительно правильности их обработки, лучше избегать их употребления в ваших программах. Если флаг MSG_OOB установлен с датаграммным сокетом, функция вернет сообщение об ошибке. Функция send в программе QFinger вызывается с третьим параметром, равным SENDJFLAGS. Он, в свою очередь, равен нулю. Как уже отмечалось, сервер протокола Finger ожидает получить запрос в формате NVT ASCII с порта 79. Чтобы запросить информацию обо всех работающих в данный момент пользователях, достаточно передать запрос в виде пустой строки. Чтобы получить информацию о конкретном пользователе, нужно передать его имя, либо идентификатор. Каждый запрос должен заканчиваться маркером конца строки. В случае NVT ASCII, это будет комбинация CRLF, возврат каретки плюс перевод строки. Текст запроса QFinger находится в константе FINGERQUERY. Константа может содержать любое имя или идентификатор, лишь бы он был известен удаленному компьютеру по имени HOST_NAME. Если функция connect отработала успешно, QFinger при помощи функции wsprintf формирует запрос в буфере szFingerQuery, добавляя CRLF (\п) к FINGER_QUERY. Далее, функции send передается дескриптор nSocket, указатель на буфер szFingerQuery, длина буфера (вычисленная при помощи lstrlen) и флаги, определенные в SEND FLAGS: if (nConnect) MessageBox(NULL, "Error connecting socket!!", PROG_NAME, MB_OK|MB_ICONSTOP) ; else // Формируем и высылаем запрос Finger { wsprintf (szFingerQuery, "%s\n", FINGER__QUERY) ; nCharSent = send(nSocket, szFingerQuery, lstrlen(szFingerQuery), SEND_FLAGS); if (nCharSent == SOCKET_ERROR) MessageBox(NULL, "Error occurred during sendO!", PROG_NAME, MB_OK | MB_ICONSTOP) ; Если все прошло успешно, функция send возвращает количество переданных байтов. Если нет, значение функции будет равно SOCKET_ERROR. Результат send присваивается переменной nCharSent, и ее значение проверяется. Если оно равно SOCKET ERROR, выводится соответствующее сообщение и программа заканчивает работу. 293 
Примечание: Благополучное окончание функции send говорит только о том, что данные были успешно переданы. Она никак не указывает, дошли ли они до места назначения. Прием ответного сообщения Finger Как только запрос передан, QFinger переходит в цикл do-while, где периодически вызывает функцию recv, чтобы принять данные из сокета. Ниже показан прототип функции recv: int PASCAL FAR recv(int s, char FAR * buf, int len, int flags); Функция recv возвращает количество считанных из сокета байтов. Первый параметр recv — дескриптор сокета. Ему следовало бы иметь тип SOCKET, но поскольку SOCKET определен в winsock.h как беззнаковое целое (unsigned int), то определение первого аргумента recv как int вполне корректно. Как обычно, переменная nSocket хранит дескриптор сокета. Второй параметр — указатель на буфер для поступающих данных. Третий параметр — длина буфера. Если сокет потоковый и количество данных превышает размер буфера, все функционирует нормально — recv заполнит буфер на всю длину и возвратится. Если сокет датаграммный — излишние данные безвозвратно пропадут, a recv вернет значение ошибки. В версии 1.1 спецификации Winsock определены два флага, которые можно указывать в качестве четвертого параметра. С одним из них, MSG_OOB, вы уже встречались. Второй, MSG_PEEK, указывает функции recv на то, что данные из входной очереди можно копировать в буфер как обычно, но при этом их нельзя стирать из входной очереди. В нормальной ситуации recv всегда стирает данные из входной очереди после того, как они переписаны в буфер. Функции recv передаются дополнительные флаги, RECV_FLAGS, равные нулю. Ниже приведен фрагмент программы QFinger, обслуживающий прием данных через сокет: if (nCharSent == SOCKET_ERROR) MessageBox(NULL, "Error occurred during send()l", PROG_NAME, MB_OK|MB_ICONSTOP) ; else // Получаем информацию Finger от удаленного компьютера { do { nCharRecv = recv (nSocket, (LPSTR)&szFingerInfo[nConnect], sizeof(szFingerlnfo) - nConnect, RECV_FLAGS); nConnect += nCharRecv; } while (nCharRecv > 0) ; 294 
Учебная программа Finger Рассмотрим одно очень важное соображение относительно длины буфера принимаемых данных. Поскольку служба Finger использует потоковый протокол TCP, чтобы собрать все данные, нужно использовать цикл типа do-while. Даже если вы и назначите большой буфер, удаленный компьютер может просто не успеть передать все данные за один прием. Предположим, что вы запрашиваете информацию обо всех работающих в данный момент пользователях (посылая пустую строку-запрос). Допустим, что на компьютере в этот момент работает значительное число пользователей. Объем ответного сообщения сервера, наконец, может превышать значение MTU (блока данных максимальной длины) для вашей сети. Чтобы не допустить фрагментации, TCP передает данные, разделив их на сегменты. То есть сокет может получить несколько сегментов TCP до того, как все данные будут переданы. Сервер Finger автоматически закрывает соединение после того, как все данные посланы. Функция recv, в свою очередь, опознает закрытие соединения и в ответ возвращает ноль. Другими словами, цикл do-while требуется, чтобы собрать все данные от сервера. Принятые данные хранятся в буфере szFingerlnfo. Количество принятых символов подсчитывается при каждом вызове recv и помещается в переменную nCharRecv целого типа: do { nCharRecv = recv(nSocket, (LPSTR)&szFingerInfo[nConnect], sizeof(szFingerlnfo) - nConnect, RECV_FLAGS); nConnect += nCharRecv; } while (nCharRecv > 0) ; Переменная nConnect подсчитывает общее количество принятых в течение работы цикла символов следующим образом: nConnect += nCharRecv; Цикл продолжается, пока значение nCharRecv не станет меньше или равно нулю. Значение меньше нуля свидетельствует об ошибке (SOCKET_ERROR равен -1), а ноль означает, что соединение было закрыто. После выхода из цикла проверяется последнее значение nCharRecv. И если случилась ошибка, выводится соответствующее сообщение: if (nCharRecv == SOCKET-ERROR) MessageBox(NULL, "Error occurred during recv()!", PROG_NAME, MB_OK|MB_ICONSTOP) ; else // Выводим информацию Finger { wsprintf(szFingerQuery,"%s@%s", FINGER_QUERY, HOST_NAME); MessageBox(NULL, szFingerlnfo, szFingerQuery, 295 
Ш1й и о пользователях : . ; .. :* } MB_OK|MB_ICONINFORMATION); Если ошибки не было, в буфере szFingerQuery формируются результаты ответа lonetech@cerfnet.com Login name: lonetech In real life: Ken Cope Directory: /users/cerfdial/lonetech Shell: /bin/csh ©Last login Mon Oct 31 13:16 on ttyqd from netcoml d.netcom. No unread mail No Plan. Puc. 10.2. Панель сообщения QFinger сервера, а именно туда помещается идентификатор пользователя и имя сетевого компьютера, выводимые на панель сообщения Qfinger в формате username@hostname. На рис. 10.2 приведен образец этого сообщения. Что такое протокол Finger? Сервер Finger не нуждается в большом количестве информации. Если запрос состоит из пустой строки, он предполагает, что необходима информация обо всех работающих в данный момент в системе пользователях. Если строка-запрос не пуста, предполагается, что ее содержимое идентифицирует известного серверу пользователя, о котором запрашивается информация. В этом случае сервер выдаст всю общедоступную информацию о пользователях, имена или идентификаторы которых совпадают с содержимым запроса. Самое трудное в программе QFinger — создать и соединить сокет. Сама по себе информация Finger (и ее формат) устроена просто. Однако помимо Finger, на свете существует большое количество не столь простых протоколов. Как мы уже отмечали, иногда самый легкий способ что-либо изучить — это начать с конца и следовать к началу. Теперь, когда вы знаете, как работает программа Finger, вам будет легко понять, как устроен сам протокол. Подводя итоги В предыдущей главе мы изучали, как работать с системой имен доменов, DNS. В большинстве программ Winsock, до того как воспользоваться тем или иным 296 
протоколом, нужно вызвать функцию DNS, чтобы узнать адрес компьютера. Учебная программа QFinger демонстрирует, как это сделать эффективно. В этой главе вы узнали, как используется протокол Finger для получения информации об удаленных пользователях. Как вы позднее увидите, многие прикладные протоколы очень похожи на Finger. Хотя формат данных этих протоколов и различается, большинство шагов, предпринимаемых для работы с ними, одинаковы. Одинаковы и функции сетевого ввода-вывода, с которыми вы познакомились в процессе обсуждения QFinger. До того как продолжить чтение, проверьте, хорошо ли вы усвоили следующие ключевые понятия: S Виртуальный сетевой терминал (NVT) представляет данные в качестве семибитных символов, а для конца строки используется комбинация CRLF. S NVT используется большим количеством прикладных сетевых протоколов, например протоколом Finger. S База данных по сетевым службам на персональных компьютерах представлена файлом SERVICES формата ASCII. В нем хранится информация о часто встречающихся сетевых службах типа Finger, Mail, Ftp, Telnet и т. д. S База данных по сетевым службам содержит официальные номера портов и протоколы для каждой сетевой службы. S Протокол Finger предназначен для получения сведений обо всех находящихся в системе пользователях или о конкретном пользователе удаленного компьютера. S Чтобы послать запрос серверу Finger, необходимо соединить сокет с портом протокола номер 79 (официальный номер порта службы Finger) и послать запрос в формате NVT ASCII, заканчивающийся комбинацией CRLF. S Чтобы получить информацию обо всех пользователях указанного компьютера, строка-запрос должна быть пустой (и заканчиваться CRLF). S Чтобы получить информацию о конкретном пользователе, строка-запрос должна содержать его идентификатор или имя (и заканчиваться CRLF). 
Асинхронные сокеты Windows В девятой главе вы познакомились с системой имен доменов Интернет и узнали, как по компьютерному имени узнавать его адрес и наоборот. В главе 10 вы узнали, как передавать информацию удаленному компьютеру при помощи сокетов. В обеих главах при этом использовались функции интерфейса Winsock, работающие в среде Windows. Но ни в одной из учебных программ мы не употребили специальных расширенных функций Windows. А ведь они являются мощным инструментом программирования в этой среде и лучше всего удовлетворяют модели программирования в Windows. В этой главе вы узнаете, как в модели программирования Windows используется передача сообщений между запущенными программами и окнами. Вы выясните, каким образом интерфейс прикладного программирования Winsock, имея в своем составе некоторые расширенные функции Windows, позволяет обрабатывать сообщения о сетевых событиях в системе. 298 
Шаблон nporpaMMbi Sockrrian Сообщения системы Windows асинхронны в том смысле, что они не происходят в какой-то наперед заданный момент или через определенные промежутки времени. Изнутри Windows-программы можно только дать команду на выполнение задачи и ждать, пока Windows проинформирует о ее завершении. Пока задача выполняется, программа может продолжать работу совместно с системой Windows. В этой главе вы узнаете, как асинхронные функции Windows используются при разработке приложений Интернет. К моменту окончания главы вы овладеете следующими важными понятиями: ♦ Аргументы (параметры), указываемые при вызове типичной асинхронной функции. ♦ Как создать асинхронную функцию для поиска в DNS. ♦ Каким образом сообщения асинхронных функций идентифицируются обработчиком задач. ♦ Как из сообщения Windows извлекается сообщение об ошибке. Как известно, все функции интерфейса Беркли синхронны. Другими словами, программа не может продолжать свою работу до тех пор, пока функция интерфейса Беркли не закончит свою. Реализация Winsock включает большинство функций из интерфейса Беркли. Однако ввиду асинхронной природы сообщений и функций Windows в спецификации Winsock присутствуют и асинхронные функции Windows. Шаблон программы Sockman Шаблон программы Sockman, с которой вы будете работать на протяжении всей второй части книги, объясняется в приложении Б. Если вы еще не ознакомились с приложением Б, следует сделать это прямо сейчас. Набор команд и операторов, из которых составлен шаблон, не выполняет никаких сетевых действий. Вместо этого он создает Windows-интерфейс для вас и ваших сетевых приложений. Создание любой программы Windows требует приложения усилий, не связанных напрямую с решаемой задачей. Такая простая, казалось бы, операция, как отображение и ввод данных из диалогового окна, требует от программиста достаточно серьезных усилий. Это лишь одна из причин, почему асинхронные Windows-функции не обсуждались в двух предшествующих главах. В этой главе мы начнем модифицировать шаблон Sockman, описанный в приложении Б. А именно, мы встроим в него функции по преобразованию имен и адресов в формате «десятичное с точкой» сетевых компьютеров в 32-разрядные двоичные адреса. Внося изменения в программу, вы изучите основные шаги, необходимые для использования асинхронных функций. Мы не будем обсуждать те функции, которые описаны в приложении. Вместо этого мы рассмотрим только новые или модифицированные варианты функций шаблона Sockman. К ним относятся функции для преобразования сетевых имен и адресов в 32-разрядный 1Р-адрес. 299 
Глава 11. Асинхронные сокеты Windows Операционная система Windows ориентирована на объект под названием «окно». В большинстве случаев «окно» — это просто кадр или поле, в котором программа может отображать данные. Windows посылает сообщения объекту «окно», если в системе происходят определенные события или если появляется сообщение от другого работающего приложения. Каждое окно, открываемое программой, имеет собственный тип класса window. Класс window позволяет задавать параметры, такие как размер, цвет или расположение меню пользователя. Эти характеристики являются общими для всех окон, принадлежащих данному классу. Для каждого типа класса window существует процедура (функция), обрабатывающая сообщения для окон этого типа. Класс window и процедура-обработчик сообщений соотносятся между собой просто. Каждый раз, когда Windows генерирует сообщение для определенного объекта, оно посылается процедуре класса window, связанной с этим объектом. Программа может открыть несколько окон одного и того же класса. Каждое окно, принадлежащее данному классу, называется его образцом (instance). Другими словами, в системе одновременно могут существовать несколько образцов одного и того же класса. Если в системе несколько образцов, в процедуре-обработчике сообщений должен быть механизм распознавания, какому из образцов адресовано то или иное сообщение Windows. Для этого каждому образцу данного класса присваивается уникальный номер-дескриптор. Когда посылается сообщение, в нем, в качестве одной из составляющих, всегда передается дескриптор того образца, кому оно адресовано. Для того чтобы успешно программировать в оболочке Windows, необходимо четко сознавать взаимоотношения между классом window, процедурами и дескрипторами окон. В Sockman добавляется поиск в DNS На приложенной к книге дискете находятся исходные тексты программы Sockman, а также ее готовые исполняемые файлы. Программа под названием Sockman2 состоит из шаблона Sockman, в который добавлены функции для работы с DNS. Для ввода параметров используется диалоговое окно под названием IDD_TEXT. Оно изображено на рис. 11.1. Окно IDD_TEXT является просто устроенным окном общего назначения и позволяет вводить информацию в пределах одной строки. 300 Рис. 11.1. Диалоговое окно IDDJTEXT из меню для работы с DNS программы-шаблона Sockman 
.. Программа Sockman2 дает возможность производить блокирующий или не блокирующий поиск компьютерного адреса в DNS по имени или по адресу в формате «десятичное с точкой». Для этого в программе предусмотрены две функции: синхронная LookupHostBlocking для блокирующего сокета и асинхронная LookupHostAsync. Как мы писали в приложении Б, большинство задач по обработке сообщений Windows берут на себя три функции (WndProc, WinMain и DoMenuCommand). Главное окно Sockman передает сообщения WinMain, а она, в свою очередь — функции WndProc (главная функция-обработчик сообщений). Далее, все сообщения WM_COMMAND передаются в DoMenuCommand. Функция DoMenuCommand обслуживает все сообщения Windows, не касающиеся сетевой работы, например опции меню File или Help. По умолчанию DoMenuCommand передает все остальные сообщения функции DoWinsockProgram. На рис. 11.2 изображен маршрут движения сообщений в приложении Sockman. Рис. 11.2. Основной маршрут движения сообщений в приложении Sockman Как показано в приложении Б, в шаблоне Sockman заранее предусмотрено место для дальнейшего расширения списка решаемых задач. Однако вместо еще не реализованных функций Sockman выдает соответствующее сообщение (окно) пользователю. В дальнейшем вместо операторов, выводящих окно с предупреждением о том, что эта функция еще не реализована, там появятся операторы, выполняющие эту функцию. При написании новой функции Sockman необходимо изменить исходный текст функции DoWinsockProgram. Как изменять функцию DoWinsockProgram? В тот момент, когда пользователь выбирает пункт Async Lookup или Blocking Lookup из меню Utilities, подменю Lookup, окну Sockman посылается сообщение 301 
Глава 11. Асинхронные сокеты Windows WM_COMMAND. Функция WndProc пересылает его DoMenuCommand, которая, в свою очередь, вызывает функцию DoWinsockProgram. Примечание: Функция DoWinsockProgram всегда присутствует в исходном тексте SOCKMAN.CPP. Исходный текст второй версии Sockman соответственно находится в файле SOCKMAN2.CPP. В третьей версии Sockman функция DoWinsockProgram находится в файле SOCKMAN3.CPP и т. д. В следующем фрагменте программы показано, как функция DoWinsockProgram обрабатывает оба пункта подменю Lookup (Async Lookup и Blocking Lookup): long DoWinsockProgram(HWND hwnd, UINT wParam, LONG IParam) { switch (wParam) { case IDM_LOOKUP_ASYNC: case IDM__LOOKUP__BLOCKING: if (LookupHostDialog()) { if (wParam == IDM_LOOKUP_ASYNC) hAsyncLookupTask = LookupHostAsync(hwnd, s zLookupText, s zLookupBuffer, (LPDWORD)&dwLookupAddr); else LookupHostBlocking(hwnd, szLookupText, szLookupBuffer, TASK_BLOCK_LOOKUP); } return(TRUE); Если пользователь выбрал пункт меню Async Lookup, переменная wParam получит значение IDM_LOOKUP_ASYNC. Если выбран пункт Blocking Lookup, параметр wParam получит значение IDM_LOOKUP_BLOCKING. В любом случае функция DoWinsockProgram вызовет функцию LookupHostDialog. Она, в свою очередь, выведет на экран диалоговое окно IDD_TEXT, изображенное на рис. 11.1. Получив данные от пользователя, программа затем выполнит требуемую операцию по поиску в DNS. Подробнее о диалоговом окне Диалоговое окно IDD_TEXT конструируется следующими операторами (их можно найти в файле SOCKMAN2.RC): IDD_TEXT DIALOG DISCARDABLE 0, 0, 161, 65 STYLE DS_MODALFRAME I WS_POPUP | WS_VISIBLE I WS_CAPTION I WS_SYSMENU CAPTION "Enter Value" FONT 8, "MS Sans Serif" 302 
Подробнее о диалоговой функции BEGIN EDITTEXT IDCLTEXTBOX,5,15,150,15,ES__AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,5,40,50,15 PUSHBUTTON "Cancel",IDCANCEL,60,40,50,15 END В диалоговом окне использован стиль WS_SYSMENU (в котором добавляется меню Control), кнопки ОК и Cancel. Пользователь может выйти из окна, выбрав пункт Close меню Control или щелкнув мышью на клавишах ОК или Cancel. Выход из окна будет успешным (IDD_TEXT вернет константу IDGOK), если пользователь нажмет на кнопку ОК (по умолчанию). Если пользователь нажмет на кнопку Cancel, результат будет равен IDCANCEL. Подробнее о диалоговой функции Как уже говорилось, когда функция DoWinsockProgram вызывает LookupHostDialog, она, в свою очередь, генерирует и отображает диалоговое окно IDD TEXT. Исходный текст функции LookupHostDialog находится в файле LOOKUP2.CPP. Она реализована при помощи следующих операторов: LPSTR LookupHostDialog(VOID) { // Диалоговая процедура использует глобальный статический // буфер szLookupText для хранения введенной // пользователем в текстовом окне информации. Вызывающие // процедуру функции должны немедленно скопировать // эту информацию в собственные внутренние буферы, так // как глобальный буфер стирается программой поиска в DNS. DLGPROC lpfnDialogProc; BOOL bOkay; // Создать диалоговое окно lpf nDialogProc = MakeProcInstance ((DLGPROC) LookupHostDialogProc, hlnstanceSockman) ; bOkay = DialogBox(hlnstanceSockman,"IDD_TEXT", hwndSockman, lpfnDialogProc); FreeProcInstance(lpfnDialogProc); return(bOkay ? szLookupText : (LPSTR) NULL); } Для генерации и отображения диалогового окна в функции LookupHostDialog используется стандартная техника. Вначале вызывается функция Windows MakeProcInstance, аргументом которой указывается функция обратного вызова (call-back) LookupHostDialogProc. Результатом MakeProcInstance является пролог (стартовый код) указанной в качестве аргумента функции, в нашем случае — 303 
| Глава 11. Аоинхроиньш сокеты Windows функции LookupHostDialogProc. Когда в дальнейшем Windows посылает сообщение диалоговому окну, на самом деле сообщение посылается функции, указанной в прологе. Что такое функции обратного вызова? Каждый класс window имеет соответствующую функцию-обработчик сообщений для объектов данного класса. Такая функция, описанная в структуре класса window, называется функцией обратного вызова (call-back). Она называется так потому, что Windows вызывает ее при поступлении сообщения для данного класса объектов. Короче говоря, Windows передает сообщения функции обратного вызова, а та, в свою очередь, обрабатывает эти сообщения. Для каждого класса window в вашей программе должна существовать функция обратного вызова. Как правило, это просто, если и описание класса и функция обратного вызова определены в одной и той же программе. Трудности возникают только тогда, когда описание класса находится за пределами программы, как, например, описание класса диалогового окна, которое используется в функции Windows DialogBox. Предположим, вы хотите, чтобы сообщения класса диалогового окна передавались в вашу программу. Для этого Windows должна вызывать вашу собственную функцию. Поскольку ваша функция-обработчик сообщений находится в адресном пространстве вашей программы, а описание класса диалогового окна — вне его, Windows должна выполнить определенные действия до того, как передать сообщение. Инструкции-операторы, выполняющие эти действия, находятся в области под названием «пролог» или «стартовый код» функции. Перед тем как функция обратного вызова получит сообщение, Windows выполнит операторы из пролога функции. С другой стороны, если функция обратного вызова вызывается из самой программы, эти операторы не выполняются. После того как функция обратного вызова закончила работу, Windows выполняет последовательность инструкций из эпилога функции. Опять-таки, если функция вызвана из программы, эти инструкции не выполняются. Чтобы правильно определить пролог и эпилог при компиляции, в исходный текст программы необходимо поместить специальный макрос CALLBACK. Его значение определено в файлезаголовке windows.h. Дополнительно компилятору задаются специальные флаги. Как правило, если вы определили и зарегистрировали собственный класс window, вам не нужно заботиться о прологе и эпилоге функции обратного вызова. Пролог и эпилог нужны, только когда программа и описание класса находятся в разных областях памяти компьютера. Адрес пролога функции обратного вызова (полученный в результате вызова MakeProcInstance) заносится LookupHostDialog в локальную переменную lpfnDialogProc. Далее вызывается функция Windows DialogBox. В параметрах вызова указывается дескриптор образца задачи Sockman. (Дескриптор образца задачи описывает конкретный экземпляр работающего приложения. Вы знаете, что можно запустить несколько копий одной и той же программы одновременно.) Функция LookupDialogBox также задает название диалогового окна IDD_TEXT, дескриптор окна Sockman (для того, чтобы Windows знала, кому адресованы сообщения Sockman) и переменную lpfnDialogProc (в которой хранится адрес пролога функции LookupHostDialogProc). Другими словами, вызов DialogBox создает новое диалоговое окно IDD_TEXT и указывает функцию обратного вызова LookupHostDialogProc, которой будут доставляться все сообщения от данного окна. 304 
Что такое диалоговая процедура? Подробнее о прологе и эпилоге функции Вы знаете, что, если функция обратного вызова и описание класса window находятся в разных областях памяти, компилятор должен генерировать специальный код пролога и эпилога. Код пролога выполняется до операторов функции, а код эпилога — после. Дело в том, что, если описание класса window и программа находятся в разных областях памяти, это значит, что они пользуются различными сегментами кода и данных. Макрос CALLBACK, включенный в программу, указывает компилятору, что в этом месте должен быть вызов функции типа FAR (дальний). То есть компилятор делает так, что разница между сегментами кода исчезает. Чтобы устранить несоответствие между сегментами данных, перед каждым вызовом функции процессор должен установить значение регистра сегмента данных так, чтобы он указывал на область данных функции обратного вызова. Эта задача и решается операторами пролога и эпилога. До начала выполнения функции обратного вызова инструкции пролога устанавливают регистр сегмента данных (DS) на область данных функции. При этом регистру DS присваивается содержимое регистра АХ. Операция (специальный код), приводящая к тому, что в регистре АХ появляется значение сегмента данных функции обратного вызова, называется «thunk». До того как функция отработает, операторы эпилога восстановят значение регистра DS из стека. Опять-таки, поскольку мы использовали макрос CALLBACK (и нужные ключи при компиляции), компилятор знал, что функция снабжается прологом и эпилогом, поэтому позаботился о том, чтобы значение DS попало в стек. Когда пользователь закрывает или выходит из диалогового окна IDD_TEXT, функция DialogBox возвращает управление в вызывавшую функцию LookupHostDialog. Она в свою очередь освобождает ресурсы, занятые обработчиком LookupHostDialogProc. Если пользователь нажал кнопку OK, LookupHostDialog вернет значение-указатель на глобальную переменную szLookupText, содержащую информацию, введенную пользователем. В противном случае, LookupHostDialog возвращает NULL. Что такое диалоговая процедура? Функция обратного вызова LookupHostDialogProc обрабатывает все сообщения Windows, поступающие от диалогового окна IDD TEXT. Вот операторы, из которых она состоит: BOOL _export CALLBACK LookupHostDialogProc(HWND hwndDlg, UINT iMessage, WPARAM wParam, LPARAM IParam) { switch (iMessage) { case WM_INITDIALOG: // Инициализация // диалогового окна SetDlgltemText(hwndDlg, IDC_TEXTBOX, (LPSTR)szLookupText ); SetWindowText(hwndDlg, "Enter HOST Name or IP Address"); 305 
Глава 111 Асинхронные сокеты CenterWindow(hwndDlg); return(TRUE); case WM_CLOSE: // Обработать так же, как и Cancel PostMessage(hwndDlg, WM_COMMAND, IDCANCEL, OL) ; return(TRUE); case WM_COMMAND: switch (wParam) { case IDOK: // Обработка нажатия кнопки OK GetDlgltemText(hwndDlg, IDC_TEXTBOX, (LPSTR)szLookupText, MAX_HOST_NAME); EndDialog(hwndDlg, TRUE); return(TRUE); case IDCANCEL: // Обработка нажатия кнопки Cancel EndDialog(hwndDlg, FALSE); return(TRUE); default: return(TRUE); } } return(FALSE); } Как видим, функция LookupHostDialogProc обрабатывает три сообщения Windows: WM_INITDIALOG, WM_CLOSE и WM_COMMAND. Сообщение WM_INITDIALOG посылается перед открытием диалогового окна. Оно используется функцией LookupHostDialogProc для инициализации диалогового окна. О стиле написания программ Взглянув на исходный текст функции LookupHostDialog, опытный программист сразу обратит внимание на избыточное количество операторов перехода goto и операторов возврата return. И это не будет большой неожиданностью, поскольку слишком большое количество таких операторов говорит о том, что программа была спроектирована неудачно. Мы, создатели этой программы, полностью поддерживаем и одобряем следующую концепцию: «Избегайте использования операторов goto всеми возможными способами. Функция должна иметь только одну точку выхода — оператор return. Следуйте этим правилам во всех случаях — за исключением тех, когда нарушение правил приведет к большим удобствам при сопровождении и лучшему пониманию смысла программы.» Главная цель программ-примеров этой книги — дать вам понять, каким образом программа следует тому или иному сетевому протоколу Интернет. Чтобы не отвлекаться и сфокусировать ваше внимание именно на проблемах разработки приложений Интернет, мы пренебрегали сложившейся в программировании практикой. Это позволило нам яснее выражать некоторую важную информацию. Ситуация с большим количеством операторов возврата —один из таких случаев. 306 
Что такое диалоговая процедура? ••;'::9.ЩШШ91... . ••• ••• ГЧЯ1 Некоторые функции анализируются в книге по частям. То есть изучение фрагментов занимает значительную часть вашего времени, и множество точек возврата помогает нам в этом процессе. Разумеется, точки возврата можно было бы устранить при помощи вложенных конструкций из операторов if и получить хорошо структурированный код. Но наша главная цель — объяснение концепций программирования приложений Интернет — так и не была бы достигнута. Инициализация диалогового окна Когда функция LookupHostDialogProc получает сообщение WM__INITDIALOG, она инициализирует содержимое текстового окна IDC_TEXTBOX данными, предварительно введенными пользователем программы Sockman. Эти текстовые данные находятся в глобальной переменной szLookupText. Содержимое szLookupText выводится в диалоговое окно IDD_TEXT. Чтобы присвоить IDG_TEXTBOX значение по умолчанию, функция LookupHostDialogProc вызывает функцию Windows SetDlgltemText, которая и присваивает значение переменной szLookupText содержимому текстового окна IDC__TEXTBOX. Кроме того, LookupHostDialogProc устанавливает значение заголовка окна: «Enter HOST Name or IP Address». Наконец вызывается функция CenterWindow (ее текст находится в файле COMMON2.CPP). Она центрирует диалоговое окно по отношению к верхней части главного окна Sockman. После того как инициализация успешно закончена, функция CenterWindow возвращает Windows значение true. Команды диалогового окна Как мы уже говорили, пользователь может закрыть диалоговое окно при помощи пункта меню Control под названием Exit, либо щелкнув мышью на кнопках ОК или Cancel. При этом Windows генерирует сообщение WM_CLOSE, если окно закрыто через меню, либо WM_COMMAND, если была нажата одна из кнопок. Если нажата кнопка Cancel, сообщение WM_COMMAND содержит параметр IDCANCEL. Когда функция LookupHostDialogProc получит такое сообщение, она вызовет функцию EndDialog с параметром false. Вызов функции EndDialog приводит к уничтожению окна. Уничтожение, однако, произойдет не раньше, чем функция DialogBox закончит выполнение. По окончании работы EndDialog LookupHostDialogProc вернет Windows значение true, как показано ниже: case WM_COMMAND: switch (wParam) { case IDCANCEL: EndDialog(hwndDlg, FALSE); return(TRUE); Весь процесс диалога состоит из следующих шагов: 1. Функция LookupHostDialog вызывает функцию DialogBox, которая образует новое диалоговое окно IDD_TEXT. При этом в процессе вызова 307 
Глава 11. Асинхронное сокеты Windows DialogBox функция LookupHostDialogProc становится функцией обратного вызова для диалогового окна. 2. Все сообщения Windows от диалогового окна IDD_TEXT посылаются функции LookupHostDialogProc. 3. Если пользователь закрывает диалоговое окно IDD_TEXT, посылается сообщение и функция LookupHostDialogProc вызывает функцию EndDialog с параметром, который возвратится в функцию LookupHostDialog от функции DialogBox. 4. Если функция LookupHostDialogProc получит сообщение WM_COMMAND с параметром, равным ШОК, она передаст функции EndDialog параметр true. Если функция LookupHostDialogProc получит сообщение WM_COMMAND с параметром IDCANCEL, она передаст функции EndDialog параметр false. Другими словами, EndDialog сообщает, какая из кнопок была нажата пользователем. 5. Функция DialogBox возвращает значение (true или false), переданное функцией LookupHostDialogProc при вызове функции EndDialog. 6. Windows уничтожает диалоговое окно IDD_TEXT. Если окно закрывается при помощи меню Control, функция LookupHostDialogProc получит сообщение WM_CLOSE. Далее это сообщение обрабатывается так, как если бы пользователь нажал на кнопку Cancel. Для этого используется функция Windows PostMessage, посылающая сообщение WM_COMMAND с параметром IDCANCEL самой себе: case WM_CLOSE: PostMessage (hwndDlg, WM__COMMAND, IDCANCEL, OL) ; return(TRUE); В данном случае Windows пошлет сообщение диалогового окна WM_COMMAND с параметром IDCANCEL. Сообщение попадет в функцию LookupHostDialogProc и обработается, как мы только что описали. Другими словами, функция LookupHostDialogProc генерирует сообщение самой себе, в точности повторяя сообщение, посылаемое при нажатии на кнопку Cancel. Если пользователь нажал на кнопку ОК, параметр сообщения WM_COMMAND будет равен ШОК, как указано в описании в файле SOCKMAN2.RC. Получив такое сообщение, функция LookupHostDialogProc вызывает функцию Windows GetDlgltem, чтобы извлечь текст, введенный пользователем в диалоговом окне. Далее LookupHostDialogProc помещает этот текст в глобальную переменную szTexpItem. case WM_COMMAND: switch (wParam) { case IDOK: GetDlgltemText(hwndDlg, IDC_TEXTBOX, (LPSTR)szLookupText, MAX_HOST_NAME); 308 
Что такое диалоговая процедура? EndDialog(hwndDlg, TRUE); return(TRUE); Записав текст, введенный пользователем, функция LookupHostDialogProc вызывает функцию Windows EndDialog с параметром true и завершает работу. Как мы уже говорили, вызов EndDialog с параметром true приводит к тому, что функция DialogBox возвращает значение true в функцию LookupHostDialog. После того как функция DialogBox закончила работу, Windows уничтожает диалоговое окно. Возвращаемся к функции DoWinsockProgram Как вам уже известно, когда окно IDD_TEXT закрывается, функция LookupHostDialog возвращает либо значение NULL, либо указатель на данные, введенные пользователем (они хранятся в переменной szLookupText). Если указатель не равен NULL, программа выясняет, чему равно значение переменной wParam, то есть какого рода операция требуется пользователю — блокирующая или не блокирующая: if (LookupHostDialog()) { if (wParam == IDM_LOOKUP_ASYNC) hAsyncLookupTask = LookupHostAsync(szLookupText, szLookupBuffer, (LPDWORD)&wLookupAddr); else LookupHostBlocking(szLookupText, szLookupBuffer, TASK_BLOCK_LOOKUP); } Когда Windows посылает сообщение в ответ на выбор пункта меню пользователем, выбранный пункт попадает в переменную wParam. Возможные значения wParam определены в файле SOCKMAN2.RC. Например, пункты меню для операций поиска в DNS определены так: POPUP "&Lookup Host..." BEGIN MENUITEM "ScAsync Lookup", IDM_LOOKUP_ASYNC MENUIТЕМ "&Blocking Lookup", IDM_L00KUP_J3L0CKING END Если пользователь выбрал пункт подменю Async Lookup, генерируется сообщение WM_COMMAND с параметром wParam, равным IDM_LOOKUP__ASYNC. Если выбрана блокирующая функция (Blocking Lookup), также генерируется сообщение WM_COMMAND, но в этом случае параметр wParam равен IDM_LOOKUP_BLOCKING. Когда функция DoWinsockProgram получает сообщение WM_COMMAND с параметром IDM_LOOKUP_BLOCKING и ре¬ 309 
Глава 11. Асинхронные сокеты Windows зультат функции LookupHostDialog не равен NULL, вызывается блокирующая функция LookupHostBlocking: LookupHostBlocking(szLookupText, szLookupBuffer, TASK_BLOCK_LOOKUP) ; Блокирующий поиск в DNS Функция LookupHostBlocking похожа на аналогичную из программы QLookup, рассмотренную в девятой главе. Правда, в данном случае вместо наперед заданных значений имени и адреса компьютера используются значения, введенные пользователем. Вот исходный текст функции LookupHostBlocking: LPHOSTENT LookupHostBlocking(LPSTR szUserEntry, szHostEntryBuffer, HTASK hTask) LPSTR LPARAM 1Param; DWORD dwIPAddr; LPHOSTENT lpHostEntry; // Параметр сообщения Windows c // сообщениями об ошибках // IP-адрес в виде беззнакового // двойного слова // Указатель на структуру данных // с информацией о сетевом // компьютере lpHostEntry = NULL; // Предположим, что введен адрес в формате "десятичное с // точкой" и попробуем его преобразовать if( (dwIPAddr = inet_addr(szUserEntry)) == INADDR_NONE) { // Это не был формат "десятичное с точкой" // Предположим, что это имя сетевого компьютера if ((lpHostEntry = gethostbyname(szUserEntry)) == NULL) { MessageBeep(0); MessageBox(NULL, "Could not get host name.", szUserEntry, MB__OK I MB_ICONSTOP) ; } } else // Преобразуем IP-адрес { if ((lpHostEntry = gethostbyaddr((LPCSTR) &dwIPAddr, AF_INET_LENGTH, AF__INET) ) == NULL) { MessageBeep(0); 310 
щ Блокирующий поиск s. DNS MessageBox(NULL, "Could not get IP address.", szUserEntry, MB_OK I MB_ICONSTOP) ; } } // Если преобразование успешно завершено, копируем // структуру данных с информацией о сетевом компьютере в // глобальную переменную szHostEntryBuffer. Это // необходимо сделать до того, как вызвать любую // другую функцию Winsock if (lpHostEntry) { memcpy(szHostEntryBuffer, (LPSTR)lpHostEntry, sizeof(HOSTENT)); // Нулевое значение переменной lParam говорит об // отсутствии ошибок lParam = 0L; } else // Получаем значение ошибки и помещаем его в старшую // половину слова lParam lParam = MAKELONG(0, WSAGetLastError()); // Посылаем сообщение об окончании блокирующего поиска в DNS SendMessage(hwndSockman, WM_BLOCK_LOOKUP_DONE, hTask, lParam); // Если все прошло успешно, возвращаем указатель на // структуру данных с информацией о сетевом компьютере. // Если нет — NULL return((lpHostEntry ? (LPHOSTENT)szHostEntryBuffer : (LPHOSTENT)NULL)); } Как видим, у функции LookupHostBlocking есть три параметра. Первый является указателем на данные пользователя из диалогового окна IDD_TEXT. Второй — указатель на глобальную переменную, хранящую данные об удаленном компьютере. Последний, третий параметр — дескриптор задачи Windows, который функция LookupHostBlocking использует, чтобы идентифицировать себя в сообщении Windows, посылаемом при помощи функции SendMessage. Для преобразования адреса в формате «десятичное с точкой» или имени компьютера в его IP-адрес, функция LookupHostBlocking выполняет следующие действия: 1. Функция предполагает, что первый параметр является адресом в формате «десятичное с точкой», и пытается преобразовать его в IP-адрес при помощи функции inet_addr. 311 
Глава tv. Асинхронные eo*©rw Windows 2. Если преобразовать данные не удалось, LookupHostBlocking рассматривает их как имя компьютера и пытается преобразовать его при помощи функции gethostbyname. 3. Если первый параметр был адресом в формате «десятичное с точкой» и функция inet_addr закончилась успешно, LookupHostBlocking вызывает gethostbyaddr, чтобы получить имя компьютера. 4. Функция LookupHostBlocking пользуется указателем (lpHostEntry) для хранения полученного от DNS адреса (то есть результата либо gethostbyname, либо gethostbyaddr). Потом LookupHostBlocking проверяет значение указателя. 5. Если указатель lpHostEntry действителен, функция LookupHostBlocking копирует содержимое буфера по этому указателю в глобальную переменную, на которую указывает ее второй параметр (в нашем случае szLookupBuffer). 6. После того как сетевые операции закончены (успешно или не успешно), LookupHostBlocking генерирует сообщение Windows, сигнализируя об окончании работы. Переменная lParam служит для передачи информации об ошибках в составе сообщения Windows. 7. Если функции LookupHostBlocking удалось получить IP-адрес компьютера, она возвращает указатель на буфер, где этот адрес хранится (в виде структуры с информацией о сетевом компьютере). Если получить IP-адрес не удается, функция возвращает указатель NULL. Несмотря на то, что в этом примере отсутствуют асинхронные функции, мы все-таки сохранили общий стиль написания Windows-программ. Другими словами, выполнение большинства функций Windows начинается в результате прихода сообщения. Большинство функций высылает сообщение, сигнализирующее об окончании работы. Другие модули приложения могут принимать и реагировать на эти сообщения, используя те же процедуры-обработчики сообщений Windows. Сетевые операции выполняются так же, как и в программе QLookup. За исключением, конечно, способа ввода исходной информации. Есть и одно значительное отличие, ближе к концу функции. Дело в том, что многие асинхронные функции в качестве результата возвращают значение дескриптора задач Windows. Дескриптор задачи Windows, как вам уже известно, является уникальным номером, присвоенным данной копии выполняющегося асинхронного задания, и используется, когда необходимо идентифицировать и обработать сообщение Windows, пришедшее от него. В функции LookupHostBlocking асинхронные задания не используются, поэтому дескриптор задания на самом деле не генерируется. Зато вызывается функция SendMessage, посылающая сообщение, аналогичное «настоящему», генерируемому при работе асинхронной функции. Переменная hTask эмулирует настоящий дескриптор задания, возвращаемый асинхронной функцией поиска в DNS. 312 
Асинхронный поиск в DNS Избыточность в ваших программах Вы можете спросить, почему функция LookupHostBlocking эмулирует асинхронные сообщения и зачем, вообще, в программе Sockman есть два варианта программы поиска в DNS. В отличие от программы QLookup, функция LookupHostBlocking должна взаимодействовать с другими функциями. Вы знаете, что практически каждое приложение Интернет нуждается в услугах по преобразованию адресов сетевых компьютеров. Поэтому если функция поиска в DNS спроектирована правильно, ее можно без труда встроить в любое другое приложение или утилиту в составе Sockman. Именно поэтому, функция LookupHostBlocking ведет себя подобно своему асинхронному аналогу, LookupHostAsync. Начинка этих функций совершенно разная, но они обе выдают одинаковые результаты. Поэтому приложению Sockman все равно, какую из этих двух функций ему использовать. Асинхронные функции Windows должны применяться везде, где только возможно. Они, бесспорно, теснее интегрированы с ориентированной на сообщения средой Windows. Правда, в некоторых реализациях Winsock DLL асинхронные функции поиска в DNS ведут себя неприемлемо плохо. Реализовав два варианта (блокирующий и асинхронный) функции для поиска в DNS, вы обеспечили себе возможный путь для отступления. По крайней мере одна из этих функций будет работать в любом случае. Для начала вызывается асинхронная функция LookupHostAsync. Если она завершается неудачно, следующей вызывается функция LookupHostBlocking. Поскольку результат выдается обеими функциями в одинаковом формате, для вызова одной можно использовать те же самые операторы, что и для вызова другой. Таким образом, конструируя надежный модуль преобразования адресов, вы делаете первый шаг в построении сложного сетевого приложения. Внесение избыточности в обыкновенные приложения затруднительно, да и не всегда нужно. Но если вы разрабатываете критическое бизнес-приложение Интернет, безусловно, без дублирования некоторых функций уже не обойтись. Просто подумайте о том, что все ваши программы окажутся бесполезными и ничего не смогут сделать до тех пор, пока блок преобразования адресов не получит из DNS IP-адрес сетевого компьютера. Асинхронный поиск в DNS Когда пользователь выбирает пункт подменю Async Lookup, Windows генерирует сообщение WM_COMMAND с параметром wParam, равным IDM_LOOKUP_ASYNC. Когда функция DoWinsockProgram получает это сообщение и результат функции LookupHostDialog не равен NULL, вызывается функция LookupHostAsync, как продемонстрировано ниже: if (LookupHostDialog()) { if (wParam == IDM_LOOKUP_ASYNC) hAsyncLookupTask = LookupHostAsync(szLookupText, s zLookupBuf f er, (LPDWORD)&dwLookupAddr); 313 
Глава 11. Асинхронные сокеты Windows else LookupHostBlocking(szLookupText, } szLookupBuffer, TASK_BLOCK_LOOKUP); Первые два параметра функции LookupHostAsync те же, что и у функции LookupHostBlocking. Первый параметр, szLookupText, указывает на глобальную переменную, в которой хранится введенная пользователем в диалоговом окне IDD_TEXT информация. Второй параметр, szLookupBuffer, указывает на глобальную переменную, в которой хранится структура данных с информацией о сетевом компьютере. Третий параметр, dwLookupAddr, является глобальной переменной для хранения IP-адреса в двоичном виде. Функция LookupHostAsync состоит из следующих операторов: HTASK LookupHostAsync(LPSTR szUserEntry, LPSTR szHostEntryBuffer, LPDWORD lpdwAddr) { HTASK hTask; // Дескриптор асинхронного задания // Предполагаем, что адрес в формате // "десятичное с точкой"и пробуем // преобразовать if ((*lpdwAddr = inet_addr(szUserEntry)) == INADDR_NONE) { lstrcpy(szHostName, szUserEntry); // Записываем имя // сетевого компьютера hTask = WSAAsyncGetHostByName(hwndSockman, WM_ASYNC_LOOKUP_DONE, szUserEntry, szHostEntryBuffer, MAXGETHOSTSTRUCT); } else { lstrcpy(szIPAddress, szUserEntry); // Записываем имя // сетевого компьютера hTask = WSAAsyncGetHostByAddr(hwndSockman, WM_ASYNC_LOOKUP_DONE, (LPCSTR)&lpdwAddr, AF_INET_LENGTH, AF_INET, szHostEntryBuffer, MAXGETHOSTSTRUCT); } return(hTask); // Возвращаем дескриптор задания Так же, как и функция LookupHostBlocking, функция LookupHostAsync вначале предполагает, что пользователь ввел адрес компьютера в формате «десятичное с точкой» и пытается преобразовать его, вызывая функцию inet_addr. Если преобразование оказалось неудачным, LookupHostAsync пробует вызвать функцию gethostbyname. В любом случае значение переменной szUserEntry копируется в глобальную переменную. 314 
;ИНХрОННЫЙ поиск & DNS Если пользователь ввел адрес формата «десятичное с точкой», адрес заносится в переменную szIPAddress. Если введенные данные — имя компьютера, имя заносится в переменную szHostName. Далее эти значения используются другими модулями Sockman. Например, когда вызывается Finger, Sockman предлагает указать в качестве сетевого компьютера имя последнего из найденных в DNS. В функции LookupHostAsync используются две новых асинхронных функции Winsock. Вместо gethostbyname используется WSAAsyncGetHostByName, а вместо gethostbyaddr — WSAAsyncGetHostByAddr. Асинхронные функции Winsock Ниже приведен прототип функции WSAAsyncGetHostByAddr. На первый взгляд, она кажется сложнее своего аналога из интерфейса Беркли: HANDLE PASCAL FAR WSAAsyncGetHostByAddr(HWND hWnd, unsigned int wMsg, const char FAR * addr, int len, int type, char FAR * buf, int buflen); У функции WSAAsyncGetHostByAddr семь параметров, на четыре больше, чем у gethostbyaddr. Три из параметров WSAAsyncGetHostByAddr в точности соответствуют параметрам gethostbyaddr. Вот прототип функции gethostbyaddr: struct hostent FAR * PASCAL FAR gethostbyaddr(const char FAR * addr, int len, int type); У обеих функций есть одинаковые параметры: const char FAR * addr; // Указатель на IP-адрес с сетевым // порядком байтов. int len; // Длина адреса. В случае Интернет // всегда равна 4 байтам. int type; // Тип адреса. Для Интернет всегда AF_INET. Главное отличие между WSAAsyncGetHostByAddr и gethostbyaddr в типе возвращаемого результата, gethostbyaddr возвращает указатель на структуру данных с информацией о сетевом компьютере (host-entry), a WSAAsyncGetHostByAddr — дескриптор задания Windows. Ключевой момент в понимании работы WSAAsyncGetHostByAddr — знать, что делать дальше с этим дескриптором. Что такое дескриптор асинхронного задания Windows? Дополнительно к обыкновенным параметрам функций Winsock в стиле Беркли, их асинхронным аналогам требуются два специальных параметра. Первый параметр — дескриптор окна Windows, которому посылается сообщение об окончании работы функции. Второй — само посылаемое сообщение. Как только 315 
Глава 11. Асинхронные сокеты Windows асинхронная функция отработает, указанное сообщение отсылается заданному окну. Например, функция LookupHostAsync вызывает функцию WSAAsyncGetHostByName следующим образом: hTask = WSAAsyncGetHostByName(hwndSockman, WM__ASYNC_LOOKUP__DONE, SzUserEntry, szHostEntryBuffer, MAXGETHOSTSTRUCT); Функция WSAAsyncGetHostByName выполняет задачу по преобразованию имени в IP-адрес, используя информацию из параметра szUserEntry. Функция WSAAsyncGetHostByName возвращает управление вызывавшему модулю сразу, как только задача запущена на выполнение. Она не ждет, пока операция поиска в DNS закончится. Поскольку на момент возврата функции WSAAsyncGetHostByName результат еще не получен, она не может вернуть указатель на структуру с информацией о сетевом компьютере, как это делает gethostbyname. Вместо этого WSAAsyncGetHostByName возвращает дескриптор задачи Windows, идентифицирующий процесс преобразования имени в IP-адрес. Дескриптор заносится в переменную hTask. Как только сетевая операция закончится, Windows пошлет сообщение окну с дескриптором hwndSockman. Идентификатором этого сообщения будет WM_ASYNC_LOOKUP_DONE. Другими словами, первые два параметра функции WSAAsyncGetHostByName определяют, кому и что послать по завершении асинхронной задачи. Асинхронные функции Winsock начинают операции и немедленно возвращают управление вызывавшему модулю. Результатом большинства асинхронных функций является дескриптор задачи Windows. Дескриптор задачи однозначно идентифицирует задачу, запущенную асинхронной функцией, и в дальнейшем используется для идентификации сообщений, генерируемых асинхронной задачей. Параметры асинхронного сообщения Windows Все сообщения Windows имеют определенный формат. Каждое сообщение состоит из четырех переменных: дескриптора окна-получателя сообщения, идентификатора сообщения, 16-битного и 32-битного параметров сообщения. Первый параметр сообщения часто называется wParam (word parameter). Второй параметр часто называется IParam (long parameter). Многие Windows-сообщения не используют оба параметра для хранения скольнибудь полезной информации. Однако это не относится к сообщениям функций Winsock. Параметр wParam сообщения Winsock содержит дескриптор асинхронного задания, который вернула функция, запустившая это задание. Если старшие 16 бит переменной IParam не равны нулю, то в них содержится код ошибки. В следующем разделе разъясняется смысл младших 16 бит переменной IParam. 316 
>нный поиск DNS Ошибки асинхронных функций Winsock При вызове большинства асинхронных функций Winsock требуется задавать указатель на буфер для хранения данных, равно как и его длину. Как правило, два этих аргумента — последние в списке параметров. Разработчики Winsock предусмотрели возможность возникновения ошибки, связанной с нехваткой места в буфере для принятых сетевых данных. Младшие 16 бит переменной IParam служат именно для этих целей. Предположим, что количество принятых сетевых данных превысило отведенный для них размер буфера, который вы задали при вызове функции. Вместо того чтобы превысить допустимый объем буфера или неудачно закончиться, функция посылает сообщение с кодом ошибки WSANOBUFS. Код ошибки WSANOBUFS попадает в старшие 16 бит переменной IParam. Ошибка означает, что либо вы не отвели места для буфера, принимающего данные, либо этого места недостаточно. При возникновении ошибки WSANOBUFS устанавливаются значения младших 16 бит параметра IParam. Если буфер-приемник данных слишком мал, в них заносится требуемый размер буфера. Другими словами, асинхронная функция Winsock не только сообщает о превышении размеров буфера, но и говорит, сколько памяти нужно для него отвести. Динамическое распределение памяти Если размер отведенного вашей программой буфера слишком мал, чтобы вместить все данные, функция возвращает значение ошибки WSANOBUFS в двух старших байтах переменной IParam. В двух младших байтах IParam находится требуемый размер буфера. Рассмотрим эту ситуацию на практике. Если прикладная программа работает в условиях нехватки памяти, память для буфера можно отводить динамически. Например, в вызове функции можно указать размер буфера, равный нулю, получить сообщение об ошибке WSANOBUFS и выделить необходимую память динамически. Для этого программа должна проанализировать два младших байта переменной IParam сообщения Windows. Определив размер буфера, программа динамически отводит память и вызывает асинхронную функцию снова, но уже с правильным размером буфера. Поступая таким образом, программа никогда не отнимет лишней памяти у системы. Конечно, такому подходу свойственны и определенные недостатки. Прежде всего страдает производительность— ведь одну и ту же функцию приходится вызывать два раза вместо одного. Разработчик должен выбрать, что важнее — потребление памяти или скорость работы системы. Для получения кода ошибки и требуемого размера буфера из параметра IParam сообщения Winsock в сокетах Windows предусмотрены два макроса. Макрос WSAGETASYNCERROR возвращает код ошибки — содержимое двух старших байтов переменной IParam, а макрос WSAGETASYNCBUFLEN — требуемую длину буфера данных — содержимое двух младших байтов переменной IParam. Макросы используются следующим образом: UINT nErrorNumber = WSAGETASYNCERROR(IParam); UINT nRequiredBuf = WSAGETASYNCBUFLEN(IParam); 317 
Глава 11. Асинхронные сокеты Windows Возвращаясь к функции DoWinsockProgram Функция LookupHostAsync, как вы помните, возвращает дескриптор задачи Windows (функции WSAAsyncGetHostByName или функции WSAAsyncGetHostByAddr). Как показано ниже, функция DoWinsockProgram заносит значение этого дескриптора в глобальную переменную hAsyncLookupTask: if (wParam == IDM_LOOKUP_ASYNC) hAsyncLookupTask = LookupHostAsync(szLookupText, s zLookupBuf f er, (LPDWORD)&wLookupAddr); С этого момента ответственность за происходящее переходит к Windows. Другими словами, в ответ на выбор пользователем подпункта меню Async Lookup Sockman произвел все необходимые действия для запуска асинхронной задачи поиска в системе DNS. Эти действия таковы: 1. Sockman вывел на экран диалоговое окно и позволил пользователю ввести имя или адрес (в формате «десятичное с точкой») компьютера. 2. Sockman передал введенные пользователем данные функции LookupHostAsync. 3. Функция LookupHostAsync запустила задачу по асинхронному поиску в DNS. 4. Sockman записал значение дескриптора задачи в глобальную переменную hAsyncLookupTask. (Глобальной эта переменная сделана потому, что это облегчает доступ остальных приложений к сообщениям задачи асинхронного поиска в DNS.) Как только задача асинхронного поиска в DNS закончится, Windows пошлет сообщение. Вот как осуществляется вызов асинхронных функций Winsock в функции LookupHostAsync: hTask = WSAAsyncGetHostByName(hwndSockman, WM_AS YNC_LOOKUP__DONE, s z Us er Entry, szHostEnt ry Bu f f er, MAXGETHOSTSTRUCT); hTask = WSAAsyncGetHostByAddr(hwndSockman, WM_ASYNC_LOOKUP_DONE, (LPCSTR)fclpdwAddr, AF_INET_LENGTH, AF_INET, szHostEntryBuffer, MAXGETHOSTSTRUCT); Обратите внимание, что обе функции (WSAAsyncGetHostByName и WSAAsyncGetHostByAddr) имеют одинаковые параметры — дескриптор окна, равный hwndSockman и идентификатор сообщения, равный WM ASYNC LOOKUP DONE. Дескриптор окна hwndSockman идентифицирует окно Sockman. Сообщение WM_ASYNC_LOOKUP_DONE высылается окну Sockman по окончании работы функции. Другими словами, его получит обработчик сообщений окна Sockman — функция WndProc. 318 
Функция DisplayHostEntry Модифицируем функцию WndProc Обе функции, LookupHostAsync и LookupHostBlocking, запускают задачу поиска в DNS и заставляют Windows послать сообщение по окончании этой задачи. Сообщение посылается процедуре WndProc, являющейся обработчиком всех сообщений, адресованных главному окну Sockman. Как только задача поиска в DNS, запущенная функцией LookupHostAsync, заканчивается, Windows посылает сообщение WM_ASYNC_LOОKUP_DONE функции WndProc. Как только функция LookupHostBlocking заканчивается, Windows посылает сообщение WM_BLOCK_LOOKUP_DONE функции WndProc. Эти сообщения обрабатываются операторами case, как показано во фрагменте исходного текста функции WndProc из файла SOCKMAN2.CPP: long FAR PASCAL __export WndProc(HWND hwnd, UINT iMessage, UINT wParam, LONG 1Param) { switch (iMessage) { case WM_ASYNC_LOOKUP_DONE: case WM_BLOCK_LOOKUP_DONE: DisplayHostEntry(hwnd, iMessage, wParam, IParam); return(0); // Закончили Как видим, оба сообщения вызывают функцию DisplayHostEntry. Если результаты работы функций поиска в DNS требуются другим модулям программы, эти операторы следует изменить. В данном случае на экран выводится окно-сообщение с этими результатами. Функция DisplayHostEntry Функция DisplayHostEntry, как следует из названия, выводит результаты поиска в DNS на экран компьютера. При этом она проверяет наличие сообщений об ошибках от асинхронных функций при помощи макроса WSAGETASYNCERROR. Примечание: Обратите внимание на то, что функция LookupHostBlocking эмулирует асинхронное сообщение об ошибке, вызывая функцию SendMessage с установленной переменной IParam. Получается, что DisplayHostEntry проверяет ошибки как асинхронной, так и блокирующей функции. VOID DisplayHostEntry(HWND hwnd, UINT iMessage, UINT wParam, LONG IParam) { int nErrCode; // Код ошибки от преобразователя адресов if (nErrCode = WSAGETASYNCERROR(IParam)) { wsprintf(szScratchBuffer, "%s LOOKUP caused Winsock ERROR No. %d", szLookupText, nErrCode); 319 
Глава11.АсинхронныесокетыШ1пс1сплг5 MessageBeep(0) ; MessageBox(NULL, szScratchBuffer, szLookupText, MB__OK I MB__I CONS TOP) ; // Если произошла ошибка, буфер обнуляется szLookupBuffer[0] = '\0'; } else { PHOSTENT pHostEntry; pHostEntry = (PHOSTENT) szLookupBuffer; lstrcpy(szHostName, pHostEntry->h_name); lstrcpy(szIPAddress, inet_ntoa(*(PIN_ADDR)*(pHostEntry->h_addr_list))); wsprintf(szScratchBuffer, "%s\tLOOKUP RESULTS\nHost Name:\t%s\nIP Address:\t%s", szLookupText,szHostName, szIPAddress); } PaintWindow(szScratchBuffer); return; } Если содержимое IParam, как показывает вызов WSAGETASYNCERROR, свидетельствует об ошибке, функция DisplayHostEntry выводит сообщение об ошибке и устанавливает нулевую длину буферу szLookupBuffer. Если ошибки не было, выводится сообщение с результатами, взятыми из структуры данных host-entry (с информацией о сетевом компьютере), записанной в szLookupBuffer, приведенной предварительно к типу PHOSTENT (указатель на структуру host-entry). Поле h_name структуры host-entry копируется в глобальную переменную szHostName. Имя сетевого компьютера, записанное в szHostName, может использоваться остальными программными модулями Sockman, как имя компьютера по умолчанию. Для преобразования элемента h_addr_list из формата структуры host-entry в простой ASCII-текст, используется функция inet_ntoa. Далее, эта строка копируется в глобальную переменную szIPAddress. Опять-таки, последний извлеченный из DNS адрес может использоваться другими приложениями Sockman по умолчанию. После того как адрес и имя компьютера сохранены, Diplay HostEntry при помощи функции wsprintf формирует строку результата в буфере общего назначения szScratchBuf fer. Указатель на этот буфер передается функции PaintWindow, которая и выводит результаты поиска в DNS в главное окно Sockman. Подводя итоги В этой главе вы узнали, как отдельная утилита, например поиска в DNS, интегрируется в приложение Windows. Вы узнали, как создаются асинхронные 320 
Подводя итоги эквиваленты сетевых функций в стиле Беркли. Вы узнали, что большинство асинхронных функций, в отличие от синхронных (возвращающих данные или указатели на данные), возвращают дескрипторы заданий Windows. Другими словами, асинхронные функции начинают сетевую операцию и немедленно возвращают дескриптор задания вызывавшему модулю. Дескриптор задания Windows однозначно идентифицирует асинхронную процедуру, запущенную асинхронной функцией. В дальнейшем дескриптор задания используется для идентификации сообщений от асинхронной процедуры. В следующей главе вы узнаете, как простая утилита Finger, рассмотренная в десятой главе, интегрируется в настоящее Windows-приложение. Также вы узнаете, как выглядит асинхронная версия той же самой процедуры. До того как перейти к двенадцатой главе, проверьте, хорошо ли вы усвоили следующие важные понятия: S При вызове асинхронной функции сетевого ввода-вывода Winsock, как правило, задается указатель на буфер данных и размер этого буфера. S Асинхронные функции ввода-вывода Winsock, как правило, возвращают дескриптор задания Windows. S Сообщение от асинхронной функции Windows содержит 16-битный параметр wParam, в котором находится дескриптор задания. S Сообщение об ошибке асинхронной функции хранится в старших шестнадцати битах 32-битного параметра IParam. S Если размеров буфера при вызове асинхронной функции не хватает, чтобы вместить все данные, генерируется сообщение об ошибке WSANOBUFS (размер буфера слишком мал), а необходимый размер буфера извлекается программой при помощи макроса WSAGETASYNCBUFLEN из 16 младших бит 32-битного параметра сообщения IParam. 11 Зак. № 1949 
Глав Дескрипторы задач Winsock В главе 11 вы научились использовать асинхронные функции базы данных Winsock для преобразования адресов Интернет. В этой главе вы узнаете, как лучше всего обращаться с этими асинхронными функциями базы данных. Для лучшего понимания этих функций вы должны хорошо разбираться в том, как Winsock использует дескрипторы задач. Как вы знаете, дескриптор — это значение, которое однозначно идентифицирует некий объект. Например, Winsock использует дескрипторы сокетов для манипулирования сокетами. Подобным же образом программа, выполняющая операции ввода-вывода, использует дескрипторы файлов. Windows как многозадачная среда, в которой могут выполняться одновременно несколько программ (задач), использует дескрипторы задач, каждый из которых однозначно связан с некоторой программой. В главе 11 мы с вами разработали программу Sockman, которая способна искать адреса в Интернет. В данной главе мы добавим к шаблону Sockman функциональную программу-модуль, изменяющую этот шаблон таким образом, что он сможет обрабатывать команду Finger из меню Utilities. В частности, пользова- 322 
' тели программы Sockman после этого смогут выполнять асинхронные и блокирующие операции Finger. К концу главы вы должны овладеть следующими ключевыми понятиями: ♦ Как инициализировать синхронные и асинхронные операции Finger в полномасштабной программе для Windows. ♦ Как использовать дескрипторы задач Winsock для отслеживания состояния асинхронных операций. Материал этой главы предполагает, что вы прочли главу 11, в которой описывается поиск в DNS в полномасштабной программе для Windows. Если вы еще не читали главу И, прочтите ее прежде, чем начинать читать эту главу. Как мы уже говорили выше, поиск в DNS — одна из важнейших частей любой сетевой функции — не имея правильного адреса хоста, ваша программа просто ничего не сможет сделать в Интернет. Дискета, прилагаемая к этой книге, содержит как исходный текст, так и исполняемый файл приложения Sockman под названием Sockman3, в которое включена утилита Finger в качестве части шаблона Sockman. Вы можете пользоваться программой Sockman3 в том виде, в каком она находится на диске, либо изменять шаблон Sockman, приведенный в приложении Б. (Если вы собираетесь изменять исходный шаблон Sockman, внесите в него сначала те изменения, о которых шла речь в главе И, так как Sockman3 использует эти новые функции.) Для построения утилиты Finger мы прежде всего должны создать диалоговое окно — точно так же, как мы делали это, когда добавляли в Sockman возможность поиска в DNS. В этом диалоговом окне пользователь должен будет вводить имя хоста и имя пользователя на этом хосте. Файл ресурса SOCKMAN3.RC определяет диалоговое окно Finger под названием IDD_FINGER. Вы можете воспользоваться файлом SOCKMAN3.RC или добавить в ваш собственный файл ресурса определение IDD_FINGER, как показано ниже: IDD_FINGER DIALOG DISCARDABLE 0, 0, 185, 73 STYLE DS_MODALFRAME I WS_POPUP I WS_VISIBLE I WS_CAPTION I Добавление функции Finger к программе Sockman WS_SYSMENU CAPTION "FINGER" FONT 8, "MS Sans Serif" BEGIN EDITTEXT EDITTEXT DEFPUSHBUTTON PUSHBUTTON IDC_FINGER_HOST, 31,10,140,15, ES__AUTOHSCROLL IDC_FINGER__USER, 31,29,140,14 , ES_AUTOHSCROLL "OK",IDOK,31,50,50,14 "Cancel",IDCANCEL,91,50,50,14 323 
Глава 12. 1Ы задан Winsock RTEXT RTEXT "Hosts",IDCjSTATIC,11,14,20,13 "User:",IDC_STATIC,5,33,25,9 END Доступ к функции Finger осуществляется через меню Utilities в Sockman. Когда пользователь выбирает из подменю Finger одну из двух опций — Async Finger или Blocking Finger, Sockman3 выводит на экран диалоговое окно IDD_FINGER, показанное на рис. 12.1. Как видно из рисунка, диалоговое окно IDD_FINGER позволяет ввести адрес сетевого компьютера и имя или идентификатор пользователя. Как вы знаете из главы И (см. рис. 11.2), функция DoWinsockProgram в конечном счете обрабатывает все сообщения Windows, целью которых является запуск программ Winsock. После того как вы добавите в свой файл ресурса определение IDD_FINGER, вам потребуется изменить функцию DoWinsockProgram, которая размещается в файле SOCKMAN.СРР. (Обратите внимание, что исходный файл SOCKMAN3.CPP, расположенный на дискете, прилагаемой к книге, уже содержит в себе все изменения, описанные в следующих разделах.) Изменение функции DoWinsockProgram для Finger Когда пользователь выбирает опцию Lookup из меню Utility и запрашивает действие Async Finger или Blocking Finger, Windows высылает сообщение WM_COMMAND функции WndProc. Она, в свою очередь, передает сообщение функции DoMenuCommand, которая вызывает DoWinsockProgram. Если выбрано действие Async Finger, переменная wParam получит значение IDM_FINGER_ASYNC. Если выбрано действие Blocking Finger, переменная wParam получит значение IDM_FINGER_BLOCKING. Поэтому, если вы пишите программу Sockman с нуля, вам нужно будет добавить в ветвь IDM FINGER оператора switch в функции DoWinsockProgram следующие операторы: long DoWinsockProgram(HWND hwnd, UINT wParam, LONG lParam) Host: User: |;:;OCancetu:,,| Puc. 12.1. Диалоговое окно ID используемое в функции Finger программы Sockman { 324 
Изменение функции DoWinsockProgram для finger switch (wParam) { case IDM_FINGER_ASYNC: case IDM__FINGER__BLOCKING: if (hFingerTask) // Sockman допускает только // один вызов Finger { MessageBeep(O); MessageBox(hwnd, "Finger utility is already in use. Please wait...", "SockMan - FINGER", MB_ICONSTOP I MB_OK); } else if (FingerDialog()) { if (wParam == IDM_FINGER_ASYNC) hFingerTask = AsyncGetServicelnfo(hwnd, TASK__ASYNC_F INGER) ; else { hFingerTask = TASK_BLOCK_FINGER; FingerHostBlocking(); hFingerTask = 0; } } break; Примечание: Определение функции DoWinsockProgram содержится в файле SOCKMAN3.CPP на приложенной к книге дискете. Для поддержки нескольких одновременных Finger-операций Sockman должен был бы манипулировать несколькими буферами данных — по одному набору буферов для каждой Finger-операции. Поэтому для упрощения механизма манипулирования буферами наша версия Sockman будет способна выполнять только одну Finger-операцию в каждый момент времени. Чтобы запретить запуск новой Finger-операции в тот момент, когда еще выполняется старая, функция DoWinsockProgram проверяет значение глобального дескриптора задачи Finger, который называется hFingerTask. Если hFingerTask содержит значение, большее нуля, то это значит, что в данный момент выполняется Finger-операция. Ниже приведен фрагмент кода, который выводит в таком случае на экран окно сообщения, предупреждающее пользователя, что он должен подождать завершения текущей Finger-операции: case IDM_FINGER__ASYNC: case IDM_FINGER_BLOCKING: if (hFingerTask) // Sockman допускает только // один вызов Finger { MessageBeep(0); 325 
Глава 12. Дескрипторы задач Winsock MessageBox(hwnd, "Finger utility is already in use. Please wait...", "SockMan - FINGER", MB_ICONSTOP I MB__OK) ; break; } Если же значение hFingerTask равно нулю, функция DoWinsockProgram вызывает функцию FingerDialog, которая выводит диалоговое окно IDD_FINGER. Диалоговое окно Finger Как видно из рис. 12.1, диалоговое окно Finger позволяет пользователю ввести имя хоста и идентификатор пользователя, информацию о которых он хочет получить. Ниже приведено определение функции FingerDialog. Как видите, эта функция практически идентичная функции LookupHostDialog, расположенной в файле LOOKUP2.CPP: BOOL FingerDialog(VOID) { DLGPROC lpfnDialogProc; BOOL bOkay; // Создаем диалоговое окно для пользовательского ввода lpfnDialogProc = MakeProcInstance((DLGPROC)FingerDialogProc, hlnstanceSockman); bOkay = DialogBox(hlnstanceSockman,"IDD_FINGER", hwndSockman, lpfnDialogProc); FreeProcInstance(lpfnDialogProc); return(bOkay); } Между этими двумя функциями есть только три различия. Во-первых, функция FingerDialog использует callback функцию под названием FingerDialogProc. Во-вторых, функция DialogBox в FingerDialog использует в качестве идентификатора окна IDDJFINGER, а не IDD_TEXT, как в функции LookupHostDialog. В-третьих, функция FingerDialog возвращает булево значение истинности или ложности (BOOL), а не длинный указатель на символ (LPSTR). Для создания и вывода на экран диалогового окна обе эти функции используют вызовы одних и тех же функций Windows API. Процедура Finger Dialog Функция обратного вызова диалогового окна Finger под названием FingerDialogProc (в этом случае она называется диалоговой процедурой) аналогична функции обратного вызова, используемой окном поиска адреса. Подобно тому, как функция LookupHostDialog обрабатывает все сообщения для диалогового 326 
Диалоговое окно Finger окна IDD_TEXT в модуле Lookup,.функция FingerDialogProc обрабатывает все сообщения для диалогового окна IDDJFINGER в программном модуле Finger. Вот как определена функция FingerDialogProc: BOOL __export CALLBACK FingerDialogProc(HWND hwndDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch(iMessage) { case WM_INITDIALOG: // Инициализируем // диалоговое окно LPSTR pHost; pHost = lstrlen(szFingerHost) ? szFingerHost : szHostName; SetDlgltemText (hwndDlg, IDC__FINGER_HOST, pHost); SetDlgltemText(hwndDlg, IDC_FINGER_USER, szFingerUser); CenterWindow(hwndDlg); return(TRUE); case WM_CLOSE: // Обрабатывается так же, // как и кнопка Cancel PostMessage(hwndDlg, WM_COMMAND, IDCANCEL, OL); return(TRUE); case WM_COMMAND: switch(wParam) { case IDOK: // Обработка нажатия кнопки OK GetDlgltemText(hwndDlg, IDC_FINGER_HOST, (LPSTR) szFingerHost, MAX__HOST_NAME) ; GetDlgltemText (hwndDlg, IDC__FINGER__USER, (LPSTR)szFingerUser, MAX_USER_NAME); lstrcpy(szLookupText, szFingerHost); EndDialog(hwndDlg, TRUE); return(TRUE); } case IDCANCEL: // Обработка нажатия кнопки // Cancel EndDialog(hwndDlg, FALSE); return(TRUE); } } return(FALSE); // Возвращаем false в случае // необработанных сообщений Как и функция LookupHostDialogProc, функция FingerDialogProc обрабатывает сообщения WM_INITDIALOG, WM_CLOSE и WM_COMMAND. 327 
Глава 12. Дескрипторы задач Winsock Инициализация диалогового окна Finger Как вы знаете, Windows посылает сообщение WM_INITDIALOG, чтобы инициализировать окно перед тем, как вывести его на экран. Когда функция FingerDialogProc получает сообщение WM_INITDIALOG, она инициализирует два поля для ввода текста и центрирует это окно относительно экрана. Диалоговое окно IDD_FINGER использует два поля ввода текста: IDC_FINGER_HOST и IDCJFINGER_USER. Первое из них предназначено для ввода адреса узла, например jamsa.com, а второе — Finger-запрос, который, как правило, является идентификатором пользователя, например kcope. Во время инициализации диалогового окна функция FingerDialogProc устанавливает значение поля IDC_FINGER_USER равным значению, хранящемуся в глобальной переменной szFingerUser. В этой переменной программа всегда хранит последний идентификатор пользователя, который был введен при запуске Finger-операции. Таким образом, в этом поле ввода вы всегда будете видеть то значение, которое вводили в него в прошлый раз. Прежде всего, функция FingerDialogProc проверяет длину значения, содержащегося в глобальной переменной szFingerHost. Эта переменная хранит последний адрес хоста, использовавшийся в Finger-операции. Если длина szFingerHost равна нулю, значит, в этом сеансе работы с Sockman пользователь еще не производил ни одной Finger-операции. В таком случае функция FingerDialogProc устанавливает значение поля ввода IDC_FINGER_USER равным последнему имени хоста (если таковое найдется), которое использовалось в модуле Lookup. Этот модуль также сохраняет последнее найденное в DNS имя хоста в переменной szHostName. Другими словами, Sockman запоминает значения, которые пользователь вводил в различных диалоговых окнах функций Sockman. Чтобы сэкономить время и усилия пользователя, Sockman всегда, когда это возможно, подставляет по умолчанию последнее из использовавшихся значений. К примеру, работу с Sockman можно начать с поиска в DNS для проверки и выяснения доменного имени некоего хоста. Затем пользователь может воспользоваться функцией Ping, чтобы попробовать соединиться с этим хостом и узнать, присутствует ли он в сети (в главе 14 вы узнаете, как создать программу Ping). Наконец, пользователь вызовет Finger, чтобы узнать, кто из пользователей работает в данный момент на этом хосте. Поскольку Sockman запоминает значения адресов, пользователю придется ввести доменный или цифровой адрес хоста только один раз. После того как функция FingerDialogProc инициализирует значения полей ввода и центрирует диалоговое окно IDD_FINGER, эта функция возвращает Windows значение TRUE, сигнализируя, что сообщение обработано. Обработка команд в диалоговом окне Чтобы закрыть диалоговое окно IDD_FINGER, пользователь может выбрать команду Close из управляющего меню этого окна или щелкнуть мышью по одной из кнопок Cancel или ОК. Как вы уже знаете, Windows генерирует сообщение 328 
Диалоговое окно М§щт ч WM_CLOSE, если пользователь выбирает команду Close из управляющего меню. Если же пользователь щелкает по одной из командных кнопок Cancel или OK, Windows генерирует сообщение WM_COMMAND. Когда пользователь выбирает команду Close из управляющего меню диалогового окна, функция FingerDialogProc вызывает функцию PostMessage, которая заставляет Windows послать сообщение WM_COMMAND с параметром IDCANCEL. Когда функция FingerDialogProc получает сообщение с параметром IDCANCEL (или сообщение WM_COMMAND после нажатия кнопки Cancel), FingerDialogProc вызывает функцию EndDialog, которая заставляет Windows закрыть диалоговое окно. Подобным же образом, когда пользователь щелкает по кнопке ОК, функция FingerDialogProc получает сообщение WM_COMMAND с параметром ШОК. Когда FingerDialogProc получает это сообщение, эта функция с помощью функции GetDlgltemText извлекает введенные пользователем значения из полей ввода IDC__FINGER_HOST и IDCJFINGER_USER. Затем функция FingerDialogProc обновляет глобальную переменную szLookupText с помощью вызова функции Windows lstrcpy, как показано ниже: lstrcpy(szLookupText, szFingerHost); Примечание: Помните, чтобы сделать работу пользователя более эффективной, Sockman устанавливает в качестве значения по умолчанию то значение, которое было введено в последний раз в одном из диалоговых окон программы. Обновляя переменную szLookupText, Sockman тем самым сохраняет адрес Finger-хоста как значение по умолчанию для следующей операции поиска. Сохранив введенное пользователем значение, функция FingerDialogProc вызывает функцию Windows EndDialog с параметром TRUE и заканчивает свою работу. Как вы знаете, функция EndDialog вызывает возврат из функции DialogBox, что, в свою очередь, заставляет Windows закрыть это диалоговое окно. Возврат в функцию DoWinsockProgram После того как функция DialogBox, вызванная из FingerDialog, завершается, функция FingerDialog возвращает управление в функцию DoWinsockProgram. При этом функция FingerDialog возвращает булево значение, возвращенное диалоговой процедурой FingerDialogProc. Это значение — TRUE или FALSE — сообщает функции DoWinsockProgram, каким путем произошло закрытие диалогового окна. Значение TRUE означает, что пользователь щелкнул мышью по кнопке ОК. Если пользователь щелкнет мышью по копке Cancel или выберет команду Close из управляющего меню, функция FingerDialog возвратит значение FALSE. Возвращение FALSE означает, что пользователь отказался от выполнения Finger-операции. В таком случае функция DoWinsockProgram не принимает никаких действий. Однако если пользователь щелкнул мышью по кнопке ОК и функция FingerDialog вернула значение TRUE, функция DoWinsockProgram проверяет значение wParam в полученном сообщении. Значение, содержащееся в wParam, сообщает функции DoWinsockProgram, какой тип 329 
Глава 12. Дескрипторы задач Winsock Г Finger-операции выбрал пользователь — асинхронную или блокирующую. Если пользователь выбирает опцию Async Finger, параметр wParam будет равен IDM_FINGER_ASYNC. В этом случае DoWinsockProgram вызывает функцию AsyncGetServicelnf о: if (wParam == IDM_FINGER_ASYNC) hFingerTask = AsyncGetServicelnfо(hwnd, TASK_ASYNC_F INGER) ; else { hFingerTask = TASK_BLOCK_FINGER; FingerHostBlocking(); hFingerTask = 0; } Функция AsyncGetServicelnf о Вы уже знаете, что база данных сетевых служб содержит названия часто используемых сетевых служб, таких как Ftp, Telnet, SMTP и Finger. В этой базе данных также содержатся номера портов, протоколы и дополнительные имена для каждой из этих служб. Функция AsyncGetServicelnf о просто инициирует асинхронный процесс, который извлекает информацию о службе из этой базы данных. Вот как определена функция AsyncGetServicelnf о: HTASK AsyncGetServicelnfо(HWND hwnd, HTASK hService) { // Сохраняем дескриптор задачи WSAAsyncGetServByName HTASK hTask = WSAAsyncGetServByName(hwnd,WM_GOT_SERVICE, (LPSTR)IPSERVICE_FINGER, NULL, (LPSTR)szFingerBuffer, sizeof(szFingerBuffer)); if (!hTask) { LPARAM lParam; hTask = hService; // Возвращаем идентификатор // сетевой службы lParam = MAKELONG(0, WSAGetLastError()); // Запоминаем код ошибки PostMessage(hwnd, WM_GOT_SERVICE, hService, lParam); } return(hTask); } Как видите, функция AsyncGetServicelnf о определена в Sockman с помощью функции Windows Sockets под названием WSAAsyncGetServByName. Как мы увидим далее, возможно использование функции WSAAsyncGetServByName вместо функции AsyncGetServicelnf о. Функция AsyncGetServicelnf о служит 330 
лишь оболочкой функции WSAAsyncGetServByName. Одно из достоинств использования такой оболочки состоит в том, что вы можете разместить в ней код для проверки ошибочных ситуаций. Таким образом, вам можно будет не дублировать этот код каждый раз при вызове функции WSAAsyncGetServByName. Вместо этого вы просто вызываете функцию AsyncGetServicelnfo, в которой выполняются одни и те же проверки ошибок для каждого вызова функции WSAAsyncGetServByName. Вынеся все эти проверки в одну функцию, можно достичь заметного сокращения объема программы, в результате чего ее текст будет проще читать, понимать и изменять. Функция WSAAsyncGetServByName Функция WSAAsyncGetServByName аналогична функции WSAAsyncGetHostByName, с которой мы уже встречались в этой главе. Однако вместо извлечения из базы данных имени хоста функция WSAAsyncGetServByName извлекает название протокола и номер порта. Следующий фрагмент кода определяет прототип функции WSAAsyncGetServByName: HANDLE PASCAL FAR WSAAsyncGetServByName(HWND hWnd, unsigned int wMsg, const char FAR * name, const char FAR * proto, char FAR * buf, int buflen); Как видим, функция WSAAsyncGetServByName требует нескольких параметров. Как и многие асинхронные функции Winsock, WSAAsyncGetServByName должна получить дескриптор окна и идентификатор сообщения в качестве первых двух параметров. Как вы уже знаете, когда асинхронная функция Winsock завершает работу, Windows, как правило, посылает сообщение в процедуру обработки сообщений вашего окна. Это сообщение от асинхронной функции означает, что она закончила свою работу. Сообщение, которое посылает Windows, представляет собой идентификатор, передаваемый в качестве второго параметра (wMsg). Windows посылает wMsg тому окну, на которое указывает дескриптор, переданный в первом параметре (hWnd). Функция WSAAsyncGetServByName является асинхронным вариантом функции getservbyname, с которой мы встречались в программе QFinger. Как вы, возможно, помните, функция getservbyname возвращает протокол и номер порта по имени службы из базы данных сетевых служб. Сравните прототип функции WSAAsyncGetServByName с прототипом функции getservbyname: struct servent FAR * PASCAL FAR getservbyname(const char FAR * name,const char FAR * proto ); Как видите, два параметра функции getservbyname (name и proto) идентичны параметрам функции WSAAsyncGetServByName. Параметр name указывает на имя службы, а параметр proto — на имя протокола. Как и getservbyname, 331 
Глава 12. Дескрипторы задач Wlnsocl функция WSAAsyncGetServByName позволяет использовать протокол по умолчанию, подставив значение NULL для параметра proto. Как и большинство асинхронных функций Winsock, извлекающих данные, функция WSAAsyncGetServByName требует, чтобы ваша программа разместила буфер для хранения данных и передала в функцию указатель (адрес) на этот буфер. В прототипе функции WSAAsyncGetServByName адрес буфера данных соответствует параметру buf. Кроме того, WSAAsyncGetServByName требует, чтобы ваша программа сообщила ей размер буфера для хранения данных в параметре buflen. Функция WSAAsyncGetServByName аналогична функции WSAAsyncGetHostByName, которую вы видели в модуле Finger. Обе функции возвращают дескриптор задачи для соответствующих асинхронных операций. Когда функция WSAAsyncGetServByName завершается, она копирует информацию о службе в буфер, на который указывает параметр buf. Параметры функции AsyncGetServicelnfo Параметры функции WSAAsyncGetServByName определяют и параметры функции AsyncGetServicelnfo. Другими словами, поскольку AsyncGetServicelnfo использует функцию WSAAsyncGetServByName для получения информации о сетевой службе, параметры функции AsyncGetServicelnfo в основном совпадают с параметрами функции WSAAsyncGetServByName. Функция AsyncGetServicelnfo предполагает, что вы не хотите, чтобы все сообщения посылались в главное окно Sockman. Поэтому вам нужно посылать в AsyncGetServicelnfo дескриптор окна. Другой параметр AsyncGetServicelnfo идентифицирует запрошенную службу дескриптором задачи (в нашем случае, TASK_ASYNC_FINGER). Проверка ошибок в функции AsyncGetServicelnfo Следующий фрагмент кода содержит проверку, которую производит функция AsyncGetServicelnfo для функции WSAAsyncGetServByName: HTASK hTask = WSAAsyncGetServByName (hwnd, WM_GOT_SERVICE, (LPSTR)IPSERVICE_FINGER/ NULL, (LPSTR)szFingerBuffer, sizeof(szFingerBuffer)); if (!hTask) { LPARAM 1Param; hTask = hService; // Возвращаем идентификатор // сетевой службы IParam = MAKELONG(0, WSAGetLastError()); // Запоминаем // код ошибки PostMessage(hwnd, WM_GOT_SERVICE, hService, IParam); } 332 
Функция AsynoGetServiceliifo Прежде всего функция AsyncGetServicelnfo проверяет дескриптор задачи (hTask), который возвращает функция WSAAsyncGetServByName. Если этот дескриптор задачи равен NULL, AsyncGetServicelnfo с помощью макроса MAKELONG создает сообщение об ошибке. Когда AsyncGetServicelnfo использует макрос MAKELONG, он сохраняет в старших 16 битах локальной переменной IParam код ошибки, который возвращает функция WSAGetLastError. (Эта функция использует локальную переменную IParam при эмулировании сообщения об ошибке того же типа, который возвращают многие асинхронные функции Winsock.) Наконец, функция AsyncGetServicelnfo использует функцию Windows PostMessage, чтобы послать сообщение, содержащее код ошибки, главному окну Sockman (который идентифицируется дескриптором окна hwndSockman). Разумеется, если никакой ошибки не было, функция AsyncGetServicelnfo возвращает дескриптор задачи асинхронного процесса в вызывающую функцию. Возвращаясь к DoWinsockProgram Когда AsyncGetServicelnfo возвращает управление функции DoWinsockProgram, эта функция сохраняет дескриптор задачи асинхронного получения информации о службе в глобальной переменной hFingerTask, как показано ниже: if (wParam == IDM_FINGER_ASYNC) hFingerTask = AsyncGetServicelnfo(hwnd, TASK_ASYNC__FINGER); else { // Пользователь выбрал блокирующий Finger // IDM_FINGER_BLOCKING hFingerTask = TASK__BLOCK__F INGER; FingerHostBlocking(); // По окончании операции Finger глобальный дескриптор // задания Finger сбрасывается в ноль, и пользователь // может начать новое задание. hFingerTask = 0; } Функция DoWinsockProgram не проверяет переменную hFingerTask, которая содержит либо значение NULL (если функция AsyncGetServicelnfo не смогла инициировать асинхронную операцию), либо дескриптор задачи по асинхронному получению информации о сетевой службе. Если дескриптор задачи равен NULL, функция DoWinsockProgram не меняет переменную hFingerTask, поскольку ее значение и так свидетельствует об отсутствии выполнения операции. Вам может показаться непонятным, почему функция DoWinsockProgram сохраняет неизменное значение TASK_BLOCK_FINGER в переменной hFingerTask. Если hFingerTask содержит значение NULL, это означает, что операция не производится. Однако, как мы увидим далее, чтобы узнать номер порта для 333 
Глава 12: Дескрипторы задач Winsock Finger, можно использовать другое средство, помимо базы данных сетевых служб. Как вы помните из программы QFinger, единственное значение, которое QFinger извлекает из записи о службе, это порт протокола Finger. Кроме того, вы, вероятно, помните, что Windows Sockets определяет константы для номеров портов известных сетевых служб, в том числе и для Finger. Вот почему в утилите Finger можно не прекращать Finger-операцию лишь потому, что Winsock не смог прочитать запись об этой службе в базе данных. Можно продолжить работу, используя официальный номер порта, записанный в файле заголовка winsock.h. Использование в программе Sockman переменной hFingerTask и константы TASK_BLOCK_FINGER станет вам понятнее по мере того, как мы будет двигаться дальше. В частности, вы узнаете, что функция WndProc использует hFingerTask и TASKJBLOCKJFINGER для идентификации задачи Finger и продолжения Finger-операции. Изменение функции WndProc Как вы знаете, асинхронные функции Winsock, такие как WSAAsyncGetServByName, заставляют Windows послать сообщение-уведомление сразу, как только асинхронная операция закончит свою работу. (WSAAsyncGetServByName посылает это уведомление тому окну, на которое указывает ее первый параметр.) В приложении Sockman Windows посылает все такие сообщения функции WndProc. Давайте еще раз посмотрим на вызов функция AsyncGetServicelnfo, который происходит в функции DoWinsockProgram: hTask = WSAAsyncGetServByName(hwnd,WM_GOT_SERVICE, (LPSTR) IPSERVICE_FINGER, NULL, (LPSTR)szFingerBuffer, sizeof(szFingerBuffer)); Как уже обсуждалось выше, функция AsyncGetServicelnfo является лишь оболочкой для функции WSAAsyncGetServByName. Поэтому функция AsyncGetServicelnfo передает все свои параметры прямо в функцию WSAAsyncGetServByName. В свою очередь, WSAAsyncGetServByName использует первый параметр, переданный ей от AsyncGetServicelnfo, как идентификатор сообщения. В этом случае рассмотренный ранее вызов функции AsyncGetServicelnfo приведет к посылке сообщения Windows с идентификатором WM__GOT_SERVICE функции WndProc. Windows пошлет это сообщение, когда работа функции WSAAsyncGetServByName завершится. Другими словами, как только WSAAsyncGetServByName извлечет запись о сетевой службе Finger из базы данных, Windows пошлет сообщение WM_GOT_SERVICE функции WndProc. Чтобы обработать это сообщение, вы должны добавить в функцию WndProc, содержащуюся в файле SOCKMAN3.CPP, следующий фрагмент кода для ветви WM_GOT_SERVICE: long FAR PASCAL _export WndProc(HWND hwnd, UINT iMessage, UINT wParam, 334 
Функция LookupFingerHost LONG 1Param) { switch (iMessage) { case WM_GOT__SERVIСE: if (wParam == hFingerTask) LookupFingerHost(lParam); return(0); Как видим, когда функция WndProc получает сообщение WM_GOT_SERVICE, она проверяет значение wParam, в котором содержится дескриптор задачи асинхронной операции Winsock. В приведенном фрагменте кода функция WndProc проверяет wParam, чтобы выяснить, не равно ли оно значению глобальной переменной, в которой хранится дескриптор задачи Finger. Если wParam совпадает с этим значением, функция WndProc вызывает функцию LookupFingerHost, которая пытается найти хост, указанный пользователем по его имени или адресу в формате «десятичное с точкой». Примечание: Далее в этой главе вы узнаете, почему следует идентифицировать асинхронные операции Winsock, запущенные посылкой сообщения Windows. Функция LookupFingerHost Главная цель функции LookupFingerHost — инициировать асинхронный поиск имени или адреса хоста, который ввел пользователь Sockman. Вот как определена функция LookupFingerHost: VOID LookupFingerHost(LPARAM lError) { // Sockman использует WSAAsyncGetServByName, чтобы // заполнить szFingerBuffer информацией о сетевой службе // Finger. LookupFingerHost должна получить номер // порта Finger из szFingerBuffer, прежде чем буфер можно // будет снова использовать для хранения информации о // сетевом компьютере. if (WSAGETASYNCERROR(lError)) // Если возникает nFingerPort = htons(IPPORT_FINGER); // ошибка, берем // официальный номер порта else nFingerPort = ((LPSERVENT)szFingerBuffer)->s_port; hFingerTask = LookupHostAsync(hwndSockman, szFingerHost, szFingerBuffer, (LPDWORD)&dwFingerAddr); 335 
Глава 12. Дескрипторы задач Winsock if (IhFingerTask) { wsprintf(szFingerBuffer, "Lookup failed: %s", (LPSTR)szFingerHost); MessageBeep(O); MessageBox(hwndSockman, szFingerBuffer, "SockMan - FINGER", MB_OKIMB_ICONSTOP); } return; Функция LookupFingerHost использует глобальную переменную szFingerBuffer для хранения данных о хосте, которые возвращает асинхронная операция. Как вы, вероятно, заметили, функция AsyncGetServicelnfo также использует переменную szFingerBuffer. (Функция AsyncGetServicelnfo сохраняет в этой переменной информацию о сетевой службе.) В результате, прежде чем функция LookupFingerHost вновь сможет использовать szFingerBuffer для хранения информации о хосте, эта функция должна сохранить требуемую информацию о сетевой службе где-то в другом месте. Единственный элемент данных из базы данных сетевых служб, который требуется для Finger-операции, — это порт протокола Finger. Функция LookupFingerHost использует макрос WSAGETASYNCERROR для проверки параметра 1Еггог и обнаружения ошибок Winsock. Параметр lError будет содержать код любой ошибки, которая случилась во время доступа программы Sockman к базе данных сетевых служб. Если LookupFingerHost обнаруживает ошибку, функция сохраняет номер порта по умолчанию, IPPORT_FINGER, в глобальной переменной для номера порта Finger, nFingerPort. Если ошибок не произошло, LookupFingerHost использует элемент s_port структуры записи из базы данных. Сохранив порт протокола Finger в переменной nFingerPort, функция LookupFingerHost использует функцию LookupHostAsync для инициализации асинхронного поиска хоста. Как уже обсуждалось выше в этой главе, функция LookupHostAsync возвращает дескриптор задачи функции асинхронного поиска. Функция LookupFingerHost сохраняет этот дескриптор задачи в глобальной переменной hFingerTask, которая обслуживает дескриптор текущей асинхронной задачи активной Finger-операции. Если дескриптор задачи, возвращаемый функцией LookupHostAsync, равен NULL, функция LookupFingerHost выводит на экран окно сообщения, предупреждающее пользователя о том, что поиск в DNS не привел к успеху. Опять-таки, после того как программа инициализирует асинхронную операцию (функция LookupFingerHost вызывает функцию LookupHostAsync), Sockman должен дождаться сообщения от Windows прежде, чем программа сможет продолжить работу. Как вам известно, функция LookupHostAsync заставит Windows сгенерировать сообщение WM_ASYNC LO ОKUP_DONE, когда эта функция завершит операцию асинхронного поиска. Поэтому следующее изменение исходного кода Sockman, которое вам нужно будет сделать, — добавление фрагмента кода в функцию WndProc для обработки сообщения WM_ASYNC_LOOKUP_DONE, порождаемого Finger-операцией поиска в DNS. 336 
Еще раз изменяем функции WndProc Еще раз изменяем функции WndProc Как вы знаете, функция LookupHostAsync заставляет Windows породить сообщение WM_ASYNC_LOOKUP_DONE, когда функция завершает операцию асинхронного поиска. В версии Sockman2 функция WndProc обрабатывала как сообщение WM ASYNC LOOKUP DONE, так и сообщение WM_BLOCK_LOOKUP_DONE вызовом функции DisplayHostEntry: case WM_ASYNC_LOOKUP_DONE: case WM_BLOCK_LOOKUP_DONE: DisplayHostEntry(lParam); return(0); Однако когда функция LookupHostAsync выполняет асинхронный поиск для Finger-операции, вам не нужно, чтобы результаты этого поиска выводились на монитор, как было в случае программы QLookup. Вместо этого ваша программа должна передать IP-адрес другим функциям, которые воспользуются им для завершения Finger-операции. Если вы только начинаете пользоваться асинхронными функциями Winsock, вам может показаться неясным, как выполнить такое действие. Ключевой пункт асинхронных функций баз данных в Winsock API — это то, как Winsock использует дескрипторы задач. Решение проблемы Рассмотрим проблему, с которой вы столкнулись в текущей реализации программы Sockman. Вы использовали функцию LookupHostAsync для асинхронного поиска адреса Интернет в двух разных командах меню. Во-первых, Sockman вызывает функцию LookupHostAsync, когда пользователь хочет произвести асинхронный поиск и выбирает опцию Async Lookup пункта Lookup меню Utilities. Во-вторых, как вы только что видели, программа вызывает функцию LookupHostAsync для поиска адреса Интернет в модуле Finger. В обоих случаях функция LookupHostAsync заставляет Windows сгенерировать одно и то же сообщение, WM_ASYNCJLOOKUP_DONE. Однако вы хотите, чтобы в каждом из этих двух случаев выполнялись разные фрагменты программы. Поэтому вам необходимо знать, какая асинхронная операция привела к появлению сообщения WM_ASYNC__LOOKUP_DONE. Чтобы получить эту информацию, нужно обратить внимание на параметр wParam этого сообщения. Как вы уже знаете, многие асинхронные функции Winsock требуют, чтобы вы указывали идентификатор сообщения при вызове функции. Windows посылает сообщение с этим идентификатором, когда асинхронная операция завершает свою работу. Любая асинхронная функция Winsock, которая требует указания идентификатора сообщения, возвращает дескриптор задачи. Когда 337 
Глава 12. Дескрипторы задач Winsock ' функция заставляет Windows сгенерировать нужное сообщение, она также требует, чтобы Windows сохранила дескриптор задачи в параметре wParam. Проверив значение wParam полученного сообщения, вы можете определить, какая асинхронная операция Winsock вызвала появление этого сообщения. Ранее, Sockman вызывал функцию DisplayHostEntry для обоих сообщений WM_ASYNC_LOOKUP_DONE и WM_BLOCK_LOOKUP_DONE. Например, чтобы реализовать в Sockman Finger-операции, вы должны изменить ветви WM_ASYNC_LOOKUP_DONE и WM_BLOCK_LOOKUP_DONE в функции WndProc: long FAR PASCAL _export WndProc (HWND hwnd, UINT iMessage, UINT wParam, LONG 1Param) { switch (iMessage) { case WM_ASYNC__LOOKUP_DONE: if (wParam == hAsyncLookupTask) DisplayHostEntry(IParam); if (wParam == hFingerTask) { PaintWindow("Asynchronous lookup for Finger completed."); FingerHostAsync(IParam); hFingerTask = 0; } return(0); case WM_BLOCK_LOOKUP_DONE: if (wParam == TASK_BLOCK_LOOKUP) DisplayHostEntry(IParam); if (wParam == TASK_BLOCK__FINGER) PaintWindow("Blocking lookup for Finger completed."); return(0); Вместо одной функции DisplayHostEntry, вызываемой в обоих случаях, Sockman теперь заключает два вызова функции DisplayHostEntry внутри двух операторов if, которые проверяют значение параметра wParam полученного сообщения. Другими словами, эти изменения позволяют функции WndProc в Sockman определить, какая асинхронная операция Winsock вызвала появление сообщения Windows. 338 
Выполнение асинхронной Finger-операции Выполнение асинхронной Finger-операции Sockman хранит дескриптор текущей асинхронной задачи для Finger-операции в глобальной переменной hFingerTask. Как видно из функции DoWinsockProgram, Sockman также использует константу TASK_BLOCK_FINGER для идентификации асинхронных Finger-операций. Как показано ниже, когда функция WndProc получает от Windows сообщение WM_ASYNC_LOOKUP_DONE, функция проверяет значение wParam, чтобы определить, принадлежит ли дескриптор задачи Finger-операции. Если wParam содержит TASK_BLOCK_FINGER или то же самое значение, что и дескриптор задачи Finger (hFingerTask), WndProc вызывает функцию FingerHostAsync: if (wParam == hFingerTask) { PaintWindow("Asynchronous lookup for Finger completed."); FingerHostAsync(lParam); hFingerTask = 0; } Функция FingerHostAsync — это последний шаг в реализации асинхронной Finger-операции в программе Sockman. Поэтому, когда функция FingerHostAsync возвращает управление, WndProc обнуляет значение hFingerTask, чтобы указать тем самым, что Finger-операция завершилась (вспомните, что Sockman позволяет выполнять только одну Finger-операцию в один момент времени). Функция FingerHostAsync Sockman3 предоставляет возможность производить как асинхронные, так и блокирующие Finger-операции. Сообщение WM_ASYNC_LOOKUP_DONE от асинхронной Finger-операции заставляет функцию WndProc вызвать функцию FingerHostAsync. Как вы увидите ниже, операции с сокетом для асинхронных и блокирующих Finger-операций ничем не отличаются. Функция, которая выполняет саму Finger-операцию, называется DoFingerOperation. Как асинхронные, так и блокирующие Finger-операции вызывают функцию DoFingerOperation. Однако асинхронные и блокирующие Finger-операции по-разному ведут себя до вызова DoFingerOperation. Для асинхронных Finger-операций функция DoFingerOperation вызывается из функции FingerHostAsync. Для блокирующих Finger-операций функция DoFingerOperation вызывается из функции FingerHostBlocking. В асинхронной Finger-операции Sockman находит IP-адрес сетевого компьютера пользователя непосредственно перед тем, как вызывает функцию FingerHostAsync. Если происходит какая-либо ошибка, Windows сохраняет код ошибки в сообщении WM ASYNC LOOKUP DONE, которое приводит к вызову функции FingerHostAsync. (Внутри этого сообщения Windows сохраняет код ошибки в старших 339 
Глава 12. Дескрипторы фа \ Witi$oek 16 битах переменной lParam.) Функция FingerHostAsync проверяет, в свою очередь, значение lParam, которое внутри этой функции называется lError: BOOL FingerHostAsync(LPARAM lError) { int nErr; // Код ошибки асинхронного поиска в DNS if (nErr = WSAGETASYNCERROR(lError)) { MessageBeep(0); PaintWindow( "Async lookup failed! Trying a blocking finger."); hFingerTask = TASK_BLOCK_FINGER; FingerHostBlocking(); hFingerTask = 0; return(FALSE); } // Нам доступны все необходимые для Finger-операции // данные, поэтому выполняем ее. return(DoFingerOperation() ) ; } Как показано в этом примере, если процесс асинхронного поиска не привел к успеху, Sockman выводит окно сообщения, уведомляя пользователя об этом. Затем Sockman пытается произвести блокирующий поиск адреса. Только если блокирующий поиск также не приведет к успеху, Sockman оставляет попытки произвести Finger-операцию. Если Sockman успешно находит IP-адрес либо асинхронно, либо с помощью блокирующей функции, функция FingerHostAsync вызывает функцию DoFingerOperation. В этот момент асинхронный модуль Finger собрал всю информацию, необходимую ему для выполнения собственно Finger-операции. Поэтому мы можем перейти теперь к изучению этой операции. Кроме того, в следующем разделе мы вернемся к функции DoWinsockProgram, чтобы вы могли освежить в памяти шаги, из которых состоит блокирующая F inger-операция. Выполнение блокирующей Finger-операции Как вы уже знаете, функция DoWinsockProgram проверяет аргумент wParam из сообщения Finger, чтобы определить, выбрал пользователь асинхронную!или блокирующую Finger-операцию. Если wParam равен IDM_FINGER_ASYNC, пользователь выбрал асинхронную Finger-операцию. В противном случае DoWinsockProgram заключает, что пользователь выбрал блокирующий вариант. Для блокирующей Finger-операции функция DoWinsockProgram выполняет следующий фрагмент кода после оператора else: 340 
.. •. -+.Л-Н «, « . П. .. ..у. . V - • Etpp* if (wParam == IDM_FINGER_ASYNC) hFingerTask = AsyncGetServicelnfо(hwnd, TASK_ASYNC__FINGER); else { // Пользователь выбрал блокирующий Finger // IDM_FINGER_BLOCKING hFingerTask = TASK_BLOCK_FINGER; FingerHostBlocking(); // По окончании операции Finger глобальный дескриптор // задания Finger сбрасывается в ноль, и пользователь // может начать новое задание. hFingerTask = 0; } Прежде всего функция DoWinsockProgram устанавливает переменную, хранящую дескриптор задачи Finger (hFingerTask), равной TASK_BLOCK_FINGER, указывая тем самым, что в данный момент выполняется Finger-операция. Как мы говорили выше, установка этой переменной-флага не позволит пользователю инициализировать другую Finger-операцию, пока первая не закончит работу. Затем функция DoWinsockProgram вызывает функцию FingerHostBlocking. (Функция FingerHostBlocking аналогична функции FingerHostAsync в том, что обе функции являются оболочками функции DoFingerOperation, которая выполняет собственно сетевую Finger-операцию.) Когда функция FingerHostBlocking завершает работу, функция DoWinsockProgram приравнивает переменную, хранящую дескриптор задачи Finger (hFingerTask), нулю, чтобы показать, что Finger-операция завершилась. Функция FingerHostBlocking Как вы уже знаете, и асинхронные и блокирующие функции Finger вызывают функцию DoFingerOperation, чтобы выполнить Finger-операцию в сети через сокет. Поэтому программные модули как для асинхронной, так и для блокирующей Finger-операции должны получать одну и ту же информацию — правильный IP-адрес и номер порта, с которого обслуживаются Finger-запросы, — прежде чем они смогут вызвать функцию DoFingerOperation. Чтобы получить эту информацию, блокирующая Finger-операция использует блокирующие функции. Как показано в нижеследующем отрывке кода, функция FingerHostBlocking сначала находит IP-адрес вызовом функции LookupHostBlocking. Затем функция FingerHostBlocking использует функцию getservbyname, синхронную функцию Winsock, чтобы получить указатель на запись о службе в базе данных сетевых служб. Затем, если функция getservbyname отработала успешно, FingerHostBlocking сохраняет номер порта Finger в глобальной переменной nFingerPort. Если функция getservbyname не достигла успеха, FingerHostBlocking использует официальный номер порта для Finger, определенный в файлезаголовке winsock.h (IPPORTJFINGER): 341 
BOOL FingerHostBlocking(VOID) { LPSERVENT IpServiceEntry; // Структура с информацией о //сетевой службе if (!LookupHostBlocking(hwndSockman, szFingerHost, (LPSTR) &szFingerBuf fer, TASK_BLOCK_FINGER) ) return(FALSE); // He удалось получить сетевой // адрес компьютера if ((IpServiceEntry = getservbyname (" finger", NULL)) == NULL) nFingerPort = htons (IPPORT_FINGER) ; // Берем // официальный // номер порта else nFingerPort = lpServiceEntry-»s_port ; return(DoFingerOperation()); } После этого, если все прошло нормально, FingerHostBlocking вызывает DoFingerOperation. Функция DoFingerOperation Ниже приведено определение функции DoFingerOperation. Как видите, функция DoFingerOperation очень похожа на программу QFinger, с которой мы работали в главе 10: BOOL DoFingerOperation(VOID) { LPHOSTENT pHostEntry; // // SOCKET nSocket; // // SOCKADDR_IN sockAddr; // int nErr; // int nCharSent; // int nCharRecv; // BOOL bOkay = FALSE; // int nLength =0; // // // Буферы данных Char szUser[MAX_HOST_NAME+MAX_USER_NAME+3]; // Буфер Структура информации о хосте Интернет Номер сокета, используемого этой программой Структура адреса сокета Код ошибки Число принятых символов Число переданных символов Код статуса Finger-операции Размер возвращаемой информации Finger 342 
Функция DoFingerOperation // запроса Finger char szFingerlnfo[MAX_JPRINT_BUFFER+1]; // Буфер // результатов Finger if ( (nSocket = socket (AF__INET, SOCK_STREAM, DEFAULT_PROTOCOL)) != INVALID_SOCKET) { lpHostEntry = (LPHOSTENT)szFingerBuffer; sockAddr. sin__family = AF__INET; // Семейство адресов // Интернет sockAddr. sin__port = nFingerPort ; sockAddr.sin_addr = * ( (LPIN_ADDR) *lpHostEntry->h_addr_list) ; if (I connect(nSocket, { (SOCKADDR *)&sockAddr, sizeof(sockAddr))) wsprintf(szUser, "%s\r\n", (LPSTR)szFingerUser); if ((nCharSent = send(nSocket, szUser, lstrlen(szUser), NO__FLAGS) ) I = SOCKET_ERROR) { do { nCharRecv = recv(nSocket, (LPSTR)&szFingerInfо[nLength], sizeof(szFingerInfo) - nLength, NO_FLAGS); nLength+=nCharRecv; } while (nCharRecv > 0); closesocket(nSocket); if (nCharRecv != SOCKET_ERROR) { // Значение NULL стоит в конце буфера Finger szFingerlnfo[nLength] = '\0'; // Устанавливаем длину буфера равной нулю szScratchBuffer[0] = '\0'; wsprintf(szScratchBuffer, "FINGER:\t%s@%s\n\n”, (LPSTR)szFingerUser, (LPSTR)szFingerHost); lstrcat(szScratchBuffer, szFingerlnfo); PaintWindow(szScratchBuffer); MessageBeep(O); bOkay = TRUE; 343 
Глава 12. Дескрипторы задан Wins } } } } if (!bOkay) { nErr = WSAGetLastError(); closesocket(nSocket); wsprintf(szFingerlnfo, "Error number %d", nErr); MessageBeep(O); MessageBox(hwndSockman, szFingerlnfo, "Finger Operation", MB__OK|MB_ICONSTOP) ; } return(bOkay); } Мы не будем повторять здесь подробно то, о чем уже говорилось в главе 10. В следующих абзацах кратко описаны основные этапы работы функции DoFingerOperation. Более подробное описание этих этапов вы найдете в соответствующих разделах главы 10. 1. Sockman запрашивает дескриптор сокета от Windows Sockets, вызывая функцию socket. 2. Чтобы определить адрес сокета, Sockman использует номер протокола, хранящийся в nFingerPort, и адрес Интернет, хранящийся в szFingerBuffer. 3. Sockman вызывает функцию Winsock connect, чтобы соединить сокет (nSocket) с удаленным компьютером. 4. Sockman использует переменные szUser и szFingerUser, чтобы создать Finger-запрос. Затем с помощью функции Winsock send этот запрос посылается на удаленный компьютер. 5. Sockman использует функцию Winsock recv в цикле do-while, чтобы получить Finger-информацию, посылаемую удаленным компьютером через сокет. 6. Sockman закрывает сокет и выводит на экран результаты операции. Finger-операция в программе Sockman в действительности кончается, когда завершает работу функция DoFingerOperation. Если пользователь запустил асинхронную Finger-операцию, DoFingerOperation возвратится в функцию Finger Host Async, которая вызвала DoFingerOperation. Если же пользователь выбрал блокирующую Finger-операцию, DoFingerOperation возвратится в функцию FingerHostBlocking. 344 
Общая картина Общая картина Теперь настало время вспомнить, как Sockman обращается с дескриптором задач для Finger-операции. Как вы уже знаете, Sockman использует глобальную переменную hFingerTask в различных функциях, входящих в Finger-модули программы. Как уже упоминалось выше, для понимания асинхронных функций поиска в базах данных вы должны понимать, как Winsock использует дескрипторы задач. Обзор выполнения Finger-операции Ниже приводится краткое перечисление этапов, выполняемых программой Sockman3 для асинхронной операции. Читая описание этих этапов, обратите внимание на то, как Sockman использует переменную hFingerTask, хранящую дескриптор задачи Finger: 1. Sockman проверяет переменную с дескриптором задачи Finger, hFingerTask, чтобы не допустить запуска второй Finger-операции во время выполнения первой. 2. Sockman вызывает функцию FingerDialog, чтобы запросить у пользователя адрес хоста и идентификатор пользователя удаленного компьютера. 3. Sockman вызывает функцию AsyncGetServicelnfo, чтобы получить из базы данных сетевых служб номер протокола Finger, и сохраняет дескриптор задачи этой асинхронной операции в переменной с дескриптором задачи Finger, hFingerTask. 4. Sockman ждет сообщения WM_GOT_SERVICE, которое сигнализирует о завершении асинхронной операции. 5. Как только Sockman получает сообщение WM_GOT_SERVICE, он проверяет 16-битный параметр этого сообщения, в котором содержится дескриптор задачи процесса, сгенерировавшего сообщение WM_GOT_SERVICE. Если дескриптор задачи соответствует переменной, хранящей дескриптор задачи Finger, hFingerTask, Sockman вызывает функцию LookupFingerHost. 6. Sockman извлекает номер протокола Finger из буфера данных, который заполняет функция AsyncGetServicelnfo. Затем Sockman вызывает функцию LookupHostAsync, чтобы асинхронно найти адрес хоста, указанного пользователем. Sockman сохраняет дескриптор задачи этой асинхронной операции также в переменной hFingerTask. 7. Sockman ждет сообщение WM_ASYNC_LOOKUP_DONE, которое сигнализирует завершение асинхронной операции поиска в DNS. 8. Как только Sockman получает сообщение WM_ASYNC_LOOKUP_DONE, он проверяет 16-битный параметр этого сообщения, в котором содержится дескриптор задачи процесса, сгенерировавшего сообщение 345 
Глава 12. Дескрипторы задач Winsock WM_ASYNC_LOOKUP_DONE. Если дескриптор задачи совпадает со значением переменной, хранящей дескриптор задачи Finger, hFingerTask, Sockman вызывает функцию FingerHostAsync. 9. Чтобы убедиться, что при асинхронной операции поиска в DNS не произошло никаких ошибок, Sockman проверяет старшие 16 бит в 32-битном параметре сообщения WM_ASYNCJLOOKUP_DONET. Если ошибок не было, Sockman вызывает функцию DoFingerOperation. 10. Sockman выполняет Finger-операцию с помощью синхронных функций Winsock на блокирующем сокете. Подводя итоги Как вы знаете, асинхронные функции базы данных, извлекающие информацию из Интернет, посылают сообщение по завершении своей работы. Асинхронные функции базы данных включают свой дескриптор задачи в 16-битный параметр посылаемого сообщения. Sockman проверяет 16-битный параметр сообщения, чтобы определить, какая асинхронная функция базы данных сгенерировала сообщение. Проверяя значение дескриптора задачи, можно различать функции, посылающие одно и то же сообщение, что позволяет запускать несколько функций одновременно и для каждой выполнять различные действия. Разрабатывая более сложные программы для работы в Интернет, помните, как Winsock использует дескрипторы задач асинхронных функций базы данных. Возможно, вам придется применить похожий принцип в своей собственной программе. Иначе говоря, вы можете использовать дескрипторы задач для идентификации своих собственных операций Winsock. Прежде чем мы перейдем к главе 13, проверьте, хорошо ли вы усвоили следующие ключевые концепции: ✓ Большинство асинхронных функций Winsock возвращают дескриптор задачи. S Ваши программы могут использовать дескрипторы задач Winsock для идентификации сообщений Windows, которые посылаются асинхронными функциями Winsock. S Ваши программы могут использовать макрос под названием WSAGETASYNCERROR, чтобы извлекать коды ошибок из сообщений Windows, посылаемых асинхронными функциями Winsock. 
Г л a Время и сетевой порядок байтов Когда вы создаете программы для использования на каком-то компьютере (например, на персональном), вы, как правило, не уделяете большого внимания тому, как компьютер хранит в себе ваши данные. Однако когда вы создаете программы, ориентированные на использование сети, вы должны выяснить не только как хранятся те или иные данные на вашем компьютере, но и как те же данные хранятся на других компьютерах, входящих в сеть. Например, представьте, что вы проиграли пари на один доллар своему приятелю, пользователю Макинтоша по имени Ларри. Чтобы дать знать Ларри о том, что вы собираетесь заплатить эту сумму, вы создаете на своем персональном компьютере специальную сетевую программу, которая посылает Ларри такое сообщение: Ларри, я должен тебе $ 1 Кен 347 
Глава 13. Бремя и сетевой порядок байтов Представьте свое удивление, когда Ларри позвонит вам на следующий день и сообщит восторженным тоном, что экран его Макинтоша показывает следующий текст: JIappUj я должен тебе $512 Кен Отправленное и принятое сообщения отличаются потому, что Макинтош и персональный компьютер на базе Intel хранят числовые данные совершенно по-разному. Поэтому, когда вы создаете программы для работы в Интернет, вам нужно помнить, что компьютеры по-разному хранят числовые данные. Хотя все компьютеры используют один или несколько байтов для представления численных значений, сами эти последовательности, представляющие одно и то же число, у разных компьютеров отличаются (последовательность хранения численных данных в памяти компьютера называется порядком байтов этого компьютера, byte-order). В этой главе мы будем использовать один из простейших протоколов Интернет, Time Protocol (протокол времени), чтобы ближе познакомиться с проблемами, связанными с порядком байтов. К концу этой главы вы должны хорошо владеть следующими важными понятиями: ♦ Чем и как порядок байтов в компьютере отличается от порядка байтов в сети. ♦ Как преобразовать порядок байтов на компьютере в порядок байтов в сети и обратно. ♦ Почему для избежания проблем нужно внимательно следить за преобразованиями типов данных, получаемых из Интернет. ♦ Какие проблемы могут быть вызваны смешиванием значений со знаком и без знака. Сетевой порядок байтов Поскольку Интернет объединяет большое количество самых разных компьютеров, в этой сети принят стандартный формат хранения числовых данных. Профессионалы Интернет называют этот формат сетевым порядком байтов. Стандартный сетевой порядок байтов позволяет минимизировать ошибки, вызванные неправильным преобразованием данных, передаваемых с хостов одного типа на хосты другого типа. Чтобы отличить сетевой порядок байтов и порядок байтов, который обычно используется в компьютере, подсоединенном к сети, порядок байтов в компьютере часто называют порядком байтов хоста. На персональном компьютере порядок байтов хоста отличается от сетевого порядка байтов. Поэтому, прежде чем ваша программа для персонального компьютера сможет передать числовое значение в модуль программы, отвечающий за связь с Интернет, она должна преобразовать это значение, изменив в нем порядок 348 
байтов хоста на сетевой порядок байтов. Подобным образом, когда ваша программа получает числовое значение из Интернет, она должна преобразовать это значение, изменив в нем сетевой порядок байтов на порядок байтов хоста. Примечание: Вашей программе не нужно делать никаких преобразований над численными данными, хранящимися в виде текста в датаграмме или сообщении. Другими словами, преобразование порядка байтов требуется, только когда программы для работы с сетью воспринимают данные как числовые. Практически все бизнес-приложения используют числовые значения. То же самое относится и к сетевым бизнес-приложениям, работающим в Интернет. Если даже они не предназначены для денежных операций, они все равно будут иметь дело с какими-то числовыми значениями — объемами заказов и продаж, инвентарными номерами и т. п. Если ваша программа передает численные данные по Интернет, вы должны хорошо разбираться в проблемах порядка байтов. Ни владение основными понятиями, связанными с порядком байтов, ни решение практических проблем не представляют больших трудностей. Однако на первых порах, возможно, практические аспекты порядка байтов покажутся вам несколько запутанными. Протоколы времени Интернет Протоколы времени разрабатываются в Интернет для достижения двух разных целей. Эти протоколы предназначены либо для синхронизации времени на компьютерах сети, либо для сообщения информации о времени пользователям. На сегодняшний день в Интернет используются четыре разных протокола времени: Time Protocol, Daytime Protocol, Network Time Protocol (NTP) и Simple Network Time Protocol (SNTP). Два из этих протоколов — Daytime Protocol и Time Protocol позволяют узнать текущее время из Интернет. Как мы увидим ниже, протоколы NTP и SNTP предназначены для совсем других целей. RFC 1305 под названием «Спецификация протокола сетевого времени, версия 3» (Network Time Protocol (Version 3) Specification, Mills, 1992) определяет протокол NTP, который позволяет точно синхронизировать время на хостах Интернет. Другими словами, этот протокол помогает установить внутренние часы на двух или более компьютерах в Интернет в одно и то же значение. В RFC 1305 описаны сложные алгоритмы, используемые в NTP для точной синхронизации времени с отклонением в пределах от 1 до 50 миллисекунд от официального стандарта времени. RFC 1361 под названием «Простой протокол сетевого времени» (Simple Network Time Protocol, Mills, 1992) определяет протокол SNTP как упрощенную версию протокола NTP. Несмотря на то, что на практике SNTP дает ту же точность, что и NTP, в отличие от NTP SNTP не гарантирует этой точности. Системы, которым не нужна гарантированная точность, обеспечиваемая протоколом NTP, чаще всего пользуются протоколом SNTP. 349 
Глава 13* Время и сетевой порядок байтов В отличие от NTP и SNTP протоколы Daytime и Time устроены очень просто. Эти протоколы, как описывается в RFC 867 под названием «Протокол времени суток» (Daytime Protocol, Postel, 1983) и в RFC 868 под названием «Протокол времени» (Time Protocol, Postel and Harrenstien, 1983), дают текущее время с погрешностью в 1 секунду. Протокол Time Protocol возвращает 32-битное число, которое представляет время в секундах, истекшее с 1 января 1900. Протокол Daytime Protocol возвращает текстовую строку в формате, поддающемся прочтению пользователем. Протокол Daytime Protocol не позволяет указывать формат этой текстовой строки; как правило, строка эта будет иметь следующий вид («\г\п» означает пару символов «возврат каретки» и «перевод строки»): Thu Jan 19 15:54:25 1995\r\n Как следует из вышеизложенного, разница между двумя группами протоколов времени весьма значительна. Так, в RFC 1305 описание алгоритмов, используемых в NTP, занимает свыше 100 страниц, в то время как RFC 867 и 868 каждый умещаются на паре страниц. Разница в длине между этими RFC говорит о том, что сложность этих двух групп протоколов несопоставима. В этой главе мы будем пользоваться протоколом Time Protocol, описанном в RFC 868, для знакомства с проблемами сетевого порядка байтов. Другие протоколы времени в этой книге не описываются. Протокол времени Если вы используете персональные компьютеры уже несколько лет, вы, возможно, помните, что были времена, когда в персональных компьютерах не было внутренних часов. В самых первых компьютерах этого класса пользователь должен был вручную вводить текущую дату и время каждый раз, когда он включал компьютер и загружал операционную систему. Если вы застали эти времена, то вы, вероятно, помните также и проблемы, которые могла вызвать неустановка часов компьютера. Если у вас файлы с одинаковыми именами располагались в разных каталогах, то по дате и времени этих файлов нельзя было судить о том, какой из них был последней версией, а какой — более старой. В наши дни неправильные показания часов персонального компьютера могут вызвать еще более серьезные проблемы. Большинство программ для персональных компьютеров полагаются на значение времени, сообщаемое часами компьютера, и используют его в своих вычислениях. Если часы компьютера работают неправильно, то программы могут давать неверные результаты. Если одна и та же программа работает на нескольких компьютерах и все копии этой программы пользуются одними и теми же данными, то на этих компьютерах может понадобиться синхронизировать встроенные часы. Для синхронизации часов компьютеры могут использовать узел сети, на котором работает сервер времени. Программы серверов времени, использующие Time Protocol, определенный в RFC 868, сообщают текущую дату и время с точностью до одной секунды программам-клиентам. 350 
Протокол Time Protocol может использовать любой из транспортных протоколов в наборе TCP/IP. Другими словами, чтобы передать данные от сервера клиенту, используя цротокол Time Protocol, вы можете применять как транспортный протокол (TCP), так и протокол пользовательских датаграмм (UDP). Официальным номером порта для протокола Time Protocol является 37. Программа сервера времени ждет установки TCP-соединения и прихода UDP-датаграмм с порта 37. Как TCP, так и UDP используют один и тот же порт — другими словами, порт номер 37 закреплен за протоколом Time Protocol, а не за TCP или UDP. Использование сервера времени Установив TCP-соединение на порту 37, сервер возвращает 32-битное число, которое представляет текущее значение даты и времени. Затем сервер инициирует активное закрытие TCP-соединения. Если сервер времени не может определить текущее время на своем компьютере, то, согласно протоколу, сервер должен либо отказать в соединении, либо закрыть соединение без возврата какого-либо значения. При использовании TCP происходит следующая последовательность событий: 1. Сервер времени следит за состоянием порта 37. 2. Клиент времени соединяется с портом 37. 3. Сервер времени посылает 32-битное число, представляющее текущее значение даты и времени. 4. Клиент времени получает это 32-битное число. 5. Сервер времени инициирует активное закрытие. 6. Клиент времени инициирует пассивное закрытие. Если сервер времени фиксирует приход датаграммы на порт 37, то он возвращает в ответ датаграмму с тем же 32-битным значением времени. Если сервер времени не может определить текущее время на своем компьютере, то, согласно протоколу, он должен отбросить датаграмму и ничего не посылать в ответ. При использовании UDP происходит следующая последовательность событий: 1. Сервер времени следит за состоянием порта 37. 2. Клиент времени посылает пустую датаграмму на порт 37. 3. Сервер времени получает пустую датаграмму. 4. Сервер времени посылает датаграмму, содержащую 32-битное число, представляющее текущее значение даты и времени. 5. Клиент времени получает датаграмму. Обратите внимание, что последовательность событий в случае работы по протоколу UDP не включает в себя фазу активного или пассивного соединения. 351 
Глава 13. Время и сетевой порядок байтов Запомните, что UDP — это протокол, не требующий установки или разрыва соединения. Никакой прямой связи между портом программы сервера вр»емени и программой клиента времени не устанавливается. Поэтому при обслуживании клиентов по протоколу UDP, сервер не должен заботиться о закрытии какоголибо соединения. Декодирование значения времени Как вы только что узнали, протокол Time Protocol использует 32-битное число для представления текущего значения даты и времени. Это 32-битное число представляет время как число секунд, истекших с момента времени 00:00 (полночь) 1 января 1900 года. Другими словами, это значит, например, что значение 1, возвращенное протоколом Time Protocol, будет означать момент времени 12:00:01 1 января 1900 года. В табл. 13.1 приведены числа, использованные в качестве примеров в спецификации Time Protocol (в RFC 868). Таблица 13.1. Примеры 32-битных чисел из спецификации протокола Time Protocol Без знака Со знаком Шестнадцатиричное Дата (полночь по Гринвичу) 2208988800 -2085978496 0х83АА7Е80 1 января 1970 GMT 2398291200 -1896676096 Ox8EF30500 1 января 1976 GMT 2524521600 -1770445696 0x96792480 1 января 1980 GMT 2629584000 -1665383296 0х9СВС4480 1 мая 1983 GMT 2997239296 -1297728000 0хВ2А63Е00 17 ноября 1858 GMT Как видно из табл. 13.1, десятичные значения, используемые протоколом Time Protocol, изменяются в зависимости от того, используете ли вы для их хранения знаковые или беззнаковые переменные. Поэтому, когда вы разрабатываете и тестируете сетевые программы, использующие и передающие по сети числовую информацию, вы должны быть особенно осторожны в тех случаях, когда для представления ваших значений используются десятичные числа. (Поскольку использование десятичных чисел для визуальной оценки дат почти ничего не дает, рекомендуется всегда использовать двоичные или шестнадцатиричные значения.) Что такое порядок байтов? Как вы знаете, байт — это наименьшая единица данных, которой может манипулировать компьютер. Все современные компьютеры используют байты, состоящие из восьми бит. Поэтому, когда вы посылаете или принимаете один байт, вероятность того, что вы столкнетесь с какими-то проблемами, крайне невелика. Однако не все данные могут быть представлены одиночными байтами. Напри- 352 
рядок байтов? мер, программисты на С и C++ часто используют для хранения значений целочисленные переменные. Целочисленные переменные бывают двух размеров — короткие целые и длинные целые. Короткое целое использует 16 бит или два байта для хранения данных, а длинное целое использует 32 бита. Как правило, размер, который переменная занимает в памяти, часто называют ее длиной. Так, вы можете сказать, что длинное целое имеет в длину 32 бита, а короткое — 16 бит. Когда вы начинаете работать с единицами данных, занимающих по нескольку бит, вы сможете встретиться с несовместимостью между разными типами компьютеров, особенно если вы работаете с таким сетевым окружением, каким является Интернет, объединяющий множество различных типов компьютеров. Все эти типы компьютеров хранят многобайтовые единицы данных по-разному. За последние десятки лет возникало немало споров по поводу того, какой тип численного хранения данных является наилучшим. Однако эти споры не привели к единому мнению, а их результатом стало лишь то, что производители компьютеров стали строить машины, хранящие численную информацию не так, как другие. Ниже мы рассмотрим основные различия между двумя наиболее распространенными форматами хранения числовых данных. С начала или с конца? В большинстве современных языков принят порядок записи букв на письме слева направо. В случае чисел (десятичных, шестнадцатиричных или двоичных) запись их начинается с самой значимой (крайней левой цифры) и продолжается до наименее значимой (крайней правой) цифры, двигаясь слева направо. Для программистов адреса компьютерной памяти являются ничем иным как числами. На диаграммах чаще всего фрагменты памяти компьютера изображаются полосками, расположенными по вертикали, а не по горизонтали. Если некий текст расположен по вертикали, более естественным является чтение его сверху вниз. Однако эта привычка уже не является столь непреодолимой и обычно не вызывает большого труда привыкнуть к тому, что адреса ячеек памяти в таких диаграммах, наоборот, возрастают снизу вверх. К сожалению, если располагать диаграммы компьютерной памяти горизонтально, сразу встает новая проблема. На рис. 13.1 приведена простая диаграмма, на которой один и тот же отрезок компьютерной памяти показан дважды. Левая половина диаграммы изображает этот отрезок вертикально, так что адреса ячеек увеличиваются сверху вниз и наименьшие адреса расположены сверху. На правой половине диаграммы тот же диапазон адресов расположен горизонтально, так что наименьшие значения адреса стоят слева, а наибольшие — справа. На этой диаграмме изображены ячейки памяти с номерами от 1001 до 1008. Каждая ячейка памяти на рис. 13.1 хранит один байт данных. Как видите, ячейки памяти, изображенные на диаграмме, хранят в себе слово INTERNET, которое размещается в восьми ячейках памяти — по одной букве в каждом байте. Данные, записанные таким образом в памяти, вы можете прочитать на рис. 13.1 12 Зак. № 1949 353 
Время и сетевой порядок: байтов Рис. 13.1. Два способа представления фрагмента памяти в компьютере без какого-либо труда: левая половина диаграммы легко читается сверху вниз, а правая — слева направо. Однако вы, вероятно, заметили, что на правой части диаграммы ячейка памяти с наименьшим номером расположена слева, а с наибольшим — справа. Это вступает в противоречие с тем, как мы записываем числа — ведь в числах запись начинается с самых значимых цифр и продолжается слева направо до наименее значимых. Теперь давайте усложним ситуацию. Предположим, что вы хотите разместить в ячейках памяти, показанных на рис. 13.1, не текстовые данные, а числа. Чтобы не усложнять себе жизнь без надобности, вы решаете использовать шестнадцатиричные значения для представления числовых данных (как вы уже, конечно, знаете, каждый байт данных может быть представлен двузначным шестнадцатиричных числом). Предположим, что вы хотите хранить два 32-битных числа (т. е. всего восемь байт) в ячейках памяти с номерами с 1001 по 1008. Предположим также, что числа, которые вам требуется записать, равны 0x12345678 (десятичное 305419896) и 0xl2ABCDEF (десятичное 313249263). Каждая пара шестнадцатиричных цифр в этой записи представляет собой один байт. Как мы только что говорили, обычно числа читаются слева направо — от наиболее значимых цифр к наименее значимым. Проблемы с порядком байтов возникают тогда, когда вам приходится решать, сохранить ли этот общепринятый порядок чтения и записи чисел или воспользоваться тем, как располагаются в памяти номера ячеек, т. е. слева направо от наименее значимых цифр к наиболее значимым. На рис. 13.2 показано размещение наших двух шестнадцатиричных чисел вместо слова INTERNET в тех же ячейках памяти. Как видно из рис. 13.2, числа, записанные так, как показано на этом рисунке, вполне поддаются прочтению прямо с диаграммы, так как порядок их записи совпадает с общепринятым. Программисты называют такой порядок байтов при хранении числовых данных порядком «с начала» — так как наиболее значимый байт расположен на том конце числа, который вы встречаете первым, продвигаясь от ячеек памяти с меньшими номерами к ячейкам памяти с большими 354 
Что такое порядок байтов? рЩйЩpxs&J Место в памяти компьютера, отведенное для 0x12345678 и 0x12ABCDEF Рис. 13.2. Фрагменты памяти, содержащие два 32-битных числа, записанных с порядком байтов с с начала» номерами. Итак, используя порядок байтов «с начала», вы храните наиболее значимый байт данных в ячейке памяти с наименьшим (наименее значимым) номером. Другими словами, при чтении слева направо значимость цифр, составляющих данные, уменьшается, а значимость номеров ячеек памяти увеличивается. Поэтому можно сказать, что последовательность (порядок) адресов памяти и порядок байтов в ваших данных направлены в противоположные стороны. В противоположность этому при применении порядка байтов «с конца» направления, в которых увеличиваются номера ячеек памяти и значимость байтов данных, совпадают. В начале располагается наименее значимый байт данных, и по мере увеличения значимости байтов увеличиваются и номера ячеек памяти. На рис. 13.3 показана запись чисел, которые мы брали в качестве примера, с использованием порядка байтов «с конца». Ц ооо -о;] ооо о о о ho i о о о 00 О) I 5 со го Место в памяти компьютера, отведенное для 0x12345678 и 0x12ABCDEF Рис. 13.3. Фрагменты памяти, содержащие два 32-битных числа, записанных с порядком байтов «с конца» бхОР QxEF ' 0л12 0x34 > 0x56 Щ Сравните расположение адресов памяти на рис. 13.3 и на рис. 13.2. Как видите, на рис. 13.3 порядок номеров ячеек памяти противоположный. Это сделано для того, чтобы вы могли прочитать записанные в память числа как обычно. Если на рис. 13.2 номера ячеек памяти увеличиваются слева направо начиная с номера 1001, в противоположность этому на рис. 13.3 номера ячеек памяти увеличиваются, если идти справа налево, так что ячейка с номером 1008 расположена в самом левом конце. На рис. 13.4 показаны оба порядка байтов — «с начала» и «с конца». Как видите, ячейки памяти на рис. 13.4 пронумерованы так же, как на рис. 13.2 — слева направо начиная с адреса 1001. Рис. 13.4, на котором два разных порядка байтов представлены рядом друг с другом, позволяет лучше понять различия между ними. Главный аргумент, выдвигаемый защитниками порядка байтов «с конца», — это то, что меньший адрес ячейки памяти соответствует меньшему по значимости байту числа. Однако такой порядок байтов требует существенных умственных усилий, если вы хотите читать записанные в одном и том же сегменте памяти текст и числовые данные. Если вы хотите, чтобы текст в памяти читался естественным образом — слева направо, то числа вам придется записывать в 355 
Глава 13* Время и порядок байтов с начала Адреса памяти компьютера Порядок расположения байт "с конца" 0x12 0X34 |ф<56 { 0x78 [ 0x12: охав 0xCD|0xEF | 1 § -*• го рЩ|Щ§| 1008 1007 1006 1005 ; 1004 | j 1003 :^гдаг|ШР|охсЬ ■я тщ Место в памяти компьютера, отведенное для 0x12345678 и 0x12ABCDEF Рис. 13.4. Сравнение порядков байтов «с начала» и «с конца» противоположном направлении. Наоборот, при записи «с начала» как текст, так и числовые данные естественным образом читаются в одном и том же направлении. Как правило, вам не приходится заботиться о порядке байтов «с начала» или «с конца». Ваш компьютер автоматически решает, какой порядок байтов ему использовать, и споры о том, какой порядок байтов наиболее эффективен и удобен, могут вызывать лишь академический интерес. Однако как только вы начнете соединять разные компьютеры вместе, вам придется самостоятельно заняться проблемами порядка байтов. Если два компьютера используют один и тот же порядок байтов, не имеет значения, какой именно этот порядок — «с начала» или «с конца». При этом оба компьютера с гарантией будут передавать и интерпретировать данные без ошибок. Однако если компьютеры используют различные порядки байтов, они должны выработать некоторое соглашение о том, как интерпретировать числовые данные. Простейшим решением было бы принять один из двух порядков байтов как стандарт для всей сети. Как вы узнаете из следующих разделов, именно это и сделали разработчики Интернет. Определение стандартного сетевого порядка байтов Порядок байтов в компьютере зависит от его микропроцессора, другими словами, аппаратная конструкция компьютера определяет порядок байтов хранящихся в нем данных. В табл. 13.2 указан используемый порядок байтов для нескольких популярных компьютерных платформ. Программируя для Интернет, вы можете столкнуться с любым из этих типов компьютеров. Таблица 13.2. Порядок байтов на различных компьютерных платформах Компьютерная платформа «С начала» «С конца» Intel 80x86 (PC) X DEC VAX и PDP X Motorola 68000 X IBM 370 X Pyramid X 356 
Что такое порядок байтов? Вы знаете, что при передаче данных с одного компьютера на другой, физический уровень сети передает последовательный поток битов данных по сетевым линиям передачи. Очевидно, что физический уровень сети не изменяет порядок битов в передаваемых данных. Так, когда вы передаете число OxABCD (десятичное 43981) с компьютера PC на компьютер Макинтош, ваш PC (на котором используется порядок байтов «с конца») передает сперва менее значимый байт (OxCD), а затем более знрчимый байт (ОхАВ). Когда Макинтош получает эту посылку, он интерпретирует, в согласии с используемым на нем порядком байтов «с начала», первый байт как старший, а второй — как младший. В результате на компьютере Макинтош будет принято число OxCDAB (десятичное 52651). Этот пример делает очевидным, как порядок байтов может привести к искажениям данных при передаче их по сети. Для предотвращения таких ошибок Интернет определяет стандартный порядок байтов в сети, в качестве которого принят порядок байтов «с начала». Другими словами, Интернет предписывает всем компьютерам, входящим в него, посылать числовые данные так, чтобы старший байт шел перед младшим байтом. Читая байты данных, представляющих целочисленные значения в асинхронном пакете, вы должны помнить, что наиболее значимый (старший) байт идет первым, т. е. ближе к началу пакета, а менее значимый (младший) байт идет последним, ближе к концу пакета. Примечание: В пределах пользовательской области данных пакета, вы вполне можете хранить данные в любом формате, какой вам нравится — «с начала», «с конца» или как-либо еще. Однако вы должны понимать, что программа, получающая ваши данные, должна в свою очередь преобразовать эти данные с использованием того порядка байтов, который принят на компьютере, на котором она работает. Вы должны преобразовать все числовые значения с использованием сетевого порядка байтов прежде, чем передавать их по сети. Кроме того, вы должны не забыть преобразовать целочисленные значения, которые ваша программа будет получать из Интернет, в тот порядок байтов, который используется на вашем компьютере. К счастью, спецификация Windows Sockets (Winsock) представляет служебные функции, которые упрощают выполнение этих преобразований. В табл. 13.3 перечислены четыре функции Winsock API, предназначенные для преобразования байтов в целых числах. Таблица 13.3. Функции Winsock, преобразующие порядок байтов Функция Winsock Назначение htonl Преобразует 32-битную величину из порядка байтов хоста в сетевой порядок байтов. htons Преобразует 16-битную величину из порядка байтов хоста в сетевой порядок байтов. ntohl Преобразует 32-битную величину из сетевого порядка байтов в порядок байтов хоста. ntohs Преобразует 16-битную величину из сетевого порядка байтов в порядок байтов хоста. 357 
Глава 13. Время и сетевой порядок байтов Использование протокола Time Protocol Как мы говорили выше, протокол Time Protocol — один из простейших протоколов Интернет. Согласно RFC 867, Time Protocol возвращает текущее время дня с точностью до одной секунды, полученное от программы сервера времени на одном из узлов Интернет. Time Protocol использует 32-битное число для представления текущего значения даты и времени, в котором хранится число секунд, истекшее с полуночи 00:00 1 января 1900 года. Компьютеры PC также имеют свою точку отсчета для функции даты и времени. К сожалению, точка отсчета на PC не совпадает с точкой отсчета протокола Time Protocol. Таймеры на персональных компьютерах представляют текущее значение даты и времени в виде числа секунд, истекших с полуночи 00:00 1 января 1970 года по Гринвичу. Как следует из табл. 13.1, в полночь 1 января 1970 года истекло 2208988800 (0х83АА7Е80) секунд с 1 января 1900 года. Поэтому, если вы хотите использовать функции даты и времени для интерпретации значений даты и времени, полученных из Интернет, из них необходимо вычитать 70-летнюю разницу (2208988800). Создание программы Quick Time Как вы знаете, обмен информацией между сервером времени и клиентом времени происходит очень просто. Общеизвестный номер порта для сервера времени равен 37. Вы можете использовать TCP для соединения с портом 37 либо послать пустую датаграмму на порт с этим номером. В любом случае сервер времени вернет вам 32-битное число, представляющее текущее значение даты и времени в виде секунд, истекших с полуночи 00:00 1 января 1900 года. В этом разделе мы с вами создадим простую программу QTime, которая использует ТСР-соединение для получения значения даты и времени из Интернет. Программа QTime построена по образцу всех остальных учебных программ, на которых в этой книге мы с вами изучали программирование для Интернет. Как и ранее, для того чтобы четче осветить основные понятия программирования для Интернет, мы не стали усложнять программу QTime сверх необходимости. QTime использует явное задание в коде программы многих значений, а чтобы избежать непроизводительных затрат, связанных с интерфейсом пользователя Windows и обработкой сообщений от окон, эта программа использует окна сообщений. Файл с исходным текстом QTIME.СРР вы найдете на дискете, приложенной к этой книге. Ниже приведен полный листинг файла QTIME.СРР. Подробное описание программы QTime расположено после листинга. Примечание: В код программы заложено обращение к серверу времени на узловом компьютере по адресу cerfnet.com. Многие компьютеры в Интернет имеют свои серверы времени, и QTime может работать с любым из них. Если вам почему-либо не удается связаться с cerfnet.com, вы можете изменить исходный текст QTime, чтобы использовать любой другой хост в Интернет. 358 
Создание программы Quick Time #include <stdlib.h> #include <time.h> #include "..\winsock.h" #define PROG_NAME "Simple Timeserver Query" #define HOSTJSTAME "cerfnet.com" #define WINSOCK_VERSION 0x0101 #define DEFAULT_PR0T0C0L 0 #define N0_FLAGS 0 #define PC_REF_TIME2208988800L // Здесь может стоять любое // (реальное) имя // Необходим Winsock версии 1.1 // Протокол не указан, // используем протокол по // умолчанию // Флаги не используются // Точка отсчета для даты и // времени на PC SOCKET ConnectTimeServerSocket(VOID) { WSADATA wsaData; // LPHOSTENT pHostEnt; // // SOCKADDR_IN sockAddr; // LPSERVENT pServEnt; // // short iTimePort; // int nConnect; // SOCKET nTimeserverSocket Сведения о реализации Winsock Структура информации о хосте Интернет Структура адреса сокета Структура информации о сетевой службе Официальный номер порта равен 37 Результаты соединения сокета : INVALID_SOCKET; // Номер // сокета по умолчанию if DLL. ", (WSAStartup(WINSOCK_VERSION, &wsaData)) MessageBox(NULL, "Could not load Windows Sockets PR0G__NAME, MB__0K I MB_ICONSTOP) ; else // Ищем хост с этим именем { pHostEnt = ge t host byname (HOST__NAME) ; if ('pHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB__OK | MB_ICONSTOP) ; else // Создаем сокет { nTimeserverSocket = socket (PF__INET, SOCK_STREAM, DEFAULT_PROTOCOL); 359 
Глава 13- Время и сетевой порядок байтов if (nTimeserverSocket == INVALID_SOCKET) MessageBox (NULL, "Invalid socket!!", PROG__NAME, MB_OK|MB_ICONSTOP) ; else // Конфигурируем сокет { // Получаем информацию службы времени pServEnt = getservbyname("time", "tcp"); if (pServEnt == NULL) iTimePort = IPPORT_TIMESERVER; //Официальный номер порта else iTimePort = pServEnt->s___port ; // Определяем адрес сокета sockAddr.sin_family = AF__INET ; // Семейство адресов Интернет sockAddr.sin_port = iTimePort; sockAddr.sin_addr = *((LPIN_ADDR)*pHostEnt->h_addr_list); // Соединяем сокет nConnect = connect(nTimeserverSocket, (PSOCKADDR)&sockAddr, sizeof(sockAddr)); socket!!", if( nConnect) { MessageBox (NULL, "Error connecting PROG_NAME, MB_OKIMB_ICONSTOP); nTimeserverSocket = INVALID_SOCKET; } return(nTimeserverSocket); int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow) { SOCKET nSocket; int nCharSent; int nCharRecv; DWORD dwNetTime; // Номер сокета для сервера времени // Число переданных символов // Число полученных символов // Значение времени,полученное из // Интернет,в виде длинногоцелого // без знака 360 
, , Создание программы Quick Time LONG lNetTime; // // // LONG lPCTime; // // char szHostOrder[33] ; // // char szNetOrder[33] ; // // // char szUnsignedValue[33]; char szSignedValue[33]; char szMsg[100]; Значение времени,полученное из Интернет, в виде длинного целого со знаком Значение времени PC в виде длинного целого со знаком Шестнадцатиричное значение времени с пог~~^ом байтов хоста Шестнадцатиричное значение времени с сетевым порядком байтов // Значение времени, полученное // из Интернет, в виде // длинного целого без знака // Значение времени, полученное //из Интернет, в виде // длинного целого со знаком // Буфер общего пользования // для хранения сообщений nSocket = ConnectTimeServerSocket(); if (nSocket != INVALID_SOCKET) { // Запрашиваем сервер о текущем времени, посылая ему CRLF nCharSent = send(nSocket, "\n", lstrlen("\n"), NO_FLAGS); if (nCharSent == SOCKET_ERROR) MessageBox(NULL, "Error occurred during send()!", PROG__NAME, MB_OK I MB_ICONSTOP) ; else // Получаем текущее время от сервера времени { nCharRecv = recv(nSocket, (LPSTR)&lNetTime, sizeof(lNetTime), NO_FLAGS); if (nCharRecv == SOCKET_ERROR) MessageBox(NULL, "Error occurred during recv()!", PROG_NAME, MB_OK I MB_I CONS TOP) ; else // Преобразуем результаты из сетевого // порядка байтов в местный { lPCTime = ntohl(lNetTime); 361 
Глава 13. Время и се» евой порядок Зайтря // Преобразуем числа в ASCII-строки для // использования в окне сообщения _ltoa(lPCTime, szHostOrder, 16); // Используем шестнадцатиричную запись _ltoa(lNetTime, szNetOrder, 16); // Используем шестнадцатиричную запись lPCTime = lPCTime - PCJREF_TIME; // // Вычитаем точку отсчета времени PC wsprintf(szMsg, "%s\n\nBYTE-ORDER\n\nNetwork:\t%s\nHost PC:\t%s", (LPSTR)ctime(&lPCTime),(LPSTR)szNetOrder, (LPSTR)szHostOrder); MessageBox(NULL, szMsg, PROG__NAME, MB__OK | MB_ICONINFORMATION) ; // Присваиваем сетевое значение времени // переменной без знака dwNetTime = lNetTime; // Форматируем 32-битное значение сетевого // времени как длинное целое без знака _ultoa(dwNetTime, szUnsignedValue, 10); // Форматируем 32-битное значение сетевого // времени как длинное целое без знака _ltoa(lNetTime, szSignedValue, 10); // Выводим 32-битное значение, полученное из // сети wsprintf(szMsg, "Unsigned:\t%s\nSigned:\t\t%s", (LPSTR)szUnsignedValue, (LPSTR)szSignedValue); MessageBox(NULL, szMsg, PROG_NAME, MB__OK | MB_ICONINFORMATION) ; } } } WSACleanup(); // Освобождаем все занятые программой // ресурсы и заканчиваем работу return(NULL); } 362 
Новое в программе Как мы уже говорили выше, программа QTime построена по тому же образцу, что и программы QLookup и QFinger, и состоит по большей части из тех же фрагментов кода. Основные отличия программы QTime от других учебных программ, которые мы писали ранее, описываются в следующих разделах. Как QTime посылает запрос серверу времени? В приведенном ниже операторе программы обратите внимание на то, какие «данные» QTime посылает серверу времени на удаленном хосте. На первый взгляд может показаться, что QTime вообще не посылает никаких данных. Однако при более внимательном рассмотрении можно обнаружить, что с помощью функции send, QTime посылает в Интернет пустую строку — просто один символ перевода строки: nCharSent = send(nSocket, "\n", lstrlen("\n"), NO_FLAGS); Почему же QTime посылает серверу времени именно символ перевода строки? Дело в том, что сервер времени должен получить символ перевода строки или пару символов возврата каретки и перевода строки, чтобы понять, что с ним устанавливают соединение. Определения констант Большинство констант, используемых программой QTime, совпадают с теми, что мы видели в текстах программ QLookup и QFinger. Единственной новой константой в программе QTime является PC_REF_TIME, вводимая таким фрагментом кода: #define PC_REF_TIME 220898880OL // Точка отсчета даты и // времени PC Как мы уже знаем, в качестве точки отсчета для функций даты и времени на PC используется 1 января 1970 года, тогда как протокол Time Protocol в качестве такой точки отсчета использует 1 января 1900 года. С точки зрения Time Protocol момент полуночи 1 января 1970 года имеет значение 2208988800. Поэтому, чтобы интерпретировать значения даты и времени, которые ваш PC получает из Интернет, вы должны вычесть из 32-битного числа, возвращаемого Time Protocol, значение 2208988800. После этого можно использовать функции даты и времени вашего персонального компьютера. Разделение программы на функции Чтобы не увеличивать без надобности сложность программы, в предыдущих учебных программах все операторы были частью одной функции WinMain. Напротив, программа QTime использует несколько функций. В других учебных программах начальные операторы программы были практически идентичными. Первые две учебные программы, рассматривавшиеся в нашей книге, должны 363 
Гmm 13. Время и сетевой порядок байтов были сделать для вас привычными эти первые шаги алгоритма. Программа QTime, рассматриваемая в этой главе, выделяет эти первые шаги в отдельную функцию под названием ConnectTimeServerSocket. Вот как выглядит прототип функции ConnectTimeServerSocket: SOCKET ConnectTimeServerSocket(VOID); Как видите, функция ConnectTimeServerSocket не имеет параметров и возвращает значение типа SOCKET. Как можно догадаться по имени этой функции, ее целью является соединить сокет с сервером времени на одном из узлов Интернет и возвратить дескриптор сокета в вызывающую функцию. Алгоритм функции ConnectTimeServerSocket В функции ConnectTimeServerSocket операторы программы и даже имена переменных практически совпадают с теми, которые вы использовали в QLookup и QFinger. Ниже приведены шаги алгоритма, реализуемого функцией ConnectTimeServerSocket. Если вам нужна дополнительная информация о какихлибо шагах этого алгоритма, обращайтесь к соответствующим разделам в главах 9 и 10. 1. Функция ConnectTimeServerSocket вызывает функцию WSAStartup, чтобы инициализировать Winsock DLL. 2. Функция ConnectTimeServerSocket вызывает функцию gethostbyname, чтобы найти IP-адрес узла, чей доменный адрес содержится в строчной константе HOST_NAME. 3. Функция ConnectTimeServerSocket создает коммуникационный сокет и вызывает функцию socket, чтобы получить дескриптор сокета. 4. Функция ConnectTimeServerSocket вызывает функцию getservbyname, чтобы получить из базы данных сетевых служб запись, соответствующую службе времени, использующей TCP. 5. Чтобы определить адрес сокета, функция ConnectTimeServerSocket заполняет значениями структуру адреса сокета Winsock (которая определяется в SOCK_ADDR_IN). ConnectTimeServerSocket указывает семейство адресов, номер порта протокола и адрес хоста для этого сокета. 6. Функция ConnectTimeServerSocket вызывает функцию Winsock connect, чтобы соединить сокет с удаленным хостом. 7. Если функция ConnectTimeServerSocket успешно соединила коммуникационный сокет с хостом, она возвращает дескриптор сокета. В обратном случае функция ConnectTimeServerSocket возвращает константу INVALID_SOCKET. 364 
Создание программы Quick Time Функция WinMain Первые несколько строк функции WinMain в программе QTime определяют локальные переменные программы. Некоторые из этих переменных вам должны быть уже знакомы по предыдущим учебным программам. Ниже перечислено несколько новых переменных, вводимых программой QTime. DWORD dwNetTime LONG lNetTime; LONG lPCTime; char szHostOrder[33]; char szNetOrder[33]; char szUnsignedVal[33] char szSignedVal[33]; char szMsg[100]; // Значение времени, полученное из // Интернет, в виде длинного целого // без знака // Значение времени, полученное из // Интернет,в виде длинного целого //со знаком // Значение времени PC в виде // длинного целого со знаком // Шестнадцатиричное значение // времени с порядком байтов хоста // Шестнадцатиричное значение // времени с сетевым порядком байтов // Значение времени, полученное из // Интернет, в виде длинного целого // без знака // Значение времени, полученное из // Интернет, в виде длинного целого // со знаком // Буфер общего назначения для // хранения сообщений Как вы уже знаете, протокол Time Protocol возвращает 32-битное число, представляющее собой текущее значение даты и времени. Вам, вероятно, известно, что длинные целые переменные также занимают 32 бита. Поэтому QTime использует две длинные целые переменные, dwNetTime и lNetTime, для хранения числа, возвращенного протоколом Time Protocol. Префиксы имен этих переменных означают, что lNetTime хранит свое значение в виде длинного целого со знаком, a dwNetTime хранит свое значение в виде длинного целого без знака. Программе QTime требуется только одно из этих двух длинных целочисленных значений. Однако, как вы уже знаете, одна и та же “последовательность битов может представлять два разных десятичных значения — одно со знаком, а другое без знака. Чтобы проиллюстрировать этот важный момент, QTime отображает полученное от Time Protocol 32-битное значение как в виде целого со знаком, так и в виде целого без знака. Третье длинное целое, lPCTime, используется в QTime для хранения 32-битного значения, полученного от Time Protocol в формате, пригодном для использования функциями 1,аты и времени вашего персонального компьютера. Чтобы получить это значение, QTime прежде всего вычитает точку отсчета даты и времени в Интернет (1 января 1900 года) из соответствующей точки отсчета 365 
Глава 13. Время и сетевой порядок Вайтов вашего PC (1 января 1970). Затем QTime вычитает полученную разницу точек отсчета из текущего времени в Интернет. Результат этой операции QTime записывает в переменную lPCTime, с которой затем можно производить операции с помощью функций даты и времени из библиотеки времени выполнения вашего компилятора. Оставшиеся переменные типа массив символов QTime использует для хранения различных интерпретаций 32-битного значения даты и времени. Переменные szHostOrder и szNetOrder содержат значения времени, проинтерпретированные с разным порядком байтов (с порядком байтов хоста и с сетевым порядком байтов соответственно). Переменные szUnsignedVal и szSignedVal хранят то же самое 32-битное значение даты и времени, проинтерпретированное как длинное целое со знаком и без знака. Наконец, переменная szMsg — это массив общего назначения, в котором QTime форматирует текстовые строки, выводимые в окна сообщений. Начинаем работу Определив локальные переменные, WinMain вызывает функцию ConnectTimeServerSocket, которая создает коммуникационный сокет и устанавливает соединение с сервером времени на удаленном хосте. Если функции ConnectTimeServerSocket не удалось создать сокет и установить соединение, она возвращает значение INVALID_SOCKET. Ниже приведены фрагменты кода, показывающие общую структуру функции WinMain: nSocket = ConnectTimeServerSocket(); if (nSocket != INVALID_SOCKET) { // Выполняем операторы программы, получающие и выводящие // на экран текущее значение даты и времени из Интернет } WSACleanup(); // Освобождаем все занятые программой // ресурсы и заканчиваем работу return(NULL); Как видите, если функция ConnectTimeServerSocket не добилась успеха, QTime вызывает функцию WSACleanup и завершает работу. Как вы уже знаете, функция WSACleanup стирает регистрационные данные вызывающей программы из области данных Winsock DLL и освобождает все ресурсы, использовавшиеся от имени вашей программы. Обмен данными с сервером времени Ниже приведен фрагмент кода, почти не отличающийся от аналогичных фрагментов в других учебных программах. Сервер времени не требует никакого обмена специальными сообщениями. Как только сервер времени фиксирует 366 
Создание программы Quick ТШш TCP-соединение, он возвращает 32-битное значение даты и времени. QTime использует функцию send, чтобы передать пару символов возврата каретки и перевода строки после успешного соединения сокета. Если при этом не происходит ошибки, QTime с помощью функции recv принимает ответ сервера времени: if (nSocket != INVALID_SOCKET) { // Запрашиваем сервер о текущем времени, посылая ему CRLF nCharSent = send (nSocket, "\n", lstrlen (" \n") , NO__FLAGS) ; if (nCharSent == SOCKET_ERROR) MessageBox(NULL, "Error occurred during send()!", PROG_NAME, MB_OKIMB_ICONSTOP) ; else // Получаем текущее время от сервера времени { nCharRecv = recv(nSocket, (LPSTR)&lNetTime, sizeof(lNetTime), NO_FLAGS); if (nCharRecv == SOCKET_ERROR) MessageBox(NULL, "Error occurred during recv()!", PROG_NAME, MB_OK I MB_ICONSTOP) ; В приведенном фрагменте кода один момент заслуживает особого рассмотрения. Как вы уже знаете, прототип функции recv в Winsock выглядит так: int PASCAL FAR recv(int s, char FAR * buf, int len, int flags): Как видите, второй параметр функции recv есть длинный указатель на буфер символьной строки (char), в которую recv будет записывать получаемые данные. Чтобы вы могли использовать функции библиотеки Winsock на разных сетях и с разными типами данных, функция recv в Winsock размещает по адресу, на который ссылается этот указатель, буфер сообщений. Если бы функция получения данных не пользовалась этим указателем, ваши сетевые программы должны были бы предусматривать разные варианты функции recv для каждого типа данных, которые им потребовалось бы передавать по с^ти. Поскольку указатель на символ ссылается на ячейку памяти с точностью до байта, он может указывать на любой тип данных. Допустим, к примеру, что из сети приходит длинное целое (длиной 32 бита). Чтобы сохранить эти данные, ваша программа должна выделить по меньшей мере четыре байта памяти. Однако сам Winsock вообще не обращает внимания на то, как вы размещаете свою память. Ваша программа может объявить длинное целое (которое займет четыре бита) или просто символьный массив длиной в четыре элемента. В любом случае функции Winsock требуется просто указатель с адресом первого байта выделенной области памяти. 367 
Глава 13. Время и сетевой порядок байтов Когда ваша программа, работающая с Winsock, использует функцию recv, она должна выделить область памяти для размещения буфера сообщений. Однако ваша программа может использовать буфер любого типа или размера. Чтобы использовать этот буфер с функцией recv, ваша программа просто преобразует адрес этого буфера к типу LPSTR (который определен как char FAR *). В свою очередь, ваш компилятор обработает такой вызов функции recv без каких-либо жалоб и возражений (т. е. с его точки зрения с типами параметров в этом случае все будет в порядке). Поскольку протокол Time Protocol возвращает 32-битное значение, QTime использует в качестве буфера сообщений длинное целое (длиной в 32 бита). С тем же успехом программа QTime могла бы использовать в качестве буфера сообщений массив символов. Однако в этом случае ей бы впоследствии пришлось преобразовывать содержимое символьного массива в длинное целое. Чтобы избежать этих ненужных преобразований, хороший тон программирования для Winsock требует, чтобы буферы сообщений имели правильный тип. Например, если ваша программа объявляет длинное целое для использования в качестве буфера сообщений, другой программист, читая вашу программу, будет сразу знать, какой тип данных вы хотите получить из сети и записать в этот буфер. Явное преобразование типа в вызове функции recv свидетельствует для программиста, читающего вашу программу, о том, что функция recv требует в качестве своего второго параметра переменную типа LPSTR. Наоборот, если бы вы использовали в качестве буфера сообщений символьный массив, тот, кто читал бы вашу программу, мог прийти к выводу, что вы собираетесь получать из сети символьные данные. Дополнительной причиной для такого вывода было бы то, что функция recv требует параметр типа LPSTR (который определен как char FAR *), и в вызове этой функции не было бы никакого явного преобразования типов. Чтобы избежать этого неверного заключения, читатель вашей программы должен был бы знать подробности реализации протокола Time Protocol. Преобразование типов значений Winsock Как правило, программисты, пишущие на С и C++, должны по мере возможности избегать преобразований типов. Дело в том, что преобразования типов могут ввести в заблуждение современные компиляторы С и C++, в большинство из которых встроены возможности отслеживания ошибок, связанных с типами переменных. Другими словами, каждый раз, когда вы прибегаете к преобразованию типа, вы лишаете компилятор возможности предупредить вас в том случае, если вы по ошибке используете переменную не того типа, что может привести к ошибкам во время выполнения программы. Конечно, полностью избежать преобразований типов невозможно. Преобразования типов иногда выполняют законную и полезную функцию. Более того, если бы не существовало возможности преобразовывать типы, библиотеки функций, такие как Winsock API, были бы гораздо более запутанными и сложными в использовании. К несчастью, программисты на С и C++ часто бывают склонны к злоупотреблению 368 
Создание программы Quick Time преобразованиями типов. Поскольку библиотека функций Winsock разрабатывалась с расчетом на максимальную гибкость, преобразование типов — это неизбежная часть программирования Интернет с Winsock. Главное здесь — помнить, что вы должны использовать преобразование типов для переменных, которые вы передаете как параметры при вызовах функций Winsock. Наоборот, использование в программе преобразования типов для манипуляции полученных данных, хранящихся в буферах, является неверными подходом. Другими словами, вы должны определять буферы сообщений таким образом, чтобы они соответствовали тому типу данных, который в них будет помещен из Интернет. Следование этому принципу позволит вашему компилятору в большем числе случаев предупредить вас об ошибке несоответствия типов. Преобразование порядка байтов Если функция recv не возвращает кода ошибки, программа QTime должна преобразовать полученное значение сетевого времени, хранящееся в буфере сообщений, из сетевого порядка байтов в порядок байтов хоста. Как следует из приведенного ниже фрагмента кода, программа QTime вызывает для этого функцию Winsock ntohl: if (nCharRecv == SOCKET_ERROR) MessageBox(NULL, "Error occurred during recv()!", PROG_NAME, MB_OK|MB_ICONSTOP); else // Преобразуем результаты из сетевого порядка байтов в // местный { lPCTime = ntohl(lNetTime); Примечание: Поскольку QTime использует в качестве буфера сообщений длинную целочисленную переменную (INetTime), вызов функции ntohl не требует никакого преобразования типов. Функция ntohl берет в качестве параметра длинное целое в сетевом порядке байтов и возвращает длинное целое в порядке байтов хоста. Вот как выглядит прототип функции ntohl: u_long PASCAL FAR ntohl(u_long netlong); Как уже упоминалось, Winsock API содержит четыре функции для преобразований, аналогичных тем, что делает функция ntohl. Две из этих функций меняют порядок байтов в 16-битовых целых числах (коротких целых), а две другие функции меняют порядок байтов в 32-битных целых (длинных целых). Как вы уже знаете, вы должны постоянно помнить о том, что целые значения передаются по Интернет только в сетевом порядке байтов. В обратном случае компьютеры, которые получают передаваемые вами данные, могут неправильно их интерпретировать. После того как QTime преобразует порядок байтов в полученном значении, программа сохраняет ASCII-представление полученного от Time Protocol 32-битного значения в виде строки шестнадцатиричных символов. Как 369 
Глава 13. Время и сетевой передок байтов следует из приведенного ниже фрагмента кода, QTime использует функцию С _ltoa для выполнения этого преобразования: lPCTime = ntohl (lNetTime) ; // Преобразуем числа в ASCII-строки для использования в окне // сообщения _ltoa(lPCTime, szHostOrder, 16); // Используем // шестнадцатиричную запись _ltoa(lNetTime, szNetOrder, 16); // Используем // шестнадцатиричную запись Когда вы будете выполнять программу, QTime выведет на экран полученное от протокола Time Protocol значение как в сетевом порядке байтов, так и в порядке байтов хоста. Например, как видно на рис. 13.5, сервер времени на одном из узлов Интернет в момент времени 03:13:10 20 февраля 1995 года возвратил значение 0xC6F7F2B2. Simple Timeserver Query Mon Feb 20 03:13:101995 BYTE-ORDER Network: c6f7f2b2 Host PC: b2f2f7c6 Puc. 13.5. Значения, полученные с помощью протокола Time Protocol 20 февраля 1995 года программой QTime Как видите, значение, которое QTime отображает в сетевом порядке байтов, равно 0xC6F7F2B2; то же самое значение, преобразованное к порядку байтов хоста, равно 0xB2F2F7C6. Помните, что каждая пара шестнадцатиричных цифр представляет один байт. Другими словами, эти восьмизначные шестнадцатиричные числа представляют четыре байта (или 32 бита) данных каждое. Обратите внимание на то, что младший байт значения в сетевом порядке байтов, 0хВ2, становится старшим (крайним левым) байтом в порядке байтов хоста. Аналогично, старший байт в сетевом порядке байтов, ОхСб, становится младшим (крайним правым) байтом в порядке байтов хоста. Если разбить каждое число на четыре пары шестнадцатиричных цифр, то вы можете заметить, что порядок байтов в этих двух числах обратный. Другими словами, четыре байта в полученном из сети значении, ОхСб 0xF7 0xF2 0хВ2 представленные в виде четырехбайтового значения на вашем компьютере, будут идти в обратном порядке: 0хВ2 0xF2 0xF7 ОхСб 370 
Создание программы Quick Time Учет разницы точек отсчета Точкой отсчета для даты и времени в Интернет служит 1 января 1900 года, тогда как на PC точкой отсчета является 1 января 1970 года. В соответствии со спецификацией протокола Time Protocol (RFC 868), значение 1 января 1970 года с точки зрения Интернет равно 2208988800. Программа QTime использует для хранения этого значения константу PC_REF_TIME: #define PC_REF_TIME 2208988800L // Точка отсчета даты и // времени PC Затем программа QTime преобразует порядок байтов 32-битного значения, полученного от сервера времени, и сохраняет новое значение времени в переменной lPCTime. Наконец, QTime вычитает из нового значения времени константу PC_REF_TIME. Как следует из приведенного ниже оператора программы, после этого QTime по-прежнему использует lPCTime для хранения значения времени, преобразованного теперь к порядку байтов хоста: lPCTime = lPCTime - PC_REF_TIME; // Вычитаем точку отсчета PC Вывод времени, полученного из Интернет После того как вы преобразуете порядок байтов и учтете разницу между точками отсчета в Интернет и на PC, вы можете использовать функции даты и времени из библиотеки времени выполнения вашего компилятора C/C++, чтобы манипулировать этими данными. Ниже показано, как QTime использует функцию ctime для преобразования значения времени и для последующего вывода его в окне сообщения Windows: lPCTime = lPCTime - PC_REF_TIME; // Вычитаем точку отсчета PC wsprintf (szMsg, "%s\n\nBYTE ORDER\n\nNetwork: \t%s\nHost PC:\t % s", ctime(&lPCTime), szNetOrder, szHostOrder); MessageBox(NULL, szMsg, PROG_NAME, MB_OKIMB_ICONINFORMATION); Вы, вероятно, знаете, что функция ctime преобразует значение времени PC в строку символов. Как видно из рис. 13.5, QTime выводит эту символьную строку в самом верху информационного окна над строками шестнадцатиричных символов, которые мы только что обсуждали. Данные без знака и со знаком Последние несколько операторов программы функции WinMain просто показывают полученные из сети значения времени в виде значений со знаком и без знака. Как следует из приведенного ниже фрагмента кода, QTime присваивает значение со знаком, INetTime, длинному целому без знака, dwNetTime, прежде, чем преобразовать десятичное значение в ASCII-строку с помощью функции _ultoa: // Присваиваем значение времени, полученное из сети, // переменной без знака dwNetTime = INetTime; 371 
// Форматируем 32-битное значение сетевого времени как // длинное целое без знака _ultoa(dwNetTime, szUnsignedValue, 10); Функция _ultoa преобразует длинные значения без знака в их ASCII-эквиваленты. Однако если вы просто передадите функции _ultoa целое значение со знаком, версия этой функции из библиотек многих компиляторов покажет, что создатели этих библиотек не зря получали деньги. Функция поступит так, как если бы вы на самом деле хотели бы использовать функцию _ltoa, которая преобразует длинное целое со знаком и преобразует ваше число в ASCII-представление длинного целого со знаком. Чтобы перехитрить этот механизм, QTime присваивает значение сетевого времени, хранящееся в INetTime, переменной, объявленной как длинное целое без знака (dwNetTime), а затем использует переменную без знака в вызове функции. Как видно из рис. 13.5, в первом окне сообщения программа QTime представляет сетевое значение времени в виде двух строк шестнадцатиричных цифр. Эти значения делают очевидной разницу в порядке байтов между вашим компьютером и сетью. Однако во втором окне сообщения QTime показывает сетевое значение времени в виде двух десятичных чисел со знаком и без знака. Таким образом, вы можете наглядно видеть, как одна и та же последовательность битов представляет разные значения, если при этом используется переменная со знаком и без знака. Ниже приведен фрагмент кода, преобразующий сетевое значение времени в ASCII-представление длинного целого со знаком. В окно сообщения Windows QTime выводит это значение и ASCII-представление длинного целого без знака: // Форматируем 32-битное значение сетевого времени как // длинное целое со знаком _ltoa(INetTime, szSignedValue, 10); // Выводим 32-битное сетевое значение wsprintf(szMsg, "Unsigned:\t%s\nSigned:\t%s", szUnsignedValue, szSignedValue); MessageBox(NULL, szMsg, PROG_NAME, MB__OKIMB_ICONINFORMATION); Примечание: Второй параметр, передаваемый функциям Jtoa и _ultoa, определяет основание системы счисления, в которой числа переводятся в ASCIIстроки. Например, вызов функции, показанный в предыдущем фрагменте кода, использует значение 10, чтобы заказать преобразования в десятичной системе счисления. Раньше мы использовали вызов функции Jtoa со вторым параметром, равным 16, чтобы запросить преобразования в шестнадцатиричной системе счисления. Добавление запроса сервера времени в программу Sockman Вероятно, на вашем компьютере есть встроенные часы. Однако скорее всего вы, как и большинство пользователей, не обращаете особого внимания на точность их хода. Так, если ваш компьютер сообщает правильную дату, то точное значение времени на его часах вас, вероятно, волнует в гораздо меньшей степени. 372 
Добавление запроса сервера времени в программу Зосктап Однако некоторые программы могут предъявлять повышенные требования к точности значений даты и времени внутренних часов. Поэтому вам может потребоваться периодически вставлять точное время на внутренних часах своего компьютера. На большинстве компьютеров само изменение показаний внутренних часов осуществляется довольно просто. Однако для получения точного времени обычно требуется позвонить по какому-то специальному номеру и выслушать сообщение автоматических часов. К счастью, когда вы соединяетесь с Интернет, вам уже не придется заниматься выяснением точного времени по телефону. Вместо этого парой щелчков мыши вы можете запустить утилиту связи с сервером времени из программы Sockman и установить точное время на часах своего компьютера. Ниже мы узнаем, как программа клиента времени встраивается в шаблон Sockman. Общая картина Утилита запроса к серверу времени в Sockman применяет тот же пользовательский интерфейс, который мы рассматривали в главах 11 и 12. С помощью команды меню пользователь открывает диалоговое окно, которое, в свою очередь, запускает модуль утилиты, передавая ему значение, введенное пользователем. Как и все программы Windows, функция WinMain обрабатывает все сообщения Windows, посылая их главной вызываемой функции (которая в Sockman называется WndProc). Как видно из рис. 13.6, в структуре программы Генерация сообщений Windows >U JL WndProc (> ' 1 ■■■ 1 Обрабатывает как обычные сообщения Windows, II так и сообщения, сгенерированные приложением Sockman Вызов процедуры DoMenuCommand для любого сообщения WM COMMAND 4JI ШМШЙйптапй (ГШЩ Обрабатывает любые сообщения WM_COMMAND, гчпГг ' • ... за исключением вызывающих Winsock Для любого неопознанного сообщения WM_COMMAND вызывается процедура DoWinsockProgram 1 \ Все неопознанные сообщения WM COMMAND Запускает операцию Winsock, запрошенную сообщением WM_COMMAND, либо возвращает единицу (1), позволяя WndProc вызвать стандартный обработчик сообщений Все операции Winsock, ^ запущенные при помощи меню Sockman к Операции f Winsock Рис. 13.6. Управляющие функции Sockman и отношения между ними 373 
Глава 13. Время и сетевой порядок байтов Sockman можно выделить функции WndProc, DoMenuCommand и DoWinsockProgram, обособление которых позволяет облегчить разработку и чтение текста программы. Функция WndProc обрабатывает сообщения Windows общего назначения и передает все командные сообщения (WM_COMMAND) функции DoMenuCommand. В свою очередь, функция DoMenuCommand обрабатывает все сообщения, которые генерируются командами меню Sockman, за исключением тех, которые непосредственно запускают процессы Winsock. Sockman инициирует все процессы Winsock с помощью функции DoWinsockProgram. Как вы уже знаете, утилита сервера времени, как и все программные модули Sockman, может генерировать сообщения Windows, которые будут обрабатываться функциями WndProc и DoMenuCommand. При этом любой процесс запускается только из функции DoWinsockProgram. Запуск утилиты сервера времени Когда пользователь программы Sockman щелкает кнопкой мыши в меню Utilities по команде Time Server, Windows посылает в функцию WndProc сообщение WM_COMMAND, аргумент wParam которого равен IDM_TIME_UTIL. Функция WndProc затем передает это сообщение в функцию DoMenuCommand, которая, в свою очередь, вызывает функцию DoWinsockProgram. Как следует из приведенного ниже фрагмента кода, DoWinsockProgram прежде всего проверяет глобальную переменную hTimeServerTask, чтобы проверить, не выполняется ли в этот момент другая операция с сервером времени. Если это так, то Winsock выводит окно сообщения, прося пользователя обождать, пока закончится эта операция: long DoWinsockProgram(HWND hwnd, UINT wParam, LONG lParam) { switch (wParam) { // ...прочие операторы case case IDM_TIME__UTIL: if (hTimeServerTask) //He более одного запроса //на сервер времени одновременно { MessageBeep(0) ; MessageBox(hwnd, "Timeserver utility is already in use. Please wait...", MB_OK) ; "SockMan - TIME SERVER", MB_ICONSTOP I } else TimeServerDialog() ; return(TRUE); 374 
Добавление запроса сервера времени в программу Sockman | // ...прочие операторы case } return(FALSE); } Как видите, если утилита сервера времени не занята в данный момент обращением к другому серверу, DoWinsockProgram вызывает функцию TimeServerDialog, которая, в свою очередь, выводит на экран диалоговое окно. Управление запросами к серверу времени пользователь осуществляет из этого диалогового окна. Утилита сервера времени Sockman в этом аспекте отличается от опций Lookup и Finger. Диалоговые окна в модулях Lookup и Finger запускают соответствующие модули программы, но сами эти диалоговые окна после запуска закрываются. В отличие от этого, при выполнении операции с сервером времени Sockman оставляет диалоговое окно открытым на все время обмена данными с сервером в Интернет. Диалоговое окно Time Server Диалоговое окно Time Server остается открытым программой Sockman во время всех операций с сервером времени. Поэтому функция этого диалогового окна, TimeServerDialogProc, обрабатывает все сообщения Windows, поступающие во время обмена данными с сервером. Как видно из рис. 13.7, диалоговое окно Time Server включает три текстовых поля ввода и три командных кнопки. TIME SERVER Server Host: Your Time: Server Time: Mon Jan 23 07:11:28 1995 _ auwy : i — - J Рис. 13.7. Диалоговое окно программы Sockman для утилиты сервера времени Из трех полей пользователь может редактировать только одно — поле Server Host. Чтобы указать адрес хоста, сервер времени которого будет запрошен, вы должны ввести в этом поле IP-адрес или имя. Как видно из рисунка, в поле Your Time отображается текущее время по часам вашего компьютера. Когда вы щелкаете мышью по кнопке Query, Sockman посылает запрос на сервер времени (точно так же, как это делалось в программе QTime), расположенный на хосте с указанным адресом. Затем Sockman отображает результаты запроса в поле Server Time. Примечание: Большинство поставщиков услуг Интернет используют для запросов сервера времени порт с номером 37. 375 
Кнопка Set Time так же выполняет запрос к серверу времени, как и кнопка Query. Однако кнопка Set Time, кроме того, устанавливает значения на внутренних часах вашего компьютера равными тем значениям, которые вернул сервер времени. Прежде чем вы захотите переустановить внутренние часы компьютера, вы можете с помощью кнопки Query проверить сервер времени на данном хосте и выяснить разницу между временем вашего компьютера и временем этого хоста. Sockman не требует от вас, чтобы вы посылали запрос на сервер времени, прежде чем менять показания внутренних часов. Это значит, что вы можете просто щелкнуть мышью по кнопке Set Time, и Sockman сам пошлет запрос на сервер времени и изменит показания внутренних часов в соответствии с результатами этого запроса. Кнопка Exit закрывает диалоговое окно Time Server. Создание диалогового окна В файле ресурсов Sockman, SOCKMAN4.RC, содержится определение ресурсов для диалогового окна Time Server. Как очевидно из следующего фрагмента кода, в диалоговом окне Time Server нет ничего слишком необычного. IDD_TIMESERVER просто определяет диалоговое окно, которое вы уже видели на рис. 13.7. IDD_TIMESERVER DIALOG DISCARDABLE 0, 0, 206, 113 STYLE DS_MODALFRAME I WS_POPUP I WS_VISIBLE I WS_CAPTION I WS_SYSMENU CAPTION "TIME SERVER" FONT 8, "MS Sans Serif" BEGIN EDITTEXT DEFPUSHBUTTON DEFPUSHBUTTON PUSHBUTTON RTEXT RTEXT EDITTEXT IDC_LOCAL_TIME/55,28,140,15,ES_AUTOHSCROLL | ES_READONLY I WS_DISABLED I NOT WS_TABSTOP RTEXT "Server Time:",IDC_STATIC,5,49,46,10 EDITTEXT IDC_SERVER_TIME,54,48,140,15,ES_AUTOHSCROLL | E S_READONLY I WS_DISABLED | NOT WS__TABSTOP END Как мы уже говорили, функция DoWinsockProgram вызывает функцию TimeServerDialog. Функция TimeServerDialog практически идентична функциям IDC_TIMESERVER,54,9,140,15,ES_AUTOHSCROLL "&Query",IDOK,55,70,65,16 "&Set Time",IDSETTIME,130,70,65,16, NOT WS_TABSTOP "E&xit",IDCANCEL,130,90,65,15 "Server Host:",IDC_STATIC,5,9,45,13 "Your Time:",IDC_STATIC,4,32,47,11 376 
Добавление запроса сервера времени в программу Sockman диалоговых окон, которые мы использовали для обработки команд меню Lookup и Finger. На самом деле, затратив небольшие усилия, вы сможете скомбинировать все три функции (TimeServerDialog, FingerDialog и LookupHostDialog) в одной. В программе Sockman эти три функции разделены с целью упростить примеры и собрать воедино функции программы, относящиеся к одной задаче. Вот как определена функция TimeServerDialog: BOOL TimeServerDialog(VOID) { DLGPROC lpfnDialogProc; BOOL bOkay; // Создаем диалоговое окно для пользовательского ввода lpfnDialogProc = MakeProcInstance((DLGPROC)TimeServerDialogProc, hlnstanceSockman); bOkay = DialogBox(hlnstanceSockman, "IDD_TIMESERVER", hwndSockman, lpfnDialogProc); FreeProcInstance(lpfnDialogProc); if (bOkay == -1) { wsprintf(szScratchBuffer, "Unable to create dialog box!"); MessageBeep(O); MessageBox(hwndSockman, szScratchBuffer, "SockMan-TIME SERVER QUERY", MB_OK|MB_ICONINFORMATION) ; bOkay = FALSE; } return(bOkay); } Как видите, функция TimeServerDialog использует функцию Windows MakeProcInstance, чтобы получить дескриптор процедуры диалога IDD_TIMESERVER, которая называется TimeServerDialogProc. Затем функция TimeServerDialog вызывает функцию DialogBox, чтобы создать IDD_TIMESERVER. Функция TimeServerDialogProc обрабатывает сообщения Windows для этого диалогового окна и управляет всеми действиями Sockman при выполнении операций с сервером времени. Когда вы выходите из диалогового окна Time Server, функция TimeServerDialog освобождает дескриптор диалогового окна и возвращает управление в функцию Do W insockProgram. 377 
Глава 13. Время и сетевой порядок байтов Управление диалоговым окном Time Server В показанном ниже фрагменте кода приведена базовая структура функции TimeServerDialogProc. Как видите, функция TimeServerDialogProc обрабатывает пять сообщений, и с некоторыми из них вы уже знакомы. Например, Windows посылает сообщение WM_INITDIALOG, прежде чем вывести на экран диалоговое окно. Когда вы щелкаете мышью по команде Close в управляющем меню окна, вы тем самым генерируете сообщение WM_CLOSE. Ниже в этой главе мы обсудим сообщения WM_GOT_SERVICE и WM_ASYNC_LOOKUP_DONE, которые являются специфическими сообщениями этого приложения (они определены в файле SOCKMAN4.H): BOOL _export CALLBACK TimeServerDialogProc(HWND hwndDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) static BOOL bSetTime; // time__t lPCTime; // time__t lNetTime; // NPSTR : npTime; // // Флаг установки времени PC Время PC Сетевое время Служебный указатель для значений времени switch (iMessage) { case WM_INITDIALOG: // Инициализируем диалоговое окно return(TRUE); case WM_CLOSE: PostMessage(hwndDlg, WM_COMMAND, IDCANCEL, OL); return(TRUE); case WM_GOT___SERVICE: LookupTimeServer(hwndDlg, lParam); return(TRUE); case WM__COMMATD: // Выполняем команды диалогового окна break; case WM_ASYNC_LOOKUP__DONE: // Посылаем запрос на сервер времени и // обрабатываем результаты return(TRUE); } return(FALSE); } 378 
Добавление запроса сервера времени в программу Шшшр Сообщения WM COMMAND генерируются, когда пользователь щелкает мышью по командным кнопкам диалогового окна. Как следует из приведенного ниже фрагмента, оператор case для сообщения WM_COMMAND включает еще одну вложенную конструкцию switch. Сообщение WM_COMMAND от функции TimeServerDialogProc, таким образом, обрабатывается разными участками кода в зависимости от того, какую именно из трех кнопок пользователь нажал в диалоговом окне: case WM_COMMAND: switch (wParam) { case IDSETTIME: bSetTime = TRUE; // Вызываем функции обработки нажатия IDOK case IDOK: GetDlgltemText(hwndDlg, IDC_TIMESERVER, (LPSTR) szTimeServer, MAX_HOST_NAME) ; if (lstrlen(szTimeServer) > 0) hTimeServerTask = AsyncGetServicelnfо(hwndDlg, TASK_TIMESERVER); else { bSetTime = FALSE; wsprintf(szScratchBuffer, "Please enter a host name."); MessageBeep(O); MessageBox(hwndDlg, szScratchBuffer, "SockMan-TIME SERVER QUERY", MB_OK I MB_I CONINFORMATI ON) ; } return(TRUE); case IDCANCEL: PostMessage(hwndSockman, WM_COMMAND, IDM_FILE_CLEAR, 0L); EndDialog(hwndDlg, FALSE); return(TRUE); } break; Когда вы щелкаете мышью по кнопке Set Time, Windows генерирует сообщение WM_COMMAND, аргумент wParam которого равен IDSETTIME. Если пользователь щелкает по кнопке Query, аргумент wParam равен IDOK. Аналогично, щелчок по кнопке Exit приведет к тому, что wParam будет равен IDCANCEL. Когда вы щелкаете мышью по кнопке Exit, функция TimeServerDialogProc посылает сообщение WM_COMMAND/IDM_FILE_CLEAR в главное окно 379 
Глава 13. Время и сетевой ш Sockman. Это сообщение заставляет Sockman очистить поле вывода своего окна. Это значит, что когда вы выходите из диалогового окна Time Server, Sockman стирает все сообщения от сервера времени, которые отображались в главном окне Sockman. Командные кнопки Set Time и Query обрабатываются, по сути, одним и тем же участком кода. Как видно из показанного выше фрагмента, ветвь IDSETTIME содержит только установку булевой переменной bSetTime в значение TRUE, после чего управление перескакивает на ветвь ШОК и выполняются те же операторы, которые выполнялись бы при нажатии кнопки Query. Другими словами, так же, как и кнопка Query, кнопка Set Time прежде всего заставляет Sockman выполнить еще один запрос на сервер времени. Единственная разница между этими двумя кнопками состоит в том, что в ветви IDSETTIME устанавливается флаг, который сообщает Sockman, что нужно изменить показания внутренних часов, установив их равными тем значениям, которые получены от сервера времени. Чтобы инициировать операцию сервера времени при получении сообщения с параметром ШОК, функция TimeServerDialogProc использует функцию GetDlgltemText, которая копирует имя или адрес хоста, введенные пользователем в поле IDC_TIMESERVER в глобальный строковый массив szTimeServer. Функция TimeServerDialogProc измеряет длину переменной szTimeServer, чтобы проверить, было ли введено какое-либо значение. Если пользователь не указал адреса хоста, функция TimeServerDialogProc выводит окно сообщения, в котором просит его указать адрес. Если же адрес был указан, TimeServerDialogProc вызывает функцию AsyncGetServicelnfo. Как вы видели на примере других учебных программ, прежде чем устанавливать соединение с программой-сервером на удаленном хосте, ваша программа должна получить определенную информацию, в частности номер протокола и IP-адрес. База данных сетевых служб содержит список протоколов Интернет и официальных номеров портов этих протоколов. Как мы говорили в предыдущих главах, функция AsyncGetServicelnfo инициализирует асинхронное получение информации из базы данных сетевых служб. Операторы функции AsyncGetServicelnfo (показанные в нижеприведенном фрагменте кода) для задачи обращения к серверу времени вполне идентичны операторам этой функции для модуля Finger, за тем исключением, что имя службы в данном случае другое (IPSERVIСЕ_Т1МЕ): HTASK AsyncGetServicelnfo(HWND hwnd, HTASK hService) Получение необходимой для запроса информации из Интернет { HTASK hTask // Дескриптор задачи // асинхронной службы 380 
Добавление запроса сервера времени в программу Sockman LPSTR IpServiceName; // Имя службы, к которой мы // обращаемся LPSTR lpBuffer; // Указатель на буфер хранения данных int nLength; // Длина буфера хранения данных switch (hService) { case TASK_ASYNC_FINGER: // ...информация службы Finger break; case TASK_TIMESERVER: IpServiceName = (LPSTR)IPSERVICE_TIME; lpBuffer = (LPSTR)szTimeServerBuffer; nLength = sizeof(szTimeServerBuffer); break; default: // ...сообщаем пользователю, что эта служба не // поддерживается return(0); } hTask = WSAAsyncGetServByName(hwnd, WM_GOT_SERVICE, IpServiceName, NULL, lpBuffer, nLength); if (IhTask) // ...посылаем сообщение об ошибке return(hTask); } Функция AsyncGetServicelnfo вызывает функцию Winsock WSAAsyncGetServByName, чтобы получить запись, соответствующую службе времени, из базы данных сетевых служб. Вызов функции WSAAsyncGetServByName заставляет Windows послать сообщение WMGOTSERVICE в указанное окно: hTask = WSAAsyncGetServByName(hwnd, WM_GOT_SERVICE, IpServiceName, NULL, lpBuffer, nLength); В нашем случае сообщение WM_GOT_SERVICE будет получено диалоговой процедурой TimeServerDialogProc. Как мы видели выше, операторы программы для сообщения WM_GOT_SERVICE в функции TimeServerDialogProc вызывают функцию LookupTimeServer со следующими параметрами: LookupTimeServer(hwndDlg, lParam); Параметр hwndDlg идентифицирует диалоговое окно, а параметр lParam содержит ошибочные условия (если таковые имели место), вызванные асинхронным 381 
Глава 13. Время и сетевой поря до к Ы тш поиском хоста сервера времени в DNS. Как следует из нижеприведенного фрагмента кода, функция LookupTimeServer прежде всего пытается обнаружить ошибки, возникшие при асинхронном поиске сервера времени в DNS. Если макрос WSAGETASYNCERROR фиксирует ошибку, функция LookupTimeServer использует номер порта по умолчанию (IPPORT_TIMESERVER), определенный в файле winsock.h. В обратном случае, LookupTimeServer извлекает номер протокола из записи о службе, полученной функцией WSAAsyncGetServByName и сохраненной в szTimeServerBuffer. Функция LookupTimeServer сохраняет номер порта в глобальной переменной nTimeServerPort, после чего вызывает функцию LookupHostAsync: VOID LookupTimeServer(HWND hwnd, LPARAM lError) { if (WSAGETASYNCERROR(lError)) nTimeServerPort = htons (IPPORT__TIMESERVER) ; else nTimeServerPort = ((LPSERVENT)szTimeServerBuffer)->s_port; hTimeServerTask = LookupHostAsync(hwnd, szTimeServer, szTimeServerBuffer, (LPDWORD)&dwTimeServerAddr); if (!hTimeServerTask) { wsprintf(szTimeServerBuffer, "Unable to lookup: %s", (LPSTR)szTimeServer); MessageBeep(O); MessageBox(hwnd, szTimeServerBuffer, "SockMan-TIME SERVER QUERY", MB_OK|MB_ICONINFORMATION) ; PaintWindow(s zTimeServerBuf f er); } return; } Хотя в приведенном фрагменте кода это не показано, функция LookupHostAsync может использовать функции Winsock WSAAsyncGetHostByName или WSAAsyncGetHostByAddr, чтобы асинхронно преобразовать имя хоста или его IPадрес в форме «десятичное с точкой». Будучи вызвана из функции LookupHostAsync, WSAAsyncGetHostByName, как и WSAAsyncGetHostByAddr, заставляет Windows послать сообщение WM_ASYNCJLOOKUP_DONE после того, как они завершат преобразование адреса. Другими словами, когда функция LookupHostAsync завершит преобразование адреса хоста, хранящегося в переменной szTimeServer, диалоговое окно Time Server получит сообщение WM ASYNC LOOKUP DONE. 382 
Добавление запроса сервера времени в программу Sockman Посылка запроса на сервер времени После того как Sockman получит номер протокола из базы данных сетевых служб с помощью функции AsyncGetServicelnfo и найдет сетевой адрес сервера времени (с помощью функции LookupHostAsync), Sockman будет иметь всю необходимую ему информацию, чтобы послать запрос на сервер времени. Когда процедура диалогового окна сервера времени, TimeServerDialogProc, получает сообщение WM_ASYNC_LOOKUP_DONE, функция TimeServerDialogProc вызывает функцию PaintWindow с сообщением, уведомляющим пользователя, что асинхронный поиск адреса для операции с сервером времени завершен. Как следует из нижеприведенного фрагмента кода, сообщив пользователю о завершении операции поиска адреса, функция TimeServerDialogProc вызывает функцию TimeServerQuery, которая, собственно, и посылает запрос на сервер времени. После того как функция TimeServerQuery завершит свою работу, функция TimeServerDialogProc приравняет дескриптор задачи сервера времени, hTimeServerTask, к нулю, тем самым сигнализируя, что операция с сервером времени завершена: case WM_ASYNC_LOOKUP_DONE: PaintWindow ("Asynchronous lookup for Time Server completed.") ; lNetTime = TimeServerQuery(hwndDlg, lParam) ; hTimeServerTask = 0; if (lNetTime != SOCKET_ERROR) // Обрабатываем результаты else SetDlgltemText (hwndDlg, IDC_.SERVER_.TIME, "") ; MessageBeep(0); return(TRUE); Функция TimeServerQuery возвращает значение времени, к которому можно сразу применять функции даты и времени вашего персонального компьютера. Другими словами, TimeServerQuery возвращает текущую дату и время в виде значения, представляющего число секунд с полуночи 1 января 1970 года. Если запрос на сервер времени не дал ожидаемых результатов, функция TimeServerQuery возвращает константу SOCKET_ERROR, определенную в Winsock, а функция TimeServerDialogProc устанавливает значение поля ввода IDC_SERVER_TIME в диалоговом окне равным строке нулевой длины. Иными словами, при возникновении ошибки функция TimeServerDialogProc стирает любое значение даты и времени, которые до этого момента отображала программа Sockman. Если же никаких ошибок не произошло (т. е. функция TimeServerQuery не вернула значение SOCKET_ERROR), функция TimeServerDialogProc обрабатывает результаты запроса на сервер времени. Вне зависимости от того, произошла при запросе ошибка или нет, функция TimeServerDialogProc использует 383 
Глава 13. Время и сетевой порядок Шайтов функцию Windows MessageBeep, чтобы сообщить пользователю о завершении операции с сервером времени. Результаты запроса на сервер времени Как мы уже говорили, когда вы щелкаете мышью по кнопке Set Time в диалоговом окне Time Server, функция TimeServerDialogProc устанавливает булеву переменную-флаг bSetTime равной True. Когда функция TimeServerQuery возвращает осмысленное значение переменной INetTime и при этом значение bSetTime равно True, функция TimeServerDialogProc изменяет показание встроенных часов вашего компьютера, присваивая им ту величину, которая хранится в переменной INetTime. Изменив (если необходимо) показания системных часов, функция TimeServerDialogProc вызывает функцию ctime, чтобы преобразовать сетевое время и время PC (хранящиеся соответственно в переменных INetTime и lPCTime) в строковые значения. Затем функция TimeServerDialogProc вызывает функцию Windows SetDlgltemText, чтобы записать эти строковые значения в поля ввода IDC_SERVER_TIME и IDC_LOCAL_TIME диалогового окна. Функция TimeServerDialogProc также вызывает функцию PaintWindow, чтобы отобразить текущее значение времени PC в главном окне Sockman. Ниже приведен фрагмент кода, в котором TimeServerDialQgProc использует стандартные функции и структуры DOS, чтобы изменить показания системных часов вашего компьютера. Функция TimeServerDialogProc использует структуры _dostime_t и _dosdate_t, а также указатель на структуру DOS tm. Чтобы преобразовать значение INetTime, полученное от TimeServerQuery, в структуру DOS tm, функция TimeServerDialogProc вызывает функцию localtime из соответствующей библиотеки компилятора С: if (bSetTime) { struct tm *npTMStruct; struct _dostime_t dosTimeStruct; struct _dosdate_t dosDateStruct; npTMStruct = localtime(&lNetTime); dosTimeStruct.hour = (BYTE)(npTMStruct->tm_hour); dosTimeStruct.minute = (BYTE)(npTMStruct->tm_min); dosTimeStruct.second = (BYTE)(npTMStruct->tm_sec); dosTimeStruct.hsecond = (BYTE)0; _dos_settime(&dosTimeStruct); dosDateStruct.year = npTMStruct->tm_year + 1900; dosDateStruct.month = (BYTE)(npTMStruet->tm_mon +1); dosDateStruct.day = (BYTE)(npTMStruet->tm_mday); _dos_setdate(&dosDateStruct); 384 
bSetTime = FALSE; Результаты запроса на сервер времени } Функция localtime возвращает указатель на внутренний статический буфер, в котором хранятся значения составных частей структуры tm. Функция TimeServerDialogProc использует значения tm для хранения даты и времени, полученных от сервера времени, в структурах __dostime_t и _dosdate_t. Сохранив соответствующие значения в структурах _dostime_t и _dosdate_t, функция TimeServerDialogProc вызывает функции _dos_settime и _dos_setdate, которые и изменяют дату и время во встроенных часах вашего PC. Прежде чем закончить эту ветвь оператора if, TimeServerDialogProc сбрасывает флаг bSetTime в значение FALSE. Запрос на сервер времени Как показано выше, функция TimeServerDialogProc вызывает функцию TimeServerQuery, чтобы инициировать посылку запроса на сервер времени: lNetTime = TimeServerQuery (hwndDlg, lParam); Функция TimeServerQuery посылает запрос на сервер времени практически так же, как это делалось в программе QTime, которую мы рассматривали выше в первой части этой главы. Дискета, прилагаемая к книге, содержит полный исходный текст функции TimeServerQuery. Однако мы не будем возвращаться к программе QTime, а вместо этого рассмотрим приведенный ниже псевдокод, описывающий алгоритм функции TimeServerQuery: LONG TimeServerQuery(HWND hwnd, LPARAM lError) { // Прежде всего объявляем локальные переменные, // затем проверяем значение ошибки в lError if (WSAGETASYNCERROR(lError)) // если ошибка зафиксирована, выводим окно сообщения и // возвращаем значение SOCKET__ERROR, // иначе, - получаем указатель на структуру данных хоста // и сохраняем значения в структуре адреса сокета // Создаем сокет if ((hSocket = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL)) == INVALID_SOCKET) // Если зафиксирована ошибка, выводим окно сообщения и // возвращаем значение SOCKET_ERROR // иначе, соединяем сокет с удаленным хостом if (connect(hSocket, (PSOCKADDR)&socketAddr, sizeof(socketAddr))) 13 Зак. № 1949 385 
Глзгёа 13. Время и си!е«'»й ii0u>i40k £&Й‘ // Если зафиксирована ошибка, выводим окно сообщения и // возвращаем значение SOCKET__ERROR // Посылаем на сервер времени CR/LF send(hSocket, " \n", sizeof("\n"), NO_FLAGS); // Сохраняем значение, возвращенное сервером времени, и // закрываем сокет nLength = recv(hSocket, (LPSTR)&lNetTime, sizeof(lNetTime), 0); closesocket(hSocket); // Проверяем, не сигнализируется ли ошибка сокета // значением, возвращаемым функцией recv if(nLength == SOCKET_ERROR) // Если зафиксирована ошибка, выводим окно сообщения и // возвращаем значение S0CKET_ERR0R, // Иначе, преобразуем порядок байтов lNetTime из // сетевого порядка байтов в порядок байтов хоста, а // затем выставляем время PC равным сетевому времени. // Запомните, что в Интернет в качестве точки отсчета // используется 1 января 1900 года, тогда как функции // времени PC используют в качестве точки отсчета // 1 января 1970 года. lPCTime = ntohl(lNetTime); lPCTime -= PC_REF_TIME; // Наконец, возвращаем сетевое время как время PC return(lPCTime); } Подводя итоги В этой главе вы учились использовать протокол Time Protocol Интернет для получения текущего значения даты и времени от программы-сервера времени на одном из хостов Интернет. По пути мы обнаружили, что компьютеры PC хранят числовые данные совсем не так, как это делают многие другие компьютеры, подключенные к Интернет. В частности, мы рассмотрели, как PC и Интернет используют разные порядки байтов для хранения числовых данных. Мы также научились преобразовывать порядок байтов в числовых данных и выяснили, к чему может привести отсутствие этих преобразований в необходимых местах. Мы также узнали, как неправильное использование переменных со знаком и без знака может привести 386 
к дополнительным ошибкам и несоответствиям при отладке программ и передачи числовых данных по сети Интернет. В следующей главе мы научимся создавать простые сокеты, которые позволят вашим программам получать доступ к низкоуровневым протоколам Интернет, таким как протокол управляющих сообщений Интернет (Control Message Protocol, ICMP). Но прежде чем мы перейдем к простым сокетам и утилите Ping, рассматриваемой в главе 14, давайте повторим основные концепции, с которыми мы познакомились в этой главе: S Порядок байтов «с конца» означает, что наиболее значимый (старший) байт числового значения сохраняется в первой (обладающей наименьшим номером) ячейке памяти в том участке, который отведен под это значение. S Порядок байтов «с начала» означает, что наименее значимый (младший) байт числового значения сохраняется в первой (обладающей наименьшим номером) ячейке памяти в том участке, который отведен под это значение. S На PC для хранения числовых значений используется порядок байтов «с конца». S Для передачи числовых значений по Интернет их требуется преобразовать к порядку байтов «с начала». S Winsock API предоставляет четыре функции для преобразования порядка байтов; две из них преобразуют 16-битовые числа и две другие — 32-битовые числа. S Во избежание проблем и недоразумений желательно объявлять буферы хранения данных, используя те типы данных, которые будут записываться в эти буферы данных по мере прихода из Интернет. S Одна и та же последовательность битов может представлять два разных значения в зависимости от того, используете вы переменные со знаком или без знака. 
Глав Простые сокеты Вы знаете, что для доставки пакетов данных в Интернет используется ненадежный, однако эффективный протокол IP. IP не гарантирует доставку сетевых пакетов и не гарантирует уведомления в случае повреждения каналов связи или пакетов. Однако в некоторых случаях программное обеспечение TCP/IP все-таки вырабатывает сообщения о сетевых ошибках. В этой главе мы подробнее рассмотрим сообщения об ошибках, условия, при которых они возникают, и доставляющий сообщения протокол — протокол управляющих сообщений Интернет (Internet Control Message Protocol, ICMP). Вы узнаете, как при помощи Winsock API программа может генерировать сообщения ICMP. Для этого используется так называемый простой (raw) сокет. Как мы уже говорили, простой сокет обеспечивает доступ к протоколам низкого уровня, например ICMP. Прочитав эту главу, вы будете знать: ♦ Как структура сети TCP/IP ограничивает возможности передачи сообщений об ошибках. ♦ Какие виды ошибок ICMP находит и как их обрабатывает. ♦ Как можно интерпретировать сообщения ICMP. 388 
■ га м СМ ♦ Как создается простой сокет и как через него передаются и принимаются данные. Существуют два варианта WINSOCK.DLL. Один обеспечивает работу с простыми сокетами, а другой — нет. (Дело в том, что спецификация Winsock версии 1.1 не обязывает включать обработку простых сокетов в WINSOCK.DLL.) Поэтому программа, в которой используются простые сокеты, будет работать не на каждой локальной сети. В этой главе мы покажем, как возможность того или иного WINSOCK.DLL обслуживать простые сокеты распознается программным путем. Примечание: Программа Trumpet WINSOCK.DLL, на прилагаемой к книге дискете, умеет работать с простыми сокетами. Это значит, что даже если ваша локальная сеть и не поддерживает простые сокеты, вы все равно можете разрабатывать программы на их основе. Что такое ICMP? Протокол управляющих сообщений Интернет, ICMP, предназначен для обработки сетевых ошибок и других ситуаций, требующих вмешательства сетевых программ. В тех редких случаях, когда программа обращается к сетевому уровню, минуя протоколы транспортного уровня TCP или UDP, скорее всего ей нужен ICMP. Протокол ICMP описан в RFC 792 (Internet Control Message Protocol, Postel, 1981). Для того чтобы обратиться к протоколу низкого уровня, в частности, к ICMP, программе нужен простой сокет. Необходимо понимать, что создание простого сокета — нетривиальная задача, требующая от программиста значительных усилий. Так, например, для работы с ICMP на простом сокете вы будете должны самостоятельно заполнять структуры данцых заголовков ICMP. Общая картина Сетевой уровень TCP/IP состоит из модуля IP и двух других модулей: ICMP и протокола групповых сообщений Интернет (IGMP). Для доставки данных ICMP, так же, как TCP и UDP, пользуется протоколом IP. На рис. 14.1 показано положение ICMP в стеке TCP/IP. Так же, как и IP, ICMP является частью сетевого уровня. Однако по отношению к IP ICMP все-таки протокол более высокого уровня. Другими словами, он доставляет собственные сообщения при помощи IP, точно так же, как это делают TCP или UDP. Вы знаете, что доставка сообщений IP происходит в среде сетей с переключением пакетов, через мосты и маршрутизаторы. В большинстве случаев сеть работает надежно, поэтому у самого IP нет никаких методов, чтобы уведомить пакетный переключатель или маршрутизатор об ошибке при доставке пакета (что сделало протокол IP гораздо проще в реализации). ICMP добавляет эти методы к арсеналу TCP/IP. Как мы уже говорили, если прикладная программа не пользуется надежным протоколом, она и не должна рассчитывать 389 
Глава 14. Простые сокеты уровень : Транспортный | Программа] | Программа] | Программа] ] Программа] TCP I Сетевой i уровень; I,...:».... юмр| > ip (fV-.fl к г- UDP «». • ...... | (соединения (уЩйштш^шШк ARP ' ' ■ ШЬ в Интерфейс ■». физического щЩ RARPL уровня Физический уровень Канал передачи данных Рис. 14.1. Сетевой уровень TCP/IP с модулями IP, ICMP и IGMP на получение каких-либо уведомлений об ошибках при доставке данных. Конструкция стека протоколов TCP/IP такова, что сообщения об ошибках видны только сетевому или транспортному уровням. Ошибки, о которых сообщается Если все сетевые компьютеры функционируют нормально и знают, как правильно маршрутизировать данные, сеть TCP/IP доставляет сообщения вполне эффективно. Разумеется, если в сети возникают проблемы, они в первую очередь влияют на доставку данных. Те или иные проблемы свойственны любой сети, не только на базе TCP/IP. Если неисправность местная, генерируется сообщение о неисправности аппаратуры, например. Для определения неисправности используются разнообразные приборы-анализаторы. Если неисправность удаленная, сначала необходимо выяснить, где она находится. Для этого исследуется структура сети, то есть совокупность маршрутизаторов, мостов и другой техники, установленной на пути к месту неисправности. В случае, если неисправность возникает где-то в объединении сетей, найти ее практически невозможно или крайне затруднительно. Назначение ICMP Изначально, ICMP проектировался как протокол, позволяющий маршрутизатору указать сетевому уровню передающего хоста на ошибку при доставке пакета. Сетевой уровень, в свою очередь, мог предпринять ответные действия. В последствии оказалось, что ICMP полезен не только маршрутизаторам. Им 390 
Что такое ICMP? .7■ ЯЯ л " ж :У;.ж жл может воспользоваться любой сетевой компьютер для передачи сообщения об ошибке управляющего либо информационного сообщения другому сетевому компьютеру на локальной или глобальной сети. Сообщения ICMP инкапсулируются в IP-датаграммы. Пункт назначения сообщения ICMP — всегда сетевой уровень и никогда — определенный пользователь или сетевое приложение. Модуль ICMP IP-уровня компьютера-получателя определяет, передать ли сообщение протоколам высокого уровня, например транспортным или прикладным. Как вы увидите дальше, TCP/IP ограничивает сферу применения некоторых сообщений ICMP. Сообщения об ошибках ICMP ICMP обеспечивает только сообщения об ошибках. То есть в нем отсутствуют какие-либо функции по исправлению ошибочных данных. ICMP никак не определяет характер действий сетевого уровня в ответ на те или иные ошибки. Свойственные ICMP ограничения Если при доставке пакета случается ошибка, ICMP доставляет сообщение только компьютеру-источнику. Как правило, это ограничение несущественно, поскольку ошибки возникают чаще всего из-за компьютера-источника сообщения. Однако поскольку ни один из промежуточных переключателей пакетов не получает ICMP-сообщения, то и компьютер-источник не всегда в состоянии исправить ошибку. Предположим, что неисправный переключатель пакетов неправильно маршрутизирует сообщение. По протоколу ICMP компьютер, принявший «неправильный» пакет, может доложить об этом только компьютеру-источнику. В результате, компьютер-источник оказывается не в состоянии определить, где именно случился сбой, и предпринять какие-либо ответные действия. Чтобы понять, почему протокол ICMP ограничен, давайте пристальнее рассмотрим дизайн семейства протоколов TCP/IP. А именно, давайте вспомним, что нам известно о маршрутизации IP-датаграмм. Заголовок IP-датаграммы содержит только адреса источника и приемника данных. Адреса в заголовке остаются неизменными на протяжении всего путешествия датаграммы по сети. Когда датаграмма попадает в переключатель пакетов, он исследует адрес назначения, сопоставляет его с имеющимися в таблице маршрутизации данными и отправляет по заданному маршруту, то есть к следующему переключателю пакетов. В конце концов, датаграмма доходит до переключателя пакетов, связанного напрямую с сетью или компьютером-получателем данных. В этом случае датаграмма отправляется прямо к компьютеру-получателю. В сетях TCP/IP маршрутизация происходит динамически, то есть таблицы обновляются и корректируются на ходу, и ни один отдельный маршрутизатор (переключатель пакетов) не знает всей таблицы маршрутизации объединения сетей. (Для упрощения протоколы маршрутизации TCP/IP сделаны невидимыми со стороны остальных протоколов семейства.) В результате, когда датаграмма доходит до получателя, ни она, ни получатель не имеют ни малейшего представ- 391 
|§р»ва' 14. Простые сокеты ления о пройденном маршруте. Также не существует метода, чтобы его выяснить. В результате, ICMP может доложить об ошибке доставки датаграммы только компьютеру-источнику данных. Для того чтобы ICMP обладал большими возможностями, понадобилось бы переделать все семейство TCP/IP. И всетаки, несмотря на ограничения, ICMP является достаточно мощным средством в ряду протоколов TCP/IP. Постановка задачи Для доставки своих данных ICMP пользуется услугами IP. В результате, приоритет сообщения ICMP и его надежность оказываются не выше, чем у любого другого пакета IP. Более того, ICMP сам может привести к некоторым проблемам при доставке данных. Например, если ICMP-сообщение возникает в результате столкновения сетевых данных, поток данных увеличивается, еще больше усугубляя ситуацию. Аналогично, если сообщение об ошибке ICMP умудряется само сгенерировать сообщение об ошибке, то оно, в свою очередь, может сгенерировать еще одно и т. д. Для того чтобы не возникало лишних сообщений об ошибках, пришлось разработать несколько важных правил. До того как вы познакомитесь с ними, необходимо познакомиться с двумя типами ICMP-сообщений: сообщением-запросом и сообщением об ошибке. Вы будете использовать сообщение-запрос ICMP для измерения величины задержки прохождения датаграммы между двумя компьютерами. Сообщения-запросы ICMP служат для переноса или запросов сетевой информации. Сообщение-запросы и сообщения об ошибках Как правило, сообщения об ошибках ICMP появляются в результате каких-либо проблем с доставкой сетевого сообщения. Таким образом, ICMP-сообщение тесно связано с IP-датаграммой — виновницей его появления. Сообщение об ошибке всегда содержит заголовок этой IP-датаграммы и первые 64 бита .(восемь байт) ее данных. В отличие от сообщения об ошибке, сообщение-запрос не связано с ошибками доставки. Сообщение-запрос доставляет информацию относительно определенной сети или сетевого компьютера. Сообщения-запросы ICMP используются для диагностических целей. Правила выдачи сообщений об ошибках ICMP В спецификации TCP/IP четко определены правила, руководствуясь которыми сетевой компьютер решает, может ли он передать ICMP-сообщение. Например, сообщение-запрос ICMP может привести к возникновению сообщения об ошибке. Чтобы сообщение об ошибке не привело к возникновению следующего сообщения ит. д., пока канал связи не исчерпает своей пропускной способности, сетевое программное обеспечение никогда не генерирует ICMP-сообщение об ошибке в ответ на другое ICMP-сообщение. Сообщение об ошибке никогда не генерируется в ответ на IP-датаграммы с широковещательным или групповым адресом. Такие адреса означают, что датаграмма направлена нескольким 392 
I v компьютерам, а их одновременная реакция на ICMP-сообщение может привести к полной перегрузке сети (broadcast storm). Чтобы лучше осознать последнее заявление, рассмотрим, что получится, если датаграмма с широковещательным адресом столкнется с проблемами при доставке и сгенерируется ICMP-сообщение об ошибке. Поскольку широковещательная датаграмма принимается всеми сетевыми компьютерами, IP-уровень каждого из них сгенерирует собственное ICMP-сообщение, и произойдет это одновременно. Ситуация лишь не намного улучшается, если адрес датаграммы не широковещательный, а групповой. Неопознанный адрес источника IP-датаграммы приведет к еще большим проблемам, поскольку ICMP-сообщения высылаются именно по его адресу. Если размер датаграммы слишком велик для физического уровня конкретной сетевой технологии, TCP/IP фрагментирует ее для пересылки по частям. Если фрагмент датаграммы по каким бы то ни было причинам повреждается или исчезает, ICMP отсылает сообщение об ошибке компьютеру-источнику. Поскольку компьютеру-источнику все равно потребуется повторить всю датаграмму целиком, ICMP-сообщение связывается только с первым поврежденным фрагментом. Такой подход также помогает справиться с проблемами перегрузки сети. Что такое ЮМР-сообщение? Как мы уже говорили, ICMP передает сообщения-запросы и сообщения об ошибках. TCP/IP инкапсулирует ЮМР-сообщение в IP-датаграмму. Сетевые программы распознают ICMP-сообщения по двум признакам: 8-битному значению Туре и 8-битному значению Code. Как показано на рис. 14.2, два этих значения являются первыми полями ЮМР-заголовка. Позиции битов < 0 7 | 8 15 > 16 31 Тип Код Контрольная сумма Состав содержимого определяется полями типа и кода ^ ICMP-пакета а. Сообщение ICMP с определенными полями заголовка 1Р-датаграмма IP-заголовок | ICMP-; ■заголовок Данные 1СМР б. Инкапсуляция ICMP Рис. 14.2. Формат и инкапсуляция сообщения ICMP Сразу за двумя полями «Тип» (Туре) и «Код» (Code) следует 16-битное поле контрольной суммы. Как известно, контрольная сумма позволяет определить повреждение пакета на другом конце сетевого соединения. Позже в этой главе вы познакомитесь с исходным текстом процедуры на языке C/C++ для вычис- 393 
Глава 14, Простые сокеты ления контрольной суммы. Контрольная сумма вычисляется не только для заголовка, но и для тела ICMP-сообщения. Дальнейшее содержимое заголовка зависит от типа сообщения. Так же как мы уже отмечали, ICMP-сообщение об ошибке содержит заголовок 1Р-датаграммы и первые 64 бита (восемь байт) ее данных. Кроме двух рассмотренных классов ICMP-сообщений существует еще 15 типов. Они приведены в табл. 14.1. Таблица 14.1. Типы сообщений ICMP Тип Запрос-Ошибка Описание 0 Запрос Ответ эхо (echo reply) 3 Ошибка Пункт назначения недоступен (destination unreachable) 4 Ошибка Столкновение данных (source quench) 5 Ошибка Перенаправление (redirect) 8 Запрос Запрос эхо (echo request) 9 Запрос Информация от маршрутизатора (router advertisement) 10 Запрос Регистрация маршрутизатора (router solicitation) 11 Ошибка Лимит времени превышен (time exceeded) 12 Ошибка Неверный параметр (parameter problem) 13 Запрос Временная метка-запрос (timestamp request) 14 Запрос Временная метка-ответ (timestamp reply) 15 Запрос Информационный запрос (устарело) (information request) 16 Запрос Информационный ответ (устарело) (information reply) 17 Запрос Запрос маски адреса (address mask request) 18 Запрос Ответ маски адреса (address mask reply) Сообщения об ошибках ICMP Как видно из табл. 14.1, ,в ICMP определено множество различных типов сообщений, включая пять типов сообщений об ошибках. В следующих разделах мы по отдельности рассмотрим каждый тип сообщения об ошибках. Некоторые из этих типов являются подмножествами сообщений. Например, тип 3, «пункт назначение недоступен», имеет 16 различных видов «недоступности». 394 
■ Сообщения об ошибках «пункт назначения недоступен» Как мы уже говорили, изначально ICMP требовался для того, чтобы маршрутизатор мог сообщить о проблемах при доставке пакета. Если маршрутизатор не в состоянии направить пакет по нужному пути, он генерирует сообщение об ошибке типа 3 и посылает его компьютеру-источнику. Поскольку генерация такого рода сообщений была основной задачей ICMP, сообщения «пункт назначения недоступен» имеют наибольшее количество вариантов. Шестнадцать (0—15) кодов ошибок сообщения «пункт назначения недоступен» приведены в табл. 14.2. Таблица 14.2. Сообщения об ошибках ICMP типа пункт назначения недоступен Код Описание 0 Сеть недоступна (network unreachable) 1 Компьютер недоступен (host unreachable) 2 Протокол недоступен (protocol unreachable) 3 Порт недоступен (port unreachable) 4 Фрагментация необходима, но запрещена (fragmentation needed and DF set) 5 Маршрутизация на заказ не удалась (source route failed) 6 Сеть назначения неизвестна (destination network unknown) 7 Компьютер назначения неизвестен (destination host unknown) 8 Компьютер-источник изолирован (устарело) (source host isolated) 9 Доступ в сеть назначения запрещен (destination network administratively prohibited) 10 Доступ в компьютер назначения запрещен (destination host administratively prohibited) И Для этого типа службы сеть недоступна (network unreachable for type-of-service, TOS) 12 Для этого типа службы компьютер недоступен (host unreachable for type-of-service, TOS) 13 Связь запрещена из-за фильтрации (communication administratively prohibited by filtering) 14 He соблюдается приоритет хоста (host precedence violation) 15 Снижение приоритета (precedence cutoff in effect) 395 
Глава 14. Простые сокеты Протокол IP не гарантирует доставку данных. С другой стороны, он почти всегда успешно справляется с доставкой. Если IP не удается доставить датаграмму, значит, возникла сетевая проблема, например ошибки при маршрутизации. Если вы внимательно изучили табл. 14.2, то наверное заметили, что большинство сообщений об ошибках относятся либо к компьютеру, либо к сети. Как правило, сообщения, относящиеся к компьютеру, означают проблему с доставкой, а сообщения, относящиеся к сети, — проблему с маршрутизацией. Так, например, код ошибки «сеть недоступна» означает проблему с маршрутизацией. На рис. 14.3 изображен формат ICMP-сообщения «пункт назначения недоступен». Формат сообщения подобен общему формату, изображенному на рис. 14.2. Тип сообщения в поле типа равен трем, а поле кода (Code) соответствует коду ошибки от 0 до 15. Кроме того, в ICMP-сообщении содержится заголовок 1Р-датаграммы, вызвавшей его появление, и первые 64 бита (восемь байт) ее данных. Позиции битов 4 0 7 32 бита 8 15 16 -► 31 Тип (3) Код (0-15) Контрольная сумма Т 8 байтов + Не используется (заполняется нулями) IP-заголовок, включая опции и первые 64 бита данных ^ их первоначальной датаграммы Рис. 14.3. Формат ICMP-сообщения «пункт назначения недоступен» Пункт назначения может быть недоступен, если IP-заголовок содержит неправильный адрес назначения, либо если какое-нибудь промежуточное сетевое устройство выключено, либо, что менее вероятно, в таблице маршрутизации отсутствует путь к сети назначения. Все маршрутизаторы обязаны сообщать о сбое при доставке пакета источнику этого пакета. К сожалению, сами эти сообщения могут теряться. Существующая вероятность потери сообщения об ошибке и делает протокол ICMP ненадежным. (Вы помните, что ICMP пользуется услугами ненадежного IP.) Сообщения об ошибках перенаправления Как вы знаете из четвертой главы, чтобы выяснить, по какому маршруту послать пакет, переключатели пакетов TCP/IP пользуются таблицами маршрутизации. Маршрутизация пакета основывается на номере сети назначения, а идентификатор сети назначения — это часть IP-адреса. Каждый маршрутизатор знает своего соседа, то есть следующую «остановку», которых может быть несколько, на пути пакета данных. В некоторых случаях к определенной сети могут вести несколько маршрутов. Для постоянного слежения за состоянием сети маршрутизаторы периодически обмениваются сообщениями. Тем не менее таблицы маршрутизации обновляются не очень часто. Исходные данные для них хранятся в местных файлах конфигурации — это минимально необходимая для начала 396 
работы маршрутизатора информация, как правило, адрес другого маршрутизатора или шлюза. Компьютеры обновляют свои таблицы, основываясь на информации от маршрутизаторов и сообщения о перенаправлении ICMP — один из способов решать эту задачу. Предположим, что ваш компьютер посылает датаграмму на компьютер коллеги, расположенный, как показано на рис. 14.4, в другой физической сети. Чтобы послать датаграмму, необходимо обратиться к маршрутизатору. Предположим, что датаграмма посылается маршрутизатору номер 2. Исследовав свою таблицу, маршрутизатор обнаружит, что компьютер коллеги находится на расстоянии одного «прыжка» от маршрутизатора номер 1 и что именно ему следует послать датаграмму. В результате, датаграмма отправляется к маршрутизатору номер 1. Маршрут *Т датаграмм < _ ►_ f f ICMP-сообщение о перенаправлении Ваш сетевой компьютер | I ^ 1 к I +,со J I i г_ __ j 0) Маршрутизатор 21 §« с| х§. ад Маршрутизатор 11 Сетевой компьютер вашего приятеля Рис. 14.4. Пример ICMP-сообщения о перенаправлении (тип 5) Кроме того, маршрутизатор номер 2 знает (из содержимого датаграммы и собственной таблицы), что ваш компьютер подсоединен к маршрутизатору номер 1 напрямую, а следовательно, датаграмму можно слать сразу ему — это будет оптимальный маршрут. Как только маршрутизатор определит, что существует лучший маршрут, он отправит ICMP-сообщение о перенаправлении компьютеру-источнику датаграммы. На рис. 14.5 показан формат сообщения о перенаправлении. Компьютер-передатчик выясняет, что следующие датаграммы лучше слать маршрутизатору, IP-адрес которого содержится в ICMP-сообщении. Другими словами, получив ICMP-сообщение о перенаправлении, ICMP-модуль компьютера должен исследовать содержимое заголовка IP-датаграммы (в теле сообщения) и IP-адрес маршрутизатора (второе 32-битное слово в заголовке сообщения). IP-заголовок даст адрес, по которому датаграммы шли неверным маршрутом, а 397 
Глава <14; ПрО«7ТЬ1€ Позиции битов И |0 7 32 бита 8 15 ► 16 31 Тип (5) Код (6-3) [~ Контрольная сумма Т 8 байтов IP-адрес маршрутизатора Заголовок IP и 64 бита данных ^ из первоначальной датаграммы Рис. 14.5. ICMP-сообщение о перенаправлении (тип 5) IP-адрес маршрутизатора — тот маршрутизатор, который нужно использовать теперь. Эта информация может потребоваться для обновления таблицы маршрутизации сетевого компьютера. В табл. 14.3 приведены четыре кода сообщений о перенаправлении (тип 5). В примере на рис. 14.4 маршрутизатор пошлет сообщение типа 5 с кодом 1 (перенаправление для хоста). Таблица 14.3. ICMP-сообщения об ошибках перенаправления (тип 5) Код Описание 0 Перенаправление для сети 1 Перенаправление для хоста (сетевого компьютера) 2 Перенаправление для сети по типу сетевой службы (TOS) 3 Перенаправление для хоста по типу сетевой службы (TOS) Как видим, маршрутизатор может перенаправить сообщение, основываясь на содержимом поля «тип сетевой службы» (TOS) IP-заголовка датаграммы. Как вам известно из четвертой главы, поле TOS заголовка датаграммы определяет ее приоритет. Хотя это поле применяется редко, в будущем, несомненно, его значение возрастет. Поэтому в ICMP заложена возможность помогать сетевым компьютерам обновлять таблицы маршрутизации, в зависимости от типа сетевой службы, которой принадлежит та или иная датаграмма. Протокол ICMP ограничивает сферу применения сообщений о перенаправлении. Каждый сетевой компьютер, в принципе, может работать маршрутизатором. Однако только системы, сконфигурированные как маршрутизаторы, могут посылать ICMP-сообщения о перенаправлении. Обыкновенный сетевой компьютер сделать этого не может. Далее, маршрутизаторы не обновляют свои таблицы, основываясь на сообщениях о перенаправлении. Вместо этого маршрутизаторы используют специальные протоколы. Ошибки типа «лимит времени исчерпан» Как известно, ошибки в таблицах маршрутизации могут привести к зацикливанию пакета между двумя маршрутизаторами. Это может случиться, когда каждый из них считает, что соседний маршрутизатор — наилучшее место для передачи пакета по назначению. Заголовок IP-датаграммы содержит специаль- 398 
• ное поле «время существования» (Time-to-Live, TTL), в котором время существования датаграммы ограничивается. Каждый маршрутизатор на пути пакета уменьшает время его существования. Время существования также уменьшается каждую секунду, которую пакет проводит во входной или выходной очереди маршрутизатора. Как только время существования в поле TTL IP-датаграммы сравняется с нулем, сетевые программы уничтожат пакет и вышлют ICMP-сообщение «лимит времени исчерпан» (тип 11) компьютеру-источнику пакета. Формат ICMP-сообщения «лимит времени исчерпан» такой же, как у сообщения «пункт назначения недоступен», но поле Туре равно 11 вместо 3. Сообщения «лимит времени исчерпан» бывают двух видов, как показано в табл. 14.4. Код 0 устанавливается в случае, если датаграмма исчерпала время существования во время пересылки (например, из-за описанного выше зацикливания). Код 1 устанавливается, если произошел сброс таймера сборки фрагментов датаграммы до того, как все фрагменты прибыли. Таблица 14.4. ЮМР-сообщения об ошибках «лимит времени исчерпан» (тип 11) Код Описание 0 Поле время существования (TTL) сравнялось с нулем во время передачи датаграммы 1 Таймер сборки фрагментов истек Как вам известно из четвертой главы, когда компьютер-получатель принимает датаграмму с установленным флагом «фрагмент-продолжение», запускается таймер сборки фрагментов. Все фрагменты обязаны появиться до того, как этот таймер истечет. Если таймер истек, а датаграмма все еще не собрана, она уничтожается. В этом случае генерируется ICMP-сообщение типа 11с кодом 1. Ошибки «неверный параметр» Компьютеры и маршрутизаторы высылают такое сообщение, если источник проблемы с маршрутизацией или доставкой неизвестен. Существуют два типа таких ICMP-сообщений, как показано в табл. 14.5. Таблица 14.5. ICMP-сообщение об ошибке «неверный параметр» (тип 12) Код Описание 0 Неверный IP-заголовок 1 Необходимая опция отсутствует Сообщение с кодом 1 генерируется, если датаграмма не содержит всех необходимых для нормальной работы TCP/IP атрибутов (опций). Предположим, вы разработали криптозащищенный протокол для работы с приложениями государственной важности. Если программа-клиент попытается передать запрос к 399 
Глава 14. Простые сокеты Я серверу и не приложит специального секретного ключа к датаграмме, сервер ответит сообщением об ошибке типа «необходимая опция отсутствует». Сообщение типа «неверный IP-заголовок» генерируется во всех остальных случаях, когда источник ошибки невозможно распознать. На рис. 14.6 приведен формат сообщения ICMP «неверный параметр». Поле указателя (pointer) идентифицирует тот байт датаграммы, который привел к возникновению ICMP-сообщения. Для сообщений с кодом 1 («необходимая опция отсутствует») поле указателя равно нулю. Позиции битов < 0 7 32 бита 8 15 > 16 31 Тип (12) г Код (0 или 1) Контрольная сумма -г 8 байтов Указатель Не используется (заполняется нулями) Заголовок IP и 64 бита данных ^ из первоначальной датаграммы Рис. 14.6. Формат ICMP-сообщения «неверный параметр» (тип 12) Сообщение об ошибке «столкновение данных» Как мы писали во второй главе, механизм контроля потока данных гарантирует, что передатчик не будет передавать быстрее, чем приемник в состоянии принять и обработать. Другими словами, гарантируется, что входной буфер приемника не переполнится. В главе 5 мы писали, что протокол TCP обеспечивает механизм управления потоком в качестве одной из сетевых служб. К сожалению, управление потоком возможно лишь тогда, когда протокол ориентирован на соединение. Поскольку IP не ориентирован на соединение, он не умеет управлять потоком данных. Поскольку маршрутизаторы работают на уровне IP, в их входных очередях может создаваться аналог транспортной пробки в часы пик в результате слишком большого количества вновь приходящих IP-пакетов. Если маршрутизатор не успевает обработать все приходящие пакеты, «лишние» пакеты уничтожаются, а компьютеру-источнику пакета направляется 1СМР-сообщение об ошибке «столкновение данных» (тип 4). Сообщение «столкновение данных» указывает компьютеру на необходимость снизить скорость передачи. На самом деле, механизм сообщений «столкновение данных» является некоторым подобием управления потоком данных на уровне IP. Для каждого уничтоженного в результате столкновения пакета, маршрутизатор высылает ЮМР-сообщение. Как только компьютер получает его, он тут же снижает скорость передачи. Если маршрутизатор продолжает передавать ICMP-сообщения «столкновение данных», компьютер продолжает снижать скорость передачи. Как только ICMPсообщения перестают появляться, компьютер вновь начинает увеличивать скорость. И так до тех пор, пока не будет достигнута оптимальная скорость. Формат сообщения ICMP «столкновение данных» тот же, что и у сообщения «пункт 400 
, назначения недоступен», только поле типа имеет значение 4. Сообщение «столкновение данных» бывает только единственного вида, то есть поле кода у него всегда имеет значение 0 — других кодов не бывает. Сообщения-запросы ICMP Кроме сообщений об ошибках протокол ICMP генерирует сообщения-запросы, переносящие информацию относительно сетевой маршрутизации, производительности, адресов подсетей и т. д. Эта информация используется в отладочных целях. В следующих разделах мы рассмотрим каждый тип сообщений-запросов ICMP. Запросы «информация о маршрутизаторах» Информация о маршрутизации находится в местных конфигурационных файлах и загружается оттуда при запуске компьютера. Чтобы таблица маршрутизации не содержала устаревших данных, в дальнейшем она обновляется динамически. Протокол ICMP — один из способов динамического обновления таблиц. В ICMP существуют два типа сообщений маршрутизаторов: «информация о маршрутизации» (тип 10) и «регистрация маршрутизатора» (тип 9). Каждый раз, когда компьютер запускается, он генерирует сообщение о регистрации. В ответ маршрутизаторы-соседи генерируют сообщение с информацией о маршрутизации, позволяющее правильно сконфигурировать таблицу. Формат сообщений о маршрутизации описан в документе RFC 1256, «/СМР-сообщения о маршрутизации» (ICMP Router Discovery Messages, Deering, 1991). На рис 14.7 показы форматы обоих типов сообщения. Формат сообщения «регистрация маршрутизатора» весь похож на формат сообщения «пункт назначения недоступен», приведенный на рис. 14.3. Однако сообщение «регистрация маршрутизатора» не содержит данных IP-датаграммы в своем теле. Как показано на рис. 14.7, в одном ICMP-сообщении могут описываться несколько адресов. (Вы помните, что у одного компьютера может быть несколько сетевых интерфейсов и у каждого собственный IP-адрес.) Поле «количество адресов» в заголовке ICMP-сообщения указывает принимающему компьютеру, сколько адресов перечислено в сообщении. % Поле «размер адреса» задает длину каждого адреса в 32-битных словах. В настоящее время длина адреса всегда равна двум. Другими словами, длина адреса равна восьми байтам (32-разрядный адрес маршрутизатора и 32-разрядное поле уровня приоритета). Поле «время существования» задает время, в течение которого информация об адресах еще не устарела. Как правило, оно равно 30 минутам (1800 секунд). Одно сообщение «информация о маршрутизации» содержит одно или несколько полей адреса маршрутизатора и соответствующих полей уровня приоритета. IP-адрес, как вы помните, соответствует не сетевому компьютеру, а его интерфейсу, которых может быть несколько. Таким 401 
Глава 14* сокеты * -■ _" ..Л |4 Позиции битов i о 7 ! - 32 бита 8 15 16 ► 31 Тип (10) Код (0) Контрольная сумма t 8 байтов Указатель Не используется (заполняется нулями) а. Формат ICMP- сообщения "регистрация маршрутизатора" (тип 10). Позиции битов |0 7 - 32 бита ► 8 15 И6 31 Тип (9) Код (0) Контрольная сумма т 8 байтов 4 Количество адресов Длина поля адреса (2) Время существования Адрес маршрутизатора 1 г Приоритет 1 Адрес маршрутизатора 2 Приоритет 2 чг’ ч б. Формат ICMP-сообщения "информация от маршрутизатора” (тип 9). Рис. 14.7. Формат ICMP-сообщения с информацией о маршрутизации (тип 9 и тип 10) образом, несколько IP-адресов могут относиться к одному и тому же компьютеру. Уровень приоритета является 32-разрядным целым числом со знаком, указывающим компьютеру, какой из адресов следует использовать первым и интенсивнее. Чем больше значение приоритета, тем выше приоритет у соответствующего адреса. Маршрутизаторы передают информационные ICMP-сообщения широковещательно через случайные интервалы времени. Интервалы, как правило, колеблются от 450 до 600 секунд, а значение поля «время существования» по умолчанию равно 30 минут^р. ^Поле «время существования» может использоваться для уведомления маршрутизаторов-соседей о том, что данный маршрутизатор выключается. Для этого маршрутизатор передает ICMP-сообщение с полем «время существования», равным нулю. ICMP-запрос «регистрация маршрутизатора» передается, как правило, три раза с интервалом в три секунды при запуске маршрутизатора и продолжает передаваться до тех пор, пока хост не получит информационного ICMP-сообщения с необходимыми для обновления таблицы данными. Несмотря на то, что ICMP-сообщения о маршрутизации обеспечивают более надежный способ конфигурирования хостов по сравнению с традиционными, этот метод не очень широко применяется на практике. 402 
Что такое ICMP? j Запросы «временная метка» Запросы «временная метка» (типы 13 и 14) позволяют вычислить время прохождения пакета между двумя компьютерами. На рис. 14.8 изображен формат сообщений обоих типов. Позиции битов i 32 бита 0_ 7^ i 8 Тип (13 или 14) Код (0) 15 116 31 Идентификатор Контрольная сумма Номер последовательности Первоначальная временная метка Принятая временная метка Переданная временная метка 20 байтов Рис. 14.8. Формат ICMP-сообщений «временная метка» (типы 13 и 14) Для того чтобы различать временные метки, используются два поля: идентификатор метки и номер последовательности. Компьютер-источник сообщения может заполнять их наиболее удобным для себя образом. Сам протокол ICMP никак не анализирует содержимое этих полей. Три поля «временная метка» содержат количество секунд, прошедшее с полуночи. До того как послать датаграмму, компьютер заполняет первое поле значением текущего времени. Компьютер-приемник датаграммы заполняет поле «принятая временная метка», как только датаграмма доходит до него. Кроме того, компьютер-приемник заполняет поле «переданная временная метка» перед тем, как послать ответную датаграмму. Как программист, вы должны понимать, что указанные в полях «временная метка» значения ненадежны, поскольку время перемещения отдельного пакета варьируется в широких пределах даже для сравнительно короткого интервала времени, поэтому действительное время прохождения пакетов абсолютно достоверно измерить невозможно. Даже если посылается множество пакетов, а затем вычисляется статистический результат, фактическая задержка распространения может сильно отличаться от него. Примечание: Большинство сетевых компьютеров устанавливают одинаковые значения для принятых и переданных временных меток. Сообщение-запрос маски адреса^" Как вы помните из четвертой главы, Информационный центр сети Интернет (InterNIC) выделяет номера сетей, а вопросами распределения номеров компьютеров занимаются сетевые администраторы. Сеть, находящаяся в распоряжении администратора, может быть разделена на несколько подсетей. Это позволяет эффективнее распределять адресное пространство. При этом адрес сети — это первая часть IP-адреса компьютера, а оставшаяся часть — номер компьютера в данной сети. Маска подсети определяет, на сколько частей (подсетей) разделена конкретная IP-сеть. Как вы помните, протокол RARP (обратного преобразования адресов) позволяет по адресу физического уровня выяснить IP-адрес 403 
компьютера. Им пользуются бездисковые станции, чтобы найти собственный IP-адрес в процессе загрузки. Как только бездисковая станция получает ответ на свой RARP-запрос, она может передать запрос по IP-адресу нужного хоста с тем, чтобы загрузить свою операционную систему. Чтобы получить маску своей подсети, бездисковая станция передает ICMP-запрос. Чтобы выполнить эту задачу, бездисковая станция должна знать, какая часть ее 32-разрядного IP-адреса является номером подсети, а какая — номером компьютера. На рис. 14.9 показан формат ICMP-сообщений на определение и получение маски подсети. Позиции битов i ' - - 32 бита 0 7 1 8 15 16 311 Тип (17 или 18) Код (0) Контрольная сумма Идентификатор Номер поел едовател ьности Маска подсети т 12 байтов X Рис. 14.9. Формат ICMP-сообщений запроса и ответа на получение маски адреса (типы 17 и 18) Поля идентификатора и номера последовательности такие же, как и в сообщении «временная метка». Другими словами, компьютер-передатчик может заполнять их, как ему удобнее. Сообщение-ответ ICMP содержит маску подсети, на которой находится компьютер-источник сообщения. Бездисковая станция не обязана знать адрес компьютера, ответственного за генерацию ответа, — она может просто воспользоваться широковещательной передачей. При этом ей ответит один или несколько компьютеров. Запрос и ответ «эхо» ICMP-сообщения «эхо» — образец диагностического инструмента, встроенного в сетевой протокол. Когда ICMP-модуль компьютера получает запрос «эхо», он высылает идентичное сообщение-ответ «эхо» передававшему компьютеру. Ответное сообщение «эхо» говорит о том, что пославший его компьютер пребывает в рабочем состоянии и способен отвечать на сетевые запросы. Сообщения-запросы и ответы «эхо» — одни из самых популярных сообщений ICMP, которые используютсИ такими программами, как Ping (Packet INternet Grouper). Как правило, программа Ping высылает сообщения-запросы «эхо», получает ответы и измеряет среднее время движения пакетов по сети. Она используется для того, чтобы выяснить, работает ли в данный момент определенный сетевой компьютер и доступен ли он по сети. На рис. 14.10 приведен формат сообщений-ответов и запросов «эхо». Чтобы привязать запрос к ответу, используются поля идентификатора и номера последовательности. Так же, как и в сообщениях временной метки и маски адреса, туда может помещаться любая приемлемая для компьютера информация. Компьютер-передатчик сообщения также может разместить информацию в поле 404 
шщ0штшшя Позиции битов 4 - 32 бита 0 7 ! 8 15 ► 16 31 Тип (8 или 0) Код (0) Контрольная сумма т 8 байтов 4 Идентификатор Номер последовательности Дополнительные данные (необязательно) Рис. 14.10. Формат сообщений-запросов и ответов ICMP «эхо» дополнительных данных. Компьютер-получатель работает, как простой повторитель сообщений «эхо» — он отсылает обратно все принятые ICMP-сообщения. Простые сокеты Теперь, когда вы узнали больше о протоколе ICMP, мы приступим к изучению простых сокетов. Простой сокет — это средство, позволяющее прикладной программе обойти стандартный транспортный уровень, с тем чтобы работать непосредственно с низкоуровневыми протоколами, например ICMP. Работа с простым сокетом происходит следующим образом. Во-первых, простой сокет создается. Вместо атрибутов сокета SOCKJSTREAM или SOCKJDGRAM указывается атрибут SOCK_RAW. Кроме того, программа должна указать используемый с сокетом протокол, например ICMP. После этого реализация Winsock добавляет IP-заголовок к данным, передаваемым через простой сокет. В поле «протокол» IP-заголовка указывается номер протокола, с которым вы работаете, заданного при создании сокета. Как только Winsock получает данные для указанного протокола, они передаются (вместе с IP-заголовком) процессу, создавшему сокет для этого протокола. Если ваша программа создала ICMP-сокет, она получит все ICMP-сообщения, адресованные данному сетевому компьютеру. Прикладная программа должна исследовать содержимое ICMP-сообщений, чтобы определить, кому именно они предназначены. Как правило, такого рода операциями занимается транспортный уровень, но в данном случае он недоступен. Вновь поступающие данные не могут связываться с определенным сокетом или номером протокола автоматически. В следующих разделах мы покажем, как создавать и пользоваться простым сокетом. Также вы узнаете, как вычисляются контрольные суммы. В заключение вы встроите разработанный код в программу Sockman. Учебная программа Ping Учебная программа Ping (QPing) представляет собой упрощенный аналог общеизвестной программы Ping, широко используемой в Интернет. Как и у всех остальных учебных программ, значения параметров в ней жестко заданы. В 405 
Глав* Гфос.ыь* сокеты Щ результате, мы получаем полнофункциональную программу, демонстрирующую приемы программирования Интернет и не отягощенную излишними, характерными для программирования в Windows деталями. Ниже приведен исходный текст программы QPing. Для начала обратите внимание на три ее основных функции: InternetChksum, DoPingOperation и WinMain. Функции InternetChksum и DoPingOperation описаны словами, а функция WinMain определена полностью: #include <time.h> #include <stdlib.h> #include "..\winsock.h" // Объявляем символьные константы // Объявляем структуры данных IP и ICMP char szPingBuffer[100]; // Буфер общего назначения для // сообщений WORD InternetChksum (LPWORD lpwIcmpData, WORD wDataLength) { // Вычислить контрольную сумму return(wAnswer); } BOOL DoPingOperation(HANDLE hlnstance) { // Определить локальные переменные // Определить IP-адрес компьютера назначения // Присвоить значения семейства адресов и адреса // компьютера структуре адреса сокета // Получить значение для протокола ICMP из базы данных // сетевых служб // Создать простой сокет и связать его с протоколом ICMP // Присвоить значения структуре данных ICMP // Присвоить полю дополнительных данных значение времени // в "тиках" // Вычислить контрольную сумму для ICMP-сообщения // Послать ICMP-датаграмму "эхо" через простой сокет // Дожидаться ответа // Приняв данные, закрыть сокет // Вычислить среднее время "пробега" датаграммы // Проверить, является ли датаграмма ICMP-сообщением // Проверить, является ли ICMP-сообщение ответом "эхо" // Проверить, являлась ли данная программа источником // запроса "эхо" // Отметить IP-адрес и имя компьютера-источника // ICMP-ответа 406 
Простые сокеть- return (TRUE) ; } int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow) { WSADATA wsaData; // Сведения о реализации Winsock // Начинаем выполнение WSAStartup(WINSOCK_VERSION, &wsaData); DoPingOperation(hlnstance); MessageBox(NULL, szPingBuffer, PROG__NAME, MB_OK|MB_ICONSTOP); WSACleanup(); return(NULL); } Первым делом WinMain вызывает WSAStartup для инициализации WINSOCK.DLL. Далее, вызывается функция DoPingOperation, которая посылает и затем принимает ICMP-датаграмму. По окончании работы функции DoPingOperation управление опять передается функции WinMain, которая выводит текстовое сообщение на экран с результатами операции, записанными в глобальной переменной szPingBuffer. После этого программа заканчивается. QPing определяет символьные константы и декларирует описания структур данных для IP-заголовка и сообщений ICMP. Как и в предыдущих учебных программах, константа PROG_NAME задает название рабочего проекта. Константа HOST_NAME определяет имя любого доступного сетевого компьютера. Константы WINSOCK_VERSION и NO_FLAGS такие же, как и в предыдущих учебных примерах. WONSOCK_VERSION определяет минимальную необходимую для работы версию реализации WINSOCK.DLL, a NO_FLAGS — то, что при вызовах функций Winsock никаких дополнительных флагов не задается. Константы ICMPJECHO, ICMPJECHOREPLY и ICMPJHEADERSIZE новые, однако их значения вам знакомы из предыдущих разделов этой главы. Всем трем константам присваиваются значения из RFC 792. (Как мы уже говорили, в документе RFC 792 описан протокол ICMP.) Константа 1СМР__ЕСНО определяет содержимое поля типа сообщения-запроса «эхо». Константа ICMP ECHOREPLY определяет содержимое поля типа сообщения-ответа «эхо». Константа ICMP_HEADERSIZE задает размер ICMP-заголовка для сообщений типа «эхо» и равняется восьми байтам, как показано на рис. 14.10: #define PROG__NAME "A Simple Ping Program" #define HOST_NAME "cerfnet.com" // Любое (настоящее) имя // сетевого компьютера 407 
1 Глава 14. I #define WINSOCK_VERSION 0x0101 // Программе требуется // Winsock версии не ниже 1.1 #define NO_FLAGS 0 // Дополнительных флагов не требуется // Значения полей // ICMP-сообщений определены в RFC 792 #define ICMP_ECHO 8 // ICMP-сообщение "эхо" #define ICMP_ECHOREPLY 0 // ICMP-сообщение "эхо-ответ" #define ICMP_HEADERSIZE 8 // Размер заголовка // ICMP-сообщения "эхо" Структуры данных QPing В двух новых структурах данных, ip и icmp, определены заголовки пакетов IP и ICMP. Имена полей структуры ip в точности повторяют имена полей заголовка IP-датаграммы, изученные вами в четвертой главе. Точно так же имена полей структуры icmp в точности соответствуют именам полей заголовка ICMP-пакета, известного вам по настоящей главе. Длины элементов структур и длины соответствующих полей также в точности совпадают. Ниже приведено описание структур ip и icmp: LCt ip // // BYTE ip__verlen; // BYTE ip_tos; // WORD ip__len; // UINT ip_id; // WORD ip__fragof f; // BYTE ip_ttl; // BYTE ip_j?roto; // UINT ip__chksum; // IN_ADDR ip_src_addr; // IN__ADDR ip_dst_addr; // BYTE ip_data [1] ; // }; Структура-заголовок IP-датаграммы Версия и длина заголовка Тип службы Общая длина пакета Идентификатор датаграммы Смещение фрагмента Время существования Протокол Контрольная сумма Адрес источника Адрес назначения Область данных переменной длины struct icmp // { BYTE icmp_type; // BYTE icmp__code; // // WORD icmp__cksum; // // WORD i cmp__i d; / / // WORD icmp_seq; // // Структура-заголовок ICMP-пакета Тип сообщения Код "подтипа" (для "эхо" равен нулю) Контрольная сумма (дополнение до единицы) Уникальный идентификатор (дескриптор задания) Мы можем отслеживать несколько процессов Ping 408 
Простые сокеты BYTE icmp__data[1] ; // Начало данных (их может и не быть) }; Чтобы создать простой сокет, требуется гораздо больше усилий, чем при создании обыкновенного стандартного сокета. Для начала вы должны определить все нужные структуры данных и заполнить их правильными значениями. Для этого требуется понимать структуру данных низлежащего сетевого протокола и общую структуру пакета. Если значения полей пакета будут неверными или длины полей пакета будут ошибочными, программа просто не заработает. С другой стороны, если вы знаете, как функционирует протокол и какие данные ему нужны для работы, создание простого сокета не будет слишком сложной задачей. Очевидно, что для создания простого сокета, требуется написать больше исходного текста, чем в обычном случае. Это не сложнее, чем описать стандартный сокет, однако следует уделять больше внимания различным мелким деталям. Другими словами, следует проверять, все ли структуры данных соответствуют спецификации протокола и все ли поля содержат правильные значения. Вычисление контрольной суммы Для обнаружения поврежденных пакетов сетевые программы пользуются контрольными суммами и CRC. Вкратце, процесс создания контрольной суммы заключается в складывании двоичных значений каждого символа пакета и помещении результата в отдельный блок, передаваемый с тем же пакетом. Для проверки целостности данных компьютер-получатель также вычисляет значение контрольной суммы и сверяет его с принятым старым значением. Если контрольная сумма не совпадает, данные были повреждены. До того как рассмотреть функцию DoPingOperation, давайте познакомимся с функцией InternetChksum для вычисления контрольной суммы ICMP-датаграммы. Алгоритм ее работы достаточно прост. Данные компонуются в ряд 16-битных значений, затем значения складываются и вычисляется дополнение до единицы полученной суммы. Полученная контрольная сумма заносится в соответствующее поле структуры данных протокола. Ниже приведен исходный текст функции для вычисления контрольных сумм. Функция используется как в программе QPing, так и в утилите Ping программы Sockman: WORD InternetChksum(LPWORD lpwIcmpData, WORD wDataLength) { long lSum; // Здесь хранится результат суммирования WORD wOddByte; // Оставшийся после суммирования // (нечетный) байт WORD wAnswer; // Контрольная сумма (дополнение до // единицы) lSum = 0L; 409 
Глава 14. Простые сокеты . while (wDataLength > 1) { lSum += *lpwIcmpData++; wDataLength -= 2; } // Если необходимо, обрабатываем оставшийся (нечетный) // байт и проверяем, что верхняя половина равна нулю if (wDataLength == 1) { wOddByte = 0; *((LPBYTE) &wOddByte) = *(LPBYTE)lpwIcmpData; // Только один байт lSum += wOddByte; } lSum = (lSum » 16) + (lSum & Oxffff); // Старшие 16 // плюс младшие 16 lSum += (lSum » 16); // Добавляем перенос wAnswer = (WORD)-lSum; // Дополняем до единицы и удаляем // лишнее. Теперь длина равна // 16 бит return(wAnswer); } Принципы вычисления контрольной суммы подробно описаны в документе RFC 1071, «Вычисление контрольной суммы Интернет» (Computing the Internet Checksum, Braden, Borman, Partridge, 1988). Там, в частности, указано, что эффективность алгоритма является ключевым моментом в обеспечении высокой производительности сети. Это значит, что алгоритм должен привязываться к аппаратным особенностям конкретной сетевой среды. Лишь в этом случае удается «вытянуть» из производительности компьютера все что только можно. Например, переменные lSum и wAnswer можно было бы (правда, не в нашем примере) объявить регистровыми (тип register). Регистровые переменные хранятся не в основной памяти компьютера, а в аппаратных регистрах процессора, увеличивая скорость выполнения функции. В любом случае, если вы планируете работать с простыми сокетами, найдите время для изучения RFC 1071 и дополнительной документации, перечисленной там. Освойте сначала общие принципы вычисления контрольных сумм, а затем рекомендации по тонкой настройке таких алгоритмов. Процедура Ping Функция DoPingOperation посылает эхо-запрос ICMP, а затем ждет эхо-ответа. В ней больше операторов, чем в предыдущих функциях. Правда, большинство из них являются объявлениями переменных, операторами присваивания и другими не относящимися к делу операторами. Если из кода удалить проверку 410 
■ - *' •- * ь сокет м ошибок и оставить только вызовы функций Winsock, мы получим следующую картину: BOOL DoPingOperation(HANDLE hlnstance) { II ... объявляем локальные переменные // Преобразуем IP-адрес удаленного компьютера if ((lpHostEntry = gethostbyname(HOST_NAME)) == NULL) return(FALSE); // ...присваиваем значения структуре адреса сокета //В случае простого сокета/ мы должны указывать протокол if ((IpProtocolEntry = getprotobyname("icmp")) == NULL) nProtocol = I PPROTO__ICMP; else nProtocol = lpProtocolEntry->p_proto; // Создаем простой сокет и указываем ICMP в качестве // протокола if ((hSocket = socket(AF_INET/ SOCK_RAW/ nProtocol)) == INVALID_SOCKET) return (FALSE) ; // ...присваиваем значения структуре ICMP-заголовка // ...располагаем счетчик "тиков" в области // необязательных данных // ... вычисляем контрольную сумму ICMP-сообщения if (pIcmpHeader->icmp__cksum !=0 ) { // Высылаем ICMP-запрос "эхо" iSentBytes = sendto(hSocket, (LPSTR) IcmpSendPacket/ iPacketSize, NO_FLAGS/ (LPSOCKADDR) &sockAddrLocal/ sizeof(sockAddrLocal)); // Получаем ICMP-ответ "эхо" iReceivedBytes = recvfrom(hSocket# (LPSTR) IcmpRecvPacket/ sizeof(IcmpRecvPacket), NO_FLAGS# (LPSOCKADDR) &sockAddrHost/ &iHostAddrLength); } // Закрываем сокет closesocket(hSocket); 411 
// ...подсчитываем среднее время пробега датаграммы и // проверяем, что принятый ICMP-ответ "эхо" был послан // именно нашей программой return(TRUE); } Как видим, в функции очень немного незнакомых вам операторов. На самом деле здесь всего три новых для вас функции: getprotobyname, sendto и recvfrom. В вызове getprotobyname всего лишь один параметр — указатель на имя протокола. Первым делом она извлекает информацию об указанном протоколе из сетевой базы данных (файла на диске), а затем возвращает указатель на структуру данных, описывающую этот протокол: struct protoent { char FAR *p_name; // Официальное имя char FAR * FAR *p_aliases; // Псевдонимы short p__proto; // Номер протокола в порядке байтов хоста }; Элемент р_паше указывает официальное имя протокола, а элемент p__aliases — список псевдонимов имен данного протокола, оканчивающийся NULL. В программе QPing используется только элемент p__proto; в нем содержится номер протокола (в компьютерном порядке следования байтов). В предыдущих учебных программах мы использовали протокол TCP на ориентированных на соединение сокетах. Для обмена данными служили функции recv и send. Простой сокет работает с не ориентированными на соединение протоколами, чьи данные переносятся в датаграммах, поэтому для обмена данными используются функции recvfrom и sendto. Разница между двумя парами функций состоит в том, что в последней мы имеем возможность задавать адрес назначения, a send и recv работают только с предварительно соединенным сокетом. Вот прототип функции sendto: int PASCAL FAR sendto(SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR * to, int tolen ); Первый параметр идентифицирует сокет. Второй параметр указывает на буфер с данными для передачи, размер которого задается третьим параметром. В качестве четвертого параметра могут использоваться два ранее описанных для функции send флага: MSG__DONTROUTE и MSG_OOB. Если вы хотите запретить маршрутизацию данных, вы должны указать флаг MSGJOONTROUTE. Если вы хотите послать данные «вне диапазона» (для немедленной обработки), указывайте флаг MSG_OOB. В большинстве учебных программ этой книги флаги не используются. 412 
•/ • .. ■■■■ ; • : a ' . &v• Пятый параметр функции sendto указывает на структуру адреса сокета. В ней хранится адрес назначения ваших данных. Последний параметр sendto задает длину этой структуры. Прототип функции recvfrom, приведенный ниже, весьма похож на прототип sendto: int PASCAL FAR recvfrom(int s, char FAR * buf, int len, int flags, struct sockaddr FAR * from, int FAR * fromlen ); Основное отличие двух прототипов в том, что пятый и шестой параметры последнего задавать необязательно. Если параметр from функции recvfrom не равен нулю, Winsock копирует адрес удаленного процесса, приславшего данные, в структуру адреса сокета, указанную параметром. То есть если программа желает знать адреса всех приславших данные для этого сокета компьютеров, необходимо отвести область памяти (буфер) для хранения структуры адреса сокета. Кроме того, длина этого буфера будет копироваться в переменную по указателю fromlen. Если программе не нужны адреса удаленных компьютеров, пятый параметр функции recvfrom должен равняться нулю. Еще одна незнакомая вам функция Windows — GetTickCount. Она возвращает время (в миллисекундах), прошедшее с момента запуска Windows. В QPing это значение используется для подсчета среднего времени пробега 1СМР-датаграммы «эхо» между двумя компьютерами. Ниже приводится полный исходный текст функции DoPingOperation. BOOL DoPingOperation(HANDLE hlnstance) { int iPacketSize; int iHos tAddrLength; int iIPHeadLength; int iReceivedBytes; int iSentBytes; int nProtocol; int iSocketError; PDWORD pdwTimeStamp; DWORD dwReturnTime; DWORD dwRoundTrip; // Локальные переменные // размер ICMP-пакета // Длина адреса сетевого // компьютера // Длина заголовка // 1Р-датаграммы // Количество принятых байтов // Количество посланных байтов // Номер протокола ICMP // Значение кода ошибки // Счетчик "тиков" при передаче // Счетчик "тиков" при приеме // Счетчик "тиков" среднего // времени пробега // Структуры, описанные в // WINSOCK.Н SOCKADDR_IN sockAddrLocal; SOCKADDR_IN sockAddrHost; SOCKET hSocket; LPHOSTENT lpHostEntry; // Структуры адреса сокета // // Дескриптор сокета // Структура данных с 413 
Глава 14. Простые сокеты LPPROTOENT IpProtocolEntry; BYTE IcmpSendPacket[1024] ; BYTE IcmpRecvPacket[4096] ; struct icmp *pIcmpHeader; struct ip *pIpHeader; LPSTR IpszHostName; IpszHostName = HOSTJNTAME; // информацией о сетевом // компьютере // Структура данных с // информацией о протоколе // Буфер для посылаемых // данных // Буфер для принимаемых // данных // Указатель на структуру ICMP // Указатель на // структуру-заголовок IP // Указатель на удаленный // сервер времени if ( (lpHostEntry = gethostbyname (HOST__NAME) ) == NULL) { wsprintf(szPingBuffer, "Could not get %s IP address.", (LPSTR)IpszHostName); return(FALSE); } sockAddrLocal. sin_family = AF__INET; sockAddrLocal.sin_addr = *((LPIN_ADDR) *lpHostEntry->h_addr_JList) ; // В случае простого сокета, мы должны указывать протокол if ((IpProtocolEntry = getprotobyname("icmp")) == NULL) nProtocol = IPPROTO_ICMP; else nProtocol = lpProtocolEntry->p__proto; // Создаем простой сокет и указываем ICMP в качестве // протокола if ((hSocket = socket(AF_INET, SOCK_RAW, nProtocol)) == INVALID_SOCKET) { wsprintf(szPingBuffer, "Could not create a RAW socket."); return(FALSE); } pIcmpHeader = (struct icmp *) IcmpSendPacket; // Указываем на область данных и заполняем ее 414 
.Л.-" ,7" -> •> *1 »| pIcmpHeader->icmp__type = ICMP_ECHO; pIcmpHeader->icmp_code = 0; // Берем дескриптор задания в качестве // уникального идентификатора pIcmpHeader->icmp_id = hlnstance; pIcmpHeader->icmp_seq =0; // Помним, что контрольную pIcmpHeader->icmp_cksum = 0; // сумму необходимо // предварительно обнулить // Значение счетчика "тиков" располагается в // необязательной области данных pdwTimeStamp = (PDWORD)&IcmpSendPacket[ICMP_HEADERSIZE]; *pdwTimeStainp = GetTickCount () ; iPacketSize = ICMP_HEADERSIZE + sizeof(DWORD); pIcmpHeader->icmp_cksum = InternetChksum((LPWORD)pIcmpHeader, iPacketSize); if (pIcmpHeader->icmp_cksum 1=0 ) { iSentBytes = sendto(hSocket, (LPSTR) IcmpSendPacket, iPacketSize, NO_FLAGS, (LPSOCKADDR) &sockAddrLocal, sizeof(sockAddrLocal)); if (iSentBytes == SOCKET_ERROR) { closesocket(hSocket); wsprintf(szPingBuffer, "The sendto() function returned a socket error."); return(FALSE); } if (iSentBytes != iPacketSize) { closesocket(hSocket); wsprintf(szPingBuffer, "Wrong number of bytes sent: %d", iSentBytes); return(FALSE); } iHostAddrLength = sizeof(sockAddrHost); iReceivedBytes = recvfrom(hSocket, (LPSTR) IcmpRecvPacket, sizeof(IcmpRecvPacket), NO_FLAGS, (LPSOCKADDR) fcsockAddrHost, &iHostAddrLength); } else { 415 
closesocket(hSocket); wsprintf(szPingBuffer, "Checksum computation error! Result was zero!"); return(FALSE); } closesocket(hSocket); if (iReceivedBytes == SOCKET_ERROR) { iSocketError = WSAGetLastError(); if (iSocketError == 10004) { wsprintf(szPingBuffer, "Ping operation for %s was cancelled.", (LPSTR)IpszHostName); dwRoundTrip = 0; return(TRUE); } else { wsprintf(szPingBuffer, "Socket Error from recvfrom(): %d", iSocketError); return(FALSE); } } dwReturnTime = GetTickCount(); dwRoundTrip = dwReturnTime - *pdwTimeStamp; // Указываем на IP-заголовок принятого пакета pIpHeader = (struct ip *)IcmpRecvPacket; // Извлекаем биты 4-7 и преобразуем количество 32-битных // слов в количество байтов ilPHeadLength = (pIpHeader->ip_verlen » 4) « 2; // Проверяем длину, чтобы удостовериться, что // ICMP-заголовок принят if (iReceivedBytes < ilPHeadLength + ICMP_HEADERSIZE) { wsprintf(szPingBuffer, "Received packet was too short."); return(FALSE); } 416 
Простые // Указываем на ICMP-сообщение, следующее сразу за // IP-заголовком pIcmpHeader = (struct icmp *) (IcmpRecvPacket + ilPHeadLength); // Проверяем, что мы приняли именно "эхо"-ответ if (pIcmpHeader->icmp_type != ICMP_ECHOREPLY) { wsprintf(szPingBuffer, "Received packet was not an echo reply to your ping."); return(FALSE); } // Проверяем, принадлежит ли этот пакет нашей программе if (pIcmpHeader->icmp_id != (WORD)hlnstance) { wsprintf(szPingBuffer, "Received packet was not sent by this program."); return(FALSE); } // Да, этот пакет был послан нашей программой. Обратите // внимание на IP-адрес и имя удаленного компьютера, // пославшего "эхо"-ответ lstrcpy (IpszHostName, (LPSTR) lpHostEntry->h__name) ; wsprintf(szPingBuffer, "Round-trip travel time to %s [%s] was %d milliseconds.", (LPSTR)IpszHostName, (LPSTR)inet_ntoa(sockAddrHost.sin_addr), dwRoundTrip); return(TRUE); } Добавляем утилиту Ping в программу Sockman Исходный текст программы Sockman5, включающий исходный текст утилиты Ping, находится на приложенной к книге дискете. В Sockman5 используется такое же, как и в Sockman4, диалоговое окно. Для того чтобы встроить Ping в Sockman, файл PING5.CPP необходимо включить в список проекта или файл makefile вашего компилятора. Файлу PING5.CPP необходим файл-заголовок SOCKRAW.H, в котором описаны заголовки протоколов IP и ICMP. В утилите Ping используются только синхронные (блокирующие) функции — никаких асинхронных, специфических для Windows функций. Чтобы пользоваться утилитой Ping, вам необходимо добавить несколько глобальных переменных и 14 Зак. № 1949 417 
Глава 14* Простыв сокеты модифицировать функцию DoWinsockProgram. В исходном тексте PING5.CPP содержится все остальное. Вот какие глобальные переменные необходимы: HTASK hPingTask; // Дескриптор задания Ping char szPingHost[MAX_H0ST_NAME+1]; // Имя удаленного // компьютера char szPingBuffer[MAXGETHOSTSTRUCT]; // Буфер данных Ping В файле-заголовке S0CKMAN5.Н указаны следующие прототипы функций Ping: // Прототипы функций для PING.CPP BOOL PingDialog(VOID); BOOL ^export CALLBACK PingDialogProc (HWND, UINT, WPARAM, LPARAM) ; BOOL DoPingOperation(HWND, LPSTR, PDWORD); WORD InternetChksum(LPWORD, WORD); Функции PingDialog и PingDialogProc похожи на другие диалоговые процедуры из предыдущих версий Sockman. Первая создает диалоговое окно, а вторая — обрабатывает сообщения этого окна. Вот определение диалогового окна в файле SOCKMAN5.RC: IDD_PING DIALOG DISCARDABLE 0, 0, 221, 98 STYLE DS_MODALFRAME I WS__POPUP I WS_VISIBLE I WS_CAPTION I WS__SYSMENU CAPTION "PING" FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_PING_HOST,30,10,140,15,ES_AUTOHSCROLL PUSHBUTTON "&Ping",IDOK,30,50,59,16 PUSHBUTTON "^Cancel",IDCANCEL,110,50,59,16 PUSHBUTTON "E&xit",IDEXIT,110,75,59,16 RTEXT "Host:",IDC__STATIC,10,10,20,13 RTEXT "Round-trip Travel Time:",IDC_STATIC,25,30,82,10 EDITTEXT IDC_PING_TIME,110,30,60,15,ES_AUTOHSCROLL I ES_READONLY I WS_DISABLED I NOT WS_TABSTOP LTEXT "milliseconds",IDC^STATIC,175,30,40,11 END Когда из меню Sockman выбирается опция Ping, новое диалоговое окно, определение которого (IDD PING) вы только что видели, создается и выводится на экран. Диалоговое окно IDDJPING изображено на рис. 14.11. Далее, вы вводите имя или адрес сетевого компьютера, которому хотите послать ICMP-сообщение. Щелкнув мышью по кнопке Ping, вы начинаете сетевую 418 
Простые -зокеты Host: Round-trip Travel Time: [о Ring Xfil 1511 Рис. 14.11. Диалоговое окно Ping программы Sockman операцию, выполняющую те же действия, что и в учебной программе QPing. Кнопка Cancel прекращает текущую операцию, а кнопка Exit закрывает диалоговое окно. Как показано ниже, в процедуре DoPingOperation из программы Sockman и в ее версии из программы QPing используются немного разные параметры. BOOL DoPingOperation(HWND hwnd, LPSTR IpszHostName, PDWORD pdwRoundTrip); В функции DoPingOperation программы Sockman указывается дескриптор окна, указатель на имя сетевого компьютера и указатель на буфер для хранения времени пробега датаграммы. Однако за исключением параметров, обе версии процедуры идентичны. Обе они вызывают одну и ту же функцию InternetChksum. Чтобы вызвать функцию PingDialog (она создает и выводит на экран диалоговое окно IDD_PING), в оператор case IDM_PING функции DoWinsockProgram необходимо внести следующие изменения: case IDM_PING: PingDialog(); break; Примечание: Функция DoWinsockProgram описывается в файле SOCKMAN5.CPP. Алгоритм работы утилиты Ping Операторы конструкции case WM_COMMAND функции PingDialogProc описывают алгоритм поведения утилиты в ответ на нажатия различных кнопок в диалоговом окне. Как показано ниже, эта конструкция содержит вложенную конструкцию case, обрабатывающую сообщения от трех диалоговых кнопок. В ответ на нажатие кнопки Ping генерируется сообщение WM_COMMAND с параметром wParam, равным ШОК. Нажатие кнопки Cancel посылает сообщение WM_COMMAND с параметром wParam, равным IDCANCEL, а нажатие кнопки Exit — сообщение WM_COMMAND с параметром wParam, равным IDEXIT: 419 
Глава 14. Простые сокеты case WM_COMMAND: // Обрабатываем управляющие кнопки switch (wParam) { case IDOK: // Нажата кнопка OK dwTravelTime = 0; if (hPingTask) { MessageBeep(0) ; MessageBox(hwndDlg, "Ping operation is already in progress use.", "SockMan-PING", MB_ICONSTOP I MB_OK); return(TRUE); } hPingTask = TASK_PING; SetDlgltemlnt(hwndDlg, IDC_PING_TIME, (UINT)dwTravelTime, TRUE); GetDlgltemText (hwndDlg, IDC_PING__HOST, (LPSTR) szPingHost, MAX__HOST_NAME) ; if (lstrlen(szPingHost) > 0) { if (WSAIsBlocking()) WSACancelBlockingCall(); bPingOkay = DoPingOperation(hwndDlg, szPingHost, &dwTravelTime); } else { wsprintf(szScratchBuffer, "Please enter a host name."); MessageBeep(0); MessageBox(hwndDlg, szScratchBuffer, "SockMan-PING", MB_OK | MB_ICONINFORMATION) ; } if(!bPingOkay) { // Если операция Ping не удалась, ее буфер // содержит код ошибки MessageBox(NULL, szPingBuffer, "SockMan-PING", MB_OK | MB__ICONSTOP) ; dwTravelTime = 0; } MessageBeep(0); SetDlgltemlnt (hwndDlg, IDC_.PING_.TIME, (UINT)dwTravelTime, TRUE); 420 
PaintWindow(szPingBuffer); hPingTask = 0; return(TRUE); case IDCANCEL: // Нажата кнопка Cancel if (WSAIsBlocking()) WSACancelBlockingCall(); return(TRUE); case IDEXIT: // Нажата кнопка Exit if (WSAIsBlocking()) WSACancelBlockingCall(); PostMessage(hwndSockman, WM__COMMAND, IDM_FILE__CLEAR, 0L) ; EndDialog(hwndDlg, FALSE); return(TRUE); } break; Если блокирующая операция Ping запущена, а пользователь нажал кнопку Cancel (IDCANCEL), функция PingDialogProc вызывает WSACancelBlockingCall, заставляющую Winsock прервать текущую блокирующую операцию. Нажатие кнопки Exit (IDEXIT) приводит к аналогичным действиям. Кроме прерывания текущей операции (если Ping уже запущен), PingDialogProc генерирует сообщение главному окну Sockman, очищающее и закрывающее диалоговое окно. Нажатие кнопки Ping (ШОК) вызывает все остальные, описанные в PingDialogProc операции. Сперва PingDialogProc обнуляет переменную dwTravelTime. В ней хранится время пробега ICMP-датаграммы «эхо», вычисленное функцией DoPingOperation. (Функция PingDialogProc передает указатель на dwTravelTime функции DoPingOperation.) Если операция Ping уже запущена (hPingTask равна true), Sockman предупреждает об этом пользователя. Пользователь может либо подождать, либо нажать кнопку Cancel, чтобы прервать операцию. Если операция Ping еще не запущена, функция PingDialogProc устанавливает значение переменной hPingTask равным символьной константе TASK_JPING (она определена в файле SOCKMAN5.H) — таким образом, операция «регистрируется». Далее PingDialogProc заносит значение dwTravelTime (время пробега датаграммы) в текстовое окно IDCJPING_TIME, стирая тем самым предыдущее значение времени. Сразу после сброса текстового окна IDC_PING_TIME PingDialogProc извлекает имя сетевого компьютера из текстового окна IDC_PING__HOST. После этого PingDialogProc проверяет значение имени (при помощи функции lstrlen). Если длина имени не равна нулю, то есть имя существует, вызывается функция DoPingOperation, которая собственно и производит обмен ICMP-датаграммами. Как только DoPingOperation заканчивает работу, PingDialogProc докладывает о результатах и заносит новое значение dwTravelTime в текстовое окно IDC_PING_TIME. Кроме того, PingDialogProc выводит главное окно Sockman и гудком динамика извещает пользователя об окончании операции. 421 
_• • \ 7V ' • • \ \A. ■ • ' • ' ' :v "V "7 Права 14. Простые сокеты Как к Sockman добавляются другие приложения? Версия программы Sockman, рассмотренная в этой главе, последняя. Теперь вы имеете представление о том, как в среде Windows программируются приложения Winsock API. Далее, вместо того чтобы добавлять новые утилиты в Sockman, мы сосредоточимся на разработке отдельных приложений Интернет. Ниже показано, как в Sockman можно добавить отдельное приложение или утилиту. Вы знаете, что все операции Winsock, перечисленные в меню Sockman, запускаются из процедуры DoWinsockProgram, проверяющей параметр wParam, чтобы определить, какая именно операция требуется. Как показано ниже, DoWinsockProgram выясняет, какое приложение — Mail или FTP — требуется, и запускает его при помощи функции Windows ShellExecute. В этом случае запускаются программы SockMail или SockFTP, в зависимости от выбора пользователя. Разумеется, этот выбор можно дополнить любым количеством других программ — достаточно немного скорректировать исходный текст. long DoWinsockProgram(HWND hwnd, UINT wParam, LONG lParam) { HINSTANCE hlnstance; LPSTR lpstr; switch (wParam) { case IDM_APP_MAIL: case IDM_APP__FTP: if (wParam == IDM_APP_MAIL) lpstr = "SOCKMAIL.EXE"; else lpstr = "SOCKFTP.EXE"; hlnstance = ShellExecute(hwnd, (LPCSTR)"open", lpstr, NULL, NULL, SW_SHOWNORMAL); if (hlnstance <= 32) { wsprintf(szScratchBuffer, "Could not run %s.\n\nShellExecute() Error# %d", (LPSTR)lpstr, hlnstance); MessageBeep(O); MessageBox(NULL, szScratchBuf fer, "SockMan - Application", MB_OK|MB_ICONSTOP) ; } else wsprintf(szScratchBuffer, "Launched %s!", (LPSTR)lpstr); PaintWindow(szScratchBuffer); 422 
■ ' чг Подводя итоги return(TRUE); // ... еще операторы Предел развития программы Sockman находится только в вашем воображении. В нее можно добавлять все больше новых утилит или использовать в качестве стартовой площадки для запуска самостоятельных сетевых приложений. Подводя итоги В этой главе вы изучили протокол управляющих сообщений Интернет, ICMP. В основном, ICMP переносит разнообразные сообщения об ошибках при доставке данных или маршрутизации. Сообщения-запросы ICMP используются для отладки программного обеспечения сетевых компьютеров и разрешения проблем, связанных с межсетевой передачей данных. Вы узнали, как устроен и работает простой сокет, используемый, в частности, с протоколом ICMP. До того как перейти к главе 17, проверьте, хорошо ли вы уяснили следующие важные понятия: S Сообщения об ошибках ICMP относятся к ошибкам при доставке пакетов данных и маршрутизации. S Сообщения-запросы ICMP помогают определять источники сетевых неисправностей и отлаживать работу программ. S Для работы с простым сокетом прикладная программа должна самостоятельно выполнять функции транспортного уровня. S Для работы с простым сокетом прикладная программа обязана самостоятельно создавать и заполнять структуры данных требуемого протокола, в частности, заголовков IP и ICMP-датаграмм. 
Глав Электронная почта Интернет Электронная почта (e-mail) является самым широко используемым приложением для большинства сетей. Действительно, половина всех устанавливаемых ТСР/IP-соединений предназначена для обмена электронной почтой. Отсюда следует, что знание принципов работы электронной почты требуется любому профессиональному программисту Интернет. В этой главе вы изучите основу передачи постовых сообщений — простой протокол передачи почты (Simple Mail Transfer Protocol, SMTP), а также некоторые дополнительные расширения. Хотя на свете существует множество почтовых программ, клиентов и серверов, изучить почтовый протокол для вас не будет бесполезной тратой времени. Концепции электронной почты можно применить везде, где требуется разработать простую, но эффективную сетевую службу Интернет. Или, что даже интереснее, вы можете реализовать дополнительный сервис на базе почтового, что не потребует разработки новых протоколов — достаточно будет уже имеющегося. Расширения службы электронной почты дают возможность обменивать- 424 
Общая Шфгтт, с я не только текстовыми сообщениями, но и передавать двоичные файлы, например изображения, звуки или исполняемые файлы. По прочтении главы вы овладеете следующими понятиями: ♦ Каким образом сообщения электронной почты перемещаются по Интернет. ♦ Принципы передачи и приема сообщений электронной почты. ♦ Кодирование и декодирование двоичных данных в почтовых сообщениях. Примечание: Информация по поводу количества почтовых соединений в Интернет содержится в статье Caceres, R., Danzig, Р., Jamin, S., и Mitzel, D.J. «Characteristics of Wide-Area TCP/IP Conversations», Computer Communication Review, vol. 21, no. 4, pp. 101-112 (Sept. 1991). Также см. Paxson, V. «Empirically-Derived Analytic Models of Wide-Area TCP Connections: Extended Report», LBL-34086, Lawrence Berkeley Laboratory and EECS Division, University of California, Berkeley (June 1993). Общая картина Поскольку электронная почта необычайно популярна, существует множество документов на эту тему. Вместо того чтобы рассматривать все документы, посвященные электронной почте, мы лучше обрисуем несколько ключевых и самых важных моментов. После прочтения данной главы вы сможете сами ориентироваться в многочисленных «почтовых» RFC, и (как результат прочтения) даже понимать, о чем идет речь. Основные компоненты На рис. 15.1 изображены основные (концептуальные) составляющие системы электронной почты Интернет. Каждому сообщению соответствует источник и получатель. И источник и получатель обладают каким-либо пользовательским интерфейсом к сетевой почтовой системе. В общих чертах, почтовая система состоит из очереди исходящих сообщений, процесса-клиента, процесса-сервера и почтовых ящиков пользователей, хранящих доставленные сообщения. Несмотря на то, что в почтовую систему встраивается пользовательский интерфейс, он не обязан там присутствовать. То есть он может представлять собой отдельную программу, устроенную по принципу клиент-сервер и взаимодействующую с самой почтовой системой. Почтовый ящик (mailbox) носит соответствующее своему владельцу название. Им является либо адрес в формате «имя_пользователя@имя компьютера.имя_домена» либо файл-контейнер для промежуточной доставки. Файл-контейнер аналогичен местному почтовому отделению. Когда вы посылаете письмо вашему другу Ларри, оно попадает сначала на местное почтовое отделение, откуда Ларри его впоследствии заберет. Файл-контейнер хранит сообщение до тех пор, пока пользователь не заберет его оттуда. 425 
Глава 15. Электронная почте Интернет Компьютер-источник Компьютер-получатель . t Программа I ! ► электронной! г > почты 1 1^ Очередь исходящих сообщений Фоновая 1 программа-клиент 1 передачи сообщений К электронной почты \ Е 1 Е и ИИ Е- Е 1 1 • ... ^• iiliili * | ш Е - • г шщ '■ : *; > ч*. .. *. ^ Itiliii - t L р,.Л ч “ '■? Г, : Программа 1 | электронной \i Н почты 1 [ Почтовые Почтовый 1 ящики сервер •• . ь прчтьг;;. " VJ <1 vH •.••I li т ■л Рис. 15.1. Концептуальные составляющие системы электронной почты Компоненты электронной почты Интернет Рассмотренные в предыдущей главе концепции замечательно работают и в электронной почте. На рис. 15.2 приведены настоящие компоненты системы электронной почты, в отличие от концептуальных на рис. 15.1. Обратите внимание на термины «агент пользователя» (user agent, UA) и «агент передачи почты» (message transfer agent, МТА). Как видим, агент пользователя заменяет почтовую программу, а агент передачи почты заменяет процесс-клиент и процесс-сервер. Термин «агент» довольно часто встречается в документации Интернет. «Агент» — это программа специального назначения, выполняющая действия для пользователя или другой программы. В большинстве случаев почтовая программа называется агентом пользователя (UA). Точно так же агент передачи почты (МТА) представляет собой клиент или сервер, выполняющий задачи по доставке или получению почты на сетевом компьютере. Вы, как пользователь, взаимодействуете с агентом пользователя. Он, в свою очередь, взаимодействует с файлом-контейнером или агентом передачи сообщений за вас. В то же время, МТА ведет себя как представитель своего компьютера в сети. Агент пользователя защищает вас от необходимости общаться с различными почтовыми хостами, а МТА защищает компьютер от необходимости общаться с различными агентами пользователя или несколькими агентами передачи почты одновременно. В принципе, пользовательский агент отделен от агента передачи почты. Конечно, их можно объединить в одной программе, но все равно это будут отдельные модули. Будучи взаимосвязаны, оба агента выполняют совершенно различные функции. Пользователи системы Unix хорошо знакомы с такими программами, как МН, Berkeley Mail, Elm, Mush и Pine. Для пользователей Windows самой 426 
Простой протокол передачи почты (SMTP] программа электронной почты или интерфейс пользователя Фоновая передача почты клиента Соединение TCP Почтовый сервер Почтовые ЯШИКИ < Рис. 15.2. Реальные компоненты почтовой системы Интернет известной программой, возможно, является PC Eudora. Все эти программы — агенты пользователя. Каждая обеспечивает интерфейс между пользователем и системой электронной почты Интернет. Задача агента пользователя — создать для него удобный и дружелюбный интерфейс к почтовой системе. Система электронной почты представлена агентами передачи почты, МТА. До того как обсудить задачи пользовательского агента, необходимо узнать немного больше о том, что же такое МТА. МТА умеют устанавливать ТСР-соединение для связи с другими МТА. Протоколом этого соединения, как правило, является простой протокол передачи почты (SMTP). В следующем разделе вы познакомитесь с основами SMTP. Этот протокол полностью описан в RFC 821, который так и называется «Простой протокол передачи почты» (Simple Mail Transfer Protocol, Postel, 1982). Простой протокол передачи почты (SMTP) Агент передачи почты — основной компонент системы передачи почты Интернет. Как уже говорилось, МТА как бы представляет данный сетевой компьютер для сетевой системы электронной почты. Пользователи редко имеют дело с МТА, поскольку он не вполне «дружелюбен», однако без него не обходится ни одна почтовая система. После того как UA пошлет сообщение в выходную очередь, за дело принимается МТА. Он извлекает сообщение и посылает его другому МТА. Этот процесс продолжается до тех пор, пока сообщение не достигнет компьютера-получателя. Для передачи сообщений по TCP-соединению большинство МТА пользуются протоколом SMTP. Сообщения форматированы по правилам виртуального сетевого терминала (NVT), то есть в NVT ASCII. Как вам известно из десятой главы, NVT подобен виртуальному сетевому протоколу и 427 
Глава 15. Злектроиная почта Интернет нужен затем, чтобы скрыть различия в восприятии разными компьютерами разных символов, например переводов каретки, переводов строки, маркеров конца строки, очистки экрана и т. д. Символ в NVT состоит из семи битов набора ASCII и является буквой, цифрой или знаком пунктуации. Семибитный набор ASCII часто называется NVT ASCII. Если вы забыли, что это такое, рекомендуется освежить в памяти содержимое главы 10. Команды SMTP Простой протокол передачи почты обеспечивает двухсторонний обмен сообщениями между локальным клиентом и удаленным сервером МТА. МТА-клиент шлет команды МТА-серверу, а он, в свою очередь, отвечает клиенту. Другими словами, протокол SMTP требует получать ответы (они описаны в этой главе) от приемника команд SMTP. Обмен командами и ответами на них называется почтовой транзакцией (mail transaction). Данные, как мы уже говорили, передаются в формате NVT ASCII. Кроме того, команды тоже передаются в формате NVT ASCII. Команды передаются в форме ключевых слов, а не специальных символов, и указывают на необходимость совершить ту или иную операцию. В табл. 15.1 приведен список ключевых слов (команд), определенный в спецификации SMTP — RFC 821. Таблица 15.1. Команды простого протокола передачи почты (SMTP) Команда Обязательна Описание HELO X Идентифицирует модуль-передатчик для модуля-приемника (hello). MAIL X Начинает почтовую транзакцию, которая завершается передачей данных в один или несколько почтовых ящиков (mail). RCPT X Идентифицирует получателя почтового сообщения (recipient). DATA Строки, следующие за этой командой, рассматриваются получателем как данные почтового сообщения. В случае SMTP, почтовое сообщение заканчивается комбинацией символов: CRLFточка-CRLF. RSET Прерывает текущую почтовую транзакцию (reset). NOOP Требует от получателя не предпринимать никаких действий, а только выдать ответ ОК. Используется главным образом для тестирования. (No operation.) 428 
Простой протокол передачи почты tSMTiPJ Таблица 15.1 (окончание) Команда Обязательна Описание QUIT Требует выдать ответ OK и закрыть текущее соединение. VRFY Требует от приемника подтвердить, что ее аргумент является действительным именем пользователя. (См. примечание.) SEND Начинает почтовую транзакцию, доставляющую данные на один или несколько терминалов (а не в почтовый ящик). SOML Начинает транзакцию MAIL или SEND, доставляющую данные на один или несколько терминалов или в почтовые ящики. SAML Начинает транзакцию MAIL и SEND, доставляющие данные на один или несколько терминалов и в почтовые ящики. EXPN Команда SMTP-приемнику подтвердить, действительно ли аргумент является адресом почтовой рассылки и если да, вернуть адрес получателя сообщения (expand). HELP Команда SMTP-приемнику вернуть сообщениесправку о его командах. TURN Команда SMTP-приемнику либо сказать ОК и поменяться ролями, то есть стать STMPпередатчиком, либо послать сообщение-отказ и остаться в роли SMTP-приемника. Примечание: В RFC 821 сказано, что команда VRFY не является обязательной для минимального набора команд SMTP. Однако в RFC 1123 «Требования для сетевых компьютеров Интернет — приложения и обеспечение работы» (Requirements for Internet Hosts — Application and Support, Braden, 1989), команда VRFY фигурирует в списке обязательных для Интернет команд реализации SMTP. В соответствии со спецификацией команды, помеченные крестиком (X) в табл. 15.1, обязаны присутствовать в любой реализации SMTP. Остальные команды SMTP могут быть реализованы дополнительно. Каждая SMTP-команда должна заканчиваться либо пробелом (если у нее есть аргумент), либо комбинацией CRLF. В описании команд употреблялось слово «данные», а не «сообщение». Этим подчеркивалось, что, кроме текста, SMTP позволяет передавать 429 
Глава 15* Электронная почта Интернет и двоичную информацию, например графические или звуковые файлы. Другими словами, SMTP способен передавать данные любого содержания, а не только текстовые сообщения. Это значит, что, рассматривая вопросы, касающиеся SMTP, не забывайте, что термин «сообщение» обозначает не только текстовые данные. Терминальная почта Во многих операционных системах для системного администратора предусмотрена возможность послать сообщение многим пользователям одновременно. Например, перед остановкой системы администратор выполняет команду, которая автоматически рассылает предупреждающее сообщение на каждый подключенный терминал. С другой стороны, пользователь может отключить прием таких сообщений. Тот или иной режим приема сообщений устанавливается при входе пользователя в систему по умолчанию и может измениться позже. Во многих компьютерах механизм рассылки таких сообщений подобен системе электронной почты. В SMTP существуют команды, позволяющие доставлять сообщения двумя путями — в почтовый ящик или на терминал. В терминах SMTP команда send означает «послать на терминал», а команда mail — «послать в почтовый ящик». Команда SEND является дополнительной, тогда как MAIL — обязательна в любой реализации. Последовательность команд SMTP Как мы уже отмечали, SMTP обеспечивает двухстороннюю связь между агентами передачи почты (МТА), клиентом и сервером. Клиенты шлют команды серверу, а серверы отвечают клиентам. Однако SMTP оговаривает последовательность SMTP-команд. Лучший способ понять это — взглянуть на образец почтовой транзакции. Следующий пример (он взят целиком из RFC 821) демонстрирует типичную почтовую транзакцию. В примере фигурирует мистер Smith (на компьютере usc.edu), посылающий сообщения мистерам Jones, Green и Brown (на компьютере mit.edu). Агент передачи почты хоста mit.edu принимает почту для мистеров Jones и Brown, однако не знает, где расположен почтовый ящик мистера Green. Для целей дальнейшего повествования каждой строке присвоен номер и обозначено, кому они принадлежат — передатчику или приемнику. Текст справа от слов «RECEIVER» или «SENDER» содержит действительно передаваемые данные. Трехзначные цифровые комбинации в начале передаваемых строк обозначают коды ответа (их значение объясняется позже). Ответ SMTP похож на сообщения-подтверждения о доставке, поскольку появляется лишь в том случае, когда приемник получил данные. 1. RECEIVER: 220 mit.edu Simple Mail Transfer Service Ready 2. SENDER: HELO usc.edu 3. RECEIVER: 250 mit.edu 4. SENDER: MAIL FROM:<Smith@usc.edu> 5. RECEIVER: 250 OK 430 
Простой протокол передачи почты (SIVtTP) V;\; A-"'.', j.: ’ л'т;д!:'::дд дЯЯ-Ш>Л 6. SENDER: RCPT ТО:<Jones@mit.edu> 7. RECEIVER: 250 ОК 8. SENDER: RCPT ТО:<Green@mit.edu> 9. RECEIVER: 550 No such user here 10. SENDER: RCPT TO:<Brown@mit.edu> 11. RECEIVER: 250 OK 12. SENDER: DATA 13. RECEIVER: 354 Start mail input; end with <CRLF>.<CRLF> 14. SENDER: Blah blah blah... 15. SENDER: ...etc. etc. etc. 16. SENDER: . 17. RECEIVER: 250 OK 18. SENDER: QUIT 19. RECEIVER: 221 mit.edu Service closing transmission channel Как видно из строки 1, когда SMTP-клиент устанавливает TCP-соединение с портом протокола 25, SMTP-сервер отвечает кодом 220. Это означает, что соединение успешно установлено: 1. RECEIVER: 220 mit.edu Simple Mail Transfer Service Ready После того как MTA компьютеров mit.edu и usc.edu установили соединение и обменялись приветствием, первой командой, согласно спецификации, должна быть команда HELO. Как указано в строке 2, SMTP-клиент передает HELO, указывая имя своего компьютера в качестве аргумента. Другими словами, он сообщает: «Привет, я — usc.edu». Команда HELO употребляется с аргументом, как показано ниже: 2. SENDER: HELO usc.edu В ответ на HELO приемник выдает код 250, сообщая передатчику о том, что команда принята и обработана: 3. RECEIVER: 250 mit.edu После установления TCP-соединения и идентификации (при помощи HELO) SMTP-клиент приступает к почтовой транзакции. Для начала он выполняет одну из следующих команд: MAIL, SEND, SOML или SAML. В нашем примере использована команда MAIL: 4. SENDER: MAIL FROM:<Smith@usc.edu> Все четыре команды, MAIL, SEND, SOML и SAML, имеют одинаковый синтаксис: 431 
Шер 15- Электронная почта Интернет MAIL <пробел> FROM:<reverse-path> ccarriage-return line-feed> Примечание: Команды SEND, SOML и SAML дополнительны и используются довольно редко. Аргумент «обратный путь» (reverse path) указывает серверу, кому в случае ошибки отослать соответствующее сообщение. Мы еще рассмотрим его подробнее. На данный момент для нас важно, что в аргументе содержится адрес источника сообщения (в нашем случае, Smith@nsc.edu). После того как сервер выдал код ответа 250 (строка 5), согласившись обработать сообщение от Smith@usc.edu, необходимо указать получателя сообщения. Это делается при помощи команды RCPT. Команда RCPT имеет аргумент — имя получателя. На одну команду приходится только одно имя, поэтому, если получателей несколько, команда RCPT выдается несколько раз. В нашем примере команды RCPT выполняются в строках б, 8 и 10. Синтаксис RCPT похож на синтаксис команды MAIL: RCPT <пробел> ТО:<forward-path> <CRLF> Однако в отличие от MAIL, аргумент RCPT начинается со слова «ТО:». Содержимое аргумента — путь передачи сообщения (forward path), а не обратный путь. На данный момент для нас важно, что в пути передачи сообщения указано имя почтового ящика получателя. Выдав команду RCPT, МТА-клиент ожидает получить ответ с кодом 250. Однако в ответ на восьмую строку 8. SENDER: RCPT ТО:<Green@mit.edu> сервер отвечает кодом 550: 9. RECEIVER: 550 No such user here Код ответа 550 означает, что МТА не в состоянии выполнить запрос клиента, поскольку не знает, как доставить почту указанному пользователю. То есть скорее всего у мистера по фамилии Green нет почтового ящика (Green@mit.edu) на этом компьютере. В протоколе SMTP сказано, что сервер обязан информировать клиента об отсутствии почтового ящика получателя сообщения. Однако в спецификации SMTP ничего не говорится о том, как клиент должен реагировать на это сообщение. После того как посланы все команды RCPT, клиент начинает передачу данных при помощи команды DATA. В строке 12 показано, как МТА-клиент (передатчик) высылает команду DATA, в строке 13 — как сервер отвечает кодом 354. Этот код означает, что передача данных разрешена и должна заканчиваться комбинацией CRLF-T04Ka-CRLF (новой строкой, содержащей только точку). 12. SENDER: DATA 13. RECEIVER: 354 Start mail input; end with <CRLF>.<CRLF> После того как получен код 354, клиент может начать передачу данных. МТА-сервер, в свою очередь, помещает принятые данные в очереди входящих сообщений. Сервер не высылает никаких ответов до тех пор, пока не получит 432 
Простой протокол передачи почты (SMTP) комбинацию CRLF-TOHKa-CRLF от клиента, означающую конец передачи данных. Как показано в строках 16 и 17, в ответ на полученную комбинацию CRLF-T04Ka-CRLF, сервер выдает код 250. Как мы уже говорили, код ответа 250 означает успешное окончание операции: 16. SENDER: 17. RECEIVER: 250 OK Для того чтобы закончить почтовую транзакцию, клиент, по правилам SMTP, обязан послать команду QUIT. Сервер, в свою очередь, отвечает кодом 221. Этот код подтверждает клиенту, что соединение будет закрыто, после чего соединение действительно закрывается: 18. SENDER: QUIT 19. RECEIVER: 221 mit.edu Service closing transmission channel В любой момент во время транзакции клиент может использовать команды NOOP, HELP, EXPN и VRFY. В ответ на каждую команду сервер высылает клиенту определенную информацию. Конечно, в зависимости от ответа клиент может предпринять определенные действия, однако спецификация SMTP ничего не говорит по этому поводу. Например, клиент-МТА может передать команду VRFY для того, чтобы убедиться, что имя пользователя действительно. Если сервер ответит, что данного имени не существует, клиент МТА может не передавать почту для этого пользователя. В спецификации SMTP, однако, на этот счет нет никаких указаний — клиент может ничего не делать в ответ на команду VRFY. МТА-клиент может ничего не делать также в ответ на команды NOOP, HELP и EXPN — ответственность целиком лежит на разработчике конкретной реализации МТА. Коды ответов SMTP В спецификации SMTP требуется, чтобы сервер отвечал на каждую команду SMTP-клиента. МТА-сервер отвечает трехзначной комбинацией цифр, называемой кодом ответа. Вместе с кодом ответа, как правило, передается одна или несколько строк текстовой информации. Примечание: Несколько строк текста, как правило, сопровождают только команды EXPN и HELP. В спецификации SMTP, однако, ответ на любую команду может состоять из нескольких строк текста. Каждая цифра в коде ответа имеет определенный смысл. Первая цифра означает, было ли выполнение команды успешно (2), неуспешно (5) или еще не закончилось (3). Как указано в приложении Е документа RFC 821, простой SMTP-клиент может анализировать только первую цифру в ответе сервера, и на основании ее продолжать свои действия. Вторая и третья цифры кода ответа разъясняют значение первой. Если вы разрабатываете SMTP-приложение, обязательно изучите конструкцию всех кодов SMTP-ответа. То, как коды составле¬ 433 
ны в самом SMTP — превосходный образец грамотного подхода к делу. В табл. 15.2 приведены возможные значения кодов ответа SMTP, определенные в RFC 821. Таблица 15.2. Коды ответа SMTP и их значение Код Значение 211 Ответ о состоянии системы или помощь 214 Сообщение-подсказка (помощь) 220 <имя_^омена> служба готова к работе 221 <имя_^омена> служба закрывает канал связи 250 Запрошенное действие почтовой транзакции успешно завершилось 251 Данный адресат не является местным; сообщение будет передано по маршруту <forward-path> 354 Начинай передачу сообщения. Сообщение заканчивается комби¬ нацией CRLF-T04Ka-CRLF 421 <имя_ущмена> служба недоступна; соединение закрывается 450 Запрошенная команда почтовой транзакции не выполнена, так как почтовый ящик недоступен 451 Запрошенная команда не выполнена; произошла локальная ошибка при обработке сообщения 452 Запрошенная команда не выполнена; системе не хватило ресурсов 500 Синтаксическая ошибка в тексте команды; команда не опознана 501 Синтаксическая ошибка в аргументах или параметрах команды 502 Данная команда не реализована 503 Неверная последовательность команд 504 У данной команды не может быть аргументов 550 Запрошенная команда не выполнена, так как почтовый ящик недоступен 551 Данный адресат не является местным; попробуйте передать сообщение по маршруту <forward-path> 552 Запрошенная команда почтовой транзакции прервана; дисковое пространство, доступное системе, переполнилось 553 Запрошенная команда не выполнена; указано недопустимое имя почтового ящика 554 Транзакция не выполнена 434 
Простой протокол передачи почть; fSMTRJ Что означает первая цифра в коде ответа SMTP? В спецификации SMTP для первой цифры кода ответа отведено пять возможных значений. Цифра 1 означает, что сервер МТА принял команду, от клиента требуется дополнительное подтверждение. Клиент обязан послать дополнительную информацию о том, продолжать или прервать выполнение запрошенной команды. Из табл. 15.2 видно, что SMTP не имеет в составе таких команд, то есть коды ответа, начинающиеся с единицы, отсутствуют. Это образец того, как разработчики, что называется, глядели в будущее. В настоящее время команд SMTP, которые бы потребовали дополнительного подтверждения, просто нет. Однако с самого начала разработчики ориентировались на то, что такие команды появятся, и зарезервировали для них коды, начинающиеся с цифры 1. Коды ответа, начинающиеся с цифры 2, означают, что сервер МТА успешно завершил выполнение команды и ожидает появления новой. Код ответа, начинающийся на 3, означает, что команда начала выполняться, но серверу необходима дополнительная информация для ее завершения. Пример такого кода — 354. В ответ на него клиент МТА должен приступить к передаче почтового сообщения. Код, начинающийся с цифры 4, означает, что сервер не принял команду и она, соответственно, не выполнена. Однако во всех ответах серии 400 предполагается, что ошибка временная и клиент может попытаться ее исправить. Коды ответа серии 500 также сообщают, что команда не выполнена. Кроме того, клиент не должен пытаться повторить ту же команду еще раз по крайней мере в составе той же последовательности. Вторая цифра кода ответа SMTP Вторая цифра кода ответа обозначает категорию ошибки. Цифра 0, например, обозначает синтаксическую ошибку. Команда может быть слишком длинной, иметь неправильный аргумент или, наконец, отсутствовать в списке команд сервера. Взглянем на сообщения с кодами 211 и 214 из табл. 15.2. Обратите внимание на то, что у обоих вторая цифра кода равна единице и оба они информационного характера. Взгляните на команды с кодами 220, 221 и 421. У всех них вторая цифра — двойка, и все они имеют дело с передачей данных или с коммуникационным каналом. Коды ответов, у которых вторая цифра равна пяти (250, 450 и 550), связаны непосредственно с почтовой системой. В настоящее время в SMTP не определены значения кодов, вторая цифра которых равна трем или четырем. Третья цифра кода ответа SMTP В спецификации SMTP указано, что каждая отдельная строка сообщения должна иметь собственную третью цифру в коде ответа. Рассмотрим, например, сообщения с кодами от 500 до 504. Каждое сообщение означает отдельную синтаксическую ошибку. Поскольку строки, описывающие различные виды ошибок, разные, то и коды ответа должны отличаться друг от друга. Каждое сообщение об ошибке имеет свой собственный порядковый номер в данной серии. Спецификация SMTP рекомендует, но не обязывает использовать строго заданные текстовые строки в ответах МТА-сервера. 435 
Главе! 15. Электронная почта Интернет SMTP-ответ бывает многострочным Как уже отмечалось, ответ МТА-сервера может состоять из нескольких строк специального формата. Каждая строка (кроме последней) многострочного ответа начинается с кода ответа, дефиса (-), текста и комбинации CRLF. Последняя строка многострочного ответа начинается с кода ответа, за которым следует пробел: 123-Первая строка сообщения из нескольких строк 123-Код ответа, 123, не изменяется 123-1 интересный момент: сообщение может начинаться с цифры 123 Последняя строка начинается не с дефиса, а с пробела Обратите внимание на то, что за кодом каждой строки, кроме последней, следует знак дефиса (-). Это необходимо, чтобы клиент МТА смог отличить строку-продолжение ответа от последней строки. За кодом ответа в последней строке всегда следует пробел. Ограничения по размерам В стандарте SMTP сказано, что реализации SMTP не должны ограничивать максимальную длину обрабатываемых объектов (возможно, для будущих расширений стандарта). Однако в настоящий момент SMTP ограничивает допустимые размеры следующими величинами, приведенными в табл. 15.3. Таблица 15.3. Ограничения на размеры объектов SMTP Объект SMTP Ограничение User Максимальная длина имени пользователя: 64 символа Domain Максимальная длина имени домена: 64 символа Path Максимальная длина обратного маршрута или маршрута доставки, включая знаки пунктуации и символы-ограничители: 256 знаков Command line Максимальная длина командной строки, включая ключевое слово и символы CRLF: 512 знаков Reply line Максимальная длина строки ответа, включая код ответа и символы CRLF: 512 знаков Text line Максимальная длина текстовой строки, включая символы CRLF: 1000 знаков Recipients Максимальное количество получателей сообщения (за одну транзакцию): 100 436 
Простой протокол щщщтгщн щочтщ Щ0ЩЩ В соответствии со спецификацией (RFC 821), если клиент МТА превысил ограничения на размер передаваемой информации, сервер МТА отвечает одним из следующих кодов: 500 Line too long. (Слишком длинная строка) 501 Path too long. (Слишком длинный путь) 552 Too many recipients. (Слишком много получателей) 552 Too much mail data. (Слишком много данных в сообщении) Что такое промежуточные агенты? Термин «маршрут доставки» (forward-path) служит для того, чтобы отличать почтовый ящик (mailbox), имя которого абсолютно, от пути (он может быть различным), по которому следует почта. Предположим, что мы хотим доставить два почтовых сообщения на один и тот же сетевой компьютер. Оба сообщения имеют один и тот же адрес, однако не обязательно будут следовать по одному и тому же маршруту. Точно так же, если на пришедшие сообщения выдаются ответы, они не обязательно будут следовать по указанному обратному маршруту (reverse-path). Как правило, конкретный маршрут для почты выбирается системным администратором. Чтобы направить почту по нужному пути, используются значения маршрута доставки и обратного маршрута, в которых указываются промежуточные агенты (relay agents). Промежуточный агент доставки — это МТА, так называемый почтовый хаб (mail hub), настроенный на передачу транзитной почты. Чтобы доставить сообщение, местный агент пользователя (UA) передает его местному МТА, который, в свою очередь, передает его промежуточному агенту МТА. В следующем примере Smith@usc.edu является почтовым ящиком, a HOST1, HOST2 и HOST3 — промежуточными агентами: MAIL FROM: <@H0ST1, (2HOST2 , (2HOST3 : Smith@usc . edu> В наше время промежуточные агенты присутствуют практически во всех сетях, входящих в Интернет. На рис. 15.3 приведена типичная конфигурация почтовой системы Интернет с участием промежуточных агентов. Чтобы упростить процесс конфигурации почтовой системы, в локальной сети устанавливается один компьютер, служащий промежуточным агентом (relay host). Вся почта пользователей попадает сначала на него. Затем этот компьютер рассылает сообщения по Интернет. Кроме всего прочего, такой компьютер может служить защитой фирмы от взломщиков-хакеров из Интернет. Ограничивая общение локальной сети с внешним миром до уровня почты, организация сводит до минимума риск нежелательного вторжения в свои собственные системы. 437 
Глава 15. Система электронной почты организации Б Рис. 15.3. Почтовая система Интернет с участием промежуточных агентов Кроме того, администрировать и защищать в этом случае приходится единственный компьютер. SMTP в состоянии послать сообщение непосредственно с компьютера пользователя на компьютер адресата в том случае, если между ними существует прямое почтовое соединение. К сожалению, это далеко не всегда так. Как правило, между двумя компьютерами находится один или несколько промежуточных агентов. Чтобы обеспечить доставку, в почтовом сообщении нужно указать имя компьютера-получателя и точное наименование почтового ящика. 438 
V Аргументом команды MAIL является обратный маршрут, включающий имя источника сообщения и имена всех промежуточных агентов. Аргумент команды RCPT — маршрут доставки, содержащий имя получателя сообщения. Обратный маршрут описывает путь, который прошло сообщение, тогда как маршрут доставки идентифицирует место назначения. Обратный маршрут используется SMTP, когда нужно передать сообщение о случившейся ошибке или о невозможности доставить сообщение, когда оно уже прошло через промежуточный агент. По мере продвижения сообщения по Интернет записи о его маршрутах изменяются. В обязанности системных администраторов входит правильно настраивать местные МТА на передачу сообщений промежуточному агенту, и наоборот, промежуточные агенты на доставку сообщений местным МТА. Если у промежуточного МТА изменится имя, все, что нужно сделать в конфигурации местного МТА — изменить имя компьютера в системе DNS. Другие параметры конфигурации не изменяются. Другими словами, повторим еще раз, что иметь один компьютер для промежуточной доставки — значит снять с себя значительную часть головной боли по настройке почтовой системы — ведь придется заботиться только об одном компьютере. Рассмотрим почтовую транзакцию между промежуточными агентами SMTP. До того как сообщение будет передано следующему указанному в маршруте (в поле ТО:) компьютеру, имя данного компьютера удаляется из маршрута доставки и добавляется в начало обратного маршрута. К тому моменту, когда сообщение достигнет пункта назначения, маршрут доставки будет содержать только имя почтового ящика. В RFC 821 приведен пример того, как изменяется содержимое маршрутов по мере обработки почтового сообщения. Когда промежуточный агент А получает почту со следующими аргументами: FROM:<USERX@HOSTY.ARPA> ГО:<@HOSTA.ARPA,@HOSTB.ARPA:USERC@HOSTD.ARPA> он переправляет почту сетевому компьютеру В со следующими аргументами: FROM:<@HOSTA.ARPA:USERX@HOSTY.ARPA> 70:<@H0STB.ARPA:USERC@HOSTD.ARPA>. Как видим, промежуточный агент A (HOSTA.ARPA) убрал свое имя из заголовка «ТО:» и добавил в заголовок «FROM:». Промежуточный агент компьютера В совершит аналогичное действие, и следующим пунктом назначения сообщения будет почтовый ящик USERC на компьютере HOSTD.ARPA. Примечание: Другими словами, обратные маршруты и маршруты доставки строятся агентами передачи почты по мере прохождения сообщения от одного агента к следующему. Если очередной на пути сообщения SMTP-агент не умеет обслуживать промежуточную доставку, он должен ответить таким же кодом, какой предусмотрен на случай отсутствия местного почтового ящика. 439 
"лава 15. Элею ройная понта Интерне1 Составные части сообщения электронной почты Сообщение электронной почты состоит из следующих частей: упаковки (envelope), для распознавания агентами передачи почты (МТА), заголовков (headers), используемых агентами пользователя (UA), и тела (body) сообщения — собственно информации, предназначенной адресату. Данные упаковки задаются полями «ТО:» и «FROM:». Вы уже познакомились с ними, когда рассматривали SMTP-команды SEND и RCPT. Другими словами, как указано в RFC 821, информация упаковки требуется для передачи сообщения от одного сетевого компьютера другому. В RFC 822, «Стандарт оформления текстовых сообщений Интернет» (Standard for the Format of ARPA Internet Text Messages, Crocker, 1982), определены форматы двух остальных частей: заголовка и тела сообщения. (В профессиональной среде текстовые сообщения этого формата иногда называются просто «822». Например, можно встретить заявление типа: «В этом протоколе используется текст формата 822».) Для иллюстрации сложного заголовка сообщения в RFC 822 приводится следующий пример: Date : 27 Aug 76 0932 PDT From : Ken Davis <KDavis@This-Host.This-net> Subject: Re: The Syntax in the RFC Sender : KSecy@Other-Host Reply-То: Sam.Irving@Reg.Organization To : George Jones <Group@Some-Reg.An-Org>, Al.Neuman@MAD.Publi sher cc : Important folk: Tom Softwood <Balsa@Tree.Root>, "Sam Irving"@Other-Host;, Standard Distribution: /main/davis/people/standard@Other-Host, "<Jones>standard.dist.3"@Tops-2О-Host>; Comment : Sam is away on business. He asked me to handle his mail for him. He'll be able to provide a more accurate explanation when he returns next week. In-Reply-To: <some.string@DBM.Group>, George's message X-Special-action: This is a sample of user-defined field-names. There could also be a field-name "Special-action" but, its name might later be preempted Message-ID: <4231.629.XYzi-What@Other-Host> 440 
Усовершенствование Как уже отмечалось, тело сообщения содержит собственно информацию, которую необходимо доставить адресату. Как правило, оно состоит из текста формата NVT ASCII. Заголовок сообщения отделяется от тела пустой строкой. Тело сообщения вводится пользователем в ходе почтовой транзакции при помощи агента пользователя (почтовой программы). Далее, агент пользователя добавляет необходимые поля заголовка, содержимое которых задается пользователем, и передает сообщение следующему агенту МТА, который осуществляет либо промежуточную, либо окончательную доставку. Усовершенствования В настоящее время ведется интенсивный поиск возможных путей усовершенствования электронной почты. Усилия прилагаются во всех трех направлениях. Они затрагивают доставку (организация информации в служебных полях упаковки сообщения), обработку агентами пользователя (информация в заголовке сообщения) и тело сообщения. Наиболее интересные возможности предоставляет модификация тела почтового сообщения. Тело сообщения может переносить мультимедиа-объекты, то есть являться двоичным файлом с графической, звуковой или видеоинформацией. В этом разделе мы рассмотрим существующие методы кодирования таких данных в почтовых сообщениях Интернет. Расширения SMTP Возможно, самым важным документом, посвященным расширениям SMTP, является RFC 1425, «Расширения службы SMTP» (SMTP Service Extensions, Klensin et al, 1993). Вдобавок к расширениям SMTP, в RFC 1425 описываются способы проектирования новых расширений, так сказать, на будущее. Реализация SMTP, работающая в соответствии с расширениями RFC 1425, называется «расширенный SMTP» или просто ESMTP (Extended SMTP). Расширенный SMTP работает почти так же, как и обычный SMTP, однако команда-приветствие у него другая: EHLO (Extended hello) вместо уже знакомого HELO. Чтобы выяснить, поддерживает ли МТА-сервер спецификацию ESMTP, МТА-клиент посылает команду EHLO. Если сервер поддерживает ESMTP, он отвечает кодом 250. Если нет, следует сообщение о синтаксической ошибке. В ответ на сообщение об ошибке, клиент может выдать обычную команду HELO и далее выполнять стандартные операции SMTP. Если сервер умеет обслуживать ESMTP, в ответ на приветствие, как правило, он выдает многострочный ответ (в известном вам формате). Каждая срока ответа содержит дополнительную команду ESMTP, с которой сервер знает, как работать. Вот пример реакции ESMTP-сервера в ответ на команду EHLO: 250-mail.server.com 2 50-EXPN 441 
Глава 15. Электронная почта Интернет 250-HELP 250 TURN Вторая, третья и четвертая строки ответа содержат названия дополнительных команд сервера. Как видим, этот сервер обеспечивает обработку перечисленных в табл. 15.1 дополнительных команд. На самом деле в качестве расширений RFC 1425 перечислены только дополнительные SMTP-команды. Другими словами, первыми шестью расширениями SMTP в RFC 1425 являются команды: EXPN, HELP, TURN, SEND, SOML и SAML. Существует также расширение SIZE, описанное в RFC 1427, «Расширения службы SMTP, касающиеся размеров сообщений» (SMTP Service Extension for Message Size Declaration, Klensin et al, 1993), позволяющее SMTP-клиенту и серверу сообщать друг другу размеры передаваемого сообщения. Если в ответе на команду EHLO присутствует ключевое слово SIZE, значит, данное расширение обрабатывается. Как известно, существует предел размеров передаваемого сообщения для сервера. Если клиент МТА попытается передать сообщение, превышающее этот предел, почтовая транзакция не состоится (закончится с ошибкой). Максимальная длина сообщения ограничивается по нескольким причинам. Основная состоит в том, что размеры жестких дисков сервера всегда ограничены, и слишком длинное сообщение может попросту не поместиться на них. С другой стороны, свободное пространство на дисках сервера может со временем увеличиться, и это сообщение будет принято при следующей попытке. К сожалению, обыкновенный МТА-клиент не имеет возможности знать заранее, хватит ли места на сервере, чтобы положить туда все сообщение. Предположим, что вы разрабатываете почтовое приложение для пересылки больших мультимедиа-файлов. Если МТА-клиент этой системы не умеет работать с ESMTP, а значит, не знает команды SIZE, вы постоянно будете сталкиваться с такой ситуацией: клиент передает несколько мегабайт информации и только потом узнает, что на диске сервера не хватило 100 килобайт места для хранения файла. Если вы планируете пересылать большие документы (графику, звук или видео), вам настоятельно рекомендуется команда SIZE (ее полное название: Message Size Declaration service extension). Ее задача — выдать клиенту максимальный размер сообщения, который сервер в состоянии принять от клиента. Максимальный размер сообщения следует сразу за кодом ответа 250. Вот пример ответа SIZE для размера в 100 мегабайт (Мб): 250 SIZE 100000000 Клиент МТА анализирует ответ SIZE и в случае необходимости предпринимает соответствующие действия. Дополнительно клиент может указывать длину сообщения в команде MAIL. В следующей ESMTP-команде клиент объявляет, что длина сообщения равна 500 килобайтам (Кб): MAIL FROM:<happy@jamsa.com> SIZE=500000 МТА-сервер ESMTP анализирует аргумент SIZE и решает, принимать ему сообщение такой длины или сообщить об ошибке. Все это происходит еще до 442 
начала передачи (однако в RFC 1427 описано несколько случаев, когда ошибка происходит уже во время передачи). Остальные несколько расширений SMTP, в общем, подчиняются тем же правилам, что и рассмотренная только что команда SIZE. То есть команда-расширение выдается в ответе сервера по запросу клиента EHLO. Расширения можно использовать в ваших программах в соответствии со спецификацией. В некоторых случаях расширение является просто дополнительной возможностью. В других случаях расширение — дополнительный аргумент к существующей команде (как в случае только что рассмотренной команды SIZE). Что такое местные расширения? По определению местным расширением является любое поле, команда или название опции, начинающееся с буквы X. Например, в почтовом сообщении из предыдущего раздела обсуждалось поле заголовка под названием «X-Special-action»: X-Special-action: This is a sample of user-defined field-names. There could also be a field-name "Special-action", but its name might later be preempted Имя этого поля начинается с X, поэтому оно местное, то есть задано пользователем. Разумеется, пользователь может придумать любое название поля, какое ему нравится. Однако при этом существует риск, что в дальнейших стандартах Интернет будет определено то же самое имя, но с другим значением. При этом пользовательское и стандартное приложения вступят в конфликт. Поэтому при разработке собственных расширений почтовой системы необходимо, чтобы имена всех новых объектов начинались с буквы X. Например, пользовательский вариант декодирования тела сообщения должен называться не DECODE, как можно было бы предположить, a XDECODE. SMTP-сервер организации при этом должен включить местное расширение XDECODE в список, выдаваемый по команде EHLO (с кодом ответа 250): 250 XDECODE MIME В документе RFC 1521 под названием « расширения почтовой системы Интернет* (MIME, Multipurpose Internet Mail Extensions, Borenstein and Bellcore, 1993) описано наиболее впечатляющее расширение для существующих почтовых систем. Система MIME не предполагает вмешательства в деятельность агентов передачи почты. Два агента пользователя, понимающие MIME, могут общаться друг с другом при помощи обыкновенных МТА. В MIME :< сообщению просто добавляются несколько полей заголовка (в соответствии : RFC 822): • MIME-Version (версия MIME) 443 
Глава 15. Электронная почта Интерне? • Content-Type (тип содержимого) • Content-Transfer-Encoding (тип кодировки содержимого) • Content-ID (идентификатор содержимого) • Content-Description (описание содержимого) Номера версий MIME меняются по мере его развития. Поле MIME-Version задает номер версии расширения MIME, которое данный агент пользователя умеет обрабатывать. Номер версии в заголовке предохраняет агента от неправильной интерпретации сообщения, в случае, если версии MIME сообщения и агента не совпадают. Вот образец полей заголовка MIME-Version и ContentType: Mime-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Как видим, сообщение создано MIME версии 1.0. Тип содержимого — TEXT, подтип — PLAIN, кодовая таблица (набор символов) US-ASCII. В табл. 15.4 приведены существующие в данный момент типы и подтипы MIME. Таблица 15.4. Существующие типы и подтипы MIME Тип Подтип Описание Text Plain Неформатированный текст. Richtext Текст с элементами форматирования и выделениями, например с курсивом, подчеркиваниями, жирными буквами и т. д. Enriched Усовершенствованный и упрощенный вариант подтипа richtext. Multipart Mixed Тело сообщения состоит из нескольких частей; обрабатывать последовательно. Parallel Тело сообщения состоит из нескольких частей; обрабатывать параллельно. Digest Дайджест электронной почты. Alternative Тело сообщения состоит из нескольких частей; все части семантически идентичны. Message RFC822 В теле содержится почтовое сообщение стандарта RFC 822. Partial Фрагмент почтового сообщения. 444 
Усовершенствования Таблица 15.4 (окончание) Тип Подтип Описание External-Body Указатель на действительное почтовое сообщение (не включенное в тело данного сообщения). Application Octet-Stream Произвольные двоичные данные. Postscript Программа на языке Postscript. Image JPEG Формат ISO 10918. GIF Графический формат фирмы CompuServe. Audio Basic Звук в 8-битном ISDN-формате mu-law. Video MPEG Формат IS011172. Поля заголовка Content-ID и Content-Description могут отсутствовать. Первое служит для идентификации MIME-содержимого электронного письма, а второе может содержать дополнительное описание. Например, если М1МЕ-содержимым является графический образ, в поле Content-Description можно поместить описание этого образа. В табл. 15.5 перечислены возможные значения поля Content-Tranfer-Encoding, доступные в настоящее время. Таблица 15.5. Допустимые значения поля Content-Transfer-Encoding Content-Transfer-Encoding Описание 7bit Формат NVT-ASCII — стандартный формат почтовых сообщений. Quoted-printable Полезен в случае, если текст содержит небольшое количество восьмибитных символов. Base64 Формат, в котором три байта данных упакованы в четыре шестибитных значения. 8bit Содержит текст, в котором не все символы принадлежат стандартному набору ASCII (то есть, в некоторых установлен восьмой бит). Binary Восьмибитные данные, как правило, без символов окончания строки. По умолчанию формат почтовых сообщений удовлетворяет кодовому набору NVT-ASCII. 8-битные агенты МТА сейчас практически отсутствуют, но как только они получат широкое распространение, вероятно, передача бинарной и текстовой информации в 8-битной кодировке возрастет. В настоящий момент 445 
Глава 15. Электронная почта Интернет для передачи 8-битной информации по 7-битным каналам Интернет лучше всего использовать кодировки quoted-printable или base64. В следующем разделе мы подробно рассмотрим обе кодировки. Способы кодирования MIME Для кодирования небольшого количества 8-битных данных в 7-битный формат NVT ASCII лучше всего подходит схема quoted printable. 8-битный символ в этой схеме представляется в виде последовательности из трех символов. Последовательность всегда начинается со знака «равно» (=). Сразу за знаком «равно» следует двузначное шестнадцатиричное число, представляющее код ASCII кодируемого символа. Рассмотрим закодированную quoted printable последовательность JAMSA PRESS. Хоть она и не содержит 8-битных символов, зато позволяет хорошо проиллюстрировать сам принцип кодирования. Закодированное сочетание JAMSA PRESS выглядит так: =4A=41=4D=53=41=20=50=52=45=53=53 Другими словами, буква J имеет шестнадцатиричный код ASCII 0x4А, буква А — 0x41 и т. д. Обратите внимание на то, что схема quoted printable передает ASCII код для каждого символа последовательности. То есть для знака A (ASCII 0x4А) передается код знака «равно» (ASCII 0x3D), код цифры 4 (ASCII 0x34) и код знака А (0x41). Данную схему довольно удобно использовать, но, к сожалению, она утраивает общее количество информации в сообщении. Таким образом, область применения quoted printable — сообщение с небольшим количеством символов, в которых установлен старший (восьмой) бит. Основная часть сообщения должна состоять все-таки из обычных 7-битных символов. Как отмечалось в табл. 15.5, схема quoted printable годится только для небольшого количества символов с установленным восьмым битом. Например, если ваша фамилия Shoenburg, вам понравится способ передавать ее в правильном виде. Для этого вы просто закодируете букву в виде =94. Другими словами, ваша подпись будет выглядеть так: Regards, Н. Sh=94enburg Если почтовый агент пользователя понимает кодировки MIME, он правильно декодирует запись и на экране появится: Regards, Н. Shoenburg Примечание: В документе RFC 1522, «MIME, часть вторая: расширения заголовка сообщения для не-ASCII текстов» (MIME (Multipurpose Internet Mail Extensions) Part Two: Message Header Extensions for Non-ASCII Text, Moore, 1993), определена схема кодирования 8-битных данных не только в теле, но и в заголовке сообщения. 446 
В отличие от quoted printable, кодирование Base-64 увеличивает размер сообщения всего лишь на одну треть. Каждая последовательность из трех байтов (24 бита) превращается в четыре шестибитовых значения (опять-таки, 24 бита). Шестибитные символы соответствуют формату NVT ASCII и приведены в табл. 15.6. Таблица 15.6. Таблица кодировки Base-64 6 бит ASCII 6 бит ASCII 6 бит ASCII 6 бит ASCII 0 А 16 Q 32 g 48 w 1 В 17 R 33 h 49 X 2 С 18 S 34 i 50 У 3 D 19 т 35 j 51 z 4 Е 20 и 36 k 52 0 5 F 21 V 37 1 53 1 6 G 22 W 38 m 54 2 7 Н 23 X 39 n 55 3 8 I 24 Y 40 о 56 4 9 J 25 Z 41 P 57 5 10 К 26 а 42 q 58 6 11 L 27 b 43 r 59 7 12 М 28 с 44 s 60 8 13 N 29 d 45 t 61 9 14 О 30 е 46 u 62 + 15 Р 31 f 47 V 63 / Примечание: Способ кодирования Base-64 идентичен способу, описанному в RFC 1421 (Privacy Enhancement for Internet Electronic Mail: Part I: Message Encryption and Authentication Procedures, Linn, 1993), где рассматриваются приложения шифрования электронной почты (Privacy Enhanced Mail applications, РЕМ). Если количество байтов (символов) в сообщении не кратно трем, используется дополняющий символ «равно». На рис. 15.4 приведены три примера кодирования методом Base-64. В слове KEN три символа, поэтому дополнять его не нужно. В слове СОРЕ есть один лишний символ, и оно дополняется двумя символами «равно». В слове JAMS А два лишних символа, и оно дополняется эдним символом «равно». В любом случае количество символов в сообщении формата Base-64 должно быть кратно трем. 447 
Глава 15. Электронная почта Интернет А. Слово KEN в формате Base-64 выглядит как "SOVO" (дополнительных символов не требуется). Буква: К Е N Шестнадцатиричное: 0x4В 0X45 0х4Е Двоичное: 01 00 10^11 01 00^01 01 оую 11 10 6 битов: 01 00 10 11 01 00 01 01 01 00 11 10 Десятичное: 18 52 21 14 Base-64: S 0 V 0 Б. Слово СОРЕ в формате Base-64 выглядит как ,,Q09QRQ==" (требуются два дополнительных символа—нуля). Буква: С 0 Р Е = = Шестнадцатиричное: 0x43 0X4F 0X50 0X45 Двоичное: 01 00 00^11 01 00J1 11 oi 01 00 00^ 01 00 01^01 6 битов заполнены ну 6 битов: 01 00 00 11 01 00 11 11 01 01 00 00 01 00 01 г-*—» 01 00 00 Десятичное: 16 52 61 16 17 16 Base-64: Q 0 9 Q R Q В. Слово JAMSA в формате Base-64 выглядит как "SkFNU0E=H (требуется один дополнительный символ—ноль). Буква: J А м S А = Шестнадцатиричное: 0х4А 0x41 0X4D 0x53 0X41 Двоичное: 01 00 10^10 01 00^00 01 0МЮ 11 01^ 01 01 00^11 01 00^00 01 Вставлены нули 6 битов: 01 00 10 10 01 00 00 01 01 00 11 01 01 01 00 11 01 00 00 01 00 Десятичное: 18 36 5 13 20 52 4 Base-64: S к F N и 0 Е Рис. 15.4. Образцы кодирования методом Base-64 с дополнением Реализации метода Base-64 Если вам редко приходится иметь дело с данными в виде отдельных битов, вы можете спросить: «Насколько трудно реализовать метод кодирования Base-64 на практике?» На это можно ответить, что если вы хорошо знакомы с операциями типа «битовое И», «битовое ИЛИ» и операциями двоичного сдвига в языках C/C++, то эта задача легка. Операторы двоичного сдвига перемещают биты числа влево или вправо на количество позиций, заданное в операторе. Оператор «битовое И» позволяет маскировать заданные биты, а оператор «битовое ИЛИ», наоборот, совместить биты одного числа с битами другого. 448 
Шдове рш енствова шт На рис. 15.5 изображен процесс кодирования слова KEN методом Base-64. Слева изображены перемещения битов, а справа — операторы программы, посредством которых все это делается. Обведенные кружками числа используются для ссылок при последующем рассмотрении. В верхнем левом углу рисунка изображены символы К, Е и N в шестнадцатиричном и двоичном ASCII-коде. Направленные вверх стрелки обозначают точки разбиения трехбайтовой последовательности для преобразования в последовательность Base-64. В правом верхнем углу рисунка приведены описания участвующих в процессе переменных. К Е | N 4В ! 45 4Е ■ 0 1 0 0 1 0^1 1 I 0 1 0 0 0 1 0 1 ■ 0 1^0 0 1110 О®®©®®!®® @ 8 © © © 8 8 © j®<Z>|@©(D<3)<2)<D BYTE cTemp, cTempHigh, cTempLow; BYTE cFirsteBits, cSecond6Bits; BYTE cThird6Bitsf cFourth6Bits; BYTE cFirstLetter = 'K'; BYTE cSecondLetter = 'E'; BYTE cThirdLetter = 'N'; Позиции битов 6 id d i о! 1 1 \W 'у v lo 0 >0 1 0 0 1 0 > CD t = CFIrs ». 2; oiooi о|шд i 1 о нЯГГГ^ 0 0 1 1 0 0 0 0 (Маска = 0X30) 0 0 0 0 0 0 @ Ш5га ■Г « 4; сТелпрИгдЬ - cTemp & 0x30; ► .4; 0 10 0 ШШМщ О О \-ff .1 -;1 1 0 0 ! 0 0 1 1 1 1 0 0 (Маска = ОХЗС) о о | о: i о 1 : о о сТетр = cSecorKjLetter « 2; 0 1110 lo 0 0 0 0 0 щ ■ц ► 0 0| 11 1 0 i 0 0 0 0 I 7 7> Т III *1 cTempHign = сТетр Ь .ОхзС cTempLow - cThirdLetter » 6; ^IcTFempLow; 15 Зак. № 1949 449 
Глава 15, Электронная почта Интернат о 1 | о о : 1 i PIPI 0 0 1111 1 1 0 0 f 0 Р 1 1 Щ, о| |0 1 0 0 •11-: :о| [*г 1: о 1 0 0 !о о о ооо litl| ^010010 0 0 1 О О 1 0 11 © м с 0x3F; сТэггфИдЬ « cFirsi6Bit$ << 2; := V- - Ч ^ ц: .cRretlefter* ctar^^^lcfer О 1 0 1 И; 11 fHTl'^00 "о ~о~о~о| 0 o.o"p"f V 1 -dr dr_ _4r ^ Hr jQ..:.f:ni):yy •'•o| Рис. /5.5. Операторы языка C/C++, реализующие способ кодирования Base-64 Кодирование данных по методу Base-64 Первое шестибитное значение составлено из первых шести бит первого символа К. Первым делом биты переменной cFirstLetter (символа К) сдвигаются вправо на две позиции. Получается новый байт, содержащий первые шесть бит нашей последовательности. Далее, полученное значение записывается в переменную cFirst6Bits. Дальше, чтобы получить следующее значение, нам нужно объединить два младших бита первого символа (К) с четырьмя старшими второго (Е). Другими словами, нам нужно переместить первый и второй биты буквы К на позиции 5 и 6 — они будут старшими битами следующего шестибитного символа. Операция 2А, двоичный сдвиг влево на четыре позиции демонстрирует, как это делается. Результат сдвига запоминается во вспомогательной переменной сТетр. Сдвиг оператором 2А очищает позиции с 1 по 4, а биты 3 и 4 буквы К теперь расположены на позициях 7 и 8. Позиции 7 и 8 необходимо очистить, поскольку 450 
*• i ‘ Усовершенствования нам нужны только первые шесть битов нового символа. Для этого выполняется оператор 2В, «двоичное И». Битовая маска 00110000 (0x30) очищает все позиции, кроме пятой и шестой (в них содержатся интересующие нас данные). Поскольку биты 5 и б теперь старшие для нового символа Base-64, результат помещается (оператором 2В) во вспомогательную переменную cTempHigh. В результате оператора 2В мы имеем два старших бита нового шестибитного символа. Биты с 1 по 4 должны быть взяты из второго символа. Чтобы получить четыре бита из второго символа, мы сдвигаем его содержимое вправо на четыре позиции (оператор 2С). Результат оператора 2С помещается во вспомогательную переменную cTempLow. Теперь в переменной cTempLow хранятся четыре младших бита нового символа, а в переменной cTempHigh — два старших. Для того чтобы их скомбинировать, мы используем оператор (2D) двоичного ИЛИ. Результат операции заносится в переменную cSecond6Bits. В третий шестибитный символ должны попасть четыре младших бита символа Е и два старших бита символа N. То есть мы должны переместить четыре младших бита символа Е с позиций 1 —4 на позиции 3 — 6. Для этого оператор ЗА сдвигает биты влево на две позиции и помещает результат в переменную сТетр. Сдвиг влево очищает позиции 1 и 2 для следующих данных. Кроме того, необходимо убедиться, что позиции 7 и 8 тоже свободны. Другими словами, нам нужны только биты в позициях 3 — 6. Чтобы очистить ненужные и оставить нужные позиции, мы выполняем оператор «двоичное И» с маской 00111100 (ОхЗС). Результат оператора ЗВ помещается в переменную cTempHigh. Далее, два старших бита символа N необходимо сдвинуть на позиции 1 и 2. Для этого выполняется оператор ЗС сдвига вправо на 6 позиций. Результат заносится в переменную cTempLow. Теперь у нас есть четыре младших бита символа Е в переменной cTempHigh и два старших бита символа N в переменной cTempLow. Оператор 3D комбинирует значения двух этих переменных при помощи двоичного сложения (ИЛИ) и помещает новое шестибитное значение в переменную cThird6Bits. Теперь нам просто получить четвертое шестибитное значение — все биты уже стоят на своих позициях с 1 по 6 в символе N. Все, что осталось сделать — маскировать значения битов позиций 7 и 8. Оператор 4 выполняет «двоичное И» с маской 00111111 (0x3F) и заносит результат в переменную cFourth6Bits. Декодирование данных по методу Base-64 Значения полученных на рис. 15.5 четырех шестибитных символов формата Base-64 хранятся в переменных cFirst6Bits, cSecond6Bits, cThird6Bits и cFourth6Bits. Операторы с 5А по 7В демонстрируют, как декодировать эти значения. Декодирование Base-64 оказывается даже проще и очевиднее, чем кодирование. Чтобы получить каждый байт (кроме третьего) в последовательности, производятся операции левого сдвига, правого сдвига и битового ИЛИ. (Для третьего символа правый сдвиг не нужен — требуются только левый сдвиг и битовое ИЛИ.) Ключевой момент — знать, на сколько позиций сдвигать в 451 
Глава 15- Электронная понта Интернет каждом направлении. Шесть битов первого символа Base-64 являются старшими шестью битами первого байта данных. Их необходимо переметить с позиций 1 — 6 на позиции 3 — 8. Для этого мы выполняем операцию сдвига влево на две позиции (оператор 5А). Поскольку эти биты старшие, результат заносится в переменную cTempHigh. Теперь нам требуются биты с позиций 5 и 6 из второго символа Base-64. В них занесены два младших бита первого байта данных. Сдвиг вправо на четыре позиции оператором 5В решает проблему. Результат оператора 5В заносится в переменную cTempLow. Теперь у нас есть шесть старших битов в переменной cTempHigh и два младших бита в переменной cTempLow. Оператор 5С, битовое ИЛИ, комбинирует их значения, и в результате получается первый байт данных. Чтобы получить второй байт данных, мы выполняем практически те же самые операторы. Только вместо сдвига влево на два и сдвига вправо на четыре мы сдвигаем влево на четыре, а вправо на два, как продемонстрировано операторами 6А и 6В. Оператор 6С, битовое ИЛИ, комбинирует полученные результаты операторов 6А и 6В и получает значение второго байта данных. Биты с 1 по 6 четвертого символа Base-64 соответствуют младшим битам третьего символа данных. Биты 7 и 8 символа данных содержатся на позициях 1 и 2 третьего символа Base-64. Поэтому значение третьего символа сдвигается вправо на шесть позиций оператором 7А. Наконец, в результате оператора 7В, битовое ИЛИ, мы получаем третий байт данных. Реализация алгоритма Метод кодирования Base-64 оперирует с 24-битными величинами. Два алгоритма (кодирования и декодирования), рассмотренные в предыдущем разделе, также оперируют 24-битными величинами. Другими словами, хотя в предыдущем разделе и не рассматривались прототипы функций для кодирования-декодирования Base-64, предполагалось, что у них 24-битные аргументы. Алгоритм кодирования состоит из десятка очень быстрых (поскольку все они работают с двоичной информацией) операторов, преобразующих три байта данных в четыре шестибитных символа Base-64 и восьми строк с такими же быстрыми операторами, преобразующими эти символы в три 8-битных символа исходных данных. Некоторые выводы 18 строчек кода, рассмотренные в предыдущих разделах, дают вам в руки механизм кодирования и декодирования двоичных данных, передаваемых по электронной почте. Этими данными могут быть программы, графика, звук, видео и, вообще, все что угодно. Теперь вы знаете, как такого рода информация передается по системе электронной почты Интернет в кодировке Base-64. В любом случае, если ваши клиенты или работодатель умеют пользоваться электронной почтой, вы сможете найти массу применений для открывающихся перед вами возможностей. 452 
Протокол Post Office Protocol (POP) Протокол Post Office Protocol (POP) В предыдущих разделах мы рассмотрели систему доставки электронной почты Интернет. В данном разделе вы познакомитесь с протоколом доставки почты пользователю из почтового ящика. Он называется Post Office Protocol (POP). Вы обнаружите, что многие концепции, принципы и понятия протокола POP \тже знакомы по предыдущему материалу. (POP выглядит и функционирует подобно SMTP.) Команды POP практически идентичны командам SMTP, отличаясь в некоторых деталях. На рис. 15.6 изображена модель клиент-сервер по протоколу POP. Сервер POP находится между агентом пользователя и почтовыми ящиками. Пользовательский L р0Р почтовый агент (UA) SMTP Пользователь (Может выполнять функции промежуточного агента (relay МТА)) Рис. 15.6. Конфигурация модели клиент-сервер по протоколу POP В настоящее время существуют две версии протокола POP — POP2 и POP3. Обе версии обладают примерно одинаковыми возможностями, однако несовместимы друг с другом. Дело в том, что у POP2 и POP3 разные номера портов протокола. Между ними отсутствует связь, аналогичная связи между SMTP и ESMTP. Протокол POP3 не является расширением или модификацией POP2 — это совершенно другой протокол. POP2 определен в документе RFC 937 (Post Office Protocol-Version 2, Butler, et al, 1985), a POP3 — в RFC 1225 (Post Office Protocol-Version 3, Rose, 1991). В следующих разделах мы кратко рассмотрим POP вообще и более подробно — POP3. Мы не рассматриваем здесь протокол POP2, однако вам не составит труда прочитать соответствующий RFC, базируясь на уже полученных в этой главе знаниях протокола SMTP, поскольку POP2 тесно с ним связан. Набор и структура команд POP2 параллельны набору и структуре команд SMTP. POP3, однако, разработан с учетом специфики доставки почты на персональные компьютеры и соответствующих операций. Зачем это нужно? В старые добрые времена почтовые сообщения большинства сетей доставлялись непосредственно от одного компьютера к другому. К сожалению, если пользователь часто менял рабочие компьютеры или один компьютер принадлежал нескольким пользователям, существовали определенные проблемы. В наши дни большинство систем электронной почты доставляют сообщения не на отдельные 453 
Ешша 15- Электронная почта Интернет компьютеры, а в специальные почтовые ящики, находящиеся на каком-либо одном почтовом сервере. Теперь, для того чтобы прочитать свою почту, пользователю необходимо войти в сеть и получить доступ к почтовому серверу. Данный процесс мог испугать неискушенного пользователя, удаленный вход часто запрещается администраторами по соображениям безопасности, и, в добавок ко всему, чтобы прочесть или отослать почту, нужно было пользоваться почтовым агентом самого сервера. Специальный протокол доставки почты POP и стал альтернативой всем вышеописанным проблемам. Протокол POP3 Конструкция протокола POP3 обеспечивает возможность пользователю войти в систему и изъять накопившуюся почту, вместо того чтобы предварительно входить в сеть. Пользователь получает доступ к POP-серверу из любой системы в Интернет. При этом он должен запустить специальный почтовый агент (UA), понимающий протокол POP3. Во главе модели POP находится отдельный персональный компьютер, работающий исключительно в качестве клиента почтовой системы. В соответствии с этой моделью персональный компьютер не занимается ни доставкой ни авторизацией сообщений для других. Также сообщения доставляются клиенту по протоколу POP, а посылаются по-прежнему при помощи SMTP. То есть на компьютере пользователя существуют два отдельных агента-интерфейса к почтовой системе — доставки (POP) и отправки (SMTP). Разработчики протокола POP3 называет такую ситуацию «раздельные агенты» (split UA). Концепция раздельных агентов кратко обсуждается в спецификации POP3. В протоколе POP3 оговорены три стадии процесса получения почты: авторизация, транзакция и обновление. После того как сервер и клиент POP3 установили соединение, начинается стадия авторизации. На стадии авторизации клиент идентифицирует себя для сервера. Если авторизация прошла успешно, сервер открывает почтовый ящик клиента и начинается стадия транзакции. В ней клиент либо запрашивает у сервера информацию (например, список почтовых сообщений), либо просит его совершить определенное действие (например, выдать почтовое сообщение). Наконец, на стадии обновления сеанс связи заканчивается. В табл. 15.7 перечислены команды протокола POP3, обязательные для работающей в Интернет реализации минимальной конфигурации. Таблица 15.7. Команды протокола POP версии 3 (для минимальной конфигурации) Команда Описание USER Идентифицирует пользователя с указанным именем PASS Указывает пароль для пары клиент-сервер QUIT Закрывает ТСР-соединение STAT Сервер возвращает количество сообщений в почтовом ящике плюс размер почтового ящика 454 
Протокол Post Office Protocol (POP) Таблица 15.7 (окончание) Команда Описание LIST Сервер возвращает идентификаторы сообщений вместе с размерами сообщений (параметром команды может быть идентификатор сообщения) RETR Извлекает сообщение из почтового ящика (требуется указывать аргумент-идентификатор сообщения) DELE Отмечает сообщение для удаления (требуется указывать аргумент-идентификатор сообщения) NOOP Сервер возвращает положительный ответ, но не совершает никаких действий LAST Сервер возвращает наибольший номер сообщения из тех, к которым ранее уже обращались RSET Отменяет удаление сообщения, отмеченного ранее командой DELE В протоколе POP3 определено несколько команд, но на них дается только два ответа: +ОК (позитивный, аналогичен сообщению-подтверждению АСК) и -ERR (негативный, аналогичен сообщению «не подтверждено» NAK). Оба ответа подтверждают, что обращение к серверу произошло и что он вообще отвечает на команды. Как правило, за каждым ответом следует его содержательное словесное описание. В RFC 1225 есть образцы нескольких типичных сеансов POP3. Сейчас мы рассмотрим несколько из них, что даст возможность уловить последовательность команд в обмене между сервером и клиентом. Авторизация пользователя После того как программа установила TCP-соединение с портом протокола POP3 (официальный номер 110), необходимо послать команду USER с именем пользователя в качестве параметра. Если ответ сервера будет +ОК, нужно послать команду PASS с паролем этого пользователя: CLIENT: USER kcope SERVER: +OK CLIENT: PASS secret SERVER: +OK kcope's maildrop has 2 messages (320 octets) (В почтовом ящике kcope есть 2 сообщения (320 байтов) ...) Транзакции POP3 После того как стадия авторизации окончена, обмен переходит на стадию транзакции. В следующих примерах демонстрируется возможный обмен сооб¬ 455 
Глава 15. Электронная почта Интернет щениями на этой стадии. Команда STAT возвращает количество сообщений и количество байтов в сообщениях: CLIENT: STAT SERVER: +0К 2 320 Команда LIST (без параметра) возвращает список сообщений в почтовом ящике и их размеры, CLIENT: LIST SERVER: +ОК 2 messages (320 octets) SERVER: 1 120 SERVER: 2 200 SERVER: . ... Команда LIST с параметром возвращает информацию о заданном сообщении: CLIENT: LIST 2 SERVER: +ОК 2 200 ... CLIENT: LIST 3 SERVER: -ERR no such message, only 2 messages in maildrop Команда TOP возвращает заголовок, пустую строку и первые десять строк тела сообщения: CLIENT: ТОР 10 SERVER: +ОК SERVER: <the POP3 server sends the headers of the message, a blank line, and the first 10 lines of the message body> (сервер POP высылает заголовки сообщений, пустую строку и первые десять строк тела сообщения) SERVER: . ... CLIENT: TOP 100 SERVER: -ERR no such message Команда NOOP не возвращает никакой полезной информации, за исключением позитивного ответа сервера. Однако позитивный ответ означает, что сервер находится в соединении с клиентом и ждет запросов: CLIENT: NOOP SERVER: +ОК Следующие примеры показывают, как сервер POP3 выполняет действия. Например, команда RETR извлекает сообщение с указанным номером и помещает его в буфер местного UA: 456 
Как все это работает? CLIENT: RETR 1 SERVER: +ОК 120 octets SERVER: <the POP3 server sends the entire message here> (POP3-сервер высылает сообщение целиком) SERVER: Команда DELE отмечает сообщение, которое нужно удалить: CLIENT: DELE 1 SERVER: +ОК message 1 deleted ... (сообщение 1 удалено) CLIENT: DELE 2 SERVER: -ERR message 2 already deleted (сообщение 2 уже удалено) Команда RSET снимает метки удаления со всех отмеченных ранее сообщений: CLIENT: RSET SERVER: +ОК maildrop has 2 messages (320 octets) (в почтовом ящике 2 сообщения (320 байтов)) Как и следовало ожидать, команда QUIT закрывает соединение с сервером: CLIENT: QUIT SERVER: +ОК dewey POP3 server signing off CLIENT: QUIT SERVER: +OK dewey POP3 server signing off (maildrop empty) CLIENT: QUIT SERVER: +OK dewey POP3 server signing off (2 messages left) ... Обратите внимание на то, что отмеченные для удаления сообщения на самом деле не удаляются до тех пор, пока не выдана команда QUIT и не началась стадия обновления. В любой момент в течение сеанса клиент имеет возможность выдать команду RSET, и все отмеченные для удаления сообщения будут восстановлены. Как все это работает? Несмотря на то, что в этой главе вы изучили большое количество нового материала, с точки зрения написания программ SMTP для среды Winsock, здесь нет почти ничего неожиданного или неизвестного. Однако мы немного подробнее рассмотрим технику подачи командных последовательностей учебными программами. SMTP и POP3 относятся к протоколам пошаговой обработки. Другими словами, после того как SMTP или РОРЗ-клиент установят соединение, они 457 
Глава 15- Электронная почта Интернет подают команду и ожидают ответа от сервера. Как только ответ получен, процесс повторяется снова: выдача команды — ожидание ответа — получение ответа. Вы знаете, что протокол SMTP предназначен для передачи сообщений, а POP3 — для получения сообщений. Чтобы наши учебные программы были как можно проще, мы разнесли функции клиентов SMTP и POP3 по двум отдельным модулям: QSMTP и QPOP3. Ключевой момент в успешной эксплуатации учебных программ — знать, какую команду и когда можно подавать. В учебных программах QSMTP и QPOP3 все возможные команды занесены в массив строк. Каждая программа пользуется собственным массивом. Массив вначале объявляется, а затем инициализируется соответствующими командами. Ниже приведен массив с командами QSMTP: char *MailMessage[] = { "HELO your.host\r\n", "MAIL FROM:<userid@yourhost.domain>\r\n", "RCPT TO:<recipient@computer.host>\r\n", "DATA\r\n", "This is my message to someone.\r\n\r\n.\r\n", "QUIT\r\n"f NULL }; В следующем примере объявляется и инициализируется массив команд для QPOP3. Он отличается от предыдущего только набором команд: char *POPMessage[] = { "USER your_mailbox_id\r\n", "PASS your_password\r\n", "STAT\r\n"f "LIST\r\n", "RETR l\r\n", "DELE l\r\n"f "QUIT\r\n"f NULL } ; Обратите внимание на то, что последние элементы массивов POPMessage[] и MailMessage[] равны NULL. Это абсолютно необходимо. Как показано ниже, учебные программы используют цикл do-while, выдавая команды по очереди до тех пор, пока не встретится NULL. Другими словами, экспериментируя с программами, не замените случайно NULL на какой-нибудь другой указатель, поскольку NULL требуется для нормальной работы цикла. Приведенные ниже операторы демонстрируют немного сокращенный вариант цикла по обработке сообщений (в нем отсутствуют проверки ошибок). 458 
Как все ato работает? int iLength; int iMsg = 0; int iEnd = 0; BYTE sReceiveBuffer[4096] ; do { send(hSocket, (LPSTR)POPMessage[iMsg], strlen(POPMessage[iMsg]), NO_FLAGS); iLength = recv(hSocket, (LPSTR)sReceiveBuffer+iEnd, sizeof (sReceiveBuffer) -iEnd, NO__FLAGS) ; iEnd += iLength; sReceiveBuffer[iEnd] = '\0'; MessageBox(NULL, (LPSTR)sReceiveBuffer, (LPSTR)POPMessage[iMsg], MB_OKIMB_ICONSTOP); iMsg++; } while (POPMessage[iMsg]); Функция send передает команду — содержимое строки POPMessage. Затем вызывается функция recv и ожидается ответ сервера. После того как ответное сообщение получено, буфер приемника ограничивается символом NULL и результат попадает в окно Windows. Переменные iEnd и iLength следят за содержимым буфера приемника. Как уже отмечалось, команды выдаются последовательно до тех пор, пока цикл не наткнется на значение NULL в массиве командных строк. Конструкция do-while программы QSMTP идентична конструкции в программе QPOP3, за исключением того, что массивы команд называются по-разному (MailMessage вместо POPMessage). Если вам понадобится изменить последовательность или добавить новые команды, программы придется компилировать еще раз. Разумеется, к ним можно добавить интерфейс пользователя, что сделает программы более дружелюбными, а эксперимент — более гибким. Интерфейс можно взять, например, из программы Sockman. Конечно, при этом понадобится обрабатывать сообщения Windows и делать прочие вещи, обычные для любой другой Windows-программы. Тексты учебных программ были сознательно упрощены с тем, чтобы сделать процесс взаимодействия с сетевой средой как можно более очевидным. Как мы уже отмечали, обе программы практически идентичны, за исключением некоторых деталей. В каждой определены три функции: WinMain, функция, соединяющая сокет с сервером (аналогичная уже известной вам функции ConnectTimerServerSocket из главы 13), и функция, посылающая и принимающая сетевые данные через сокет. Для получения почтового сообщения в QPOP3 существует функция GetMail. Для посылки почтового сообщения в QSMTP применяется функция SendMail. Исходные тексты программ QSMTP и QPOP3 находятся на приложенной к книге дискете. 459 
Глава 15. Электронная почта Интернет Подводя итоги Прочитав эту главу, вы узнали об электронной почте вообще и о системе электронной почты Интернет в частности. Вы узнали, как агенты передачи почты (МТА) работают по протоколу SMTP и как системные администраторы настраивают их для работы промежуточными агентами. Вы узнали, как два протокола пользуются одинаковыми схемами подачи команд и одинаковыми схемами выдачи ответов. В следующей главе вы узнаете, что протокол передачи файлов (FTP) тоже строится на концепциях протоколов SMTP и POP, за исключением некоторых специфических деталей. Если вы разобрались в принципах работы SMTP и POP3, изучение FTP не будет обременительным. До того как продолжить чтение, проверьте хорошо ли вы усвоили следующие важные понятия: S Система электронной почты Интернет состоит в первую очередь из агентов пользователя, агентов передачи почты и почтовых ящиков. S Агенты пользователя создают для него «дружелюбный» интерфейс к системе электронной почты. S Агенты передачи почты (МТА) перемещают почту между сетевыми компьютерами. S Промежуточный агент (relay agent) является агентом передачи почты, настроенным для сбора почты от нескольких местных МТА и передачи ее в Интернет (и обратно). S Способ кодирования Base-64 предназначен для передачи 8-битных двоичных данных по 7-битной транспортной почтовой системе Интернет. S Протокол POP (Post Office Protocol) дает возможность пользователю, оснащенному соответствующим агентом, читать сообщения в своем почтовом ящике с любой системы, подключенной к Интернет. 
Глав " : г. . Л *■ • I щр . Протоколы передачи файлов Хотя электронная почта, вероятно — самое широко используемое приложение Интернет, протокол передачи файлов (File Transfer Protocol, FTP) несет наибольшую нагрузку по передаче данных. Вы, наверное, знаете, что файлы передаются от одного компьютера к другому при помощи программы Ftp. В большинстве случаев до того, как получить доступ к файлам, нужно зарегистрироваться в системе. Чтобы облегчить процедуру получения доступа к файлам, многие системы разрешают анонимный доступ для пользователей по имени anonymous. С анонимным FTP пользователь может войти в систему FTP-сервера и передавать файлы не имея зарегистрированных прав доступа в системе. Другими словами, анонимный FTP позволяет людям свободно получать файлы данных из Интернет. Возможность получать файлы анонимно делает Ftp одной из наиболее популярных программ в Интернет. Протокол FTP управляет большинством операций по передаче файлов в Интернет. Эта глава обсуждает структуру и работу FTP. Как вы узнаете, для выполнения передачи файлов требуется установить два TCP-соединения, в отличие от других протоколов, обсуждавшихся в этой книге, для которых 461 
Глава 16* Протоколы необходимо только одно TCP-соединение. Одно TCP-соединение служит для передачи команд и ответов на них так же, как в POP3 или SMTP, а другое — для передачи собственно данных. К тому времени, когда вы закончите читать эту главу, вы овладеете следующими ключевыми концепциями: ♦ Какие команды FTP используются для передачи файла. ♦ Как FTP использует два соединения TCP, чтобы управлять передачей файлов. ♦ Как TELNET и FTP управляют TCP-сообщениями с данными для неотложной обработки. ♦ Как FTP использует протокол TELNET, чтобы отменить операцию передачи файла. Основы FTP Вам, как программисту, протокол передачи файлов может быть интересен по нескольким причинам. Одна из них — его необычайная популярность. Хороший прикладной программист должен уметь не только разработать FTP-приложение, но и любое приложение, умеющее обмениваться файлами. Поэтому вам нужно знать принципы передачи файлов в сети. FTP похож на протоколы SMTP и POP3, в которых и команды и ответы на них передаются в формате строк NVT ASCII. То есть если вы читали главу 15, обмен FTP-командами должен показаться уже знакомым. При обмене командами используется принцип последовательного выполнения (пошлите команду и ждите ответа; только после можно посылать следующую). Однако в отличие от «почтовых» протоколов, FTP открывает два TCP-соединения — одно для команд, а другое для данных. Вам необходимо научиться манипулировать одновременно несколькими открытыми сетевыми соединениями — это ключ к созданию чрезвычайно сложных и эффективных сетевых приложений. Интерактивный сеанс FTP Интернет привлекает большое количество пользователей, в частности, потому, что предлагает им возможность легко получать информацию, программы, различную документацию и другие полезные ресурсы. Для получения файлов, хранящихся на различных компьютерах Интернет, пользователи сети прежде всего используют программы, основанные на протоколе FTP. Среди большого количества программ-клиентов FTP клиент FTP с интерфейсом командной строки наиболее популярен. Вы будете сталкиваться с программами-клиентами FTP, работающими с командной строкой в большинстве операционных систем UNIX (а также подобных системах). Сеанс с программой FTP, общающейся с пользователем при помощи командной строки, может помочь вам лучше понять принципы работы протокола передачи файлов, особенно если вы никогда не пользовались им ранее. Если вы не знакомы с FTP, попробуйте войти на FTP-сервер, получить строку-приглашение и пройти весь путь через интерактивный FTP-сеанс, как описано в следующих абзацах. Большинство поставщиков услуг Интернет (работающих в системе UNIX) обеспечивают доступ к командной строкеприглашению через командный процессор (интерпретатор команд) UNIX типа csh (C-shell). На большинстве компьютеров Интернет, чтобы запустить FTP-клиента, вы просто печатаете ftp после приглашения к вводу команды. Чтобы указать, что вы начали сеанс FTP, компьютер изменит вид командной строки, включив в него символы f-t-р, как показано ниже: 462 
Неновы FTP ftp> Увидев приглашение FTP, вы можете напечатать команды, которые сообщают программеклиенту, какое действие она должна исполнить. Например, чтобы передать файлы с сетевого компьютера, с ним необходимо установить соединение. Чтобы программа-клиент FTP установила TCP-соединение, вы должны напечатать команду open, сопровождаемую именем требуемого компьютера, как показано ниже: ftp> open nic.ddn.mil <Enter> После приглашения FTP, выданного вашим компьютером, напечатайте команду open, показанную выше, и нажмите Enter. Клиент FTP, в свою очередь, попробует установить TCP-соединение с хостом nic.ddn.mil DOD Network Information Center. (Этот FTP-сервер интересен потому, что в нем содержится архив документов Request for Comments (RFC), к которому вы могли бы обратиться.) В ответ на запрос соединения большинство серверов FTP отвечают выводом подсказки, которая сообщает вам, как войти в систему. Например, если вы напечатаете open nic.ddn.mil после приглашения FTP (в предположении, что соединение успешно установлено), вы должны увидеть ответ, подобный показанному ниже: Connected to nic.ddn.mil. 220-*****Welcome to the DOD Network Information Center***** *****Login with username "anonymous" and password "guest" *****You may change directories to the following: ddn-news - DDN Management Bulletins domain - Root Zone Files gosip - DOD GOSIP Registration and Information internet-drafts - Internet Drafts netinfo - NIC Information Files rfc - RFC Repository see - DDN Security Bulletins std - Internet Protocol Standards 220 and more! Name (nic.ddn.mil:happy): Как видим, сервер в ответ на ваш запрос соединения передает сообщение, которое подсказывает вам, как войти в систему с использованием имени пользователя anonymous и пароля guest. FTP-сервер также показывает приглашение для входа в систему. Как в нем указано, наберите слово anonymous в приглашении к вводу имени пользователя и нажмите Enter. В ответ сервер, вероятно, передаст другое сообщение, подобное показанному здесь: 331 Guest login ok, send "guest" as password. Password: Другими словами, в ответ на имя пользователя сервер передает другое информационное сообщение, которое включает напоминание об использовании слова guest в качестве пароля. Напечатайте guest и нажмите Enter. Если вы успешно вошли в систему, сервер передаст другое сообщение, которое должно выглядеть примерно так: 230 Guest login ok, access restrictions apply, f tp> После получения сообщения «login ok» клиент снова будет показывать приглашение FTP. Этот образец команды клиента, сопровождаемой ответом сервера, представляет собой общую картину коммуникационного потока клиент-сервер для всех FTP-сеансов. После того как вы войдете в систему, вы можете передавать сообщения, которые заставляют сервер изменять рабочие каталоги, выдавать списки файлов, содержащихся в каталоге, и передавать файлы. Например, для сообщения серверу, чтобы он изменил рабочий каталог на каталог rfc (где находятся все документы RFC), надо ввести команду cd: ftp> cd rfc <Enter> 463 
Глава 16. Протоколы передачи файлов FTP-сервер изменит рабочий каталог на rfc. Чтобы сообщить клиенту, что он должен получить самый свежий список документов For Your Information (К вашему сведению, FYI), введите команду get: ftp> get fyi-index.txt <Enter> 200 PORT command successful. 150 Opening ASCII mode data connection for fyi-index.txt (8584 bytes). 226 Transfer complete. local: fyi-index.txt remote: fyi-index.txt 8765 bytes received in 0.77 seconds (11 Kbytes/s) Клиент и сервер FTP во время передачи файла будут показывать сообщения, информирующие о ходе операции. После того как передача файла закончится, вы можете закрыть ТСРсоединение, напечатав команду close (сервер обычно отвечает сообщением «Goodbye») и закончить сеанс FTP, напечатав quit: ftp> close <Enter> 221 Goodbye. ftp> quit <Enter> Что такое протоколы передачи файлов? Хотя FTP — наиболее широко известный протокол передачи файлов на Интернет, вам должно быть известно, что существуют и другие протоколы тоже для передачи файлов. Фактически, три протокола передачи файлов (и одна программа передачи файлов) достойны упоминания. Документ RFC 959, «Протокол передачи файлов» (File Transfer Protocol, FTP, Reynolds, 1985), определяет протокол, который используется большинством пользователей и на котором мы сосредоточим внимание в этой главе. В следующих разделах кратко описываются остальные протоколы. Простой протокол передачи файлов RFC 783, «Протокол TFTP версии 2» (The TFTP Protocol, Sollins, 1981), определяет простой протокол передачи файлов. TFTP умышленно опускает большинство возможностей FTP и вместо этого узко специализируется на выполнении двух операций передачи файлов: чтение и запись файла. Чтобы исполнять эти операции, TFTP использует протокол пользовательских датаграмм (UDP). В отличие от протокола FTP, TFTP не показывает список файлов каталога и не проверяет права пользователей. TFTP использует систему подтверждений, чтобы гарантировать доставку данных между TFTP-сервером и TFTP-клиентом. Действие TFTP начинается датаграммой UDP, которая запрашивает передачу файла. Если сервер принимает запрос, он посылает требуемый файл блоками длиной 512 байт. Сервер ждет подтверждения принятия клиентом каждого блока данных прежде, чем передать следующий. (Обратите внимание, что такое выполнение гарантирует для передаваемого файла получение блоков в правильном порядке). Как сигнал завершения передачи, TFTP-сервер посылает UDP-датаграмму с длиной меньшей, чем 512 байтов. Другими словами, заключительная датаграмма 464 
Что такое протоколы переда*»* файлов? i UDP будет содержать последние несколько байтов данных файла. Клиент TFTP инициирует операцию передачи файла и после этого просто циклически получает от сервера по 512 байтов запрошенного файла за один раз. Когда сервер посылает UDP-датаграмму, которая содержит меньше, чем 512 байтов, клиент понимает, что это конец файла. Хотя по своим способностям протокол TFTP не очень быстр или устойчив, он очень маленький и легок в реализации — это два критерия его разработки, заявленных в спецификации (RFC 783). Подобно простому протоколу передачи почты (SMTP) TFTP кажется нежизнеспособным, из-за простоты и очевидных ограничений. Однако в 1984 году Росс Финлейсон (Ross Finlayson) из Стенфордского университета предложил интересную возможность для использования TFTP. В RFC 906, «Загрузка при помощи TFTP» (Bootstrap Loading using TFTP, Finlayson, 1984), он предложил использовать TFTP для передачи загрузчиков операционной системы по сети. Загрузчик операционной системы— мини-программа, которая выполняется каждый раз при включении компьютера. Загрузчик помещает необходимые части операционной системы в память компьютера так, чтобы он, в свою очередь, мог завершить процесс загрузки. Например, бездисковая рабочая станция должна загрузить один или несколько файлов через сеть, чтобы начать работу. Различные изготовители используют различные методы загрузки бездисковых рабочих станций. Как правило, бездисковая рабочая станция считывает записанные в ROM (постоянное запоминающее устройство, ПЗУ) управляющие инструкции и выдает сетевой запрос серверу, инициируя загрузку операционной системы по сети. Финлейсон и другие столкнулись с проблемой, заключающейся в том, что каждое новое решение изготовителя компьютеров по передаче загрузчика через сеть требовало нового типа сетевого сервера из-за постоянно меняющихся реализаций. В RFC 906 Финлейсон предложил установить TFTP как стандарт Интернет для выполнения задачи копирования загрузчиков по сети для бездисковых рабочих станций. Для вас не должно оказаться неожиданным, что Финлейсон был также одним из авторов RFC 903, Протокол обратного преобразования адресов (Reverse Address Resolution Protocol, Finlayson at al, 1984). Как вы можете вспомнить из четвертой главы, этот протокол (RARP) также направлен на решение проблем бездисковых рабочих станций. RARP отображает адрес уровня соединения, например адрес Ethernet, в IP-адрес. Бездисковая рабочая станция может считать свой адрес уровня соединения с собственной сетевой карты (адаптера). Тогда, используя RARP, станция передает широковещательный запрос, чтобы другой сетевой компьютер (сервер бездисковой загрузки) нашел и сообщил правильный IP-адрес по известному адресу уровня соединения. Подробнее о простом протоколе передачи файлов Как предварительно упоминалось, FTP использует два TCP-соединения для выполнения операций по передаче файлов; одно соединение для передачи команд и другое — для передачи собственно данных. С другой стороны, TFTP 465 
Глава 16. Протоколы передачи файлов использует для передачи файлов UDP-датаграммы. Протокол Simple File Transfer Protocol (RFC 913, Simple File Transfer Protocol, Lottor, 1984) является попыткой найти золотую середину между FTP и TFTP. SFTP поддерживает проверку прав пользователя (контроль доступа), передачу файлов, списки содержимого каталогов, изменение каталогов, переименование и удаление файлов. Подобно FTP, SFTP использует TCP. Однако в отличие от FTP, SFTP использует только одно TCP-соединение. Команды, коды возврата и эксплуатационные особенности SFTP и FTP сильно схожи. К сожалению, мы не смогли создать учебную программу, основанную на SFTP, потому что мы не смогли найти компьютер Интернет, являющийся SFTP-сервером. SFTP никогда не достигал популярности своего кузена, простого протокола передачи почты. В то время как серверы SMTP доминируют в системах электронной почты Интернет, FTP доминирует в системах передачи файлов Интернет. Таким образом, в этой книге все учебные программы основываются на FTP. Модель FTP Как уже говорилось, протокол передачи файлов подобен SMTP и POP3 — в нем используются строки ASCII в качестве команд и кодов ответа. Однако в отличие от всех предыдущих протоколов, обсуждаемых в этой книге, FTP использует два TCP-соединения для выполнения операций по передаче файлов. В FTP два TCP-соединения определяются как управляющее соединение и соединение данных. Управляющее соединение подобно другим TCP-соединениям, которые вы создавали в предыдущих главах. Другими словами, управляющее соединение — типичное соединение клиент-сервер. Сервер FTP обеспечивает пассивное открытие на официальном порту (порт протокола 21) и ждет запроса на установление соединения от клиента. Клиент FTP, в свою очередь, входит в контакт с FTP-сервером на официальном порту протокола и устанавливает с ним ТСР-соединение (как обсуждалось в главе 5). Управляющее соединение остается активным на протяжении всего FTP-сеанса. Клиент и сервер обмениваются строками команд NVT ASCII и кодами ответа через управляющее соединение. FTP создает отдельное соединение данных для каждой операции по передаче файла (а также в некоторых других случаях). На рис. 16.1 изображена типичная конфигурация для операций по передаче файлов. Основа операции — интерпретаторы протокола (PI) и процессы передачи данных (DTP). Как видим, клиент и сервер имеют свой собственный интерпретатор протокола и процесс передачи данных. Процессы передачи данных устанавливают и управляют соединением данных. Интерпретаторы протокола интерпретируют FTP-команды и общаются через управляющее соединение, которое устанавливается в начале FTP-сеанса интерфейсом протокола пользователя. Интерфейс пользователя ограждает пользователя от непосредственного общения с командами и ответами FTP. Программы Ftp вообще бывают двух видов: 466 
Программа пользователя Клиент Пользовательский интерфейс Ш$&дачи данных. и пшьзовршня (Команды и ответы) Соединение данных (Передача файлов) Щодепь Управляющее соединение Рис. 16.1. Типичная конфигурация для операций по передаче файлов VB Sockman FTP Host: USER ID: NIC.DDN.MIL anonymous tt Internet Programming ’ SockMae FTP PASSWORD: System Type: guest ШШШ Working Dir: 1 if 1 Itel '■ *| ;I;‘13* fog Щ-:... | 1 ® IMAGE (Binaiy) О ASCII Puc. 16.2. Windows-сеанс Ftp 467 
Глава ft 6* Протоколы передачи файлое полноэкранные (ориентированные на работу с меню) или строчные (ориентированные на работу с командной строкой). Большинство UNIX-программ Ftp — строчные. Другими словами, пользователь UNIX выполняет программу Ftp (обычно и называемую Ftp) и вводит команды в строке приглашения FTP, подобном приглашению DOS на персональном компьютере. Однако в большинстве случаев команды, напечатанные в командной строке, фактически не являются командами FTP. Вместо этого программа интерфейса пользователя грамматически разбирает команду, введенную пользователем, и переводит запросы в команды FTP. Например, пользователь мог бы напечатать «get» вместо «RETR» и «put» вместо «STOR». Большинство Windows-программ Ftp ориентировано на меню и диалоговые окна. Во многих случаях интерфейс пользователя в Windows-программе Ftp будет представлен окном диалога с кнопками команд, окнами-списками и полосами прокрутки, как это показано на рис. 16.2. Управление данными С самого начала проектировщики протокола FTP разрабатывали его для работы с различными компьютерами, использующими различные операционные системы, структуры файлов и наборы символов. В результате FTP требует, чтобы пользователи выбрали необходимые пункты из широкого разнообразия опций для операций по передаче файла. Опции FTP можно разбить на четыре категории: тип файла, формат файла, структура файла и способы передачи. Следующие части описывают каждую из этих опций. Что означает тип файла в FTP? FTP может управлять четырьмя различными типами файлов: локальными, файлами изображений (или двоичными), EBCDIC и ASCII. Локальный тип файла предназначен для передачи файла между хостами, которые используют различные размеры байта. Как вы помните из обсуждения термина октет в третьей главе, много ранних разработок TCP/IP созданы на компьютерных системах, где байт не равнялся восьми битам. Локальный тип файла — пережиток тех дней. Другими словами, локальный тип файла позволяет данным пользователя с компьютера, использующего 8 бит на байт, передаваться компьютеру, использующему другой размер байта (7 или 10 бит, например). Для системы, которая использует байты из 8 бит, локальный тип файла является идентичным типу файла «изображения» (двоичному файлу). Так как современные компьютеры используют байты, состоящие из 8 бит, локальный тип файла сегодня не слишком часто используется. Файл типа «изображение» (или двоичный тип файла) передается как непрерывный поток данных. Другими словами, передача файла изображения не включает (и не идентифицирует) никаких границ во внутренней структуре файла данных 468 
V г. равл&ние данными (типа возврата каретки или перевода строки). Обычно пользователи FTP передают большинство файлов именно в этом режиме. Как вы узнали, большинство компьютеров для представления текстовых данных используют коды ASCII. Однако некоторые системы, типа универсальных ЭВМ фирмы IBM (мэйнфреймов) и мини-компьютеров, используют код EBCDIC. EBCDIC (произносится И-би-си-дик) расшифровывается как Extended Binary Coded Decimal Interchange Code. Хотя и EBCDIC и ASCII используют 8 битов для представления символа, их коды, соответствующие одинаковым символам, совершенно различны. Таким образом, компьютер, который понимает код EBCDIC, не будет понимать код ASCII (хотя существует большое количество программ, которые могут переводить из одной кодировки в другую). Передача файла с типом EBCDIC в FTP — альтернативный метод передачи файла для двух компьютеров, которые используют кодировку EBCDIC. Другими словами, если хосты на каждом конце соединения FTP используют EBCDIC, они могут использовать тип файла EBCDIC, чтобы упростить передачу текстовых файлов. Передачи файла с типом ASCII — принимаемый по умолчанию тип для передачи файлов по FTP. Чтобы использовать передачу файла ASCII, посылающий хост должен преобразовать локальный текстовый файл в NVT ASCII (ASCII с 7 битами на символ). Хост-получатель должен перевести NVT ASCII к собственному соглашению для хранения текста. Основная проблема с передачей файла ASCII — маркеры конца строки (end-ofline). Как вы узнали, маркер конца строки у различных компьютеров отличается от соглашения NVT ASCII, согласно которому конец строки отмечается парой символов CRLF (перевод строки, возврат каретки). Таким образом, хосты, использующие другие идентификаторы конца строки, должны просмотреть каждый байт поступающих данных, чтобы идентифицировать концы строк текста. Очевидно, такое требование добавляет работы по обработке данных программе-приемнику. Другими словами, приемник, который использует маркеры CRLF (то же соглашение, что используется при передаче файлов типа ASCII), может просто читать данные из очереди поступающих данных и записывать их в локальный файл. С другой стороны, приемник, который использует другое соглашение, должен исследовать каждую пару байтов в очереди поступающих данных, чтобы определить, когда программа должна заменить CRLF собственным маркером конца строки (иногда им бывает единственный символ перевода строки, LF). Что такое формат файла в FTP? Как сказано в предыдущей части, пользователь FTP может выбирать, как передавать файлы — с типом ASCII или с типом EBCDIC. Когда пользователь определяет ASCII или EBCDIC как тип файла для передачи, он должен также определить используемое управление форматом. FTP определяет три типа управления форматом: Nonprint, Telnet и FORTRAN. Для текстовых файлов используемое по умолчанию управление форматом — Nonprint, которое означа- 469 
Глава 16. Протоколы передачи файлов ет, что файл не содержит никакой информации о вертикальном формате типа вертикальной прокрутки страницы, которую принтер мог бы использовать для правильного расположения текста на бумаге. Управление форматом Telnet, напротив, использует управление вертикальным форматом для принтеров. Управление форматом Telnet — это символьные последовательности, которые сообщают принтеру, как печатать текст. FORTRAN (язык программирования) также использует специальные, вложенные в текст символы. Управление форматом FORTRAN означает, что первый символ каждой строки — символ управления FORTRAN, который, в свою очередь, определяет формат строки. Управление форматом Telnet и управление форматом FORTRAN — пережиток ранних дней Интернет. Сегодня большинство реализаций FTP (особенно на UNIX-системах) ограничиваются управлением форматом Nonprint. Режимы передачи FTP Заключительный параметр для передачи файла по FTP, который должен быть определен пользователем, — режим передачи. Режим передачи определяет, как FTP передает файл через TCP-соединение. FTP определяет три режима передачи: блочный, со сжатием и потоковый. В блочном режиме файл передается как последовательность блоков, где каждый блок включает один или больше байтов заголовка. FTP посылает блоки данных файла в той же последовательности, в которой они находятся в файле. Заголовок в каждом блоке данных определяет размер блока данных (который может изменяться), а также дескрипторы, которые идентифицируют конец файла или конец записи (если таковые имеются). В режиме со сжатием простой алгоритм сжимает последовательные возникновения одного и того же байта — они кодируются специальным символом, сопровождаемым количеством одинаковых символов в последовательности. Например, предположим, что файл, который вы хотите передать, — сильно прокомментированный файл исходного кода. Предположим, что вы разграничили каждое определение функции строкой, состоящей из прямых слэшей. Другими словами, в начале и конце каждой функции ваш файл включает ряд из 70 прямых слэшей (обозначение комментария в C++): ////////////////////////////////////////////////////////////////////// void main (void) { return(0) } ////////////////////////////////////////////////////////////////////// Алгоритм сжатия заменил бы последовательные слэши тремя символами: символом {}, числом и передним слэшем. Например, в выражении {}70/, символ {} отмечает начало закодированной последовательности, число 70 определяет количество повторяющихся символов и прямой слэш определяет, из каких символов состоит повторяющийся участок. Хотя практические реализации алгоритма сжатия различны, в этом примере иллюстрируется его основная идея. 470 
F v u;.^« >СИТСЯ : . Алгоритм сжатия прежде всего упаковал бы пробелы в текстовом файле и строки нулевых байтов в двоичном файле. Пользователи FTP редко используют режим передачи со сжатием, поскольку немногие реализации FTP его поддерживают. Пользователи предпочитают использовать более мощные алгоритмы сжатия, нежели описанный примитивный алгоритм сжатия данных «на лету». В потоковом режиме FTP передает файл как непрерывный поток байтов. Если тип структуры FTP — структура с записями, FTP использует специальную пвухбайтовую последовательность символов, чтобы отметить конец записи и конец файла. Если FTP использует файловую структуру, конец файла отмечается закрытием TCP-соединения. Другими словами, после того как передан последний байт файла, FTP закрывает соединение данных. Приемник понимает, -пго это означает, что последние принятые байты являются последними байтами переданного файла. Выбор невелик Вообще большинство реализаций FTP поддерживают только управляющий Ьормат Nonprint, файловую структуру и потоковый режим передачи. Другими :ловами, единственный выбор, который пользователь обычно должен сделать, — использовать ли ему текстовый ASCII или двоичный тип для передачи Ьайла. Как FTP соотносится с TELNET? Подобно некоторым другим прикладным протоколам (типа SMTP и POP3) протокол FTP зависит от определения протокола TELNET для некоторых шераций. В дополнение к использованию строк текста ASCII, чтобы передавать команды (как SMTP и POP3), FTP использует коды управления TELNET для передачи сигнала о существования данных для неотложной обработки. Например, всякий раз, когда FTP-клиент хочет прервать передачу файла, он передает глгнал TELNET Synch, чтобы попросить сервер остановить передачу файла. Другими словами, FTP использует протокол TELNET на управляющем соединении, чтобы управлять действиями по передаче файла. Как упоминалось, FTP пределяет собственные строки команд ASCII (подобно используемым в SMTP н POP3). Хотя FTP зависит от протокола TELNET, он использует лишь небольшое подмножество его команд. Чтобы понимать определенные операции типа отмены передачи файла) в FTP, вам нужно понимать принципы построена протокола TELNET. Фактически, спецификация FTP (RFC 959) явно предполагает знание протокола управления транспортировкой (TCP) и протокола TELNET. Вы поймете, зачем вам нужно разбираться в протоколе TELNET нэсле того, как прочитаете следующие разделы, в которых содержится краткий обзор протокола TELNET и в которых сигнал Synch TELNET обсуждается более подробно. 471 
Глава 16. Протоколы передачи файлов Использование TELNET для тестирования других протоколов Как вы наверное знаете, с помощью программы-клиента TELNET можно войти в систему удаленного компьютера, как если бы ваш компьютер был терминалом того. Вы сообщаете клиенту имя (адрес) удаленного компьютера, к которому вы хотите присоединиться. Клиент, в свою очередь, установит TCP-соединение с портом 23 (официальный порт TELNET). Тогда удаленный компьютер покажет приглашение для входа в систему, в котором вы можете напечатать имя пользователя. В зависимости от сервера TELNET, с которым вы соединяетесь, вы можете также получить приглашение для ввода пароля. Тысячи специальных серверов TELNET существуют повсюду в Интернет. Эти специальные серверы обеспечивают свободный доступ к любой информации, начиная с сельского хозяйства и заканчивая сводками погоды. Большое количество специальных серверов TELNET рекламируют имя пользователя и пароль для свободного входа в систему. Другими словами, подобно анонимным FTP-серверам в Интернет существует большое количество общедоступных серверов TELNET. Различие между ними состоит в том, что серверы TELNET не имеют стандартного имени или пароля для анонимного доступа. Клиент TELNET может устанавливать TCP-соединение с любым портом протокола фактически на любом сетевом компьютере Интернет. Как программист, вы можете использовать эту способность клиента TELNET, чтобы проверить и протестировать работу других протоколов. Как обсуждалось в другом месте этой книги, большинство протоколов используют ASCII-строки формата TELNET для передачи данных. Как вы узнали, большое количество прикладных протоколов типа SMTP и POP3 также используют символы ASCII для передачи команд. Например, вы можете использовать клиента TELNET, чтобы соединиться с портом 79 какого-нибудь компьютера и посмотреть, как работает протокол информации о пользователях — Finger. Еще один образец использования TELNET — вы можете послать электронную почту, соединившись с портом 25 (официальный порт протокола SMTP). Если вы попробуете, то обнаружите, что можете вручную напечатать SMTP команду и просмотреть коды ответа от сервера SMTP. При помощи клиента TELNET вы можете получить вашу собственную электронную почту, соединившись с портом 110 (официальный порт протокола POP3) почтового компьютера. Конечно, в любом случае вы должны знать, какой протокол в данный момент работает и какой синтаксис команд он использует. Клиента TELNET можно использовать для того, чтобы проверить собственное понимание принципов работы протокола до того, как написать программу, построенную на этом протоколе. Основные концепции программирования TELNET обсуждаются в этой книге в контексте других прикладных протоколов, которые используют принципы или определения TELNET. Например, как упоминалось, один существенный компонент протокола удаленного входа в систему — универсальный, понятный для всех формат данных. Вы уже сталкивались с ним и часто использовали этот формат в предыдущих главах этой книги. Это формат данных — NVT ASCII. Как мы уже говорили, протокол TELNET определяет набор NVT ASCII. Независимо от типа компьютера (или терминала), каждый из них должен выполнять определенные действия с экраном, например стирать строки или весь экран. Для каждого типа терминала существуют управляющие последовательности (типа Esc) или управляющие комбинации клавиш (типа Ctrl-C). Операционная система использует их, чтобы закончить или прервать текущую программу. Как мы уже говорили, протокол TELNET использует протокол виртуального терминала, чтобы сделать разночтения управляющих команд между терминалами различных изготовителей незаметными для конечного пользователя. TELNET обеспечивает эту способность, определяя специальные последовательности для таких операций. Другие основанные на TELNET программы могут интерпретировать и переводить эти последовательности в соответствующие команды сетевому компьютеру. 472 
Как FTP соотносится с TELNET? Команды TELNET RFC 854, «Спецификация протокола TELNET» (TELNET Protocol Specification, Postel and Reynolds, 1983), определяет протокол TELNET, включая коды травления, используемые программами TELNET (и протоколами, которые зависят от команд TELNET). Основные команды TELNET не слишком трудны -ля понимания или запоминания. Однако важная особенность протокола TELNET состоит в том, что он разрешает клиентам и серверам TELNET использовать широкое разнообразие опций. Без этой особенности протокол TELNET весьма -рост. К сожалению, выбор опций не только добавляет возможностей, но и усложняет реализацию протокола. В результате, написание клиента TELNET может превратиться в нетривиальную задачу. Как вы узнаете в следующих абзацах, несколько команд TELNET фокусируются на выборе опций. Примечание: Ряд RFC (RFC 855-861, а также некоторые другие) определяют опции протокола TELNET. Как вы узнали, большое количество протоколов Интернет (в основном, призе ладных) используют синхронный обмен командами и ответами при выполнении гетевых операций. Например, клиенты SMTP и POP3 посылают строки символов NVT ASCII, которые являются командами протокола и ждут ответа от гервера. TELNET тоже использует подобный подход. Однако в любой момент зремени любая сторона TELNET-соединения может передавать TELNET-команды (и делает это). Другими словами, в рассмотренных ранее прикладных протоколах поток команд был, по существу, направленным в одну сторону — :т клиента к серверу. TELNET позволяет потоку команд идти в обоих направлениях. Также TELNET не использует строки символов NVT ASCII для предоставления команд. Вместо этого TELNET передает команды как специально определенные управляющие escape-последовательности. У правляющая последовательность использует специальный управляющий символ (escape-символ), чтобы идентифицировать начало команды. Символ (или гимволы), следующий за управляющим символом, определяет команду. Например, на персональном компьютере обычно используемый управляющий escapeзимвол — символ ASCII с десятичным номером 27 (0x1 В). Название символа лля ASCII кода 27 и есть ESC (escape). Управляющий символ протокола TELNET называется IAC (interpret as command), то есть «далее следует •:оманда». Важная особенность кодов команд TELNET состоит в том, что они ммеют длину восемь бит. Как вы помните, одна из проблем с NVT ASCII заключается в том, что данные кодируются 7 битами (принимая во внимание, -:то сегодня большее число компьютеров используют все восемь бит для кодирования данных). Причина того, что TELNET ограничивает NVT ASCII 7 битами з том, что протокол использует восьмой бит для команд TELNET. В документе RFC 856, «Двоичная передача TELNET» (TELNET Binary Transmissions, Postel and Reynolds, 1983), описывается опция, позволяющая TELNET передавать данные с 8 битами. Однако эта возможность создавалась для передачи двоичных 473 
Глава 16. Протоколы передачи файлов данных, а не текста. Как уже говорилось, для передачи текстовых данных TELNET использует семибитный набор NVT ASCII. Десятичный код числа ASCII для управляющего символа IAC TELNET 255 (двоичный 11111111). Каждая управляющая последовательность TELNET (или команда) должна начинаться с IAC. В табл. 16.1 приведен список команд TELNET, которые выделяются символом IAC. Другими словами, команды табл. 16.1 имеют значение, только если символ IAC предшествует им. Пример управляющей последовательности — «IAC GA» (OxFF 0xF9), которая сообщает приемнику продвинуться вперед. TELNET обращается с байтом данных со значением 0xF9 как с обычными данными, если IAC (OxFF) не предшествует ему. Таблица 16.1. TELNET-команды IAC (интерпретируются как команды, если предшествующий символ — escape) Название Код Назначение EOF 236 (OxEF) Конец файла. SUSP 237 (OxED) Приостанавливают текущий процесс. ABORT 238 (OxEE) Аварийное прекращение работы. EOR 239 (OxEF) Конец записи. SE 240 (OxFO) Конец переговоров о параметрах. NOP 241 (OxFl) Нет операции. DM 242 (0xF2) Порция данных потока Synch. (Вместе с ним всегда должно следовать уведомление о наличии данных для неотложной обработки.) BRK 243 (0xF3) NVT символ break. IP 244 (0xF4) Функция прерывания процесса. AO 245 (0xF5) Функция аварийного прекращения вывода. AYT 246 (0xF6) Функция Are You There (Вы здесь?). EC 247 (0xF7) Функция удаления символа. EL 248 (0xF8) Функция удаления строки. GA 249 (0xF9) Сигнал продвижения вперед. SB 250 (OxFA) Указывает, что дальше следует параметр. (То есть согласование для уже выбранной опции.) WILL (код опции) 251 (OxFB) Указывает на желание начать выполнять указанную опцию или подтверждение, что вы уже выполняете ее. 474 
Как FTP соотносится с TELNET? Таблица 16.1 (окончание) Название Код Назначение WON’T (код опции) 252 (OxFC) Указывает на отказ выполнить или продолжать выполнять указанную опцию. DO (код опции) 253 (OxFD) Указывает запрос, который другая сторона должна исполнить, или подтверждение того, что вы ожидаете, чтобы другая сторона выполнила указанную опцию. DON’T (код опции) 254 (OxFE) Указывает требование к другой стороне остановить выполнение операции, или подтверждение того, что вы больше не ожидаете, чтобы другая сторона исполнила обозначенную опцию. IAC 255 (OxFF) Байт данных 255. (Другими словами, IAC escaре-последовательность представляет байт данных со значением 255.) TELNET использует команды WILL, WONT, DO и DON'T с параметром для согласования опций TELNET. Команда WILL сообщает, что отправитель хочет разрешить выбор опции. Команда WON'T сообщает, что отправитель хочет запретить выбор опции. Аналогично, команда DO сообщает, что отправитель хочет, чтобы приемник разрешил опцию, и команда DON'T сообщает, что отправитель хочет, чтобы приемник запретил опцию. Правила протокола TELNET позволяют любой стороне соединения принимать или отклонять запрос выбора опции. Однако правила также требуют, чтобы обе стороны приняли любой запрос на запрещение опции. Кроме того, согласование опций TELNET может следовать только по одному из шести сценариев, показанных в табл. 16.2. Таблица 16.2. Некоторые сценарии согласования опций TELNET Запрос Ответ Описание WILL DO Отправитель хочет разрешить опцию, и приемник сообщает ДА. WILL DON’T Отправитель хочет разрешить опцию, а приемник сообщает НЕТ. DO WILL Отправитель хочет, чтобы приемник разрешил опцию, и приемник отвечает ДА. DO WON’T Отправитель хочет, чтобы приемник разрешил опцию, а приемник отвечает НЕТ. WON’T DON’T Отправитель хочет запретить опцию. Приемник ДОЛЖЕН сообщить Да. DON’T WON’T Отправитель хочет, чтобы приемник запретил опцию. Приемник ДОЛЖЕН сообщить ДА. 475 
Глава Шш Протоколы передачи файлов Согласование опций ТЕШЕТ состоит из трех байтов: символа IAC, байта команды (WILL, WONT, DO или DON’T) и кода опции. ТЕШЕТ включает более чем 40 опций согласования. В этой книге мы не будем обсуждать опции ТЕШЕТ. Чтобы найти самый последний список опций ТЕШЕТ и RFC, которые определяют их, вы должны обратиться к самой последней версии RFC «Присвоенные номера» (Internet’s Assigned Numbers), выпускаемого комиссией по присвоению номеров Интернет, IANA. Сигнал Synch Как вы помните из главы 5, 16-битный указатель данных для неотложной обработки в TCP-заголовке указывает местоположение данных для неотложной обработки в области данных сегмента TCP. Цель флага URG TCP и указателя на данные для неотложной обработки состоит в том, чтобы уведомить получающий модуль TCP, что существуют некоторые срочные данные, и указать на них. Как объяснялось в главе 5, несмотря на некоторое противоречие, профессионалы сошлись на том, что указатель данных для неотложной обработки указывает на их последний байт. Мы также упоминали, что ТЕШЕТ практически всегда приводится в качестве примера приложения, для нормального функционирования которого требуется использовать данные для неотложной обработки. Примечание: Данные для неотложной обработки и режим их передачи находятся среди наименее документированных (и таким образом, наименее понятных) особенностей протокола TCP. Вероятно, одно из наиболее полных обсуждений этого предмета вы можете найти в главе 16 второго тома книги «Межсетевое взаимодействие сетей на базе TCP/IP» (Internetworking with TCP/IP, Volume 2: Design, Implementation, and Internals, Douglas E. Comer and David L. Stevens, Prentice Hall, 1994). Большинство компьютерных систем обеспечивают некоторый способ, который позволяет пользователю прервать затянувшийся процесс (типа программы, пойманной в бесконечном цикле). ТЕШЕТ для этой цели включает функции Interrupt Process (IP) и Abort Output (АО). Большинство операционных систем признают некоторый сигнал прерывания, который сообщает операционной системе, что необходимо остановить программу или процесс. Например, DOS распознает комбинацию клавиш Ctrl-C. Windows распознает комбинацию клавиш Ctrl-Alt-Del. Обе комбинации клавиш посылают сигнал прерывания операционной системе. На сетевых компьютерах механизмы контроля потока могут буферизовать сигнал прерывания. Например, сигнал прерывания, переданный другому хосту, может задерживаться в буфере отправляемых данных из-за увеличения трафика сети. Как вы узнали, TCP использует дуплексный способ соединения. Таким образом, в то время как сигнал прерывания может еще находиться в движении, другая сторона сетевого соединения (посылающая данные пользователю) может продолжать работать. Чтобы справиться с этой проблемой, ТЕШЕТ определяет механизм синхронизации Synch. Сигнал ТЕШЕТ Synch состоит из уведомления о срочности TCP и команды ТЕШЕТ DATA MARK. Вы знаете, что уведомление 476 
Как FTP соотносится с TELNET? о срочности TCP не подчиняется управлению потоком. Также TELNET может использовать уведомление о срочности TCP, чтобы обойти управление потоком и потребовать от приемного модуля TCP немедленно обработать данные для неотложной обработки, находящиеся в сети. В следующих абзацах описывается, как TELNET обрабатывает данные для неотложной обработки. Когда прибывает кадр TCP с установленным флагом URG, это означает, что в нем содержатся данные для неотложной обработки. Приемник TCP должен немедленно уведомить о них соответствующее приложение. Другими словами, TCP не ждет, чтобы приложение обработало данные, которые уже прибыли. Вместо этого TCP переключает приложение в «срочный» режим, который означает, что прибыли данные для неотложной обработки. Обычно, когда приложение находится в срочном режиме, оно читает данные TCP до тех пор, пока не достигнет конца области данных для неотложной обработки (он отмечен указателем в заголовке кадра TCP). После того как приложение прочитает последний байт данных для неотложной обработки, TCP уведомляет приложение, что чтение окончено. Интерпретация данных для неотложной обработки Как вы помните из главы 5, указатель на данные для неотложной обработки должен указывать на их последний байт. Однако множество существующих реализаций ошибочно устанавливают его на байт, следующий за последним байтом данных для неотложной обработки. Как вы узнаете, обработка срочного режима TELNET DATA MARK делает эту несогласованность несущественной. Вы можете вспомнить, что спецификация TCP не определяет данные для неотложной обработки. Нет также определения того, как приложение должно обращаться с данными для неотложной обработки. В настоящее время в отношении обработки таких данных существуют две школы. Некоторые профессионалы предпочитают обращаться с данными для неотложной обработки, как со специальным типом данных («данные вне диапазона»). На сегодняшний день трактование данных для неотложной обработки, как данных специального типа, не пользуется особой популярностью. И в нашей книге мы не будем обсуждать такой вариант их интерпретации. Примечание: Интерпретация «данные вне диапазона» подразумевает, что протоколы должны передавать срочные данные отдельно от «нормальных» данных и быстрее, чем «нормальные» данные. На сегодняшний день множество приложений копируют интерпретацию данных для неотложной обработки с механизма обработки сигнала Synch TELNET, который использует байт команды DATA MARK. Этот принцип, названный «интерпретация DATA MARK», рассматривает срочные данные так, как обсуждается в следующих абзацах. В срочном режиме TCP приложение может читать, браковать или заносить в буфер поступающие данные до конца указателя данных для неотложной обработки. Как приложение ведет себя и обрабатывает поступающие данные для неотложной обработки — его собственное дело. Например, в срочном режиме TELNET немедленно начинает просматривать поступающий 477 
Глава ШЩяттты передачи файлов» поток данных команд TELNET и просто отказывается от любых несрочных данных. Чтобы послать сигнал Synch ТЕШЕТ, приложение посылает кадр TCP с флагом данных для неотложной обработки (URG) и DATA MARK в качестве последнего (или единственного) байта данных. DATA MARK — синхронизирующий сигнал в потоке данных, который сообщает приложению-приемнику, когда он может возобновить нормальную обработку потока данных. Другими словами, когда приемник находится в срочном режиме, сигналы DATA MARK означают конец срочной обработки. В нормальном режиме приемник не обрабатывает DATA MARK, другими словами, в нормальном режиме приемник никак не реагирует на сигнал. Если приемник получил сигнал конца данных для неотложной обработки до того, как он находит DATA MARK, спецификация ТЕШЕТ требует, чтобы приемник продолжил специальную обработку потока данных, пока он не отыщет DATA MARK. Другими словами, после того как TCP переключает приемник в срочный режим, приемник продолжает специальную обработку данных (например, прочитывая и отбрасывая их), пока он не находит DATA MARK. Спецификация ТЕШЕТ также позволяет приемнику объединять многократные уведомления о данных для неотложной обработки. Например, предположим, что приемник находит DATA MARK, но сигналы TCP говорят, что он находится в срочном режиме. (Другими словами, TCP не уведомляет приемник о том, что он достиг конца данных для неотложной обработки.) Это может означать, что TCP получил другой сигнал Synch. В таких случаях приложение-приемник должно продолжать специальную обработку потока данных, пока не найдет другой DATA MARK. В действительности, сигнал Synch ТЕШЕТ очищает канал связи (между ТЕШЕТ-передатчиком и приемником) от всех данных, кроме команд ТЕШЕТ. Спецификация ТЕШЕТ описывает, как другие протоколы могут использовать сигнал Synch для подобных целей. Например, как вы узнаете, FTP определяет команду аварийного прекращения работы (ABOR), предназначенную для того, чтобы отменить операцию передачи файла. Согласно спецификации ТЕШЕТ, приложение, которое использует сигнал Synch TELNET, должно выполнять следующие действия: 1. Послать символ IP ТЕШЕТ. 2. Послать последовательность Synch ТЕШЕТ. То есть передать DATA MARK (DM) как единственный символ в срочном режиме TCP. 3. Послать другие срочные данные, команду аварийного прекращения работы FTP (ABOR), например. 4. Послать сигнал протокола, эквивалентный DATA MARK ТЕШЕТ, если таковой существует. Как вы узнаете в дальнейшем, именно таким способом FTP прерывает процесс передачи файла. 478 
УпраЕшениь ^^емчиеьиеы Управление соединением Как вы узнали, программы-клиенты FTP используют управляющее соединение, чтобы посылать команды и получать ответы от сервера. Обычно команды передаются через управляющее соединение, запрашивая сервер исполнить некоторые связанные с файлами действия на сервере или передать информацию через соединение данных. Клиент FTP создает управляющее соединение тем же образом, что и другие клиенты, рассмотренные в этой книге. Другими словами, клиент создает сокет и соединяет его с официальным портом сервера. Клиент посылает команды серверу через управляющее соединение подобно клиентам SMTP и POP3, передающим команды своим серверам. Другими словами, в управляющем соединении для вас нет ничего нового. Вы уже встречались с ним, рассматривая предыдущие программы-клиенты этой книги. Однако соединение данных протокола FTP — дело другое. Как вы узнаете в следующем разделе, для создания соединения данных клиент FTP обязан следовать незнакомой для вас процедуре. Процедура создания соединения данных нова в смысле используемых в ней операторов. Однако вы уже знакомы со всеми используемыми в ней концепциями. Другими словами, в предыдущих главах книги вам не приходилось писать программы, создающих соединение, подобное TCP-соединению данных. Тем не менее принцип создания такого соединения должен быть хорошо понятен. Типичный сеанс FTP Типичный сеанс FTP состоит из четырех стадий. Сначала программа-клиент FTP соединяется с FTP-сервером. Второй шаг — пользователь регистрируется на FTP-сервере. Третий шаг — клиент и сервер обмениваются командами и ответными сообщениями, подобно обмену между клиентом и сервером SMTP и POP3. (Обычно некоторые команды приводят к передаче файла между сообщающимися сетевыми компьютерами.) На четвертом шаге FTP-клиент закрывает соединение с сервером. Для передачи любых данных протокол FTP пользуется только TCP-соединениями. Клиенты FTP устанавливают TCP-соединение с FTP-сервером тем же способом, что и любое другое ТСР-соединение. FTP-клиент входит в контакт с FTP-сервером по официальному порту с номером 21. Серверы FTP обычно принимают запрос на установление соединения и передают код ответа 220, как показано ниже: 220 Service ready Во многих случаях ответное сообщение 220 будет состоять из нескольких строк текста, которые сообщают пользователю, как войти в систему. После того как FTP-клиент получает код ответа 220, начинается процесс регистрации в системе (login). Обычно FTP-сервер требует, чтобы клиент передал имя пользователя и пароль. Сервер FTP высылает сообщение в ответ на каждую введенную команду. Типичный обмен FTP-команда — ответ сервера во время процесса регистрации в системе выглядит так: USER username 331 User name ok, need password PASS secret 230 User logged in После регистрации и получения доступа к системе пользователь посылает FTP-команды для изменения текущего каталога на сервере. После того как пользователь находит требуемый файл, протокол FTP требует от клиента и сервера, чтобы они установили второе ТСР-соединение данных для передачи файла. Чтобы сообщить серверу, какой порт протокола у FTP-клиента назначен на прием файла по второму TCP-соединению, клиент передает FTPкоманду PORT. Затем, чтобы начать операцию по передаче файла, клиент передает команду RETR. Сервер, в свою очередь, отвечает таким образом: 479 
Глава Ш. Протоколы передачи файлов RETR filename 150 File status okay; about to open data connection После этого FTP-сервер передает код ответа 150 и устанавливает TCP-соединение с FTPклиентом. Сервер соединяется с хостом клиента по порту протокола, указанному клиентом в FTP-команде PORT, После того как FTP-клиент установил соединение с сервером, последний начинает немедленно передавать требуемый файл. Если передача файла заканчивается успешно, сервер передает код ответа 256, как показано ниже: 226 Closing data connection, file transfer successful Клиент FTP использует подобный процесс, чтобы передать файлы от компьютера клиента на компьютер сервера. Однако вместо команды приема (RETR) клиент передает команду записи (STOR). Сервер обычно не принимает файлы от анонимного клиента. В этом случае обмен анонимного клиента и сервера выглядит так: STOR filename 550 Access denied Когда пользователь решает завершить FTP-сеанс, клиент передает команду выхода (QUIT), и сервер отвечает, как показано ниже: QUIT 221 Good-bye Установление соединения данных Основанные на протоколе FTP программы используют соединение данных для трех основных целей: • Чтобы послать список файлов или каталогов от сервера клиенту. • Чтобы послать файл от клиента серверу. • Чтобы послать файл от сервера клиенту. Серверы FTP используют соединение данных, чтобы послать списки файлов клиенту FTP. Хотя сервер мог бы использовать многострочный ответ на запрос выдачи списка файлов, соединение данных обладает парой преимуществ. Вопервых, реализация FTP может ограничивать число строк, которые могут включать многострочный ответ. Во-вторых, посылка списка файлов через соединение данных делает более легким для работающего за терминалом пользователя FTP захват и сохранение принятого списка в файл. Когда клиент или сервер использует соединение данных для передачи файлов (или другой информации, типа списка файлов), они обычно следуют процедуре, описанной ниже. Сначала клиент создает соединение данных. Так как клиент инициирует все команды, которые требуют использования соединения данных, он должен также создать само соединение. Однако помните, что клиент передает запросы через управляющее соединение, а не непосредственно через соединение данных. Такой принцип функционирования выдвигает на первый план важное различие между FTP-клиентом и предыдущими, рассмотренными в этой книге, клиентами’ Предыдущие программы-клиенты также создавали соединение. Однако в предыдущих случаях клиент выполнял активное открытие соединения. То есть он создавал сокет и затем по своей инициативе соединял его с удаленным хостом. 480 
Управление соединением В действительности, клиент и сервер обмениваются сообщениями через сокет, соединенный с сервером через официальный порт протокола. Управляющее соединение FTP остается открытым в течение всего сеанса связи клиента с сервером. Однако FTP-клиент создает и поддерживает соединение данных только на протяжении операции передачи данных. После того как операция передачи заканчивается, клиент закрывает соединение данных. Другими словами, FTP-клиент поддерживает соединение данных только на время операции передачи. Каждый раз, когда клиент должен обменяться данными с сервером (через соединение данных), он создает новое соединение. Обратите внимание на то, что передача данных FTP не происходит через официальный порт. Она происходит через порт, который выбирает сетевой компьютер клиента. Таким образом, FTP-клиент должен выполнить пассивное открытие сокета соединения данных и затем сообщить серверу, какой порт на компьютере клиента он должен использовать, чтобы установить соединение. Иначе говоря, сервер FTP не имеет никаких предположений относительно того, через какой порт посылать клиенту данные, запрошенные по управляющему соединению. После того как клиент сообщает FTP-серверу, какой порт протокола использовать, сервер исполняет активное открытие и использует сокет и порт протокола FTP-клиента, указанный компьютером клиента. Другими словами, для соединения данных FTP-клиент действует подобно серверу. Клиент создает сокет, связывает его с местным адресом, сообщает серверу, какой адрес использовать, чтобы войти в контакт, и ожидает входящее соединение. Однако различие между FTP-клиентом и настоящим сервером в том, что первый принимает соединение только от FTP-сервера на другом конце управляющего соединения. Как вы помните из материала предыдущих глав, сокет сервера хранит маску для адреса удаленного компьютера. Другими словами, сокет сервера обслуживает запросы на установление соединения, пришедшие от любого удаленного компьютера. FTP-клиент хранит адрес FTP-сервера в сокете, созданном для соединения данных. Таким образом, сокет будет принимать запрос на установление соединения только от FTP-сервера. Один и тот же процесс создания соединения данных происходит независимо от того, хочет клиент послать или получить Ьайл. В обоих случаях клиент создает сокет, связывает его с местным адресом, гообщает FTP-серверу, какой порт использовать для контакта, и ждет запроса на установление соединения от FTP-сервера. Другими словами, и в случае досылки, и в случае получения файлов FTP-клиент исполняет пассивное открытие, а FTP-сервер исполняет активное открытие соединения. Прерывание передачи файла Как уже обсуждалось, для управления FTP-сервером клиент использует протонол ТЕШЕТ на управляющем соединении. SMTP и POP3 клиенты, рассмотренные в предыдущей главе (а также большинство других прикладных протоколов, не обсуждаемых в этой книге), также используют протокол ТЕШЕТ для работы : соответствующими серверами. ТЕШЕТ представляет собой протокол виртуального терминала и используется в качестве переносчика собственных команд других протоколов. Зак. № 1949 481 
Глава 16. Протоколы передачи файлов Различие между FTP и другими прикладными протоколами, рассмотренными в данной книге, состоит в том, что FTP использует часть самого протокола ТЕШЕТ, а не только протокол виртуального терминала NVT ASCII. SMTP и POP3 используют протокол ТЕШЕТ для передачи команд к их серверам и получения ответов. Протокол FTP также передает строки команд, подобные SMTP и POP3 командам. Однако, как обсуждалось в разделе «Как FTP соотносится с TELNET», FTP также использует команду Synch, которая определена в спецификации самого протокола ТЕШЕТ. FTP-команды делятся на три категории: идентификаторы контроля доступа, установка параметров передачи и запросы на обслуживание. Клиент FTP может послать определенные команды (типа ABOR, STAT, QUIT) по управляющему соединению во время передачи данных. Некоторые серверы не могут контролировать управляющее соединение и соединение данных одновременно. Такие серверы требуют специальных действий, чтобы привлечь их внимание во время процесса передачи. Спецификация FTP (RFC 959) выделяет следующую процедуру для привлечения внимания FTP-сервера во время операции по передаче файла. Как вы увидите, процедура по существу идентична процедуре, определенной спецификацией ТЕШЕТ, как описано в этой главе в разделе «Сигнал Synch». Спецификация FTP рекомендует следующую процедуру: 1. Система пользователя вставляет сигнал ТЕШЕТ IP (прерывание процесса) в поток ТЕШЕТ (через управляющее соединение). 2. Система пользователя посылает сигнал Synch в сегменте, который содержит только байт команды DATA MARK ТЕШЕТ. 3. Система пользователя вставляет команду FTP (ABOR, например) в поток ТЕШЕТ (через управляющее соединение). 4. Интерпретатор протокола сервера (PI), после получения байта команды IP просматривает поток ТЕШЕТ, чтобы получить единственную команду FTP. Другими словами, во время операции по передаче файла FTP использует данные для неотложной обработки TCP, чтобы переключить FTP-сервер в срочный режим. После переключения FTP-сервера в срочный режим, клиент передает команду FTP. Когда FTP-сервер находится в срочном режиме, он просматривает поток данных управляющего соединения до тех пор, пока не найдет строку команды FTP, и отвечает на нее. Если сервер не может управлять двумя TCP-соединениями одновременно, он прерывает операцию по передаче файла, отвечает на запрос клиента и затем возобновляет операцию по передаче файла. Как упоминается в спецификации, эта процедура может быть и не нужна для некоторых серверов, но выполнение действий из приведенного выше списка не должно приводить к возникновению побочных эффектов. Другими словами, если сервер может управлять двумя TCP-соединениями одновременно, он будет отвечать на запрос, продолжая операцию по передаче файла без паузы. В любом случае результат должен быть тем же самым. FTP-сервер отвечает на запрос клиента во время операции по передаче файла без прерывания передачи. 482 
Очевидно, что если запрос клиента требует прервать передачу файла, сервер так и сделает. Однако прерывание передачи файла здесь происходит как результат прямого запроса клиента. Прерывание (по управляющему соединению) не заставляет FTP-сервер прерывать передачу файла. Команды FTP В протоколе передачи файлов определено более тридцати команд, которые программа-клиент может использовать для управления сервером. FTP-команды делятся на три категории: команды контроля доступа, команды передачи параметров и команды обслуживания. Команды контроля доступа передают информацию, идентифицирующую пользователя серверу или сообщает серверу, к каким каталогам программа-клиент желает получить доступ. Команды, передающие параметры, позволяют клиенту определять опции FTP, уже рассмотренные нами: регистрируют тип, формат файла, структуру файла и режим передачи. Команды обслуживания FTP задают выполнение операций по передаче файлов. Описание каждой команды сопровождается возможными кодами ответа на нее. Далее, в табл. 16.9 все возможные коды ответа описываются подробнее. Команды контроля доступа Контроль доступа идентифицирует пользователя FTP-серверу или сообщает серверу, к каким ресурсам программа клиента желает получить доступ. Прежде чем вы исследуете команды контроля доступа подробно, вы должны понимать, что пользователи FTP могут передавать команды контроля доступа USER, PASS и АССТ несколько раз в течение отдельного FTP-сеанса. Команда USER передает имя пользователя, команда PASS передает пароль, а команда АССТ передает название или номер ресурса. Некоторые FTP-серверы позволяют клиенту передавать новую команду USER в любое время в течение FTP-сеанса. Клиент мог бы сделать это, чтобы изменить управление доступом и /или учетную информацию. По существу, новая команда USER снова начинает процедуру регистрации в системе. Однако все передаваемые параметры остаются неизменными, и сервер будет заканчивать любые уже запущенные операции по передаче Ьайла. В действительности новая команда USER позволяет вам изменить пользователя программы-клиента FTP без того, чтобы вынуждать его заканчивать сеанс FTP и снова проходить регистрацию в системе с новым именем пользователя. Новый пользователь, вероятно, будет требовать нового пароля, так что сервер будет разрешать также новую команду PASS. FTP не связывает команду АССТ с командой USER. Некоторые сети могут требовать информацию о ресурсе, только когда пользователь выполняет определенные действия типа сохранения файлов. Также FTP не ограничивает использование команды АССТ. Другими словами, клиент может передать команду АССТ (и аргумент) в любое время в течение ТТР-сеанса (то есть всякий раз, когда соответствующее действие потребуется). 483 
Глаег- 16. Протоколы передачи файлов Имя пользователя (USER) Как вы узнали, прежде чем вы сможете получить доступ к файлам FTP-сервера, вы должны сначала войти в систему, определяя имя пользователя и пароль. С командой USER указывается аргумент, определяющий пользователя для сервера. Аргумент (параметр команды) должен содержать значение, которое сервер требует для доступа. Например, в случае анонимного FTP параметр пользователя был бы anonymous. Обычно команда USER — первая команда, которую клиент передает после того, как установит управляющее соединение. Она не гарантирует получения доступа к серверу. Другими словами, сервер может затребовать дополнительную информацию идентификации в форме пароля и/или ресурса. Возможные коды ответа: 230, 331, 332, 421, 500, 501, 530. Пароль (PASS) После того как вы определите имя пользователя, необходимо определить пароль. Команда PASS требует аргумент, который определяет пароль пользователя. Клиент может передать эту команду только сразу после команды USER. Многие FTP-клиенты скрывают (не печатают на экран) пароль пользователя. Однако все они передают пароль в виде обычных ASCII-символов. Возможные коды ответа: 202, 230, 332, 421, 500, 501, 503, 530. Ресурс (АССТ) Команда АССТ требует аргумента, который описывает требуемый пользователем ресурс. Как правило, учетная информация позволяет удаленной системе отслеживать бюджет пользователя. Другими словами, предположите, что компания имеет большой центр обработки данных. Служащие в центре могут работать для нескольких различных отделов или подразделений в пределах компании. В таких случаях компания может требовать, чтобы пользователи вводили учетную информацию, когда они входят в компьютерную сеть компании. Компания может использовать учетную информацию для подсчета времени и затрат, связанных со специфическими проектами или деловыми операциями. Возможные коды ответа: 202, 230, 421, 500, 501, 503, 530. Изменение рабочего каталога (CWD) Подобно большинству компьютерных систем FTP-серверы хранят файлы во вложенных друг в друга каталогах. Большинство связанных с файлами FTP-команд, например передающих или сохраняющих файлы, работают в текущем (рабочем) каталоге удаленного компьютера. Пользователи могут перемещаться от одного каталога к другому. Команда CWD позволяет пользователю перейти в другой каталог на удаленном компьютере. CWD требует аргумент, который определяет путь к требуемому каталогу. Возможные коды ответа: 250, 421, 500, 501, 502, 530, 550. 484 
Команды РРР Переход в родительский каталог (CDUP) Часто при изменении рабочего каталога на удаленном компьютере пользователь меняет текущий каталог на родительский. CDUP — специальный случай команды CWD. CDUP изменяет текущий каталог, переходя на один уровень выше по дереву каталогов. Наличие команды CDUP, прежде всего, упрощает реализацию программ, которые передают полные деревья каталогов между операционными системами с различным синтаксисом для обозначения родительских каталогов. Возможные коды ответа: 250, 421, 500, 501, 502, 530, 550. Монтирование файловой системы (SMNT) Как вам наверное известно, операционная система UNIX позволяет привилегированным пользователям устанавливать (монтировать) множество различных файловых систем (дисков) которые, в действительности, добавляют файлы и каталоги к файловой системе UNIX. Подобным способом команда SMNT позволяет пользователю FTP монтировать файловую структуру. SMNT требует аргумент, который определяет путь к каталогу или некоторому другому обозначению группы файлов системы. Возможные коды ответа: 202, 250, 421, 500, 501, 502, 530, 550. Повторная инициализация (REIN) REIN возвращает клиента к состоянию, в котором он находится сразу после установления управляющего соединения. Другими словами, после получения команды REIN сервер ожидает команду USER, как следующую команду через управляющее соединение. Клиент может использовать команду REIN, чтобы передавать файлы для нескольких пользователей без того, чтобы закрывать и повторно открывать соединение для каждого пользователя. Другими словами, команда REIN отменяет последнюю команду USER. Команда сбрасывает все текущие операции ввода-вывода и информацию о доступе. Однако команда позволяет текущей передаче завершиться без прерывания. FTP повторно устанавливает все параметры к используемым по умолчанию, но сохраняет управляющее соединение открытым. Возможные коды ответа: 120, 220, 421, 500, 502. Выход (QUIT) Когда пользователь заканчивает все передачи файлов, он может использовать команду QUIT, чтобы закончить FTP-сеанс. Если передача файла завершена, сервер закрывает управляющее соединение. Если передача файла не завершена, соединение остается открытым на время, достаточное для передачи кода ответа. Однако после передачи кода ответа, сервер немедленно закрывает соединение. Неожиданное завершение управляющего соединения заставит FTP-сервер выполнить действие по аварийному прекращению работы (ABOR) и выход (QUIT). Возможные коды ответа: 221, 500. 485 
Глава 16. Протоколы передачи файлов Команды установки параметров передачи Как вы узнали, пользователи FTP могут определить тип файла, формат файла, структуру файла и режим его передачи. Команды, устанавливающие параметры, позволяют клиенту определять эти опции для FTP-сервера. Все параметры передачи данных имеют значение по умолчанию, которое клиент может не изменять. Другими словами, если клиент не хочет изменять специфический параметр, он не должен выдавать команду изменения параметра. Клиенты FTP могут задавать команды установки параметров в любом месте. Однако команды установки параметров должны предшествовать любому запросу FTP-сервиса. Команды, показанные в табл. 16.3, определяют параметры передачи данных. Таблица 16.3. Команды установки (изменения) параметров передачи FTP Команда Описание PORTG Порт данных PASV Пассивный TYPE Тип представления STRU Структура файла MODE Режим передачи Порт данных (PORT) Как вы знаете, FTP-клиент и FTP-сервер обязаны устанавливать новое ТСР-соединение для каждой операции по передаче файла. Операции по передаче файлов не происходят через официальный порт протокола FTP. Вместо этого сервер соединяется с клиентом по порту, указанному клиентом. Клиент использует команду PORT, чтобы сообщить серверу, на каком порту установить соединение. С командой PORT задается аргумент, определяющий порт протокола для соединения данных. Аргумент команды PORT — комбинация 32-битного IP-адреса компьютера и 16-битного адреса TCP-порта. Клиент должен разбить эту информацию на части по 8 бит и передавать каждое значение (отделяемое запятой), как десятичное число в кодировке ASCII. Например, предположим, что ваша программа-клиент работает на компьютере jamsa.com (IP адрес 168.158.20.102) и вы хотите использовать порт протокола 1150 (0х047Е), чтобы получить данные. Ваша программа передала бы команду PORT, как показано ниже: PORT 168,158,20,102,4,126 Возможные коды ответа: 220, 421, 500, 501, 530. Пассивный способ соединения (PASV) Обычно FTP-клиент сообщает серверу, какой порт на компьютере клиента он должен использовать для установления соединения, и сервер устанавливает TCP-соединение для канала данных. Однако клиент может использовать 486 
Команды ftp l команду PASV, чтобы запросить процесс передачи данных сервера ожидать (прослушивать) установления соединения на собственном порту данных, а не устанавливать соединение самостоятельно. Сервер отвечает на эту команду адресом компьютера и портом протокола, на котором он будет ждать входящего запроса на установления соединения. Возможные коды ответа: 227, 421, 500, 501, 502, 530. Тип файла (TYPE) Команда ТУРЕ сообщает тип передаваемого файла. FTP понимает четыре различных типа файлов: локальный, изображение (двоичный), EBCDIC и ASCII. Для ASCII- и EBCDIC-файлов пользователь может также определить один из трех типов управления форматом: Nonprint, управление форматом TELNET и управление форматом FORTRAN. Табл. 16.4 содержит список различных комбинаций параметров, доступных для команды ТУРЕ. Таблица 16.4. Параметры команды TYPE Тип Описание А N Тип файла ASCII, управление форматом Nonprint (по умолчанию). А Т Тип файла ASCII, управление форматом TELNET. А С Тип файла ASCII, управление форматом FORTRAN. Е N Тип файла EBCDIC, управление форматом Nonprint. Е Т Тип файла EBCDIC, управление форматом TELNET. Е С Тип файла EBCDIC, управление форматом FORTRAN. I Тип изображение (двоичный файл). L 8 Локальный тип (байт состоит из восьми бит). Возможные коды ответа: 200, 421, 500, 501, 504, 530. Структура файла (STRU) Как вы узнали, FTP определяет три типа структур: файловую, с разбивкой на записи и страничную. Клиенты FTP используют команду STRU, чтобы определить структуру передаваемого файла. Команда STRU требует единственного символа в качестве параметра. Как показано в табл. 16.5, параметр определяет структуры FTP, рассмотренные ранее. Таблица 16.5. Параметры команды STRU Параметр STRU Описание F Файл (не структурированный) (по умолчанию). R Структура с разбивкой на записи. Р Страничная структура. Возможные коды ответа: 200, 421, 500, 501, 504, 530. 487 
Глава 16. Протоколы передачи файлов Режим передачи (MODE) FTP также определяет три режима передачи файла. Клиенты FTP используют команду MODE, чтобы определить, какой режим использовать. Подобно команде STRU команда MODE также требует единственного символа-параметра. Как показано в табл. 16.6, параметр определяет один из режимов передачи FTP, обсуждавшихся ранее. Таблица 16.6. Параметры команды MODE Параметр MODE Описание S Потоковый режим (используется по умолчанию). в Блочный режим передачи. с Режим со сжатием. Возможные коды ответа: 200, 421, 500, 501, 504, 530. Команды сервиса FTP Команды сервиса FTP определяют операции по передаче файла. Как можно было ожидать, команды сервиса представляют самую обширную категорию команд, определяемых протоколом FTP. Аргумент команды сервиса FTP, как правило, путь к файлу. Синтаксис имени пути должен соответствовать условным обозначениям, принятым на стороне сервера. Клиент FTP может задавать команды сервиса в любом порядке, за исключением команды переименования в, которая должна следовать за командой переименования из. Также клиент должен давать команду перезапуска (restart) после команды прерывания обслуживания (STOR или RETR, например). Кроме определенных ответов информационного характера, FTP-сервер всегда передает данные в ответ на команду сервиса через соединение данных. Получить (RETR) FTP существует, чтобы помочь пользователям передавать файлы между удаленным и локальным компьютерами. Клиенты FTP используют команду RETR, чтобы сообщить серверу, что он должен послать файл другому компьютеру (обычно, хотя не обязательно, местному клиенту). Команда RETR заставляет DTP (data transfer process, процесс передачи данных) сервера передать копию файла, указанного в имени маршрута, серверу или пользователю-DTP в другой конец соединения данных. Возможные коды ответа: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530, 550. Сохранить (STOR) Точно так же как команда RETR позволяет получить файл из удаленного компьютера, команда STOR позволяет пользователю передать файл на удаленный компьютер. STOR заставляет DTP (процесс передачи данных) сервера 488 
Команды F1=P .. принять данные, переданные через соединение данных, и сохранить их как файл. Если файл, указанный в маршруте, уже существует на сервере, реализация FTP перезаписывает содержимое файла вновь полученными данными. Если файла, указанного в маршруте, не существует, операция передачи файла создает новый файл на сервере. Возможные коды ответа: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 452, 500, 501, 530, 532, 551, 552, 553. Сохранить с уникальным именем (STOU) Команда STOU ведет себя подобно STOR за исключением того, что файл, который она создает, находится в текущем каталоге с именем, уникальным для этого каталога. Код ответа 250 от FTP-сервера (передача начата) будет включать вновь созданное сервером имя. Возможные коды ответа: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 452, 500, 501, 530, 532, 551, 552, 553. Добавить в конец файла (с созданием) (АРРЕ) Команда АРРЕ ведет себя подобно STOR за исключением того, что АРРЕ не перезаписывает заново уже существующий файл. АРРЕ заставляет сервер принимать данные, переданные через соединение данных, и сохранять данные в файле на сервере. Если указанный файл уже существует на сервере, то сервер добавляет данные в конец этого файла. Если указанного файла не существует, операция по передаче файла создает новый файл. Возможные коды ответа: 110, 120, 150, 226, 250, 331, 332, 350, 421, 425, 426, 450, 451, 452, 500, 501, 502, 530, 532, 550, 551, 552, 553. Зарезервировать место (ALLO) Подобно любому другому компьютеру FTP-серверы имеют конечное количество свободного места на жестких дисках. Некоторые серверы могут требовать, чтобы команда ALLO зарезервировала достаточно места для хранения нового файла, который пользователь хочет передать. В качестве аргумента ALLO передается целое десятичное число (в виде ASCII-текста), которое представляет собой количество байтов, отводимое сервером для хранения файла. Обычно клиент выдает за командой ALLO команду АРРЕ или STOR. Серверы, которые не используют команду ALLO, обращаются с ней, как с командой NOOP (нет операции). Возможные коды ответа: 200, 202, 421, 500, 501, 504, 530. Рестарт (REST) Иногда пользователь хочет временно остановить операцию по передаче файла. Команда REST позволяет пользователю повторно начать передачу файла без необходимости повторной передачи уже переданных ранее данных. REST требует аргумент, который сообщает серверу, с какого места надо начать повторную передачу. REST не заставляет заново передавать весь файл. REST заставляет сервер пропустить все данные из файла вплоть до указанной контрольной точки. Клиент после этой команды должен дать соответствующую команду сервиса, которая заставляет возобновить передачу файла. Возможные коды ответа: 350, 421, 500, 501, 502, 530. 489 
Переименовать из (RNFR) Точно так же как пользователь может передавать файлы удаленному компьютеру, пользователь может переименовывать уже существующие на удаленном компьютере файлы. RNFR определяет старое имя файла, которое пользователь хочет изменить. После команды RNFR должна немедленно следовать команда «переименовать в» (RNTO), которая определяет новое имя файла. Возможные коды ответа: 350, 421, 450, 500, 501, 502, 530, 550. Переименовать в (RNTO) Команда RNTO работает вместе с командой RNFR. RNTO определяет новое имя файла, идентифицированного в предшествующей команде «переименовать из» (RNFR). Вместе RNTO и RNFR заставляют FTP-сервер переименовать файл. Возможные коды ответа: 250, 421, 500, 501, 502, 503, 530, 532, 553. Аварийное прекращение работы (ABOR) Иногда пользователю необходимо прервать операцию по передаче файла до того, как она закончится. Команда ABOR просит сервер прервать предыдущую команду сервиса FTP и любую текущую передачу данных. Чтобы вынудить сервер распознать этот запрос, может использоваться сигнал Synch TELNET, рассмотренный ранее. Сервер не выполнит никаких действий, если предыдущая команда уже закончилась. Хотя сервер закрывает соединение данных в ответ на эту команду, управляющее соединение остается открытым. FTP-сервер должен уметь правильно обрабатывать две ситуации, которые могут возникнуть при поступлении команды ABOR. Во-первых, команда FTP-сервиса может уже завершиться. Во-вторых, команда FTP-сервиса может все еще выполняться. В первом случае сервер закрывает соединение данных (если оно открыто) и отвечает кодом 226. Код ответа 226 сообщает клиенту, что сервер успешно обработал команду аварийного прекращения работы. Во втором случае сервер прерывает выполняемую FTP-команду и закрывает соединение данных. В этом случае сервер возвращает код ответа 426, чтобы указать, что запрос на обслуживание закончен неправильно. После этого сервер посылает код ответа 226, который указывает, что сервер успешно обработал команду аварийного прекращения работы. Возможные коды ответа: 225, 226, 421, 500, 501, 502. Удалить файл (DELE) Так же как пользователи могут переименовывать файлы на удаленном компьютере, они могут и удалять их. Команда DELE заставляет сервер удалить файл, имя которого указано в ее аргументе. Важно обратить внимание на то, что протокол FTP не требует подтверждать выполнение этого запроса. Другими словами, если вы хотите, чтобы пользователи получили запрос на подтверждение удаления файла, ваша программа-клиент самостоятельно должна включить запрос подтверждения. Возможные коды ответа: 250, 421, 450, 500, 501, 502, 530, 550. Глава 16- Протоколы передачи файлов 490 
Команды щр Удалить каталог (RMD) Точно так же как пользователи могут удалять файлы на сетевом компьютере, они могут удалять и каталоги. Команда RMD заставляет сервер удалить каталог, указанный в маршруте-аргументе команды. Возможные коды ответа: 250, 421, 500, 501, 502, 530, 550. Создать каталог (MKD) Если пользователи хранят файлы на удаленном компьютере, им иногда нужно создавать новые каталоги. Для этого предназначена команда MKD — она заставляет сервер создать каталог, имя которого указано в аргументе команды. Возможные коды ответа: 257, 421, 450, 500, 501, 502, 530, 550. Печать рабочего каталога (PWD) Иногда пользователь сменяет рабочий каталог и забывает, в каком каталоге он оказался. Команда PWD заставляет сервер вернуть имя рабочего каталога, как часть кода ответа. Команда PWD более ценна для пользователей программ Ftp с интерфейсом командной строки (которые не показывают рабочий каталог), чем для пользователей программ Ftp, основанных на оконном интерфейсе. Обычно основанная на оконном интерфейсе программа Ftp постоянно отображает название текущего (рабочего) каталога. Возможные коды ответа: 257, 421, 500, 501, 502, 550. Показать содержимое каталога (LIST) Увидеть имена файлов, расположенных в каком-либо каталоге удаленного компьютера, можно при помощи команды LIST. Команда LIST заставляет сервер послать список файлов текущего каталога. Если имя (маршрут), заданное в команде, определяет каталог или другую группу файлов, сервер передает список файлов указанного каталога. Если имя определяет файл, сервер посылает текущую информацию относительно этого файла. Пустой аргумент подразумевает рабочий или используемый по умолчанию каталог пользователя. Передача данных происходит через соединение данных с типом файла EBCDIC или ASCII. (Пользователь самостоятельно должен гарантировать правильность типа файла.) Так как информация, выдаваемая по поводу файла, может сильно меняться от одной системы к другой, ее сложно обрабатывать автоматически (в самой программе-клиенте), но в любом случае она весьма полезна для пользователя. Возможные коды ответа: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502, 530. Список имен (NLST) Команда NLST очень похожа на LIST. Однако она возвращает информацию о файлах, которую программа может обработать автоматически. Подобно команде LIST NLST заставляет сервер послать клиенту список содержимого каталога. Аргумент должен определить, какой каталог или другую существующую в системе группу файлов надо использовать. Пустой аргумент подразумевает 491 
текущий каталог. Сервер возвращает поток данных, состоящий только из имен файлов. Сервер передает эти данные с типом ASCII или EBCDIC через соединение данных, как строки с именами файлов, отделяемые друг от друга CRLF или NL. (И снова пользователь должен гарантировать, что тип файла ТУРЕ установлен правильно.) Возможные коды ответа: 125, 150, 226, 250, 257, 421, 425, 426, 450, 451, 500, 501, 502, 530. Параметры сервера (SITE) Иногда сервер может обеспечивать специфические только для него услуги по передаче файлов. Однако такие услуги не являются достаточно универсальными, чтобы включить их в список команд протокола. Обычно в ответ на команду HELP SITE, сервер сообщает природу этих услуг и спецификации их синтаксиса. Возможные коды ответа: 200, 202, 500, 501, 530. Операционная система (SYST) Пользователь может передать команду SYST, чтобы определить тип операционной системы, используемой на удаленном компьютере. Первое слово ответа сервера на эту команду должно быть одним из названий систем, которые содержит текущая версия RFC «Присвоенные номера». Возможные коды ответа: 215, 421, 500, 501, 502. Состояние (STAT) В ходе длительных операций по передаче файлов пользователь может проверить состояния FTP-сервера. Команда STAT заставляет сервер послать ответ о состоянии по управляющему соединению. Клиент может послать команду STAT в ходе передачи файла (используя сигнал Synch TELNET, если необходимо). Сервер может послать ответ о состоянии операции в процессе передачи или может послать информацию между операциями по передаче файлов. В последнем случае команда может иметь аргумент. Если аргумент — маршрут, то команда подобна команде LIST за исключением того, что сервер передает данные через управляющее соединение. Если пользователь определяет частичное имя маршрута, сервер может ответить списком имен файлов или признаков, связанных с этой спецификацией. Если пользователь не задает никакого аргумента, сервер возвращает общую информацию о состоянии процесса FTP-сервера. Обычно такая информация включает текущие значения для всех параметров передачи и состоянии соединений. Возможные коды ответа: 211, 212, 213, 421, 450, 500, 501, 502, 530. Помощь (HELP) Если пользователь имеет вопросы относительно определенной команды FTP или, возможно, относительно определенного FTP-сервера, клиент может передать команду HELP. HELP заставляет сервер выслать полезную информацию по управляющему соединению. Команда HELP может иметь аргумент (например, любое имя команды) и возвращать определенную информацию по этой команде. Коды ответа — 211 или 214. Спецификация FTP рекомендует разре- 492 
коды тшта ФТР , шать пользователям использовать команду HELP до того, как зарегистрироваться в системе (выполнить команду USER). В ответ на команду HELP сервер может выдавать специфическую, касающуюся именно его информацию. Возможные коды ответа: 211, 214, 421, 500, 501, 502. Пустая операция (NOOP) Хорошо разработанные программы-клиенты FTP могут использовать команду NOOP, чтобы проверить состояние соединения с FTP-сервером. NOOP не имеет отношения ни к каким параметрам или предварительно введенным командам. Она не определяет никакого иного действия сервера, кроме посылки ответа ОК. Возможные коды ответа: 200, 421, 500. Коды ответа FTP Протокол FTP использует схему кодов ответа, которая фактически идентична описанной в главе 15 для SMTP. Другими словами, каждая цифра в коде ответа имеет специальное значение. В табл. 16.7 кратко описывается значение первой цифры в коде ответа FTP. Таблица 16.7. Смысл первой цифры в коде ответа FTP Код Описание lyz Предварительный положительный ответ; сервер начал требуемую операцию; ожидайте другой ответ перед вводом новой команды. 2yz Положительный ответ завершения; сервер успешно закончил требуемое действие. Клиент может выдать новый запрос. 3yz Промежуточный положительный ответ; сервер принял команду, но требуемое действие требует большего количества информации. 4yz Временный отрицательный ответ завершения; сервер не принял команду, и требуемое действие не выполнялось. 5yz Постоянный отрицательный ответ завершения; сервер не принял команду, и требуемое действие не выполнялось. Аналогично, вторая цифра в кодах ответа FTP идентифицирует сообщение более подробно, как показано в табл. 16.8. Таблица 16.8. Смысл второй цифры в коде ответа FTP Код Описание xOz Синтаксис: эти ответы относятся к ошибкам синтаксиса и синтаксически правильным командам, которые не попадают ни в одну функциональную категорию. 493 
Главе 1б? Протоколы передачи файлов Таблица 16.7 (окончание) Код Описание xlz Информация: ответы на просьбы о дополнительной информации, типа информации о состоянии или помощи. x2z Соединения: ответы относятся к управляющему соединению или соединению данных. x3z Авторизация и учет использования ресурсов: это ответы на регистрацию в системе и процедуру учета. x4z Пока не определен. x5z Файловая система: эти ответы информируют о состоянии фай¬ ловой системы сервера в ответ на запрос файловой операции. В табл. 16.9 приведен список управляющих кодов, определенных в настоящее время в спецификации FTP. Таблица 16.9. Определенные в настоящее время коды ответа FTP Код Описание 110 Маркер рестарта. 120 Служба будет готова через п минут. 125 Соединение данных уже открыто; передача начата. 150 Файл доступен; открывается соединение данных. 200 Команда выполнена. 202 Команда не реализована. 211 Состояние системы или системная подсказка. 212 Состояние каталога. 213 Состояние файла. 214 Сообщение-подсказка. 215 Тип системы NAME. 220 Сервис готов для нового пользователя. 221 Служба закрывает управляющее соединение. Если необходимо, производится выход из системы. 225 Соединение данных открыто; передача отсутствует. 226 Закрытие соединения данных. Требуемая операция выполнена успешно. 494 
Коды oiветa -Ftp. Таблица 16.9 (продолжение) Код Описание 227 Начало пассивного режима. 230 Пользователь зарегистрирован в системе, можно работать. 250 Требуемая операция с файлом закончена успешно. 257 Маршрут PATHNAME создан. 331 Имя пользователя в порядке, требуется пароль. 332 Для доступа к системе требуется указывать ресурс. 350 Для операции с файлом необходима дополнительная инфор¬ мация. 421 Служба недоступна; управляющее соединение закрывается. 425 Сервер не может открыть соединение данных. 426 Соединение закрыто; передача прервана. 450 Требуемая операция не принята. Файл недоступен. 451 Операция по передаче прервана: местная ошибка при обработке. 452 Требуемая операция не принята. Недостаточно места для размещения файла в системе. 500 Синтаксическая ошибка; команда не принята. 501 Синтаксическая ошибка в параметрах или аргументах. 502 Команда не реализована. 503 Неправильная последовательность команд. 504 Команда не реализована для этого параметра. 530 Не произведена регистрация в системе. 532 Требуется ресурс для хранения файлов. 550 Требуемая операция не принята. Файл недоступен. 551 Требуемая операция прервана: тип страницы неизвестен. 552 Операция по передаче файла прервана. Превышение зарезервированного для хранения места. 553 Требуемая операция не принята. Имя файла не разрешено в системе. 495 
Глава 16. Протоколы передачи файлов Подводя итоги В этой главе вы узнали, что в Интернет существуют три протокола передачи файлов. Хотя пользователи Интернет используют один протокол (протокол передачи файлов, FTP), другие протоколы, например простой протокол передачи файлов, TFTP, предлагают разные интересные возможности. В этой главе обсуждалась модель FTP, которая для передачи файла между клиентом и сервером использует два отдельных, но тесно связанных друг с другом ТСР-соединения. В этой главе описывались опции FTP, предназначенные для обработки различных типов файлов, их форматов, структур и режимов передачи. Вы узнали, что большинство опций FTP — пережиток ранних лет Интернет и большинство сетевых компьютеров умеют работать лишь с некоторыми из них. В этой главе рассматривалось как FTP использует протокол TELNET на управляющем соединении. Вы узнали, как FTP-клиент использует интерпретацию сигнала DATA MARK и данные для неотложной обработки, чтобы «достучаться» до FTP-сервера в середине операции по передаче файла. В этой главе также описана каждая из строковых команд FTP и коды ответа FTP-сервера. В следующих главах этой книги вы узнаете, как использовать методы визуального программирования при разработке приложений Интернет, и познакомитесь с примерами программ, которые работают со «всемирной паутиной» — World Wide Web. Оставшиеся главы проведут вас через создание гипотетического пользовательского протокола. Прежде чем вы продолжите чтение, проверьте, хорошо ли вы усвоили следующие ключевые понятия: S Простые протоколы, типа простого протокола передачи файлов, TFTP, обеспечивают превосходную базу, отталкиваясь от которой вы можете создать собственный пользовательский протокол. S В схеме передачи файла FTP используются два TCP-соединения: по одному переносятся команды и управляющая информация, а по другому — данные, например файлы и информация о каталогах. S TELNET основывается на протоколе виртуального сетевого терминала (Network Virtual Terminal, NVT). S В FTP существуют более тридцати основанных на NVT ASCII команд, которые определяют идентификаторы контроля доступа, параметры передачи файла и запросы на FTP-сервис. S TELNET определяет команды, как специальные управляющие последовательности. S FTP-клиент использует сигнал Synch TELNET, чтобы посылать команды FTP-серверу во время выполнения операции по передаче файла. 
Глав Динамические библиотеки в приложениях Интернет По мере того как вы приобретаете новые знания по программированию Интернет, вы начинаете составлять собственный набор сетевых функций общего пользования. Фактически, на протяжении нескольких последних глав вы уже собрали их некоторое количество. Разместив эти функции в динамической библиотеке (DLL), вы облегчите их использование в дальнейшем. Например WINSOCK.DLL — динамическая библиотека Windows, функции которой обеспечивают все ваши программы доступом к сокетам TCP/IP. Для того чтобы использовать функцию WINSOCK.DLL, необходимо знать только то, что делает эта функция; как она это делает — знать необязательно. 497 
Глава 17- Динамические библиотеки в приложениях Ингернет В этой главе вы будете разрабатывать простую библиотеку DLL Windows, которую можно будет использовать практически с любым инструментом визуального программирования для создания программ-клиентов FTP. Вы будете встраивать в нее основные сетевые алгоритмы, скрывая, таким образом, подробности сетевого ввода-вывода низкого уровня и сосредотачиваясь на аспектах «визуализации» приложений. К тому времени, когда вы закончите чтение этой главы, вы овладеете следующими ключевыми понятиями: ♦ Как создавать FTP-соединение и управлять им. ♦ Как создавать сокет, принимающий запросы на установление соединения от FTP-сервера. ♦ Как создать простую, но мощную библиотеку DLL, содержимым которой являются функции клиента FTP из WINSOCK.DLL. План работ На протяжении глав 17 и 18 мы создадим простую учебную программу, работающую по протоколу FTP. Процесс создания программы происходит в четыре стадии. Первая стадия (QFTP1) заключается в формировании окружения для работы с управляющим соединением FTP. На второй стадии (QFTP2) мы добавим работу с каналом данных FTP. На третьей стадии (QFTP3) прибавятся несколько функций и будет сделано несколько модификаций для обслуживания сокетов Winsock. Наконец, на четвертой стадии, мы создадим динамическую библиотеку QFTPLIB и пользовательский интерфейс на Visual Basic под названием SOCKFTP. В этой главе мы рассмотрим первые три стадии. В 18 главе мы рассмотрим последнюю, четвертую стадию создания программы. Исходные тексты всех рассматриваемых примеров QFTP (QuickFTP), а также SOCKFTP, есть на дискете, приложенной к книге. Краткий обзор протокола FTP Как объясняется в предыдущей главе, протокол FTP использует два ТСР-соединения: канал управления и канал данных. Канал управления остается открытым в течение всего времени сеанса связи с удаленным хостом. Программа использует канал управления, чтобы сообщить серверу, какие действия с файлами он должен исполнить. Программа QFTP1 (стадия 1) устанавливает управляющее соединение с сервером FTP. QFTP2 (стадия 2) открывает канал данных (другое TCP-соединение). Как объясняется в предыдущей главе, FTP-клиенты открывают и закрывают канал данных для каждой транзакции. Другими словами, каждый раз, когда клиенту требуется передать файл, он открывает новый канал данных. В большинстве случаев сервер закрывает канал данных немедленно после того, как передача файла завершена. 498 
тадия: управление сервером Сервер использует канал данных, чтобы передать и получить данные, например файлы или списки файлов в каталоге. Через канал управления сервер получает команды от клиента и передает ему ответы. Другими словами, в процессе FTP-сеанса одновременно открываются два дуплексных соединения. Как объясняется в следующем разделе, QFTP1 полностью игнорирует наличие канала данных и концентрируется только на канале управления. Первая стадия: управление сервером Если в FTP-сеансе не требуется передача файлов (или списка файлов каталога), FTP-клиент не должен использовать канал данных. Для упрощения в этой главе функции по управлению контрольным каналом и функции по управлению каналом данных описываются в разных частях. В этой части разрабатывается FTP-клиент под названием QFTP1 — простая программа, которая пользуется только каналом управления FTP. Проектирование программы Обычно в предыдущих учебных программах вы использовали только одну или две функции. Например, первые учебные программы использовали только функцию WinMain. В более поздних учебных программах отдельная функция содержала операторы, устанавливающие соединения клиент-сервер. Хотя предыдущие программы этой книги могли бы состоять из еще меньших блоков, мы собрали большинство операторов в большие группы. Таким образом, мы не вынуждали вас перескакивать от одной функции к другой во время изучения книги. Хотя QFTP1 использует шесть функций, вы поймете, что сталкивались с такими же или подобными им функциями в предыдущих главах книги. Другими словами, QFTP1 соединяется с сетью уже знакомым вам путем. Еще раз о кодах возврата FTP Как вы узнали в предыдущей главе, протокол FTP определяет гибкую, но вместе с тем четкую систему кодов возврата. Коды возврата FTP (подобно используемым в простом протоколе передачи почты, SMTP) состоят из трехзначных чисел. Каждая цифра в коде возврата существенна. Например, первая цифра определяет, удачно или неудачно выполнена операция. Вторая и третья цифры обеспечивают более подробное описание результата. В зависимости от того, нужен ли вам сложный или простой клиент, программы могут анализировать, а могут и не анализировать все цифры в коде ответа, кроме первой. Функция GetReplyCode получает указатель на буфер, содержащий строку символов, составляющих код ответа от FTP-сервера. Как показано дальше, функция преобразует три символа текста из буфера к целому типу и возвращает число в программу: 499 
UINT GetReplyCode(LPSTR IpszServerReply) { UINT nCode; char c; // Код ответа (число) // Временная переменная с = *(lpszServerReply+З); // Запоминаем символ *(lpszServerReply+З) = '\0'; // Ограничиваем код ответа nCode = atoi((const char *)IpszServerReply); // Обратите внимание, что функция atoi требует, чтобы строка заканчивалась нулем. Однако, так как буфер IpszServerReply содержит полный текст ответа сервера, а не только код возврата, функция GetReplyCode должна вставить ноль в строку сразу за кодом возврата. Прежде чем вставить ноль в строку, IpszServerReply сохраняет заменяемый символ в переменной с. После возвращения значения функцией atoi GetReplyCode восстанавливает замененный нулем символ. Как обсуждалось, ответ сервера содержит код возврата и сообщение в виде строки текста. В некоторых случаях программы могут использовать часть текста из ответа FTP-сервера. Для этого необходимо исследовать содержимое буфера ответа. Однако программы могут работать быстрее и с меньшим количеством выполняемого кода, если они используют функцию GetReplyCode, чтобы преобразовывать символы возврата в целые числа. Обработка ошибок Предыдущие учебные программы не включали в себя проверку на наличие ошибок. Хотя выявление ошибок при выполнении программ чрезвычайно необходимо для хорошо сделанных коммерческих продуктов, многочисленные проверки ошибок, расставленные тут и там по тексту программы, наводят в ней беспорядок и затеняют детали ее алгоритма. Если в примерах программ вы сталкиваетесь с изъянами в работе, которые следуют из-за недостаточности в проверках ошибок, имейте в виду нашу цель: разработать легкие для понимания примеры, иллюстрирующие важные концепции программирования приложений Интернет. Хотя цель этой книги (обучение вас основным концепциям программирования в сети) не меняется и в этой главе, программы QFTP содержат значительно больше проверок по сравнению с предыдущими учебными программами. Дело в том, что проверка ошибок становится совершенно необходимой, если вы включаете функции в динамическую библиотеку, как это мы сделаем далее в этой главе. В исходных текстах программ вы будете находить операторы вроде следующих: *(lpszServerReply+З) = с; // Кодпреобразуется в число // Восстанавливаем символ return(nCode); // Возвращаем код ответа } 500 
Первая стадия: управление сервером ■■ЯШ»! if ((result = functionO) == ERROR_CONDITION) { int iWinsockErr = WSAGetLastError(); wsprintf(szBuffer, "Error #%d occurred while doing this operation.", iWinsockErr); MessageBeep(MB_ICONHAND); MessageBox(NULL, szCommandBuffer, IpszFunctionName, MB_OK|MB_ICONSTOP); return(ERROR_CONDITION); Обратите внимание на использование переменной IpszFunctionName в вызове функции MessageBox. Другая особенность программ QFTP в том, что каждая функция содержит оператор программы, аналогичный приведенному ниже: IpszFunctionName = "ConnectFTPControlSocket"; Переменная IpszFunctionName — это дальний указатель на строку символов (тип данных, определенный как LPSTR). Во всех программах QFTP в начале каждой функции содержится оператор программы, который присваивает имя функции некоторому буферу в памяти и присваивает адрес начала этого буфера указателю IpszFunctionName. Как показано в предыдущем примере с MessageBox, каждая функция может использовать указатель IpszFunctionName для того, чтобы показать вам при помощи MessageBox, что происходит в программе QFTP в данный момент. Эта информация может оказаться чрезвычайно полезной, если случается какая-либо проблема при работе DLL в другой среде (например, в Visual Basic). Конструкция If-Else против многократных операторов Return Можно заметить, что более поздние программы в этой книге часто используют многократные return, когда теория программирования призывает использовать вложенные конструкции if-else. Мы решили избегать вложенных конструкций if-else из-за ограниченной ширины наших страниц и потому, что вложенный код труден для прочтения. Если вам не нравятся многократные return и вы предпочитаете вложенный оператор if-else, вы можете легко переделать определения функций. Общая картина Подобно предыдущим программам QFTP1 использует символьные константы, вместо того чтобы получать данные непосредственно от пользователя. Чтобы изменить имя компьютера и некоторые другие параметры, вы должны изменить эти константы и затем скомпилировать программу повторно. Многие FTP-cepверы в Интернет разрешают анонимный вход в систему. Хост NIC.DDN.MIL, 501 
Глава IT. Динамические библиотеки ж 1г содержащий документы RFC, — один из таких серверов. NIC.DDN.MIL — также удобный испытательный полигон, с которым можно попробовать некоторые FTP-команды начиная с запроса login, в котором используется пароль guest (а не адрес вашей электронной почты). Программы QFTP используют следующие символьные константы, чтобы соединиться с компьютером NIC.DDN.MIL: #define HOST_NAME "NIC.DDN.MIL" #define PASSWORD "PASS guest\r\n" Функция WinMain для QFTP1 приведена ниже: int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow) { WSADATA wsaData; SOCKET hControlChannel; UINT nReplyCode; IpszFunctionName = "WinMain"; if (WSAStartup(WINSOCK_VERSION, &wsaData)) // ...обработать ошибку и выйти hControlChannel = ConnectFTPControlSocket ( (LPSTR) HOST__NAME) ; if (hControlChannel != INVALID_SOCKET) { // Если управляющий сокет в порядке, войти в систему nReplyCode = AnonymousFTPLogln(hControlChannel); if (nReplyCode == 230) // Пользователь вошел в // систему. Продолжаем выполнение { SendFTPCommand(hControlChannel, "QUIT\r\n"); closesocket(hControlChannel); } } } WSACleanup(); MessageBeep(MB_ICONEXCLAMATION); MessageBox(NULL, return(NULL); "THE END!!", PROG_NAME, MB_OK|MB_ICONEXCLAMATION) ; Мы видим, что после инициализации Winsock функцией WSAStartup, WinMain вызывает ConnectFTPControlSocket и сохраняет возвращенный дескриптор сокета 502 
Первая стация: управление оерверр^ в переменной hControlChannel. Как и подразумевает название, ConnectFTPControlSocket создает сокет и соединяет его с FTP-сервером. Этот сокет предоставляет канал управления для всех других функций QFTP, которые должны общаться с сервером. После установления соединения и если функция ConnectFTPControlSocket возвращает действительный дескриптор, WinMain вызывает функцию AnonymousFTPLogln для анонимного входа в систему. Как видим, WinMain ожидает возвращения кода 230 от процесса регистрации login. Если вы обратитесь к спецификации FTP, то увидите, что код возврата 230 выдается, если удаленный компьютер разрешил пользователю войти в систему, и что этот код — разрешение продолжать работу. В этом случае после успешной регистрации программа просто отсоединяется от сервера и заканчивает выполнение. Для отсоединения от сервера WinMain посылает ему FTP-команду «QUIT» и закрывает управляющий сокет. Открытие канала управления Функция ConnectFTPControlSocket создает сокет и соединяет его с FTP-сервером. Функция ConnectFTPControlSocket похожа на любую другую функцию учебной программы для соединения с сервером и создания сокета. Короче говоря, ConnectFTPControlSocket исполняет следующие шаги. Во-первых, определяет имя хоста, используя функцию gethostbyname. Во-вторых, создает сокет, используя функцию socket. В-третьих, использует функцию getservbyname для получения информации о протоколе (в данном случае, об FTP) из базы данных по сетевым службам. В-четвертых, заполняет структуру адреса сокета (SOCKADDR__IN), назначая порт и адрес. В-пятых, использует функцию connect для соединения сокета с удаленным сетевым компьютером. В-шестых, принимает ответ сервера, используя функцию ReadFTPServerReply. Определение функции ConnectFTPControlSocket в QFTP1 остается одинаковым для всех версий QFTP. Приведенные ниже операторы демонстрируют завершенную функцию ConnectFTPControlSocket (без обработки ошибок): (LPSTR IpszHost) SOCKET ConnectFTPControlSocket LPHOSTENT lpHostEnt; // // SOCKADDR_IN sockAddr; // LPSERVENT IpServEnt; // // short nProtocolPort; // int nConnect; // Структура с информацией о сетевом компьютере Структура адреса сокета Структура с информацией о сетевой службе Порт протокола Результат соединения сокета SOCKET hControlSocket = INVALID_SOCKET; IpszFunctionName = "ConnectFTPControlSocket"; 503 
• Дин» эмические библиотв! А ^ . j (И в прилож« зниях Интернет .""" V' if (!(lpHostEnt = gethostbyname(IpszHost))) / / ... обработать ошибку и выйти if ((hControlSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) // ...обработать ошибку и выйти IpServEnt = getservbyname("ftp", DEFAULT_PROTOCOL); if (IpServEnt == NULL) nProtocolPort = htons (IPPORT__FTP) ; else nProtocolPort = lpServEnt->s_port; // Определяем адрес сокета sockAddr.sin_family = AF_INET; sockAddr.sin_port = nProtocolPort; sockAddr.sin_addr = * ( (LPIN_ADDR) *lpHostEnt->h_addr__list) ; // Соединить сокет if (nConnect = connect(hControlSocket, (LPSOCKADDR)&sockAddr, sizeof(sockAddr))) // ...обработать ошибку и выйти if (ReadFTPServerReply(hControlSocket) >= 400) return(INVALID_SOCKET); else return(hControlSocket); } Как вы помните из предыдущих учебных программ, большинство протоколов требуют, чтобы программа-клиент послала некоторое сообщение серверу после того, как соединение установлено. Напротив, FTP-сервер сам посылает клиенту приветствие после установления соединения. Перед возвратом управления, на шестом шаге, ConnectFTPControlSocket вызывает функцию ReadFTPServerReply, которая считывает ответ сервера из канала управления. Если код ответа сервера 400 или больше (ошибка), ConnectFTPControlSocket возвращает значение INVALID_SOCKET. Иначе, ConnectFTPControlSocket возвращает дескриптор сокета управляющего канала. Чтение ответов сервера На второй стадии модификации нашей программы (QFTP2) вы внесете существенные изменения в функцию ReadFTPServerReply. Однако на первой стадии (QFTP1), единственное, что она делает — вызывает функцию recv из библио- 504 
первая стадия: управление сервером теки Winsock. Обратите внимание на то, что функция добавляет завершающий ноль к концу буфера до вызова функции Windows MessageBox. Также обратите знимание на то, что значение, возвращаемое ReadFTPServerReply, пропускается через уже известную вам функцию GetReplyCode. Другими словами, основная л ель функции ReadFTPServerReply состоит в том, чтобы проверить канал управления и сообщить код ответа сервера вызывающей функции: VINT ReadFTPServerReply(SOCKET hControlChannel) { char sReceiveBuffer[1024]; int iLength; IpszFunctionName = "ReadFTPServerReply"; if ((iLength = recv(hControlChannel, (LPSTR)sReceiveBuffer, sizeof(sReceiveBuffer), NO_FLAGS)) == SOCKET_ERROR) // ...обработать ошибку и выйти sReceiveBuffer[iLength] = '\0'; MessageBeep(MB_ICONASTERISK); MessageBox(NULL, (LPSTR)sReceiveBuffer, IpszFunctionName, MB_OK I MB_ICONINFORMATION) ; return(GetReplyCode(sReceiveBuffer)); } Обычно программы, посылающие команды FTP-серверу, также вызывают функцию ReadFTPServerReply, чтобы получить ответ сервера. Другими словами, функции программы, которые соединяются с FTP-сервером и принимают решения, основанные на ответах сервера, сильно зависят от функции ReadFTPServerReply, что делает функцию ReadFTPServerReply важным игроком в конечном проекте QFTP. Посылка команд FTP-серверу Прежде чем FTP-сервер начнет принимать запросы на передачу файлов, пользователь должен пройти регистрацию в системе FTP-сервера. Вообще говоря, функция AnonymousFTPLogln в нашей программе необязательна; ее единственная цель — сделать структуру программы удобной для экспериментирования с командами FTP, возвращающими ответы через канал управления. Как показано ниже, AnonymousFTPLogln просто выдает команду USER с символьной константой PASSWORD, которая определена в начале программы: VINT AnonymousFTPLogln(SOCKET hControlSocket) { 505 
Глава 17. Динамические библиотеки в приложениях Интернет int nReplyCode; // Код ответа FTP-сервера int iMsg =0; // Индекс массива строк-команд FTP lpszFunctionName = "AnonymousFTPLogln"; char *LoginCommand [ ] = { "USER anonymous\r\n", PASSWORD, NULL } ; do { nReplyCode = SendFTPCommand(hControlSocket, (LPSTR)LoginCommand[iMsg++]); } while (LoginCommand[iMsg] && nReplyCode < 400); return(nReplyCode); } Для экспериментирования с другими командами, такими как PWD и SYST, вы просто вставляете строки команд в массив LoginCommand [] перед элементом NULL. Если вы экспериментируете с QFTP1, не передавайте команды, для выполнения которых необходим канал данных. (Если вы не знаете, использует команда канал данных или нет, обратитесь к описаниям команд FTP в предыдущей главе.) Как видим, ядро функции в AnonymousFTPLogln — запрос к SendFTPCommand. Так же как функция ReadFTPServerReply является оболочкой для Winsock-функции recv, функция SendFTPCommand является оболочкой для Winsock-функции send. Как показано ниже, SendFTPCommand передает строку команд FTP-серверу через управляющее соединение (сокет с дескриптором hControlHandle): UINT SendFTPCommand(SOCKET hControlChannel, LPSTR s zCommandBuf f er) { lpszFunctionName = "SendFTPCommand"; // Посылаем FTP-команду if ((send(hControlChannel, (LPSTR)szCommandBuffer, lstrlen(szCommandBuffer), NO_FLAGS)) == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); wsprintf(szCommandBuffer, "Error %d from the send() function!!", iWinsockErr); MessageBeep (MB__ICONHAND) ; 506 
Первая стадия: управление сервером W" '' У".' i, - ... . , ' v... ; о - !г ••• ' " "" - > - 4 v 4 * • Mes sageBox(NULL, s zCommandBuf f er, lps zFunct ionName, MB_OK | MB__I CONS TOP) ; // Если произошла ошибка, возвращаем 999 return(999); } return(ReadFTPServerReply(hControlChannel)); } Зкончательная версия QFTP использует ту же самую функцию SendFTPComnand, что и QFTP1. Другими словами, операторы в QFTP2 и QFTPLIB, оставляющие функцию SendFTPCommand — те же, что и здесь. Таким образом, в предыдущем примере приведено полное определение функции, включая проверку и обработку ошибки SOCKET_ERROR. Когда происходит ошибка, как написано в соответствующем комментарии, функция SendFTPCommand возвращает число 999. Если вы разрабатываете коммерческую версию программы, основанную на учебных программах QFTP, вы должны определить ее геакцию на каждую ошибку, с которой она может встретится. Однако в учебных программах QFTP этой книги используется лишь код 999, как общее указание на то, что в функции QFTP произошла ошибка. Канал управления Хотя QFTP1 включает вдвое больше определений функций, чем предыдущие примеры учебных программ, на самом деле в нем нет никаких новых программных конструкций. Точно так же QFTP1 не использует Winsock API никаким новым или необычным для вас способом. Другими словами, программы управляют каналом контроля FTP тем же самым способом, каким они управляют большинством других соединений. В QFTP1 определены три важных функции: EonnectFTPControlSocket, SendFTPCommand и ReadFTPServerReply. Функция ConnectFTPControlSocket устанавливает активное соединение с FTP-сервером через официальный порт протокола. Это соединение будет служить каналом управления между вашим клиентом и FTP-сервером. Для того чтобы посылать команды через канал управления, программы будут вызывать функцию SendFTPCommand. Чтобы прочитать ответы сервера из канала управления, программы будут вызывать ReadFTPServerReply. Единственная другая важная (но вместе с тем простая) функция, обсуждаемая з этом разделе, — это функция GetReplyCode, которая извлекает код возврата ЕТР-сервера из строки ответа и преобразует его в числовое значение. Чтобы упростить разработку любого модуля программы, который должен анализировать ответы сервера, используется функция GetReplyCode — она приводит все коды возврата к целому типу. Если вы еще не скомпилировали и не запускали 3FTP1, сделайте это сейчас. Чтобы увидеть взаимосвязь между различными модулями программы, используйте отладчик вашего компилятора для ее пошагового выполнения. 507 
"Глава 17. Динамические библиотеки р приложениях Интернет Вторая стадия: передача данных Сейчас мы рассмотрим QFTP2 — вторую стадию разработки программы-клиента FTP. Полный исходный текст программы QFTP2 находится на дискете, приложенной к книге. Из шести функций, определенных в первой версии QFTP, QFTP2 использует без изменения четыре: ConnectFTPControlSocket, AnonymousFTPLogin, SendFTPCommand и GetReplyCode. Так как они идентичны функциям из QFTP1, мы не будем обсуждать их в дальнейшем. В функции WinMain QFTP2, в отличие от QFTP1, добавляется еще один оператор. Как показано в нижеследующем фрагменте программы, если код возврата от анонимного входа в систему показывает, что регистрация прошла успешно, функция WinMain вызывает функцию DemonstrateCommand: // Если контрольный канал в порядке, входим в систему nReplyCode = AnonymousFTPLogln(hControlChannel); if (nReplyCode == 230) // Пользователь вошел в систему, // продолжаем выполнение { DemonstrateCommand(hControlChannel); SendFTPCommand(hControlChannel, "QUIT\r\n"); closesocket(hControlChannel); } Кроме вызова функции DemonstrateCommand, функция WinMain из QFTP2 идентична функции WinMain из QFTP1. DemonstrateCommand иллюстрирует, каким образом должны обрабатываться FTP-команды, для выполнения которых требуется канал данных. Еще раз о канале данных В предыдущей главе вы выяснили, что программы-клиенты FTP используют канал данных для получения списков файлов или самих файлов от FTP-сервера. Канал данных также используется для передачи файлов от клиента к серверу. Данные, получаемые из канала данных, обычно не нуждаются в интерпретации. В большинстве случаев FTP-клиент просто записывает поступающие данные в файл. Напротив, ваши программы могут столкнуться с проблемами, пытаясь интерпретировать данные, получаемые из канала управления. К сожалению, канал данных установить сложнее, чем канал управления. Как объясняется в предыдущей главе, трудность с каналом данных заключается в том, что клиент должен временно действовать наподобие сервера. Другими словами, клиент должен принимать и идентифицировать запросы на установление соединения от FTP-сервера. Канал управления FTP остается открытым в течение всего сеанса. Однако клиент и сервер поддерживают соединение данных только на протяжении каждого отдельного сеанса передачи. То есть для каждого обмена данными клиент и сервер должны установить новое соединение. Ключевым моментом 508 
Вторая стадия: передача данные десь является то, что передача данных не происходит через официальные юэрты — она происходит через порты, назначенные Winsock на сетевом компьютере клиента. Так как FTP-клиент инициирует все операции по обмену данными, он должен обеспечить пассивное открытие сокета и затем сообщить ерверу, с каким портом на компьютере клиента он должен соединиться, другими словами, прежде чем клиент запросит у сервера передачу файла, он :элжен сообщить серверу, на какой порт посылать данные. После того как клиент сообщает серверу, по какому адресу сокета (IP-адресу и эрту протокола) слать данные, он должен ожидать запрос на установление зединения от сервера по этому адресу. Для того чтобы сервер знал, какой адрес ::пользовать, клиент посылает FTP-команду PORT. Команда PORT просит грвер установить соединение по адресу, определенному ее параметрами. В юействительности, FTP-клиент должен действовать как сервер до тех пор, пока гТР-сервер не установит соединение и не откроет канал данных. Единственное газличие между FTP-клиентом и настоящим сервером в том, что клиент подзерждает соединение только от FTP-сервера на другом конце управляющего эединения, а сервер — от любого другого компьютера. Использование канала данных Программы FTP-клиент и FTP-сервер используют один и тот же процесс эздания соединения для работы с данными, независимо от того, хочет клиент дослать или получить файл данных. В обоих случаях клиент создает сокет, зязывает его с местным адресом, сообщает FTP-серверу, какой порт использовать для соединения, и после этого ожидает установления соединения от гТР-сервера. Другими словами, в обоих случаях (посылка и получение файлов), FTP-клиент пассивно, а FTP-сервер активно открывают канал данных. Чтобы открыть и использовать канал данных FTP, клиент выполняет следующие юействия. В начале он создает сокет для прослушивания запроса на установление эединения от FTP-сервера. Далее FTP-клиент просит FTP-сервер установить эединение для канала данных и сообщает ему адрес своего только что создан-:эго сокета. Затем FTP-клиент посылает команду FTP-серверу, требующую передачи информации по каналу данных. FTP-клиент устанавливает соединение : FTP-сервером и после этого закрывает приемный сокет. (В Winsock подтверждение запроса на соединение автоматически приводит к созданию нового сокета.) Палее канал данных используется по назначению. По окончании передачи лиент закрывает сокет и тем самым канал данных. Сокеты FTP Во время сеанса FTP с удаленным компьютером вы, по существу, открываете ~эи различных сокета. Первый сокет соединяет вашу программу с каналом правления. Второй сокет, который ваша программа создает каждый раз, когда :й нужен канал данных, ожидает соединения с сервером. Третий сокет, который *. insock создает каждый раз, когда программа соединилась с FTP-сервером, 509 
Ш бибг илржэншх Интерне? получает (или передает) данные. В нашей книге будут использоваться следующие термины, чтобы различать эти три сокета. Первый сокет — сокет управления. В зависимости от контекста программы и ее назначения, этот сокет будет представлен переменной hControlSocket или hControlChannel. Второй сокет — ожидающий, он прослушивает канал в ожидании запроса на установление соединения от сервера. Для идентификации этого сокета в учебных программах будет использоваться переменная hListenSocket. Третий сокет — сокет данных. В учебных программах он будет представлен переменными hDataSocket или hDataChannel. Функция DemonstrateCommand предназначена для выполнения шагов, описанных в предыдущей схеме: во-первых, DemonstrateCommand вызывает функцию CreateListenSocket. Как вы узнаете, CreateListenSocket создает ожидающий сокет и сообщает серверу (по контрольному каналу), по какому адресу соединяться. Во-вторых, DemonstrateCommand передает (используя SendFTPCommand) FTP-команду серверу, которая заставляет сервер установить соединение. Как показано в программе DemonstrateCommand, QFTP2 передает команду NLST (которая направляется серверу, чтобы передать список имен файлов, имеющихся в текущем рабочем каталоге). Если вы решите поэкспериментировать с QFTP2, вы можете заменить ее любой командой, для выполнения которой необходим канал данных. В-третьих, DemonstrateCommand вызывает функцию AcceptDataConnection, чтобы воспринять запрос на установление соединения от сервера. В качестве параметра функции AcceptDataConnection передается дескриптор ожидающего сокета, а возвращает она дескриптор сокета данных. Как отмечено в комментариях, AcceptDataConnection закрывает ожидающий сокет после того, как он примет запрос на установление соединения от FTP-сервера: VOID DemonstrateCommand(SOCKET hControlChannel) SOCKET hDataChannel; SOCKET hListenSocket; UINT nReplyCode; IpszFunctionName = "DemonstrateCommand"; if ((hListenSocket = CreateListenSocket(hControlChannel)) == INVALID_SOCKET) return; // Команда NLST для тестирования if (nReplyCode = SendFTPCommand(hControlChannel, Открытие канала данных { "NLST\r\n") >= 400) 510 return; 
// Принять запрос сервера на соединение // Функция AcceptDataConnection() самостоятельно // закрывает уже ненужный сокет hListenSocket if ((hDataChannel = AcceptDataConnection(hListenSocket)) == INVALID_SOCKET) return; ReadDataChannel(hDataChannel, "NLST.CMD"); closesocket(hDataChannel); return; } Когда функция AcceptDataConnection возвращает дескриптор сокета данных, канал данных FTP открыт и готов к работе. Функция DemonstrateCommand, в свою очередь, вызывает функцию ReadDataChannel, чтобы получить данные. В качестве параметра функции ReadDataChannel передается дескриптор сокета. (Она должна знать, из какого сокета считывать данные.) ReadDataChannel также требует второго параметра, который попросту является именем файла. В большинстве случаев клиент записывает поступающие данные в локальный файл (фактически, поступающие данные и являются файлом). Поэтому QFTP2 записывает в файл любые данные, получаемые из канала данных. Обычно, программа-клиент FTP показывает результаты команды NLST в окне (это, скорее всего список файлов) или печатает результаты на экране, если у пользователя не компьютер, а просто текстовый терминал. Таким образом, посредством записи результата в файл вы можете изменить эту программу для пспользования команды RETR, чтобы получить файл с сервера. После возвращения результата функции ReadDataChannel DemonstrateCommand закрывает :окет данных и заканчивает работу. После того как функция DemonstrateCommand возвращает управление WinMain, QFTP2 заканчивает выполнение программы, так же как QFTP1. Ожидание запроса сервера 7ТР-клиент должен получить запрос на установление соединения от FTP-сервера до того, как передаст команду, для выполнения которой требуется канал санных. В QFTP2 эту задачу выполняет функция CreateListenSocket. С параметрами сокета, задающимися в функции CreateListenSocket, вам еще не приходилось встречаться. Далее показан исходный текст функции CreateListenSocket (без обработки ошибок): SOCKET CreateListenSocket(SOCKET hControlSocket) { SOCKADDR_IN sockAddr; SOCKET hListenSocket; IpszFunctionName = "CreateListenSocket"; 511 
Глава 17. Динамические библиотеки я приложениях Интернет if ((hListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))== INVALID_SOCKET) II ... обработать ошибку и выйти // Система самостоятельно назначит адрес сокета sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(O); // htons() здесь на // всякий случай sockAddr. sin_addr. s_addr = INADDR_ANY; // Связываем сокет if (bind(hListenSocket, (LPSOCKADDR)&sockAddr, sizeof(sockAddr))) // ...обработать ошибку и выйти // Ждем запрос на соединение от FTP-сервера if (listen(hListenSocket, QUEUE_SIZE)) II ... обработать ошибку и выйти } return(RequestDataConnection(hControlSocket, hListenSocket)); Для создания ожидающего сокета функция CreateListenSocket использует знакомую вам функцию socket. Однако обратите внимание на значение, которое функция присваивает структуре адреса сокета. В предыдущих учебных программах элементу sockAddr.sin_port присваивался номер официального порта протокола — ведь мы должны были установить соединение с сервером. Как видим, CreateListenSocket присваивает нулевое значение элементу порта протокола в структуре адреса сокета и символьную константу Winsock INADDR_ANY — элементу адреса. Используя ноль в качестве адреса порта, функция CreateListenSocket позволяет (современной) реализации Winsock (WINSOCK.DLL) назначить адрес порта самостоятельно. Конечно, программа могла бы назначить адрес FTP-сервера элементу адреса в адресной структуре сокета. В этом случае модуль TCP не направлял бы любые чужие запросы на установление соединения на ваш ожидающий сокет. Однако вероятность того, что другой хост (кроме FTP-сервера) запросит соединение по вашему IP-адресу и порту, назначенному Winsock, достаточно мала. Таким образом, определение INADDR_ANY в качестве адреса удаленного хоста хорошо подходит для данной реализации. Что такое имя сокета? Из функции CreateListenSocket вызываются две функции Winsock, которые мы до сих пор не использовали: bind и listen. Как обсуждалось в главах 7 и 8, программы обычно используют их для создания приложений-серверов. Так как FTP-клиент должен временно, во время открытия канала данных, действовать 512 
как сервер, программа-клиент FTP тоже должна использовать эти функции. Имя сокета состоит из трех компонент: IP-адреса хоста, номера протокола (символьная константа для UDP или TCP) и порта протокола, который идентифицирует конкретное сетевое приложение. Нам обычно не важно, какой порт используют наши приложения. Однако программа-сервер должна ожидать запроса на соединение на определенном порту, который программа-клиент будет использовать, чтобы соединиться с ним. Функция bind связывает имя (комбинация IP-адреса, номера протокола и порта протокола) с сокетом. Функция CreateListenSocket не заботится о том, какой порт протокола система назначает ожидающему сокету. Вместо этого программа QFTP2 сообщает FTP-серверу номер назначенного системой порта. Функция listen переводит сокет в режим пассивного прослушивания. Другими словами, она сообщает сокету, что он должен ожидать поступающие запросы на установление соединения. Ожидающий сокет подтверждает запросы на установление соединения и размещает их в очереди поступающих сообщений. Первый параметр функции listen сообщает Winsock, какой порт контролировать. Второй параметр определяет максимальное число поступающих запросов для помещения в очередь. Максимальное число запросов в очереди, разрешенных в Winsock версии 1.1, — пять. Функция listen не устанавливает соединение — она только предписывает сокету ожидать запросы на установление соединения и посылать подтверждение запросившей стороне. Другими словами, ожидающий сокет контролирует указанный адрес и передает запросы программе. Чтобы фактически установить соединение, программа должна вызвать функцию accept. Однако FTP-клиент подобно QFTP2 должен сообщить серверу, какой порт протокола ему обслуживать, до того, как клиент попробует установить соединение данных с FTP-сервером. Если вы изучите текст функции CreateListenSocket, то увидите, что CreateListenSocket вызывает функцию RequestDataConnection. Функция RequestDataConnectiоп читает имя сокета (комбинация IP-адреса, номера протокола и порта протокола), которое назначает функция listen, вызываемая в функции CreateListenSocket. После этого функция RequestDataConnection сообщает имя FTP-серверу. Использование FTP-команды PORT Функция RequestDataConnection сообщает FTP-серверу адрес для создания соединения данных FTP. RequestDataConnection использует функцию getsockname, чтобы определить, какой порт протокола Winsock назначил сокету ожидания. RequestDataConnection использует FTP-команду PORT, чтобы передать IP-адрес и порт протокола клиента через канал управления. Однако прежде, чем вы изучите команду PORT, вам надо понять некоторое специфическое поведение Winsock. Как вы узнали, протоколы TCP/IP связывают IP-адрес с сетевой интерфейсной картой, а не с самим компьютером. Компьютер может содержать несколько сетевых интерфейсных карт. (Как известно, такие компьютеры называются «multihomed».) Зак. № 1949 513 
Глава 17. Динамические библиотеки в приложениях Интернет Когда вы связываете сокет с местным адресом и определяете INADDR_ANY в качестве адреса сокета, Winsock не станет немедленно назначать ваш IP-адрес сокету (даже если ваш компьютер содержит только одну интерфейсную карту и, таким образом, имеет один IP-адрес). Другими словами, предположим, что вы создаете сокет и определяете INADDR_ANY в качестве его адреса. Затем предположим, что вы вызываете функцию bind, чтобы присвоить сокету местное имя (комбинацию из IP-адреса, номера протокола и порта протокола). Winsock немедленно назначит сокету порт, но подождет с IP-адресом. Winsock так и не назначит IP-адрес, пока не установит сетевое соединение. Тот факт, что Winsock не сразу назначает IP-адрес структуре сокета, может оказаться неожиданностью и стоить дополнительного времени на поиски ошибок, если вы не имеете представления о таком поведении Winsock (оно, однако, описано в спецификации). Приведенный ниже текст представляет собой определение функции RequestDataConnection (без обработки ошибок). Для того чтобы получить порт, назначенный Winsock при вызове bind из функции CreateListenSocket, функция RequestDataConnection вызывает функцию getsockname. SOCKET RequestDataConnection(SOCKET hControlSocket, SOCKET hListenSocket) SOCKADDR__IN sockAddr; int iLength; UINT nLocalPort; UINT nReplyCode; // Структура адреса сокета // Длина структуры адреса сокета // Локальный порт для // прослушивания // Код ответа FTP-сервера IpszFunctionName = "RequestDataConnection"; // Получаем адрес hListenSocket iLength = sizeof(sockAddr); if (getsockname(hListenSocket, (LPSOCKADDR)&sockAddr, SciLength) == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); wsprintf(gszCommandBuffer, "Error #%d occurred while getting listen socket name!!", iWinsockErr); MessageBeep(MB_ICONSTOP); MessageBox(NULL, gszCommandBuffer, IpszFunctionName, MB_OK|MB_ICONSTOP); return(INVALID_SOCKET); } 514 // Извлекаем локальный порт из hListenSocket nLocalPort = sockAddr. sin__port; 
Вторая стадия: передана данных // Теперь повторно используем структуру адреса сокета, // чтобы получить IP-адрес из управляющего сокета if (getsockname(hControlSocket, (LPSOCKADDR)fcsockAddr, SciLength) == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); wsprintf(gszCommandBuffer, "Error #%d occurred while getting control socket name!I", iWinsockErr); MessageBeep(MB_ICONSTOP); MessageBox(NULL, gszCommandBuffer, IpszFunctionName, MB_OK | MB_ICONSTOP) ; return(INVALID_SOCKET); } // Формируем команду PORT wsprintf(gszCommandBuffer, "PORT %d,%d,%d,%d,%d,%d\r\n", sockAddr. sin_addr. S__un. S_un_b. s_bl, sockAddr. sin_addr. S__un. S_un_b. s_b2, sockAddr.sin_addr.S_un.S_un_b.s_b3, sockAddr.sin_addr.S_un.S_un_b.s_b4, // Порядок байт в порту — сетевой. Это значит, что // FTP-сервер ожидает, что старший байт придет // первым. В персональных компьютерах ситуация // обратная — вначале передается младший байт, а // затем — старший, и это необходимо учитывать. // Подробнее о сетевом и компьютерном порядке // байтов см. главу 13 этой книги. nLocalPort & OxFF, nLocalPort » 8) ; // Указать порт, используемый для передачи данных, серверу if (nReplyCode = SendFTPCommand(hControlSocket, gs zCommandBuf f er) != 200) { wsprintf(gszCommandBuffer, "Error %d from PORT command to сервер!", nReplyCode); MessageBeep(MB_ICONSTOP); MessageBox(NULL, gs zCommandBuf f er, IpszFunctionName, MB_OK I MB_I CONS TOP) ; return(INVALID_SOCKET); } else 515 
Глава 17. Динамические библиотеки в приложениях Интернет return(hListenSocket); } Если вы пройдете эту функцию при помощи отладчика и исследуете содержимое структуры sockAddr после вызова getsockname, результаты могут вас удивить. Вы найдете, что структура адреса сокета содержит порт протокола, но адрес может быть неверным. Другими словами, библиотека Winsock назначила (и запомнила) адрес порта во внутренней структуре данных сокета, но не записала IP-адрес. Такое поведение не создает значительной проблемы. Вы должны передать ваш IP-адрес и местный адрес порта FTP-серверу. Чтобы получить местный IP-адрес, программа может вызвать функцию gethostname, которая возвращает имя DNS местного компьютера. Однако если вы выбираете такой способ, ваша программа также должна будет выполнять поиск в DNS, а это лишняя работа и большое количество ненужного сетевого трафика, только за тем, чтобы выяснить ваш собственный IP-адрес. В качестве альтернативы программа может вызывать функцию RequestDataConnection. Сначала, как показано в следующем примере программы, RequestDataConnection получает имя сокета ожидания. Помните, что функция getsockname извлекает имя сокета из структуры данных сокета и сохраняет его значение в локальной структуре адреса сокета, на которую указывает второй параметр функции getsockname: // Получаем адрес hListenSocket iLength = sizeof(sockAddr); if (getsockname(hListenSocket, (LPSOCKADDR)&sockAddr, SciLength) == SOCKETJERROR) / / ... обработать ошибку и выйти Затем функция извлекает и запоминает адрес порта из ожидающего сокета, так что программа знает, какой номер порта сообщить FTP-серверу: // Извлекаем локальный порт из hListenSocket nLocalPort = sockAddr . sin__port; Наконец RequestDataConnection вызывает функцию getsockname во второй раз, но с дескриптором управляющего сокета вместо дескриптора ожидающего сокета. Так как соединение на управляющем сокете активно, в его структурах данных содержится местный IP-адрес. Повторный вызов функции getsockname заставляет Winsock заполнить структуру sockAddr IP-адресом местного сетевого компьютера: // Повторно используем структуру адреса сокета, // чтобы получить IP-адрес из управляющего сокета if (getsockname(hControlSocket, (LPSOCKADDR)&sockAddr, SciLength) == SOCKET_ERROR) Winsock также помещает номер порта управляющего соединения в поле адреса порта. Однако, как показано выше, RequestDataConnection уже хранит адрес 516 
> Щ Dp* '■ r I Щ' pel ВД ” I Л кННЫ> порта ожидающего сокета в локальной переменной nLocalPort. Следующие несколько строк в RequestDataConnection форматируют строку, соответствующую FTP-команде PORT. Команда PORT выглядит примерно так (обратите внимание — между запятыми не должно быть пробелов): PORT 168,158,20,192,150,4 Как отмечено в комментариях, при размещении восьмибитных значений в позициях строки команды PORT необходимо соблюдать меры предосторожности. Перестановка значений байт местами — обычная ошибка, которую легко допустить, но тяжело обнаружить. Проблема состоит в том, что вы можете передать команду PORT с переставленными местами байтами, и это останется незамеченным. Но только до тех пор, пока сервер не попробует соединиться с неправильно указанным портом. Другими словами, если вы переставите местами байты номера порта в команде PORT, сокет будет ожидать соединение на одном порту, а сервер — пытаться установить соединение с вашей программой на другом. Скорее всего сервер выдаст код возврата 425 на канал управления, чтобы сообщить о том, что он не может открыть соединение данных. К сожалению, сервер не знает, почему. Сервер не подозревает, что клиент и не думает ожидать соединение на том порту, с которым сервер пытался установить соединение. После того как функция RequestDataConnection сформирует строку-команду PORT, она вызывает функцию SendFTPCommand, чтобы передать ее (через контрольный канал) серверу. Наконец, RequestDataConnection заканчивается, возвращая либо дескриптор ожидающего сокета, либо значение ошибки (INVALID_SOCKET ). Установление соединения с сервером Как показано выше, оператор возврата в функции CreateListenSocket включает запрос к функции RequestDataConnection. Другими словами, когда RequestDataConnection возвращается с дескриптором ожидающего сокета (если предположить, что нет ошибок), дескриптор ожидающего сокета возвращается в функцию DemonstrateCommand. Если дескриптор ожидающего сокета действителен, DemonstrateCommand передает тестовую команду (NLST) и вызывает функцию AcceptDataConnection. Без обработки ошибок AcceptDataConnectiоп — довольно маленькая функция, как и показано в нижеследующем тексте программы: SOCKET AcceptDataConnection(SOCKET hListenSocket) { SOCKET hDataSocket; SOCKADDR_IN sockAddr; int iAddrLength; IpszFunctionName = "AcceptDataConnection"; 517 
Глава 17. Динамические библиотеки в приложениях Интернет hDataSocket = accept(hListenSocket, (LPSOCKADDR)&sockAddr, SciAddrLength); // Ожидающий сокет закрывается за ненадобностью closesocket(hListenSocket) ; if (hDataSocket == INVALID_SOCKET) II ... обработать ошибку и выйти else return(hDataSocket); Как видим, функция AcceptDataConnection вызывает функцию Winsock accept и ждет соединения с FTP-сервером. Когда на порту появляется запрос на установление соединения, Winsock создает новый сокет и функция accept возвращает его дескриптор. Функция AcceptDataConnection сохраняет новый дескриптор сокета в hDataSocket и возвращает его в программу. Новый сокет предоставляет канал данных FTP, который как клиент, так и сервер могут использовать для передачи данных. Чтение из канала данных Когда функция AcceptDataConnection возвращает действительное значение дескриптора сокета данных в DemonstrateCommand, DemonstrateCommand вызывает функцию ReadDataChannel. Функция ReadDataChannel, показанная ниже, является оболочкой Winsock-функции recv: BOOL ReadDataChannel(SOCKET hDataSocket, LPSTR IpszFileName) IpszFunctionName = "ReadDataChannel"; if ((hFile = OpenFile(IpszFileName, (OFSTRUCT far *)&openFileBuff, OF_CREATE)) == HFILE_ERROR) // ...обработать ошибку и выйти } { char sDataBuffer[4096] int nBytesRecv; // Буфер канала данных // Счетчик принятых из канала // данных байтов // Дескриптор файла данных // Структура Windows для // открытия файла // Счетчик принятых и записанных // в файл байтов HFILE hFile; OFSTRUCT openFileBuff; LONG IData = 0L; do { 518 
Вторая стадия: передача данных nBytesRecv = recv(hDataSocket, (LPSTR)&sDataBuffer, sizeof(sDataBuffer), NO_FLAGS); lData += nBytesRecv; if (nBytesRecv > 0 ) { if (HFILE_ERROR == __lwrite (hFile, sDataBuffer, nBytesRecv)) // ... обработать ошибку и выйти } } while (nBytesRecv > 0); // Закрыть файл и проверить на наличие ошибки _lclose(hFile); if (nBytesRecv == SOCKET_ERROR) II ... обработать ошибку и выйти else { wsprintf(gszCommandBuffer, "%lu bytes written to %s\n"/ IData, lpszFileName) ; MessageBeep.(MB_ICONINFORMATION) ; MessageBox(NULL, gszCommandBuffer, IpszFunctionName, MB_OK|MB_ICONINFORMATION) ; } return (TRUE) ; } Как видим, функция ReadDataChannel использует цикл do-while для того, чтобы читать данные из указанного сокета. Этот цикл очень похож на те, с которыми вы столкнулись в предыдущих примерах программ. Однако ReadDataChannel также использует Windows-функции OpenFile и _write, создавая файл на жестком диске и записывая в него данные. Когда ReadDataChannel выходит из цикла, она сразу же закрывает этот файл. Второй параметр функции ReadDataChannel используется для передачи имени файла. Другими словами, программы должны определять имя и местоположение файла до вызова ReadDataChannel. Модули программы QFTPLIB (разрабатываемой в третьей части) используют данное определение функции ReadDataChannel, плюс обработка ошибок. Проблемы с именами файлов Соглашения об именах файлов, а также соглашения об именах каталогов меняются от одной операционной системы к другой. DOS и 16-разрядная Windows для персональных компьютеров используют соглашение «восемь- 519 
Глава 17. Динамические библиотеки в приложениях Интернет точка-три» для обозначения имен файлов (имя файла состоит из восьми символов плюс точка и плюс расширение из трех символов). UNIX-системы и 32-разрядные Windows (Windows NT и Windows 95) не столь ограничены в выборе имен. Программа, которая считывает и записывает файлы, созданные в других системах, должна приводить «чужие» имена в соответствие с соглашениями локальной системы. Показанная выше функция ReadDataChannel разработана так, что задачи присвоения имен файлам и задачи чтения и записи данных из канала данных в ней разделены. Чтобы не перегружать программные модули работой по преобразованию имен файлов, QFTP использует простой подход, согласно которому файл, принявший данные, называется именем команды, по которой эти данные поступили в систему. Например, QFTP пишет данные, поступившие по команде NLST в файл «NLST.CMD». Разумеется, каждая последующая команда с тем же именем перезапишет результаты выполнения предыдущей. На третьем этапе разработки (QFTPLIB) добавляются три функции, которые обеспечивают файловый ввод-вывод для программы-клиента FTP: ExtractFileName, CreateTransferFile и TransferFile. Функция ExtractFileName преобразует имя файла в формате UNIX в имя, допустимое в DOS. Функция CreateTransferFile просто создает файл с указанным именем и возвращает его дескриптор. Функция TransferFile считывает данные из канала данных и записывает их в файл на жесткий диск. Функция TransferFile очень похожа на функцию ReadDataChannel, однако в ней не используется цикл do-while. Вместо этого функция TransferFile вызывает функцию recv один раз и возвращает число байтов, считанных из канала данных. Другие модули программы могут создавать свой собственный цикл и вызывать функцию TransferFile из него. Программный модуль, который вызывает функцию TransferFile таким способом, может выводить информацию о состоянии процесса приема пользователю. Так как функция TransferFile возвращает число прочитанных байтов, программа может подсчитывать общее количество переданных байтов и показывать его пользователю. Обратите внимание на то, что функция ReadDataChannel не дает такой возможности, так как читает из канала данных в собственном внутреннем цикле, и программа не может получить информацию о состоянии процесса передачи. Еще раз о кодах ответа сервера Когда функция ReadDataChannel возвращает управление функции DemonstrateCommand, DemonstrateCommand закрывает сокет данных и возвращает управление WinMain. WinMain, в свою очередь, передает серверу команду QUIT, закрывает управляющий сокет и заканчивает работу — программа QFTP2 завершена. До перехода к третьему этапу разработки QFTP вы должны изучить функцию ReadFTPServerReply из QFTP2. Как уже говорилось, функция ReadFTPServerReply исполняет критическую задачу по разбору строк ответа FTPсервера и переводу кодов в числа. Имейте в виду, что FTP-программа может и не получить весь ответ сервера целиком, вызвав функцию recv однократно. 520 
ктор< mz переда Другими словами, хорошо разработанный FTP-клиент обычно выполняет некоторый цикл (подобный циклу do-while для чтения из канала данных) для считывания данных из канала управления. При этом имейте в виду некоторые тонкие моменты. Как известно, канал управления остается открытым в течение всего FTP-сеанса. Если программа вызывает функцию recv на блокирующем сокете, а сервер будет «молчать», то есть не будет передавать никаких данных, функция recv повиснет в бесконечном ожидании, что, по существу, остановит всю программу. Функция recv блокировала ваше единственное соединение с сервером. Функция recv желает получить ответ от сервера прежде, чем она разблокирует сокет, но ваша программа не может получить доступ к каналу управления, чтобы запросить у сервера ответ. Таким образом, функцию ReadFTPServerReply необходимо тщательно проектировать, чтобы избежать любой возможности возникновения такой ситуации. Так как версия функции ReadFTPServerReply из QFTP1 не считывает ответы сервера в цикле, QFTP1 обходит эту проблему. Однако если вы проектируете программу, основанную на QFTP1, и один из ваших модулей вызывает функцию ReadFTPServerReply в тот момент, когда сервер не передает ответ, даже версия ReadFTPServerReply без цикла будет подвешивать вашу программу. Как показано дальше, версия ReadFTPServerReply из QFTP2 выполняет простой цикл, в котором возникновение условий, подвешивающих программу, невозможно: UINT ReadFTPServerReply(SOCKET hControlChannel) { // Обратите внимание на то, что буфер-приемник данных // объявлен глобальным. Это сделано для того, чтобы // другие программные модули имели // доступ к тексту ответа сервера, а не только к // числовым кодам, возвращаемым этой функцией. int iBytesRead; int iBufferLength; int iEnd; int iSpaceRemaining; // Количество считанных из канала // управления байтов // Длина буфера для хранения // ответа серверу // Индекс буфера ответа // Свободное место, оставшееся в // буфере IpszFunctionName = "ReadFTPServerReply"; iEnd = 0; iBufferLength = iSpaceRemaining = sizeof(gsServerReplyBuffer); do { iSpaceRemaining -= iEnd; iBytesRead = recv(hControlChannel, 521 
Глава 17. Динамические библиотеки в приложениях Интернет (LPSTR)(gsServerReplyBuffer+iEnd), iSpaceRemaining, NO_FLAGS); i End+=iBytesRead; // Проверяем, не является ли комбинация CRLF // последней принятой. В противном случае, recv() // повиснет, ожидая следующий пакет if (*(gsServerReplyBuffer+(iEnd-2)) == 'Xr' && *(gsServerReplyBuffer+(iEnd-1)) == ' \n') break; } while (iBytesRead > 0 && iEnd < iBufferLength); if (iBytesRead == SOCKET_ERROR) II ... обработать ошибку и выйти gsServerReplyBuffer[iEnd] = '\0'; MessageBeep(MB_ICONINFORMATION); MessageBox(NULL, (LPSTR)gsServerReplyBuffer, IpszFunctionName, MB__OK | MB_ICONINFORMATION) ; // Извлекаем код из ответа сервера и возвращаем, // преобразовав в целое return(GetReplyCode(gsServerReplyBuffer)); } По причинам, которые станут понятными позже, версия ReadFTPServerReply из QFTP2 использует глобальный, а не локальный буфер приема. Как видим, в QFTP2 к ReadFTPServerReply добавляется цикл do-while. Этот цикл подобен тем, которые вы видели в предыдущих учебных программах. В операторе while проверяется, принимает ли функция recv данные и не переполнился ли буфер приема (чтобы не выйти за границы отведенной ему памяти). Единственная новая особенность цикла — следующий условный оператор: if (*(gsServerReplyBuffer+(iEnd-2)) == 'Xr' && *(gsServerReplyBuffer+(iEnd-1)) == 'Xn') break; Одно условие, которое могло бы заставить цикл зависнуть, — если приемный буфер содержит завершающий ответ сервера. Другими словами, сервер посылает ответ, оканчивающийся маркером конца строки NVT ASCII (возврат каретки и перевод строки, CRLF). В предыдущих примерах программ цикл продолжал вызывать функцию recv для чтения данных до тех пор, пока она не возвращалась с нулевой длиной прочитанной строки. В этой программе возврат нуля функцией recv также является условием для выхода из цикла. Однако в предыдущих 522 
Третьи стадйя; программах recv всегда возвращала ноль, потому что сервер закрывал ТСР-соединение после передачи последнего блока. В данном случае сервер не закроет канал управления FTP после передачи ответа (канал управления остается открытым в течение всего сеанса). Вместо этого сервер отмечает конец каждой строки ответа символами CRLF. Показанный здесь оператор if проверяет последний символ в приемном буфере и, если находит там символ LF, то прерывает цикл. Если в цикле не содержится проверки этого условия и вместо этого снова вызывается функция recv, функция будет бесконечно долго ждать ответа, потому что сервер не имеет больше данных для передачи, и комбинация CRLF указывает на это. Если вы пройдете цикл программы ReadFTPServerReply с отладчиком в тот момент, когда программа получает данные от канала управления, вы увидите, что это условие обычно служит признаком выхода из цикла. Другими словами, сервер обычно посылает ответ, который ReadFTPServerReply собирает при однократном вызове recv. Перейдя к третьей стадии разработки, вы узнаете, что к функции ReadFTPServerReply необходимо добавить обработку различных типов ответов сервера, в том числе и многострочных. Однако для версии программы QFTP2 показанный выше цикл do-while вполне достаточен. С QFTP2 можно поэкспериментировать — запускать ее с различными FTP-серверами Интернет и смотреть на различные варианты многострочных ответов, с которыми вы обязательно столкнетесь. Захват многострочных ответов (который делает и QFTP2) — не проблема. Проблема состоит в том, что ваша программа должна правильно разбирать ответ любого типа. Работая в реальном мире, ваш FTP-клиент может обнаружить в буфере не только многострочные ответы, но также и часть многострочного ответа, смешанную с другими ответами, состоящими из единственной строки. Другими словами, при реальной работе ваша программа может столкнуться с множеством ответов от сервера в буфере управляющего канала, и некоторые из ответов могут быть многострочными. Анализ приемного буфера канала контроля для получения правильного кода ответа очень быстро усложняется. На третьем этапе добавляется алгоритм, призванный решить эту проблему. Третья стадия: разработка DLL Работая с QFTP1 и QFTP2, вы положили основу для создания маленькой, приспособленной для ваших целей динамической библиотеки (DLL). Как уже говорилось, Winsock API не ориентирован на визуальное программирование. Один из подходов к визуальному программированию приложений Интернет состоит в создании внутренних, основанных на Winsock подпрограмм общего назначения, которые бы оставались скрытыми в течение всего процесса разработки. После того как вы определите и создадите их, ваши Winsock-под программы практически станут частью вашего собственного сетевого API. И тогда вы сможете пользоваться любимыми инструментами визуального программирования самым обыкновенным образом. Когда ваш визуальный модуль должен 523 
Глава 17. Динамические библиотеки в приложениях Интерне! выполнять ввод-вывод сетевых данных, он вызывает заранее определенные функции API из вашей пользовательской динамической библиотеки. Если раньше вы не создавали динамических библиотек Windows, вы упустили весьма мощный инструмент разработки. Слегка изменив прототипы функций, определенных в QFTP1 и QFTP2, вы сможете разместить их в DLL в фактически неизменном виде. В этом разделе обсуждается QFTPLIB — динамическая библиотека QFTPLIB.DLL. Исходные тексты всех приведенных здесь функций вы найдете в файле QFTPLIB.СРР на дискете, приложенной к книге. Здесь мы не будем обсуждать проблемы разработки динамических библиотек. Если вы не знакомы с ними и вам необходима более подробная информация, ищите ее в книгах по программированию для Windows. В частности, мы советуем прочитать главу 19 «Динамические библиотеки» книги «Программирование Windows 3. /» (Programming Windows 3.1, Third Edition, Charles Petzold, Microsoft Press, 1992). Сейчас вы можете просто изменить прототипы функций QFTP, как показано ниже: extern extern extern extern extern extern extern extern extern extern extern extern "C" "C" "C" "C" "C" "C" "C" "C" "C" "C" "C" "C" UINT FAR PASCAL GetReplyCode(LPSTR IpszServerReply) LPSTR „export FAR PASCAL GetFTPServerReplyText(VOID) UINT FAR PASCAL ReadFTPServerReply(SOCKET hControlChannel) UINT _export FAR PASCAL SendFTPCommand(SOCKET hControlChannel, LPSTR gszCommandBuffer) UINT __export FAR PASCAL ReadDataChannel(SOCKET hControlSocket, SOCKET _export FAR PASCAL ConnectFTPControlSocket(LPSTR IpszHost) SOCKET FAR PASCAL RequestDataConnection(SOCKET hControlSocket, SOCKET hListenSocket) SOCKET _export FAR PASCAL CreateListenSocket(SOCKET hControlSocket) SOCKET „export FAR PASCAL AcceptDataConnection(SOCKET hListenSocket) UINT „export FAR PASCAL TransferFile(SOCKET hControlSocket, SOCKET hDataSocket, HFILE hFile) void „export FAR PASCAL ExtractFileName(LPSTR IpPathString, LPSTR IpszFileName) HFILE „export FAR PASCAL CreateTransferFile(LPSTR IpszFileName) Как видим, каждая функция теперь содержит определение extern «С», которое предотвращает изменение имен компилятором C++. (Вы, вероятно знаете, что в части объектного файла, содержащей информацию для компоновщика, компиляторы C++ изменяют имена функций на значения, которые указывают типы параметров функций и тип возвращаемого значения. Эти значения позволяют произвести некоторую проверку ошибок еще в процессе компоновки исполняе¬ 524 
|Щ|етья стадия: фаарфтка|В||| мого модуля.) QFTPLIB описывает каждую функцию как FAR PASCAL, что требуется для Windows. Некоторые прототипы функций также включают ключевое слово _export. Программы Windows, загружающие DLL, могут вызывать экспортируемые функции (чьи прототипы включают модификатор _export). Другими словами, ваш компилятор экспортирует определения этих функций. Функции, которые не содержат модификатор _export, могут быть вызваны лишь функциями, расположенными непосредственно в DLL. Итак, если это слишком непонятно, пожалуйста, смотрите книгу по программированию в Windows, в которой подробно рассказывается о динамических библиотеках. А теперь просто имейте в виду, что функции без префикса _export предназначены только для внутреннего использования DLL. Другими словами, функции в QFTPLIB.DLL могут вызывать другие функции (находящиеся в контексте вызывающей функции) из QFTPLIB.DLL. Однако другие программы (типа Visual Basic или Visual C++) могут иметь доступ только к функциям QFTPLIB.DLL, определенным с префиксом _export. В следующих разделах рассматриваются некоторые незначительные изменения определений функций QFTP2, вносимые для использования их в QFTPLIB. В следующих абзацах также обсуждаются десять новых функций QFTPLIB (большинство из них весьма просты и легки для понимания). Обратите внимание, что QFTPLIB.DLL не включает функции QFTP2: WinMain, AnonymousFTPLogin и DemonstrateCommand. LibMain заменяет WinMain в DLL. Другие две функции (AnonymousFTPLogin и DemonstrateCommand) были нужны для экспериментирования, чтобы понять, как пользоваться FTP. Вместо AnonymousFTPLogin и DemonstrateCommand вы скоро сможете использовать визуальный интерфейс, создаваемый в QFTP на четвертой стадии. Небольшие дополнения Как упоминается в предыдущем разделе, в QFTPLIB добавляются десять новых функций. В данном разделе мы обсудим пять из них. В каждой программе на С должна быть функция main, и каждой программе Windows необходима функция WinMain, поэтому нет ничего странного в том, что в каждой динамической библиотеке должна быть функция LibMain. Функция LibMain, показанная ниже, чрезвычайно проста: int _export FAR PASCAL LibMain(HANDLE hlnstance, WORD { WORD wHeapSize, LPSTR IpszCmdParam) wDataSeg, if (wHeapSize > 0) UnlockData(0) ; // Размер "кучи" (динамически // распределяемой области памяти), // определенный в DEF-файле // Разблокируем текущий сегмент // данных return(1); } 525 
Глава 17. Динамически^ библиотеки в Подпрограмма инициализации LibMain может быть и довольно сложной — все зависит от требований подпрограмм, составляющих библиотеку. В нашем случае она просто разблокирует сегмент данных, который стартовая подпрограмма компилятора (LIBENTRY.OBJ для Microsoft и CODS.OBJ для Borland, например) захватывает на входе в DLL. WEP (Windows exit procedure) — функция обратного вызова, вызываемая для возврата ресурсов, захваченных DLL перед тем, как Windows выгрузит библиотеку. В ранних версиях Windows (до версии 3.1) WEP-функции требовались для каждой DLL. В версии Windows 3.1 функция WEP необязательна. Однако большинство DLL используют WEPфункцию, и QFTPLIB содержит простое определение функции, которое вы можете изменить, если решите создать собственную реализацию QFTPLIB.DLL: int _export FAR PASCAL WEP(int nParam) { return(1); } Как известно, для инициализации WINSOCK.DLL требуется вызывать функцию WSAStartup. Как показано ниже, QFTPLIB содержит простую функцию, названную LoadWinsock, которая является оболочкой для вызова WSAStartup: extern "С" BOOL _export FAR PASCAL LoadWinsock(VOID) { WSADATA wsaData; // Сведения о реализации Winsock if (WSAStartup(WINSOCK_VERSION, &wsaData) ) { MessageBeep(MB_ICONSTOP); MessageBox(NULL, "Could not load Windows Sockets DLL. ", PROGJNAME, MB_OK I MB_I CONS TOP) ; return (FALSE) ; } else return(TRUE); } Как видим, если LoadWinsock не может найти WINSOCK.DLL, она сообщает об этом. Если WINSOCK.DLL не загружен, когда QFTPLIB-программа начинает работу, функцию LoadWinsock можно изменить с тем, чтобы она искала WINSOCK.DLL в пути, установленном переменной PATH (или, возможно, по всему диску). Еще один способ: вызвав из функции LoadWinsock функцию LoadLibrary, вручную загрузить WINSOCK.DLL. В нашем случае QFTPLIB просто сообщает об ошибке и заканчивает выполнение. Если вы создаете качественные пользовательские программы, то должны позаботиться о том, чтобы функция возвращала структуру данных с информацией о Winsock и остальные программы могли бы проверить номер версии и другие характерис¬ 526 
Третья стадия: разработка DLL тики. Также неплохо было бы вызывать WSACleanup перед окончанием работы. Образец функции CloseWinsock — антагониста LoadWinsock приведен ниже: extern "С" BOOL ^export FAR PASCAL CloseWinsock(VOID) { if (WSACleanup()) { int iWinsockErr = WSAGetLastError(); wsprintf(gszCommandBuffer, "WSACleanup() caused error# %d"/ iWinsockErr); MessageBeep(MB_ICONSTOP); MessageBox(NULL, gszCommandBuffer, PROG_NAME, MB_OK|MB_ICONSTOP) ; return (FALSE) ; } else return(TRUE); } Если WSACleanup терпит неудачу, ваша программа может сделать немногое. Однако вызывая функцию CloseWinsock вместо непосредственного вызова WSACleanup, вы добиваетесь того, что пользователи получат внятную информацию о месте и смысле ошибки (при помощи функции WSAGetLastError). Другое простое дополнение к QFTPLIB — функция GetFTPServerReplyText. Как уже говорилось, в версии ReadFTPServerReply из QFTP2 для того, чтобы хранить строки ответа FTP-сервера, использовалась глобальная переменная по имени gsServerReplyBuffer. Тем самым, QFTPLIB позволяет другим функциям получить доступ к полному тексту ответа сервера. (Помните, что, как правило, функции QFTP сообщают только коды ответа в виде чисел.) GetFTPServerReplyText, показанная ниже, просто возвращает указатель на переменную, которая хранит ответы сервера. (Обратите внимание, что в QFTPLIB изменилось имя глобальной переменной буфера с gsServerReplyBuffer на gsServerReplyText.) extern "С" { LPSTR ^export FAR PASCAL GetFTPServerReplyText(VOID) return((LPSTR)gsServerReplyText); } В некоторых случаях программе кроме кода ответа может понадобиться текстовая строка-ответ сервера. FTP-команда PWD, например, возвращает рабочий каталог сервера в виде строки текста. Аналогично, команда SYST возвращает тип системы сервера в виде текстовой строки. Всякий раз, когда программе требуется доступ к полному тексту ответа сервера, она может вызвать функцию GetFTPServerReplyText и получить указатель на буфер ответов. Затем текст ответа можно скопировать в локальную область памяти и проанализировать его так, как требуется программе (например, извлечь из него текущий каталог или 527 
Глава 17. Динамические библиотеки в приложениях Интернет тип системы). Обратите внимание, что QFTPLIB использует один и тот же глобальный буфер для хранения всех поступающих ответов сервера. Поэтому, если функции нужен текстовый ответ, она должна вызвать GetFTPServerReplyText сразу после выдачи соответствующей команды. В противном случае ответ на следующую команду может уничтожить предыдущий. Разблокирование FTP-каналов Если сокет канала контроля — блокирующий и на нем вызывается функция recv, программа может зависнуть. Это произойдет в том случае, если блокирующий вызов recv бесконечно ожидает отсутствующий ответ сервера. Одно из решений этой проблемы — таймер, управляющий временем ожидания функции recv. Однако в самой функции recv такая возможность отсутствует. Чтобы избежать проблемы, для управляющего соединения можно использовать не блокирующий сокет. Как известно, для этого существует функция select. В QFTPLIB добавляется простая функция по имени IsReadyToRead, предназначенная для любого модуля программы, которому нужен не блокирующий сокет: extern "С" BOOL FAR PASCAL IsReadyToRead(SOCKET hSocket) { FD_SET setReadyToRead; // Проверочная последовательность // готовности сокета к чтению TIMEVAL timeTimeOut; // Тайм-аут ожидания изменения // состояния int nReady; // Флаг готовности к чтению IpszFunctionName = "IsReadyToRead"; timerc1ear(&timeTimeOut); FD_ZERO(&setReadyToRead); FD_SET(hSocket, &setReadyToRead); if ((nReady = select (NULL, (LPFD__SET) &setReadyToRead, NULL, NULL, &timeTimeOut)) == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); wsprintf(gszCommandBuffer, "Error %d from the select()function!!", iWinsockErr); MessageBeep (MB__ICONSTOP) ; MessageBox(NULL, gszCommandBuffer, IpszFunctionName, MB_OK|MB_ICONSTOP) ; return(FALSE); } return(nReady ? TRUE : FALSE); } 528 
.... Третья стадия. разработка ми В следующих абзацах функция IsReadyToRead рассматривается подробнее. Если необходимо, вы можете повторить предыдущие главы книги, в частности, главу 8, где особо обсуждалась функция select и вопросы, связанные с сокетами различных типов. Здесь мы предполагаем, что вы уже знакомы с этими материалами. Как уже говорилось, функция select позволяет программе следить за состоянием одного или нескольких сокетов. Для этой цели в Winsock используется структура fd_set: #define FD_SETSIZE 64 // Максимальное число сокетов в // одном наборе typedef struct fd_set { u_int fd__count; // Количество сокетов в наборе SOCKET fd__array [FD_SETSIZE] ; // Массив дескрипторов // сокетов } fd_set; typedef struct fd_set FDJSET; // Расширенный тип Windows Эти определения находятся в файле заголовков winsock.h. Структура fd_set — не более, чем список сокетов, расположенный в массиве. Если вы исследуете действия программы, содержащей структуры fd_set и управляющие макросы, при помощи отладчика, то обнаружите, что они весьма просты. Подробные объяснения сделали бы процесс сложнее, чем он есть на самом деле. В структуре типа fd_set хранится список дескрипторов сокетов. Когда модули программы вызывают функцию select, они передают ей один или несколько указателей на структуру fd_set. Winsock сличает сокеты со списком в структуре fd_set, чтобы узнать, какие из них программа хочет проверить. После этого Winsock проверяет сокеты по списку. Для каждого сокета Winsock проверяет, соответствует ли его состояние требуемому. Можно выяснить, находится ли сокет в состоянии чтения, записи или состоянии ошибки. Winsock подсчитывает число сокетов во всех списках указателей функции select и возвращает общее число выполняющих условия сокетов. В табл. 17.1 приведен список макросов FD__SET, обсуждавшихся в восьмой главе. Как видите, в Winsock определены четыре макроса для управления сокетами, входящими в структуру FD_SET. Таблица 17.1. Макросы, управляющие списком дескрипторов сокетов для функции select Имя макроса Функция FD_CLR Удаляет дескриптор сокета из списка. D_ISSET Возвращает значение true, если дескриптор сокета установлен и false — если нет. FD_SET Добавляет дескриптор сокета в список. FD_ZERO Инициализирует список дескрипторов сокетов. 529 
Глава 17. Динамические библиотеки в приложениях Интернет Содержимое структуры FDJSET остается неопределенным после ее создания. Другими словами, Winsock не присваивает элементам структуры никакого значения. Таким образом, для инициализации структуры FDJSET необходимо всегда вызывать макрос FDZERO. В Winsock определена структура TIMEVAL, которую вы также можете использовать с функцией select. Аргумент TIMEVAL сообщает функции select, как долго ждать, чтобы состояние сокета пришло в соответствие запрошенному условию: struct timeval { long tv_sec; // Секунды long tv_usec; // Микросекунды }; typedef struct timeval TIMEVAL; Если элементы структуры TIMEVAL равны нулю, функция select проверит текущее состояние каждого сокета и возвратится, ничего не ожидая. Обратите внимание, что установка элементов структуры TIMEVAL в ноль отличается от передачи значения NULL в качестве указателя для функции select. Если вместо указателя на существующую структуру TIMEVAL вы передадите NULL, функция select заблокируется и будет ждать, пока один из сокетов в списке не придет в соответствие требуемому состоянию. Обратите внимание, что происходит, когда вы передаете функции select указатель на структуру TIMEVAL, в которой оба элемента равны нулю. Когда элементы TIMEVAL обнулены, функция select считает, что вы хотите произвести не блокирующие операции на всех сокетах, внесенных в список. Другими словами, чтобы изменить блокирующий сокет (например, когда вы используете функцию socket для создания сокета) на не блокирующий, вы должны вызвать функцию select с указателем на структуру. TIMEVAL, в которой оба элемента равны нулю. После этого на указанных сокетах будут производиться только не блокирующие операции. Как вам известно из восьмой главы, не блокирующие сокеты приводят к возникновению некоторых других проблем. Тем не менее, если сокет не блокирующий, то и программа не может зависнуть из-за небрежного обращения к функции recv на канале управления. Другими словами, если сокет не готов для чтения, функция recv будет возвращаться с ошибкой, а не ждать бесконечно. (Эти ошибки и есть те проблемы, к которым ваши программы должны быть готовы и с которыми должны уметь обращаться.) В winsock.h определен макрос с именем timerclear, он устанавливает оба элемента TIMEVAL в ноль. Как показано ниже, функция IsReadyToRead вызывает timerclear, чтобы обнулить элементы структуры timeTimeOut: timerclear (SctimeTimeOut) ; FD_ZERO(&setReadyToRead); FD_SET(hSocket, &setReadyToRead); Затем IsReadyToRead вызывает макрос FD_ZERO для инициализации структуры setReadyToRead типа FD_SET. После этого IsReadyToRead вызывает макрос 530 
Третья стадия: FD_SET, чтобы добавить дескриптор сокета, переданный ему в качестве параметра, в структуру setReadyToRead. Наконец, IsReadyToRead вызывает функцию select, как показано ниже: if ((nReady = select(NULL, (LPFDJ3ET)&setReadyToRead, NULL, NULL, SctimeTimeOut) ) == SOCKET_ERROR) Если нет ошибок, IsReadyToRead возвращает значение true или false в зависимости от того, готов сокет к чтению или не готов. Функции из QFTPLIB, вызывающие функцию IsReadyToRead, фактически игнорируют возвращаемое значение, так как основная цель функции IsReadyToRead состоит в том, чтобы демонстрировать метод изменения блокирующего сокета на не блокирующий. Незначительные дополнения Теперь, когда вы знаете, как QFTPLIB заменяет блокирующий сокет на не блокирующий, вы понимаете, почему в определения трех функций, включенных в версию программы QFTP2 вносится незначительная поправка. Как вам известно, модули QFTP используют функцию ConnectFTPControlSocket, чтобы создать и сконфигурировать сокет для использования в качестве управляющего канала FTP. Аналогично, функция AcceptDataConnection возвращает дескриптор сокета для использования в качестве канала данных. Только перед тем, как обе функции (ConnectFTPControlSocket и AcceptDataConnection) возвращают соответствующий дескриптор сокета, они вызывают функцию IsReadyToRead. Следующий текст программы демонстрирует применение оператора return в ConnectFTPControlSocket: // Управляющий сокет переводится в не блокирующий режим // перед возвратом IsReadyToRead(hControlSocket); if (ReadFTPServerReply(hControlSocket) >= 400) return (INVALID__SOCKET) ; else return(hControlSocket); В следующем фрагменте программы демонстрируется оператор return для AcceptDataConnection. Как видим, AcceptDataConnection также вызывает функцию IsReadyToRead перед возвращением дескриптора сокета (на этот раз, канала данных) в вызывающую функцию: if (hDataSocket == INVALID__SOCKET) // ...обработать ошибку и выйти else { // Управляющий сокет переводится в не блокирующий режим // перед возвратом IsReadyToRead(hDataSocket); return(hDataSocket); } 531 
Глава 17* Динамические библиотеки в приложениях Интернет Прежде чем функция ConnectFTPControlSocket возвратит дескриптор сокета, который будет использоваться следующим модулем программы, ConnectFTPControlSocket создаст не блокирующий сокет контрольного канала. AcceptDataConnection делает то же самое для сокета канала данных. Так как сокет данных теперь не заблокирован, функция ReadDataChannel из QFTPLIB изменяется для того, чтобы вы (и пользователи вашей программы) были готовы к тому, что программа пробует читать из канала данных в то время, когда никаких данных там нет. Конечно, программа могла бы просто замалчивать возникновение такой ситуации. Однако теоретически, программные модули никогда не должны вызывать функцию ReadDataChannel, если входные данные отсутствуют. Чем позволять таким случаям проходить незамеченными, лучше показать сообщение, которое даст знать о возникновении какой-то проблемы. Во время тестирования программы такие ситуации можно отлавливать. А отловив — подумать, не следует ли усовершенствовать алгоритм программы. Вот заключительные операторы программы ReadDataChannel из QFTPLIB: if (nBytesRecv == SOCKET_ERROR) // ...обработать ошибку и выйти else if (IData == 0) { MessageBeep(MB_ICONINFORMATION); Me s sageBox(NULL, (LPSTR) "Nothing on the data channel to read!", IpszFunctionName, MB__OK I MB_ICONINFORMATION) ; return (FALSE) ; } else { wsprintf(gszCommandBuffer,"%lu bytes written to %s\n", IData, IpszFileName); MessageBeep(MB_ICONINFORMATION); MessageBox(NULL, gszCommandBuffer, lpszFunctionName, MB_OK|MB_ICONINFORMATION) ; } // Читаем управляющий канал и узнаем мнение сервера на этот // счет return(ReadFTPServerReply(hControlSocket)); Если счетчик байтов (содержимое IData) равен нулю, то ReadDataChannel сообщает об этом при помощи диалогового окна. Точно так же перед возвращением ReadDataChannel читает канал управления (вызывая ReadFTPServerReply) для получения сообщения сервера о завершении операции чтения. Обычно после успешной операции по передаче файла (или прерывания передачи) FTP-сервер передает код ответа 226 («Закрытие соединения данных. Требуемая операция выполнена успешно»). Как видим, функция ReadDataChannel теперь возвращает код-результат функции ReadFTPServerReply вместо true или false, как было в программах QFTP1 и QFTP2. 532 
Третья стадия: разработка DLL Ответы сервера Основные изменения в QFTPLIB — в функции ReadFTPServerReply. Однако они никак не связаны с перемещением функции в динамическую библиотеку. В основном, мы обеспечили более тщательный разбор ответов сервера. К сожалению, эта задача оказывается сложнее, чем можно было предположить на основании первых версий программ QFTP. Подробнее вы узнаете об этом при разработке устойчивой и стабильной функции ReadFTPServerReply. Вообще, разрабатывая любую функцию, необходимо начинать с изучения спецификации протокола, обращая внимание буквально на каждое предложение. Скучная проза документов RFC не должна запугать вас. Профессионалы, которые пишут RFC, — хорошо осведомленные специалисты, и они не любят тратить слова впустую. И уж если автор пишет о чем-нибудь, на это действительно имеется весомая причина. Далее, постарайтесь не писать программ, предполагающих что-либо, что не оговорено в RFC — можно столкнуться с проблемами. Следующая версия ReadFTPServerReply умеет обрабатывать большинство ответов FTP-сервера. И все равно, можно столкнуться с таким ответом, который будет обработан неверно. Если это произойдет, вы можете изменить QFTP2 с тем, чтобы использовать версию функции ReadFTPServerReply из QFTPLIB. Модифицированный вариант QFTP2 можно затем еще раз попробовать с «проблематичным» сервером. Неоценимую помощь в обнаружения таких проблем может оказать проход программы отладчиком. Цель же учебной программы состоит в том, чтобы сохранить логику простой настолько, насколько необходимо, чтобы понять все в ней происходящее. Если вы сталкиваетесь с проблемами из-за функции ReadFTPServerReply, то скорее всего правила нарушает не сервер, а именно она. После того как определена причина неполадки, функцию ReadFTPServerReply можно модифицировать соответствующим образом. Еще раз о цикле Do-While функции ReadFTPServerReply Вы помните, что в функции ReadFTPSrverReply из QFTP2 добавился цикл do-while. Как показано ниже, в проекте QFTPLIB основная структура функции осталась без изменений. Однако обратите внимание, что теперь ReadFTPServerReply анализирует содержимое буфера ответа сервера перед входом в цикл и непосредственно перед выходом из него: extern "С" UINT FAR PASCAL ReadFTPServerReply(SOCKET hControlChannel) { int iBytesRead; // Количество считанных из канала // управления байтов // Длина буфера для хранения // ответа сервера // Индекс буфера ответа сервера // Свободное место, остающееся в // буфере // Длина первого ответа в буфере int iBufferLength; int iEnd; int iSpaceRemaining; int iReplySize; 533 
Глава 17. Динамические библиотеки в приложениях Интернет lpszFunctionName = "ReadFTPServerReply"; // Буфер заполняется нулями (на всякий случай) _fmemset(gsReplyBuffer, 0, sizeof(gsReplyBuffer)); if ((iBytesRead = recv(hControlChannel, (LPSTR)(gsReplyBuffer), sizeof(gsReplyBuffer), MSG_PEEK)) > 0) // // ...до того, как извлечь данные, анализируем // содержимое буфера // iEnd =0; // Начинаем с нуля do { iSpaceRemaining -= iEnd; iBytesRead = recv(hControlChannel, (LPSTR)(gsServerReplyText+iEnd), iSpaceRemaining, NO__FLAGS) ; // Проверить, не является ли комбинация CRLF // последней принятой. В противном случае, recv() // повиснет, ожидая следующий пакет if (*(gsServerReplyText+(iEnd-2)) == '\r' && *(gsServerReplyText+(iEnd-1)) == '\n') // // ...проанализировать буфер ответа и возвратиться // } while (iBytesRead > 0 && iEnd < iBufferLength); if (iBytesRead == SOCKET_ERROR) // ...обработать ошибку и выйти gsServerReplyText[iEnd] = 'NO'; MessageBeep (MB_ICONINFORMATION) ; MessageBox(NULL, (LPSTR)gsServerReplyText, lpszFunctionName, MB_OK |MB_ICONINFORMATION) ; return(GetReplyCode(gsServerReplyText)); } Кроме того, функция ReadFTPServerReply теперь использует промежуточный буфер (gsReplyBuffer) для чтения ответа сервера с упреждением. Как показано, для обнуления gsReplyBuffer ReadFTPServerReply вызывает функцию 534 
Третья1 стадия: радрабстка Pbt _fmemset. В результате, в буфере не остается данных от предыдущей операции чтения, что облегчает его визуальное восприятие программистом, вооруженным отладчиком. Флаг MSG.PEEK Для предварительного анализа перед входом в цикл функция recv вызывается с флагом MSG_PEEK. Как вам известно из восьмой главы, этот флаг позволяет просмотреть данные во входной очереди предварительно, до их настоящего считывания. Наличие флага MSG_PEEK указывает Winsock, что данные из входной очереди необходимо скопировать в буфер. При этом транспортный уровень не удаляет скопированные данные из очереди, хотя в обычном случае они удаляются сразу после считывания: // Просматриваем данные заранее, до поступления из входной // очереди if ((iBytesRead = recv(hControlChannel, (LPSTR)(gsReplyBuffer), sizeof(gsReplyBuffer), MSG_PEEK)) > 0) { if ((iReplySize iBufferLength else iBuf ferLength = ReadReplyLine(gsReplyBuffer)) == 0) = iSpaceRemaining = sizeof(gsServerReplyText) = iSpaceRemaining = iReplySize; } else { MessageBeep (MB__ICONINFORMATION) ; MessageBox(NULL, (LPSTR)"Nothing on the control channel to read!", 1ps zFunct i onName, MB_OKIMB_ICONINFORMATION); return(999); // Если произошла ошибка, возвращаем 999 } Как видим, для предварительного анализа данных ReadFTPServerReply вызывает новую функцию по имени ReadReplyLine. Если recv вернула ноль считанных байтов, ReadFTPServerReply выводит сообщение о том, что в канале управления нет данных для чтения. Если данные присутствуют, ReadFTPServerReply передает функции ReadReplyLine указатель на буфер gsReplyBuffer. Функция ReadReplyLine Зот текст функции ReadReplyLine, которая проверяет буфер ответа сервера на выполнение нескольких условий: extern "С" UINT FAR PASCAL ReadReplyLine(LPSTR lps zReplyBuf f er) { 535 
Глава 17. Динамические библиотеки в приложениях Интернет LPSTR IpEOL; // Конец строки UINT nLimitReplyBytes; // Конец ответа PSTR pLastLineCode = "123?"; LPSTR lpLastLine; int i ; // IpszFunctionName = "ReadReplyLine"; nLimitReplyBytes = 0; if (*(IpszReplyBuffer+3) == MULTILINE__REPLY) { // Получить код из буфера ответа for (i = 0; i < 3; i++ ) *(pLastLineCode+i) = *(IpszReplyBuffer+i); // Ищем следующую строку по пробелу *(pLastLineCode+i) = ' ' ; // Ищем следующую строку в буфере if ((lpLastLine = __fstrstr(IpszReplyBuffer, pLastLineCode))) { // Проверяем, есть ли CRLF IpEOL = (LPSTR)_fstrstr(lpLastLine, EOL_MARKER); // Если в буфере не одна строка, учитываем длину // для чтения nLimitReplyBytes = IpEOL ? (UINT)((IpEOL - IpszReplyBuffer)+2) : 0; } else nLimitReplyBytes = 0; } else { // Если ответ состоит из одной строки, находим ее // конец IpEOL = (LPSTR)_fstrstr(IpszReplyBuffer, EOL_MARKER); } // Если маркер конца строки не найден, читаем все // подряд // Если найден — читаем только до него nLimitReplyBytes = IpEOL ? (UINT)((IpEOL - IpszReplyBuffer)+2) : return(nLimitReplyBytes); 0; } 536 
- Функция ReadReplyLine решает одну основную и одну дополнительную задачу. Прежде всего она гарантирует, что ReadFTPServerReply не сочтет два различных ответа сервера в буфере за один-единственный. А именно, если в буфере содержится не один код ответа, а несколько, ReadReplyLine находит конец первого ответа и сообщает функции ReadFTPServerReply, до какого места в буфере читать. Для этого ReadReplyLine решает, сколько байтов ReadFTPServerReply должна прочитать, и возвращает их количество в ReadFTPServerReply. Дополнительно ReadReplyLine помогает удостовериться, что частичный ответ сервера не будет расценен, как полный. Ключ к решению этих задач — определения символов конца строки и многострочного ответа сервера — находится в спецификации протокола FTP. Преобразование имен файлов в формат DOS Как уже говорилось, имена многих файлов в Интернет не удовлетворяют стандартам DOS. Так происходит из-за того, что большинство компьютеров Интернет работают в операционной системе UNIX. Функция ExtractFileName, текст которой приводится ниже, решает проблему совместимости в именах файлов для программ-клиентов FTP для Windows. Функция ExtractFileName создает имя файла DOS из строки символов, в которой содержится имя файла в формате UNIX. Сначала функция находит последний прямой слэш в строке. (Другими словами, функция находит последний каталог в строке, содержащей имя файла, если таковой имеется.) Затем функция входит в цикл for, чтобы запомнить следующие восемь символов строки. В цикле for также проверяется условие конца строки (завершающий ноль) и наличие точки, которая указывает на начало расширения имени файла. Наконец функция добавляет точку и запоминает следующие три символа строки, чтобы создать расширение. Вот исходный текст функции ExtractFileName: extern { "С" void _export FAR PASCAL ExtractFileName(LPSTR IpPathString, LPSTR IpszFileName) LPSTR lp; // Указатель общего назначения UINT i; // Индекс общего назначения UINT iExtLength = 0; // Длина расширения имени (после // дефиса) // Ищем последний прямой слэш (обозначение каталога в // нотации ОС UNIX/NT) if (lp = _fstrrchr(IpPathString, '/')) lp++; else lp = IpPathString; // Цикл до тех пор, пока: длина имени не станет меньше 8 // символов (допустимой для DOS); символ не является 537 
?: Глава 17. Динамические библиотеки в приложениях Интернет // точкой; указатель действителен for (i = 0; i < 8 && *1р i= && lp; i++) *(lpszFileName+i) = *(lp++); // Добавляем точку к имени, начиная писать расширение *(IpszFileName+i) = ' i++; // Находим последнюю точку, и следующие за ней три .// символа составят новое имя файла if (lp = _fstrrchr(IpPathString, ' for (iExtLength = 0; iExtLength < 3 && (*(lp)); iExtLength++) *(lpszFileName+(i++)) = *(++lp); // Имя файла заканчивается нулем * (IpszFileName+i) = 'Nonreturn; } Примечание: В функции ExtractFileName предполагается, что вызвавшая ее программа выделила достаточное количество памяти для хранения тринадцати символов имени DOS плюс нулевой завершающий символ. Для открытия (создания) файла-приемника данных от FTP-сервера программаклиент SockFTP (с которой вы познакомитесь подробнее в следующей главе) использует имена файлов, созданные функцией ExtractFileName. Клиент SockFTP будет использовать функцию CreateTransferFile, текст которой приведен ниже: extern "С" HFILE _export FAR PASCAL CreateTransferFile(LPSTR IpszFileName) { HFILE hFile; // Дескриптор файла данных OFSTRUCT openFileBuff; // Структура Windows для // открытия файла IpszFunctionName = "CreateTransferFile"; if ((hFile = OpenFile(IpszFileName, (OFSTRUCT far *)&openFileBuff, OF_CREATE) ) == HFI LE__ERROR) ' { _lclose(hFile); wsprintf(gszCommandBuffer,"Error creating file: %s\n", IpszFileName) ; MessageBeep(MB_ICONSTOP); 538 
Третья стадия: разработка Г } MessageBox (NULL, gszCoimnandBuf f er, MB_OK I MB_ICONSTOP) ; } return(hFile); 1ps z Func t i onName, Как видим, чтобы создать файл, функция CreateTransferFile вызывает Windowsфункцию OpenFile с установленным флагом OF_CREATE. Если создание файла вызвало ошибку, функция CreateTransferFile выводит сообщение. Иначе она возвращает дескриптор только что созданного файла. Программа-клиент SockFTP передает дескриптор файла в функцию TransferFile, текст которой приведен ниже: extern "С" UINT „export FAR PASCAL TransferFile(SOCKET hControlSocket, SOCKET hDataSocket, HFILE hFile) { char szFileData[1024]; // Буфер данных файла int nCharRecv; // Счетчик принятых символов char szMsg[100]; // Буфер общего пользования // для сообщений nCharRecv = recv(hDataSocket, (LPSTR)&szFileData, sizeof(szFileData), NO_FLAGS); if (nCharRecv > 0 ) { if (HFILE_ERROR == „lwrite (hFile, szFileData, nCharRecv)) { _lclose(hFile); wsprintf(szMsg, "%d Error occurred during recv()!", nCharRecv); MessageBox(NULL, szMsg, PROG_NAME, MB_OK|MB_ICONSTOP); return(HFILE_ERROR); } } else if (nCharRecv == SOCKET„ERROR) { „lclose(hFile); nCharRecv = WSAGetLastError(); wsprintf(szMsg,"%d Error occurred during recv()!", nCharRecv); MessageBox(NULL, szMsg, PROG„NAME, MB__OK|MB__ICONSTOP) ; return(SOCKET„ERROR); } 539 
Глава 17. Динамические библиотеки в приложениях Интерне? if (nCharRecv == 0) { _lclose(hFile); ReadFTPServerReply(hControlSocket); } return(nCharRecv); } Можно заметить, что функция TransferFile очень похожа на функцию ReadDataChannel, рассмотренную ранее в этой главе. Однако, как уже отмечалось, функция TransferFile не входит в цикл do-while подобно ReadDataChannel. Вместо этого TransferFile вызывает функцию reev один раз и возвращает число байтов, прочитанных из канала данных. Функция TransferFile записывает любые полученные данные в файл, который задан дескриптором, переданным ей в третьем параметре (hFile). Если функция reev возвращается с нулевым количеством байтов, прочитанных из канала данных (указание на то, что сервер закрыл канал данных), функция TransferFile закрывает файл и читает ответ сервера из канала управления. Чтобы определить, закончил ли сервер работу, вызывающий модуль программы может проверить байт, возвращаемый функцией TransferFile. Если он равен нулю, это значит, что сервер закончил передачу и что функция TransferFile закрыла файл данных на жестком диске. Подводя итоги В этой главе вы узнали, как создать программу, которая эффективно демонстрирует ключевые особенности достаточно сложного протокола передачи файлов по сети Интернет. Вы также узнали, как переместить функции, используемые в этой программе, в динамическую библиотеку Windows. Обратите внимание на то, что ни в одной из этих функций не вызываются асинхронные функции Winsock. Как вы обнаружите в следующей главе, ограничивая библиотеку DLL, основанную на Winsock, исключительно синхронными функциями, вы создаете инструмент, который можно использовать в среде Visual Basic, а также в среде C/C++. Среды визуального программирования предлагают множество полезных возможностей, особенно для тестирования и экспериментирования. Вы можете использовать простые DLL типа QFTPLIB, как опытные образцы для проверки алгоритмов программы и ее работы в сети. Ключевые элементы приложения станут понятнее при испытании программ и экспериментировании с реальными сетевыми компьютерами Интернет. Если вы пишите программы на языках C/C++, исходя из этих результатов, вы можете начать разработку более сложных программ и библиотек DLL, эффективно использующих асинхронные функции Windows. Если вы используете другие среды программирования (типа Visual Basic), где асинхронные функции Windows имеют ограниченное значение, вы можете продолжать разрабатывать сложные сетевые приложения, основанные на простых реализациях DLL типа 540 
■ ■ 1ГИ QFTPLIB. В следующей главе вы будете разрабатывать программу FTP-клиента, которая использует Visual Basic для доступа к функциям QFTPLIB, созданным в этой главе. Вы узнаете, что среду визуального программирования можно объединить с простой динамической библиотекой, основанной на Winsock, что позволит быстро создавать сетевые приложения и инструментарий для работы с Интернет. Прежде чем продолжить чтение, проверьте, хорошо ли вы усвоили следующие ключевые понятия: S FTP-клиенты инициируют TCP-соединение управляющего канала FTP. У FTP-серверы инициируют TCP-соединение канала данных FTP. S FTP-клиенты используют ожидающий сокет, чтобы обнаружить запрос на установление соединения по каналу данных от FTP-сервера. S Программы-клиенты конфигурируют ожидающий сокет, действуя подобно серверу. S Флаг MSG_PEEK используется при вызове функции recv для предварительного анализа данных — до того, как они поступят из входной очереди. S Winsock-программы легко встраиваются в динамическую библиотеку Windows, создавая базу, которую, в дальнейшем, смогут использовать другие приложения. 
Глав Визуальные приложения Интернет Инструментом визуального программирования, как правило, называется любое изделие, позволяющее рисовать объекты, например диалоговые окна, вместо того, чтобы писать эквивалентный код. Инструменты визуального программирования управляются событиями, а не выполняются в виде процедур. Они создают асинхронное, типа «наведи-и-щелкни», окружение среды Windows. Благодаря популярности асинхронных операционных систем и их стандартных графических интерфейсов (GUI) все больше и больше разработчиков приложений используют визуальные инструменты программирования. Язык программирования Visual Basic фирмы Microsoft — вероятно, самый популярный из существующих инструментов визуального программирования. Хотя сейчас мы сосредоточимся на использовании Visual Basic в написании Интернет-программ, эта глава не посвящена программированию на нем. Методы, рассмотренные в этой главе, можно использовать с любыми, предпочитаемыми вами визуальными инструментами программирования. В данной главе мы покажем, как функции динамической библиотеки для FTP-клиента объединя- 542 
SockFTP в действии ! . •••...:• • • •;'. ." .Л-:: V\ . '• •. ;:;у: •- < ются с интерфейсом пользователя, создаваемым инструментом визуального программирования. В итоге мы получим полностью функциональную программу-клиент FTP. В этой главе вы узнаете: ♦ Как эффективно использовать Winsock-функции, скрытые в динамической библиотеке Windows. ♦ Как создать несколько простых подпрограмм, которые смогут работать вместе для выполнения сложных сетевых действий. ♦ Как простая динамическая библиотека Windows объединяется с другими программными модулями, позволяя создавать полностью функциональные и сложные программы-клиенты. ♦ Как инструменты визуального программирования используются для разработки новых инструментов — для испытаний и экспериментирования с Интернет. SockFTP в действии Чтобы без труда разобраться в проекте программы-клиента FTP, необходимо запустить программу SockFTP. Увидев программу SockFTP в действии, вы сразу поймете назначение большинства ее функций. Чтобы запустить SockFTP, дважды щелкните мышью на изображении SockFTP — оно находится в окне программной группы «Internet Programming». Главное окно SockFTP выглядит, как показано на рис. 18.1. По умолчанию в SockFTP заданы параметры для входа в анонимный FTP-сервер NIC.DDN.MIL — основное хранилище документов RFC. Для начала щелкните мышью на кнопке Connect. SockFTP, в свою очередь, попробует установить соединение с портом 21 (порт протокола FTP) сервера NIC.DDN.MIL. После того как SockFTP соединится с хостом, он попытается зарегистрироваться в системе, используя параметры в текстовых окнах USER ID и PASSWORD. Если регистрация прошла успешно, SockFTP передает команды в такой последовательности: PWD, SYST и NLST. Если FTP-сервер реагирует на команду NLST, вы заметите активность жесткого диска — SockFTP читает все данные, которые система получает через канал данных, и записывает их на жесткий диск. В качестве имени файла SockFTP использует название той FTP-команды, которая вызвала передачу данных. Например, после того как закончится передача по команде NLST, вы найдете на жестком диске файл, названный NLST.CMD. Если вышеизложенное вам знакомо, так и должно быть. SockFTP просто вызывает функцию ReadDataChannel, которую вы создали в программе QFTPLIB. После того как сервер закрывает канал данных, SockFTP читает NLST.CMD с жесткого диска и выводит имена файлов в окно-список, расположенное с правой стороны главного окна SockFTP. 543 
Глава 18. визуальные приложения Интернет VB Sockman FTP Host: USER ID: NIC.DDN.MIL anonymous Ю Internet Programming SockMan FTP PASSWORD: System Type: guest 1 jj|;;fiOl Working Dir: fc| <§> IMAGE (Binary) О ASCII Рис. 18.1. Окно программы SockFTP Показав результаты выполнения команды NLST, SockFTP разрешает доступ к пяти кнопкам (команды Name List, Chg Working Dir, CWD Up, List и Retrieve), которые управляют действиями сервера, как показано на рис. 18.2. Полосу прокрутки можно использовать для перемещения по окну-списку файлов. Найдите таким образом каталог rfc. Щелкните мышью на имени каталога rfc, чтобы его выбрать (при этом подсветка имени каталога изменится), и щелкните мышью на кнопке Chg Working Dir. SockFTP, в свою очередь, использует пункт, выбранный в окне List (каталог rfc) в качестве параметра FTP-команды CWD. Другими словам, щелкните мышью на кнопке Chg Working Dir, и SockFTP передаст FTP-команду CWD с параметром «rfc». Если сервер примет команду, он изменит рабочий каталог вашего FTP-сеанса на каталог rfc. Если SockFTP получит от сервера удовлетворительный код ответа, SockFTP передаст команду NLST, чтобы показать содержимое каталога rfc. Другими словами, каждый раз, когда вы щелкаете на кнопке Chg Working Dir и SockFTP получает в ответ разрешение от сервера, SockFTP автоматически передает команду NLST, чтобы показать содержимое нового рабочего каталога. 544 
VB Sockrnan FTP SockFTP в действии Host NIC.DDN.MIL USER ID: anonymous tt internet Рго£гяттщ£ SockMan FTP PASSWORD: System Type: guest ibis&arttkci | ■■■I UNIX Type: L8 Working Dir: /rfc 4®lJ 257 пшп л insiMiiil 226 150 NLST 250 CWD rfc 226 150 NLST Logged into NIC.DDN.MIL 215 SYST 257 PWD 230 PASS guest 331 rfc-by-author.txt rfc-by-title.txt rfc-index.txt rfcl 0.txt rfc1000.txt rfcl 001 .txt rfc1002.txt rfcl 003.txt rfcl 004.txt rfcl 005.txt rfc1006.txt rfcl 007.txt rfcl 008.txt © HMAiSE fBlnairyj] О ASCII &etue^l Puc. 18.2. Окно программы SockFTP Теперь попробуйте с помощью полосы прокрутки найти файл по имени rfcindex.txt. А найдя, выберите его из списка и нажмите кнопку Retrieve. SockFTP, з свою очередь, начнет операцию по передаче файла, копируя rfc-index.txt на жесткий диск вашего компьютера. (В файле rfc-index.txt находится самый свежий список документов RFC.) После того как передача файла начата, в текстовые окна Host, USER ID, PASSWORD и System Type выводится информация о процессе передачи. Когда передача файла закончится, SockFTP выведет окно сообщения, как показано на рис. 18.3. Нажатие кнопки CWD Up приводит к передаче FTP-команды CDUP — и сервер меняет рабочий каталог на родительский. Если в ответ на команду CDUP 226 Transfer complete. шщт ■ ■ За к. № 1949 Рис. 18.3. Окно сообщения, выводимое SockFTP после успешной передачи файла 545 
Глава 18. Визуальные приложений Интернет SockFTP получит разрешение от сервера, он самостоятельно передаст команду NLST — и вы получите содержимое нового рабочего каталога. В любое время команду NLST можно передать вручную, нажав кнопку Name List. Нажатие кнопки List заставляет SockFTP передать FTP-команду LIST, в ответ на которую вы получите более подробную информацию о файлах в рабочем каталоге сервера. Нажатие кнопки Retrieve заставляет SockFTP передать FTPкоманду RETR, чтобы получить имя файла, выбранного в окне-списке. Если вы хотите закончить работу с SockFTP, нажмите кнопку Disconnect (если вы все еще соединены с FTP-сервером) и после этого — кнопку Exit, чтобы завершить программу. Цели проекта Хотя программа SockFTP полностью функциональна, центром внимания в этой реализации служит демонстрация визуальных методов программирования и операций протокола FTP. Другими словами, SockFTP не обеспечивает типичную обработку ошибок, которая требуется для FTP-клиента коммерческого качества. Например, когда вы щелкаете мышью на кнопке Retrieve, SockFTP просто извлекает выбранный в настоящее время пункт из окна-списка и передает его значение, как параметр, с FTP-командой RETR. Если вы предварительно нажали кнопку List, чтобы получить детальный список файлов и каталогов, FTP-сервер не поймет параметр, который SockFTP передаст стсомандой RETR. Такое поведение программы — не изъян в проекте SockFTP, а побочный продукт быстрой макетной разработки. Как известно, сила визуального программирования состоит в том, что вы можете быстро разрабатывать или изменять програм-. мы. Визуальный проект SockFTP — не исключение. Вы можете легко экспериментировать с различными командами и операциями, работая со многими FTP-серверами. Иногда вы можете узнать больше о протоколе, наблюдая, как сервер обращается с ошибочными командами и параметрами. Иногда, вы можете предсказать результаты, а в других случаях — нет. Вы можете проверить понимание протокола, передавая различные команды и рассматривая результаты их выполнения. Если сервер отвечает не так, как ожидалось, не огорчайтесь — существует множество тонких моментов, связанных с протоколом FTP, которых вы еще не знаете. Например, незначительная деталь в одной из процедур Click в начальной реализации SockFTP заставила FTP-сервер сообщить, что он не понимает команду CDUP. Более близкое рассмотрение модуля показало, что строка команды CDUP включала пробел между CDUP и CRLF. В спецификации FTP это однозначно запрещается. Однако с протоколами, в которых ответы сервера определены неясно, вы могли бы легко изменять командные строки, передавать их и наблюдать за поведением сервера. Все примеры программ, описанные в книге, широко используют окна сообщений. Окна сообщений удобны и сильно помогают в обучении. Однако они могут 546 
!4е*ш npoeoq препятствовать нормальному использованию программы. Чтобы сделать программу SockFTP более полезной и не уменьшить при этом ее обучающее значение, исходные файлы на дискете, приложенной к книге, содержат описание двух окон сообщений QFTPLIB внутри операторов #IFDEF. Первый оператор #IFDEF расположен в конце функции ReadFTPServerReply, как показано ниже: #ifdef _DEBUG MessageBeep (MB__ICONINFORMATION) ; MessageBox(NULL, (LPSTR)gsServerReplyText, IpszFunctionName, MB_OK I MB__ICONINFORMATION) ; #endif Если вы имеете опыт работы с компилятором Microsoft Visual C++ , то знаете, что Visual C++ позволяет хранить опции компиляции файла для окончательной и для отладочной версий вашего исходного кода одновременно. Когда вы выбираете опцию компиляции, чтобы создать отладочную версию программы, Visual C++ автоматически определяет символьную константу _DEBUG. Таким образом, когда вы собираете отладочную версию вашей программы, то разрешаете компиляцию кода, расположенного в пределах оператора #IFDEF. Другой оператор #IFDEF расположен в конце функции ReadDataChannel, как показано здесь: #ifdef _DEBUG else { wsprintf(gszCommandBuffer,"%lu bytes written to %s\n", lData, IpszFileName); MessageBeep(MB_ICONINFORMATION); MessageBox(NULL, gszCommandBuffer, IpszFunctionName, MB_OK | MB_ICONINFORMATION) ; } #endif Окна сообщений, скрытые таким образом в функциях ReadFTPServerReply и ReadDataChannel, сообщают о каждом выполнении операции recv для канала контроля FTP и каналов данных. Если вы изменяете программу SockFTP или сталкиваетесь с проблемами, то можете повторно собрать QFTPLIB с определенной константой _DEBUG. Это позволит окнам сообщений активизироваться и поможет контролировать работу DLL. Однако для нормальной работы программы эти два окна сообщений не нужны. Примечание: Все компиляторы позволяют определять константы, подобные JDEBUG. Если вы используете компилятор Borland C++, взгляните в его руководство для получения инструкций о том, как определять постоянные типа _DEBUG, чтобы управлять компиляцией программ. Если вы выполните программу SockFTP, то увидите окно-список с левой стороны окна SockFTP, в котором пробегает диалог между SockFTP и FTP-сервером. В 547 
Главе 18- Визуалк»нь1в приложения Рттштш течение FTP-сеанса SockFTP добавляет в это окно и некоторую другую информацию. SockFTP вставляет каждый новый регистрационный вход в систему сверху окна-списка. Сначала такое поведение может показаться странным. Было бы логичнее, если бы каждая новая регистрация входа в систему добавлялась в конец окна-списка. Однако если вы добавляете новые строки в конец окна-списка для того, чтобы найти результаты выполнения последней команды сервера, вы должны постоянно прокручивать окно-список вниз. Если вставлять команды сверху, то последние результаты выполнения команд всегда будут на виду. Обратите внимание на то, что инструменты визуального программирования помогают вам не только быстро создавать опытные образцы приложений и испытывать их, но и изучать поведение серверов и протоколов Интернет. Несмотря на то, что SockFTP — не полностью функциональный FTP-клиент, этот проект обеспечивает базу для построения мощного инструмента для экспериментирования е Интернет. На протяжении всей книги модель учебных программ обеспечивала превосходную базу для обучения. Кроме этого, вы можете легко преобразовать учебные программы в функции для пользовательской DLL. Отталкиваясь от пользовательской DLL, вы можете строить даже более мощные, чем представленные в этой книге, инструменты, которые помогут вам экспериментировать с различными программными проектами для Интернет. Инструменты для экспериментирования можно создавать, пользуясь любым инструментом программирования. Однако обычно визуальные инструменты программирования позволяют быстрее построить опытный прототип. Visual Basic — особенно мощный инструмент для исследования и экспериментирования благодаря его интерпретирующей природе. Использование библиотеки DLL, исполняющей сетевые операции низкого уровня, позволит вам быстро проверить программные проекты в сети с очень небольшими затратами на программирование. Комбинация пользовательской DLL и Visual Basic позволит вам сосредоточиться на окончательном проекте программного приложения. Например, чтобы сделать SockFTP более похожим на FTP-клиента, командные кнопки автоматически передают FTP-команды с правильным синтаксисом. Чтобы обеспечить большую гибкость, вы могли бы изменить SockFTP и добавить другое текстовое окно, принимающее команды, которые бы вы печатали во время FTP-сеанса. Другими словами, SockFTP реализует лишь немногие из доступных команд FTP. Вы могли бы добавить к SockFTP текстовое окно командной строки для эксперимента с другими FTP-командами и затем решить, какую команду добавить к SockFTP. Вы можете изменить SockFTP, чтобы соединяться с различными портами сервера. Другими словами, вы можете использовать SockFTP, намереваясь создать общее приложение Интернет для использования с различными протоколами и серверами. Однако для этого вам придется изменить несколько функций в динамической библиотеке QFTPLIB. Простые программные модели в предыдущих главах продемонстрировали, как это делается. 548 
Шетт ттшшш^т : Как вы используете SockFTP — ваше дело. Однако если вы серьезно настроены на программирование приложений Интернет, то найдете множество применений для SockFTP или некоторых частей учебного проекта. Как только вы столкнетесь с новыми требованиями, которые не поддерживаются QFTPLIB.DLL, вы можете разработать простую программу, которая моделирует новое требование. Затем вы можете добавить разработанные функции в собственную версию DLL. Если вы — прежде всего программист на Visual Basic, вы вероятно будете брать из экспериментальных проектов этих программ приложения Visual Basic. Если вы — программист на C/C++, вы можете получить из этих программ мощные инструменты для разработки программного обеспечения. Другими словами, независимо от того, как вы разрабатываете конечные приложения, вы можете использовать инструменты визуального программирования типа Visual Basic, чтобы повысить производительность. Например, выполните SockFTP, исследуйте тексты программы и поэкспериментируйте с различными модификациями. После этого спросите себя, сколько времени потребуется, чтобы выполнить те же самые задачи в среде С/C++? В следующих разделах рассматриваются детали реализации SockFTP. Исследуя различные компоненты, не рассматривайте SockFTP только как FTP-клиента. Если вы поступите так, то упустите настоящее значение и возможности программы. Вы можете купить или получить бесплатно гораздо лучших FTP-клиентов. Однако вы вряд ли найдете инструмент экспериментирования с Интернет, подобный SockFTP, с полным исходным текстом, легко приспосабливаемый к вашим персональным потребностям. Основная цель проекта SockFTP состоит в том, чтобы создать именно такой, доступный для вас инструмент. Цель следующих разделов в том, чтобы объяснить этот инструмент в достаточной детализации, чтобы вы могли легко изменить его, как вам потребуется. Другими словами, работа с SockFTP — бесконечный процесс, который вы можете закончить, когда удовлетворите собственные потребности. Ваши возможности В этой главе обсуждается проект программы-клиента FTP на Visual Basic, который обращается к динамической библиотеке QFTPLIB, разработанной в предыдущей главе. Если вы предпочитаете использовать другой инструмент визуального программирования, просто воплотите в жизнь алгоритмы, обсуждаемые в этой главе, при помощи вашего любимого инструмента. Тексты программ, рассмотренных в этой главе, относительно просты. Хотя опытные программисты на Visual Basic знакомы с методами использования DLL, большинство не часто пользуются их преимуществами. Вместо этого пишущие на Visual Basic предпочитают использовать VBX (Visual Basic Extensions). Файлы VBX — тоже динамические библиотеки Windows, но с несколькими особенностями, специально предназначенными для программирования на Visual Basic. К сожалению, 32-разрядные версии Windows не поддерживают VBX. Все версии Windows поддерживают и будут продолжать поддержать 549 
Глава 18. Визуальные приложение Иит^р;‘ет стандарт Windows DLL. Для преобразования основанных на VBX программ для работы с 32-разрядными версиями Windows VBX необходимо привести к новому стандарту Microsoft OLE. Вы узнаете, как использовать DLL с инструментами визуального программирования типа Visual Basic, прилагая минимум дополнительных усилий. Вы можете также легко перенести основанную на DLL программу Visual Basic в Windows с 32-разрядной архитектурой. Однако если вы хотите разрабатывать программы, основанные на VBX, и переносить их в 32-разрядные версии Windows, то должны сначала узнать, как использовать OLE в Visual Basic. Кроме того, вы должны узнать, как преобразовать все ваши VBX в OLE. Если вы планируете программировать для Windows в будущем, вы должны или использовать OLE, или проектировать новые программы на Visual Basic при помощи стандарта Windows DLL. Если вы предпочитаете временно избежать глубокого изучения OLE, то можете проектировать пользовательский интерфейс, основанный на DLL, который будет одновременно поддерживать Visual Basic и программы на C/C++. Если вы используете подход DLL при разработке Winsock-программ, то можете легко поддерживать широкое разнообразие сетевых программ на Visual Basic и на C/C++, которые вдобавок выполняются как в 32-разрядных, так и в 16-разрядных версиях Windows. В этой главе не обсуждается программирование на Visual Basic с VBX или OLE. Центр внимания этой главы — использование Windows DLL (особенно, QFTPLIB.DLL) с Visual Basic. Как уже говорилось, вы можете использовать Visual Basic или любой другой визуально-ориентированный инструмент программирования, чтобы создать то же самое приложение, которое здесь обсуждается. Четвертая стадия QFTP: визуализация Как известно, Visual Basic обеспечивает программистам огромное повышение производительности при проектировании интерфейса пользователя. Типичному программисту на C/C++ может потребоваться неопределенное количество времени и строчек кода, чтобы осуществить проект, который вы можете создать за несколько часов с помощью Visual Basic. Хотя большинство программистов Visual Basic проектируют как front-end (интерфейс пользователя), так и backend (работа с файлами, аппаратными средствами и сетевые функции) с помощью Visual Basic, вы можете использовать Visual Basic для интерфейса пользователя и пользовательскую DLL для низкоуровневых функций ваших программ. Кроме того, вы можете использовать Visual Basic для создания опытного образца интерфейса пользователя и большого количества программной логики, которая обычно используется при проектировании полностью функционального изделия. Красота использования Visual Basic для программирования высокоуровневых функций заключается в том, что, как вы увидите, Visual Basic легко выполняет все ваши требования для программирования функций высокого уровня, таких как интерфейс пользователя. Ну а если это так, вы можете полностью устранить время, требуемое на разработку интерфейса пользователя на C/C++. Если 550 
Интерфейс между VisuаI Basic и Ш± а необходимо, вы можете позже заменить высокоуровневый интерфейс Visual Basic на C/C++ или любой другой интерфейс, который позволит использовать системные функции для доступа к Windows DLL. Другими словами, помещая ядро ваших Интернет-программ в стандартные Windows DLL, вы обеспечиваете себе очень большую гибкость. Короче говоря, вы можете полностью заменить внешний интерфейс приложений Интернет, не воздействуя на другие части программы. Инкапсуляция основанных на Winsock функций в DLL — установившаяся практика; вы можете непрерывно улучшать и изменять визуальную сторону вашего приложения без изменения устойчивых сетевых алгоритмов, с которыми можно работать независимо. Интерфейс между Visual Basic и DLL Эта глава использует Visual Basic, чтобы разработать программу-клиент FTP (SockFTP). Программа использует динамическую библиотеку QFTPLIB, которую вы разработали в предыдущей главе. Однако перед обсуждением деталей проекта SockFTP вы можете найти полезным рассмотрение интерфейса между Visual Basic и DLL. Что такое оператор Declare? В многих случаях проекты программ на Visual Basic можно упростить включением часто используемых подпрограмм в DLL. Кроме этого, иногда программе требуются функции, которые легче написать на С или C++. Помещая такие функции в DLL, вы можете легко вызывать их из Visual Basic. Как и в случае использования C/C++, ваши программы на Visual Basic могут вызывать любую экспортируемую функцию, расположенную в DLL. Ключ к использованию DLL в Visual Basic — оператор Declare. Оператор Declare объявляет ссылку на внешнюю процедуру, расположенную в DLL. Для DLL оператор Visual Basic Declare эквивалентен прототипу функции в C/C++. Другими словами, оператор Declare определяет интерфейс Visual Basic с функцией, расположенной в DLL. Как показано в следующем списке, оператор Declare может содержать до шести частей: • Тип процедуры Visual Basic (функция или подпрограмма), представленной функцией DLL. • Название функции. • Название DLL, содержащей функцию. • Необязательный псевдоним функции. • Необязательный список аргументов. • Описание возвращаемого значения, если процедура возвращает значения. 551 
Глава Шг &ъщш1ъыыщ щщ^тштт Штщшв! Например, программа SockFTP объявляет функцию lstrcpy как внешнюю процедуру: Declare Function lstrcpy Lib "KERNEL.DLL" (ByVal lpToString As Any,ByVal lpFromString As Any) As Long Аналогично, SockFTP объявляет функцию closesocket и функцию ReadDataChannel QFTPLIB внешними процедурами: Declare Function closesocket Lib "WINSOCK.DLL" (ByVal hSocket As Integer) As Integer Declare Function ReadDataChannel Lib "QFTPLIB.DLL" (ByVal hCtrlSocket As Integer, ByVal hDataSocket As Integer, ByVal IpszFileName As String) As Integer Оператором Declare можно описать прототип практически любой функции C/C++. Однако для приложения критично правильное определение переменных в операторе Declare. Если вы ошибетесь с описанием, то при выполнении такая программа скорее всего вызовет общую ошибку защиты Windows (General Protection Fault, GPF)! В нашей книге термин «процедура» (procedure) будет использоваться для ссылок на функции и подпрограммы Visual Basic вообще. Термин «функция» (для которого в Visual Basic используется ключевое слово Function) объявляет процедуру, которая возвращает значение. Подпрограмма (для которой в Visual Basic используется ключевое слово Sub) определяет процедуру, которая не возвращает значение. Хотя синтаксис у подпрограмм и функций Visual Basic слегка различен, вы можете использовать их так, как потребуется вашей программе. Однако если вы объявляете процедуру как функцию (возвращающую значение), Visual Basic требует, чтобы вы сохраняли возвращаемое значение. Другими словами, работая с Visual Basic, вы не можете игнорировать значение, возвращаемое функцией, как можно поступить в C/C++. (Строго говоря, игнорировать возвращаемое значение можно, но в начале его нужно сохранить.) Вообще, использование термина «функциям в этой главе будет относиться к функциям C/C++ в DLL. После того как вы должным образом определите оператор Declare для функции, находящейся в DLL, вы можете использовать эту функцию подобно любой другой процедуре Visual Basic. Например, следующий фрагмент кода Visual Basic вызывает функции QFTPLIB AcceptDataConnection и ReadDataChannel, функцию closesocket Winsock и внутреннюю подпрограмму Visual Basic, названную subShowServer Reply Code: If intServerReplyCode < 400 Then 552 
Интерфейс между Visual Basic и DLL intDataSocket = AcceptDataConnection(intListenSocket) If intDataSocket <> INVALID_SOCKET Then intServerReplyCode = ReadDataChannel(glbintControlSocket, intDataSocket, "NLST.CMD") End If subShowServerReplyCode (intServerReplyCode) intWinsockReplyCode = closesocket(intDataSocket) End If Как видим, программа использует функцию DLL и подпрограмму Visual Basic в пределах оператора if. Чтобы использовать функцию DLL, программа просто объявляет ее и после этого вызывает функцию. Хотя SockFTP не использует функций типа подпрограммы, вы можете объявлять их точно таким же способом, как и функции, но с ключевым словом Sub и без возвращаемого значения. Исходный файл SOCKFTP. В AS содержит завершенные определения для всех функций из DLL, которые использовались в SockFTP. Соглашения о вызовах Visual Basic В документации по Visual Basic обычно используется термин «аргумент», чтобы сослаться на значение переменной, переданной функции или подпрограмме. В документации Windows, с другой стороны, обычно используется термин «параметр». В контексте этой главы аргументы и параметры — одно и то же. В C/C++ вы можете передавать параметры в функции по значению или по ссылке (указателю на переменную). Visual Basic обеспечивает ту же возможность; однако синтаксис Visual Basic сильно отличается от C/C++. По умолчанию Visual Basic передает все параметры как указатели. Если не определено иначе, когда Visual Basic передает переменную в процедуру, он передает указатель на переменную в вызывающей процедуре. Это означает, что по умолчанию любые действия, которые процедура Visual Basic выполняет с переменной, будут отражаться в переменной, расположенной в вызывающей функции. Если вы хотите передавать в процедуру Visual Basic не саму переменную, а ее копию, вы должны использовать ключевое слово Visual Basic ByVal в описании процедуры. Обработка строковых переменных в Visual Basic требует особой осторожности. Как вы можете знать, C/C++ обращается со строками символов, как с простой последовательностью байтов. Visual Basic обращается со строковыми значениями, как со специальным типом данных, названным HLSTR. Детали реализации HLSTR несущественны. Главное — обратить внимание на то, что нормальная Windows DLL не знает, как обращаться с типом данных HLSTR. 553 
Глава 18. Визуальные приложения интернет Так как цель этой книги состоит в том, чтобы проектировать программные модули на Visual Basic, которые могут использоваться без модификации программами на C/C++, вы должны знать, как избежать использования данных с типом HLSTR. Как вы узнаете, Visual Basic обеспечивает возможность обойти использование этого типа. Когда вы передаете строковую переменную Visual Basic в DLL, используя модификатор ByVal, Visual Basic завершает строку нулевым символом перед передачей ее функции DLL. В том случае, если строка является параметром функции Visual Basic, ключевое слово ByVal сообщает Visual Basic получить копию строки, измененной в функции DLL. Visual Basic в действительности не передает DLL указатель на местоположение строки в памяти. Однако Visual Basic получает копию измененной строки для использования в вашей программе. Чтобы изменить строку Visual Basic в функции DLL, вы должны явно указать место в памяти для буфера строки, передаваемой в DLL. Если вы — прежде всего программист С, то можете не реализовывать это соглашения. Как вы знаете, в программе на С вы и так всегда должны явно указывать буфер строки. Однако программисты на Visual Basic редко используют эту возможность. Программист на Visual Basic может просто присвоить некоторое значение переменной типа String, a Visual Basic будет заниматься распределением памяти. Однако так как Visual Basic не распределяет память для строкового параметра, передаваемого DLL автоматически, ваша программа должна делать это самостоятельно. Итак, детали реализации Visual Basic несущественны. Однако имеются два ключевых момента, на которые необходимо обратить внимание. Во-первых, вы не можете передавать реальную строку Visual Basic в DLL библиотеку, если вы специально не проектируете DLL, чтобы она умела обращаться с типами данных, HLSTR. Во-вторых, если вы используете ключевое слово ByVal со строковым параметром для DLL, вы должны явно зарезервировать место для этой строки (включая нулевой завершающий символ). Делая так, вы можете использовать функции DLL, чтобы работать со строковыми переменными Visual Basic. Позже в этой главе вы будете следовать за этим руководящим принципом, чтобы изменить символьную строку Visual Basic, используя DLL. Типы параметров Visual Basic C/C++ может работать с гораздо более разнообразными типами данных, чем Visual Basic. В результате, вы должны обращаться с определенными типами параметров, требуемых функциями DLL, с особой осторожностью. Хотя Visual Basic включает и 16- и 32-разрядные типы данных для числовых переменных, он не различает знаковые и беззнаковые переменные. Все значения типа integer в Visual Basic — знаковые. Visual Basic ссылается на числовой 16-разрядный тип данных, как на тип данных Integer. Подобно всем описаниям переменных в Visual Basic, чтобы объявить переменную Integer, вы должны использовать следующий синтаксис: IntVariableName As Integer 554 
Как работает SockFTP? Вы можете использовать тип данных Integer, чтобы выразить следующие типы данных Windows и C/C++: int, short, unsigned int, unsigned short, BOOL и WORD. Использование типа LPSTR в Visual Basic Многие функции Windows API возвращают значение типа LPSTR. Тип LPSTR — просто дальний (far или 32-разрядный) указатель на адрес в памяти компьютера. Блок памяти, указываемый переменной LPSTR, содержит символьные данные, которые могут завершаться нулевым символом, а могут и не завершаться. Так как переменная-указатель фактически представляет собой 32-разрядное значение, вы можете использовать тип данных Long, чтобы хранить указатель LPSTR. Функции API Windows, требующие параметр LPSTR, фактически ожидают получить 32-разрядное значение, которое представляет адрес памяти. Обычно Windows использует переменные типа LPSTR, чтобы передавать ссылки на символьные данные. Как обсуждалось ранее, в действительности вы можете передавать функции DLL указатель (типа LPSTR) на строковые данные, объявляя параметр как ByVal As String. Такое описание позволяет DLL работать со строковыми данными, и ваша программа Visual Basic сможет использовать строку, полученную в результате такой операции. Этот процесс использует специально форматированную копию данных, которая доступна и DLL и Visual Basic. Однако, как вы узнали, программа на Visual Basic должна явно зарезервировать место в памяти для такой строки. Visual Basic не имеет доступа к распределению памяти вне своего контекста. Кроме этого, для доступа к строковым данным, которые созданы не в модуле программы Visual Basic, можно использовать тип данных Long, как дальний указатель. Когда DLL возвращает указатель LPSTR в Visual Basic, вы должны сохранить его значение в переменной с типом данных Long. В свою очередь, вы можете передать значение указателя функции API Windows, которая может получить доступ к памяти по указателю. Другой путь для доступа к строковым данным, не принадлежащим Visual Basic, состоит в том, чтобы использовать Windows-функцию lstrcpy. Как известно, функция lstrcpy работает подобно C/C++ функции strcpy. Другими словами, функция принимает две переменные-указателя и копирует данные из памяти, на которую указывает одна переменная, в память, на которую указывает другая. Как работает SockFTP? Подобно программам Windows, основанным на C/C++, программы Visual Basic состоят из различных типов программных объектов. Вероятно, наиболее фундаментальный объект в Visual Basic — объект Form. 555 
Рлава 18. Визуальный Интернет Объект Form (форма) очень похож на диалоговое окно Windows. Форма Visual Basic может содержать различные типы объектов, такие как текстовые окна, окна-списки, кнопки команд и т. д. В программе SockFTP использована единственная форма, названная frmSockFTP, и программа-клиент FTP строится путем размещения объектов на этой форме. В следующих разделах обсуждаются различные объекты, которые определяются и используются в проекте SockFTP. Объекты, свойства и события Так же как основная работа в программе Windows происходит вокруг сообщений, основная активность в программе Visual Basic наблюдается вокруг событий. В Visual Basic, когда объект распознает некоторое адресованное ему действие, он переключается на обработку события. Например, когда вы щелкаете мышью на кнопке команды Visual Basic, то вызываете обработку события для этой командной кнопки. Аналогично, если щелкнуть мышью, когда курсор расположен в текстовом окне, вызывается обработка события для текстового окна. События Visual Basic могут происходить в результате действий пользователя или вызываться программным оператором. Кроме того, операционная система Windows сама по себе может вызывать события. Если вы не знакомы с Visual Basic, эта часть бегло ознакомит вас с концепциями Visual Basic, связанными с объектами, свойствами и событиями. Кроме этого, эта часть также ознакомит вас со всеми объектами Visual Basic, включенными программу SockFTP. Сущность создания сложной программы Windows состоит в обработке сообщений Windows. Аналогично, программирование событий — основная часть хорошо разработанной программы Visual Basic. Visual Basic обеспечивает программиста доступом к каждому событию, распознаваемому объектом Visual Basic. Программист может позволить Visual Basic обрабатывать некоторые события самостоятельно или может определить процедуру Visual Basic, которая выполняется каждый раз, когда происходит определенное событие. Эта событийно-управляемая модель программирования чрезвычайно мощна. И поэтому она используется в визуальном программировании. Вы расставляете объекты на форме и используете стратегию «укажи-и-щелкни», чтобы сконфигурировать различные свойства, связанные с формой. Visual Basic обеспечивает окна-списки, которые автоматически идентифицируют все объекты и доступные ^ для них свойства. Примечание: Подобные инструменты существуют и для программистов на C/C++, но там они не столь популярны. Программа SockFTP лишь поверхностно прикасается к модели событийного программирования Visual Basic. Фактически, SockFTP обрабатывает лишь одиннадцать событий. Как сказано выше, Windows может вызывать события подобно пользователю. Два из использованных в SockFTP событий генерируются системой. Например, каждый раз при запуске SockFTP пользователем программа загружает главную форму SockFTP и Windows вызывает событие 556 
Как работает SockFTP? Form Load. Аналогично, когда пользователь выходит из программы, Visual Basic закрывает (или выгружает) форму и Windows вызывает событие Form Unload. Другие девять событий происходят, когда пользователь щелкает мышью на одной из кнопок, определенных в SockFTP. Если вы снова посмотрите на рис. 18.1, то обнаружите, что видны только семь кнопок. Видимость (Visibility) — это одно из свойств, ассоциированных в Visual Basic с большим количеством объектов. Когда свойство Visibility истинно, объект видим, когда свойство Visibility ложно, Visual Basic скрывает объект. SockFTP определяет для двух кнопок свойство Visibility ложным. Другими словами, две кнопки, Disconnect и Clear, невидимы. Другой путь управления опциями, которые могут выбрать пользователи, состоит в том, чтобы изменить свойство Enabled. Например, изучите рис. 18.1 снова и специально обратите внимание на кнопки List, Name List, Retrieve, Chg Working Dir и CWD Up. Вы увидите, что все они затемнены, другими словами, их использование запрещено. Хотя пользователь может их видеть и щелкать на них мышью, для запрещенных объектов, подобных запрещенным кнопкам команд, Visual Basic предотвращает любые допустимые для объекта события. Единственные кнопки команд, разрешенные при запуске, — кнопки Exit и Connect. Когда пользователь SockFTP щелкает мышью на кнопке Connect, происходит событие, назначенное кнопке Connect. Как вы узнаете, когда происходит событие, ассоциированное с кнопкой Connect, SockFTP использует информацию о сетевом компьютере и идентификаторе пользователя формы SockFTP, чтобы начать FTP-сеанс. Процедура, которая выполняется по событию-нажатию кнопки Connect, использует операторы программы Visual Basic, показанные ниже, для того, чтобы переключить свойство Visible для кнопок Connect и Exit на False: frmSockFTP.cmdConnect.Visible = False frmSockFTP.cmdExit.Visible = False Другими словами, когда вы нажимаете кнопку Connect, кнопки Connect и Exit исчезают. В соответствии с проектом событийно-управляемые программы предотвращают введение пользователем потенциально опасных комбинаций событий или ненужных в данный момент событий, которые вызывают дополнительную бесполезную загрузку процессов операционной системы или программы. Например, так как проект SockFTP использует одновременно только одно соединение с FTP-сервером, SockFTP запрещает кнопку Connect, когда вы уже соединились с одним. Аналогично, SockFTP скрывает кнопку Exit, чтобы напомнить вам закрыть соединение с FTP-сервером до того, как выйти из программы. SockFTP не предотвращает ваш выход (программа автоматически закрывает соединение с сокетом, когда вы выходите). Однако общая практика 557 
Глэва 18. Визуальные приложения Интернет заключается в том, что вы должны аккуратно закрыть FTP-соединение (передав команду QUIT). Так, проект SockFTP поощряет ваше корректное поведение. Обратимся снова к рис. 18.2, чтобы увидеть, как форма SockFTP изменится после того, как SockFTP успешно соединит вас с FTP-сервером. Вы обнаружите, что кнопки Connect и Exit невидимы и Visual Basic теперь показывает кнопки Clear и Disconnect. Аналогично, другие пять кнопок команд больше не затемнены, их свойство Enabled установлено в True. Точно так же как установка свойства Visible в False скрывает объект, установка свойства Visible в True показывает объект. SockFTP использует следующие программные операторы, чтобы показать кнопку Clear и Disconnect: frmSockFTP.cmdDisconnect.Visible = True frmSockFTP.cmdClear.Visible = True Если вы используете SockFTP, чтобы соединиться с FTP-сервером, SockFTP временно запрещает большинство кнопок команд каждый раз, когда начинает сетевую операцию, которой может потребоваться некоторое время (типа поиска адреса сетевого компьютера). SockFTP определяет подпрограмму Visual Basic, названную subShowButtons (показанную ниже), которая устанавливает свойство Enabled для каждой кнопки в True или False, как требуется программе: Sub subShowButtons (intButtonToggle As Integer) frmSockFTP.cmdListDir.Enabled = intButtonToggle frmSockFTP.cmdRetrieve.Enabled = intButtonToggle frmSockFTP.cmdChgWorkingDir.Enabled = intButtonToggle frmSockFTP.cmdCwdUp.Enabled = intButtonToggle frmSockFTP.cmdNameList.Enabled = intButtonToggle End Sub Как видим, подпрограмма subShowButtons принимает параметр Integer и использует его, как булеву переменную, чтобы переключить свойство Enabled для каждой из пяти кнопок команд. Чтобы использовать подпрограмму subShowButtons, программный модуль просто вызывает подпрограмму с параметромТгие или False. Как вы узнаете, программный модуль, который должен уберечь вас от нажатия этих командных кнопок, обычно включает оператор вызова подпрограммы subShowButtons в начале и конце определения. Как уже говорилось, Visual Basic автоматически определяет события и свойства для объектов, которые вы добавляете к вашим программным модулям. Например, когда вы добавляете кнопку команды к форме, Visual Basic автоматически добавляет процедуру Click, которую вы можете использовать для обработки события, которое происходит, когда пользователь нажимает кнопку команды. В следующих разделах процедуры обработки событий обсуждаются более подробно. 558 
Kai работает Sock TPS Процедуры обработки событий Как вы узнали, объекты Visual Basic распознают определенные действия программы или системы, названные событиями. Как программист Visual Basic, вы управляете программными действиями, которые вызывают или обрабатывают события. Visual Basic автоматически связывает процедуры с событиями, распознаваемыми объектами. Другими словами, вы не должны объявлять или называть процедуры обработки событий. Вы просто создаете определение процедуры, которое отвечает требованиям вашей программы. В табл. 18.1 содержится список объектов (с процедурами обработки событий), определенных для программы SockFTP. За исключением событий, связанных с формой, Visual Basic создает имена процедуры обработки события, объединяя название объекта (типа cmdConnect) с подчеркиванием и названием события (типа Click). Visual Basic всегда называет процедуры обработки событий, связанных с формой, начиная с названия Form вместо названия объекта формы, например Form_Load, а не frmSockFTP_Load. Таблица 18.1. Описание процедур обработки событий SockFTP Объект Событие Название и описание процедуры обработки события FrmSockFTP Load FormJLoad выключает командные кнопки и загружает WINSOCK.DLL. FrmSockFTP Unload Form_Unload вызывает cmdExit_Click, которая, в свою очередь, выгружает WINSOCK.DLL. CmdChgWorkingDir Click cmdChgWorkingDir_Click изменяет рабочий каталог FTP-сервера на значение, выбранное в в данный момент в окнесписке. CmdClear Click cmdClear_Click очищает содержимое окнасписка. CmdConnect Click cmdConnect_Click выполняет процедуры входа в систему FTP-сервера с параметрами, указанными в текстовых окнах SockFTP. CmdCwdUp Click cmdCwdUp_Click изменяет рабочий каталог в дереве каталогов сервера на родительский. CmdDisconnect Click cmdDisconnect_Click закрывает соединение с FTP-сервером. 559 
ГЛава 18 визуальные приложения Ишгррнег Таблица 18.1 (окончание) Объект Событие Название и описание процедуры обработки события CmdExit Click cmdExit_Click вызывает функцию DLL CloseWinsock для освобождения ресурсов WINSOCK.DLL и заканчивает выполнение программы. CmdListDir Click cmdListDir_Click показывает список файлов FTP-сервера, расположенных в его рабочем каталоге. CmdNameList Click cmdNameList показывает подробный список файлов, расположенных в рабочем каталоге FTP-сервера. CmdRetrieve Click cmdRetrieve_Click возвращает имя отмеченного в данный момент в окне-списке файла. В следующих разделах этой главы подробно обсуждается каждая из процедур обработки событий SockFTP. Процедура Form_Load Каждый раз, когда вы запускаете программу SockFTP, программа загружает главную форму SockFTP (frmSockFTP), которая вызывает событие Form Load. Visual Basic вызывает подпрограмму обработки события Form_Load до того, как система выводит форму SockFTP: Sub Form_Load () Dim intSuccess As Integer subCenterForm frmSockFTP subShowButtons (False) intSuccess = LoadWinsock() End Sub Form_Load вызывает подпрограмму subCenterForm для размещений формы на экране и после этого вызывает подпрограмму subShowButtons, чтобы запретить все кнопки команд сервера. Наконец, Form_Load вызывает функцию LoadWinsock из QFTPLIB, чтобы инициализировать Winsock DLL. Процедура Form_Unload Когда вы завершаете работу программы SockFTP, Visual Basic будет закрывать и выгружать форму SockFTP. Visual Basic вызывает Form_Unload прежде, чем 560 
Как работает SppfcFTP? j выгрузить форму и освободить все ресурсы. Когда происходит событие Unload, как показано ниже, SockFTP просто вызывает процедуру, определенную для кнопки команды Exit, чтобы выполнить процедуры закрытия SockFTP: Sub Form_Unload (Cancel As Integer) cmdExi t_Click End Sub Примечание: Установка параметра Cancel в любое, отличное от нуля значение предотвращает удаление формы, но не останавливает другие события, например выход из Windows. Процедура cmdChgWorkingDir_Click Когда вы щелкаете мышью на кнопке команды Chg Working Dir, Visual Basic вызывает событие Click, которое выполняет подпрограмму cmdChgWorkingDir_Click. Процедура CmdChgWorkingDir_Click использует метод Listlndex, чтобы получить номер индекса выбранного в настоящее время пункта в окнесписке. CmdChgWorkingDir_Click использует этот индекс, чтобы получить значение строки, выбранной в окне-списке. Строка, в настоящее время отмеченная в окне-списке, может содержать имя подкаталога или имя файла в текущем каталоге сервера. SockFTP не проверяет значение, содержащееся в строке. Процедура CmdChgWorkingDir_Click просто передает строку процедуре SockFTP strDoCWDCommand, которая начинает процесс передачи команды CWD серверу. Функция strDoCWDCommand возвращает новый рабочий каталог, который, как показано далее, cmdChgWorkingDir_Click сохраняет в текстовом окне рабочего каталога: Sub cmdChgWorkingDir_Click () Dim intServerReplyCode As Integer Dim intListlndex As Integer Dim strDir As String intListlndex = frmSockFTP.IstServerFiles.Listlndex strDir = frmSockFTP.IstServerFiles.List(intListlndex) txtWorkingDir.Text = strDoCWDCommand(strDir) End Sub Процедура cmdClear_Click Окно SockFTP содержит окно-список, показывающее сообщения, которыми обмениваются SockFTP и FTP-сервер. Каждый раз, когда вы соединяетесь с новым FTP-сервером, вы можете очистить сообщения и ответы от предыдущего FTP-сервера. Вместо того чтобы делать это автоматически, SockFTP определяет кнопку Clear. Кнопка Clear заставляет SockFTP очистить окно-список сообщений, которое содержит список сообщений и ответов сервера и клиента. Вы можете использовать эту кнопку в любое время в ходе FTP-сеанса. Когда вы щелкните мышью на кнопке команды Clear, Visual Basic производит событие 561 
Глава 18. визуальные приложения Интернет ;; Click, приводящее к вызову подпрограммы cmdClear_Click. Как показано ниже, процедура cmdClear_Click использует метод Clear, чтобы очистить содержимое окна-списка сообщений: Sub cmdClear_Click () frmSockFTP.IstServerDialog.Clear End Sub Процедура cmdConnect_Click Чтобы начать сеанс с FTP-сервером, необходимо щелкнуть мышью на кнопке Connect. Кнопка Connect заставляет SockFTP установить соединение между клиентом SockFTP и хостом, указанным в соответствующем текстовом окне. Когда вы щелкаете мышью на кнопке команды Connect, Visual Basic производит сообщение Click, которое заставляет SockFTP выполнить процедуру cmdConnect_Click. Процедура CmdConnect_Click устанавливает TCP-соединение с FTP-портом сервера, заданном в соответствующем текстовом окне. На каждом шагу процесса соединения cmdConnect_Click вызывает процедуру subShowServerCommand, чтобы показать команду, посланную серверу, или код ответа сервера. Чтобы установить соединение, процедура cmdConnect_Click сначала вызывает функцию QFTPLIB ConnectFTPControlSocket (которую вы разработали в предыдущей главе). Вы помните, что функция ConnectFTPControlSocket просто выполняет типичный процесс соединения с сокетом, который вы использовали в большинстве программ, изучаемых в данной книге. ConnectFTPControlSocket возвращает дескриптор сокета, который ваша программа должна использовать для доступа к каналу контроля FTP-сервера. Если ConnectFTPControlSocket возвращает действительный дескриптор сокета, cmdConnect_Click обновляет окно-список сообщений и передает команду USER, вызывая intDoUSERCommand. Если сервер возвращает код 331 («имя пользователя в порядке, нужен пароль»), cmdConnect__Click передает команду PASS, вызывая процедуру intDoPASSCommand. Процедура CmdConnect_Click ожидает получить в ответ на команду PASS код 230. Код ответа 230 означает, что сервер разрешил вам войти и работать в системе. Если сервер указывает, что вы вошли в систему, cmdConnect__Click вызывает еще две процедуры SockFTP, чтобы получить полезную информацию от сервера и сохранить возвращенные значения в текстовом окне SockFTP. Например, процедура cmdConnect_Click вызывает strDoPWDCommand, чтобы получить рабочий каталог сервера и сохранить значение в одноименном текстовом окне. Аналогично, cmdConnect_Click вызывает strDoSYSTCommand, чтобы получить тип системы сервера и запомнить значение в текстовом окне System Туре. Наконец, процедура cmdConnect_Click делает кнопки команд Disconnect и Clear видимыми и сообщает процедуре intDoListCommand, что надо передать команду NLST, чтобы получить список имен файлов рабочего каталога сервера. После 562 
возвращения результатов intDoListCommand cmdConnect_Click позволяет вам использовать кнопки команд сервера. Чтобы разрешить кнопки команд, cmdConnect_Click вызывает процедуру subShowButtons, уже рассмотренную нами ранее. Если функция ConnectFTPControlSocket QFTPLIB не может получить действительный дескриптор сокета, cmdConnect_Click не скрывает кнопки Connect и Exit. Если функция ConnectFTPControlSocket получает действительный дескриптор сокета, а команды USER и PASS терпят неудачу, cmdConnect_Click вызывает процедуру subCloselncompleteConnection. Процедура subCloselncompleteConnection в свою очередь закрывает управляющий сокет, скрывает кнопки Disconnect и Clear и не скрывает кнопки Exit и Connect. Ниже показан завершенный исходный текст, выполняющий процесс соединения: Sub cmdConnect_Click () Dim intServerReplyCode As Integer Dim strCommand As String frmSockFTP.cmdConnect.Visible = False frmSockFTP.cmdExit.Visible = False frmSockFTP.IstServerFiles.Clear glbintControlSocket = ConnectFTPControlSocket(frmSockFTP.txtHostName) If glbintControlSocket <> INVALID_SOCKET Then subShowServerCommand ("CONNECTED to: " & frmSockFTP.txtHostName) intServerReplyCode = intDoUSERCommand() If intServerReplyCode = 331 Then intServerReplyCode = intDoPASSCommand() If intServerReplyCode = 230 Then frmSockFTP.txtWorkingDir = ^trDoPWDCommand() frmSockFTP.txtSystemType = strDoSYSTCommand() frmSockFTP.cmdDisconnect.Visible = True frmSockFTP.cmdClear.Visible = True subShowServerCommand ("Logged into " & frmSockFTP.txtHostName) intServerReplyCode = intDoListCommand("NLST") subShowButtons (True) Else subCloselncompleteConnection End If Else 563 
Глава 18. Визуальные приложения Интернет subCloselncompleteConnection End If Else 7 Делаем кнопки видимыми, если попытка установить 7 соединение окончилась неудачно. Иначе 7 cmdDisconnect_Click сделает их видимыми при 7 отсоединении. frmSockFTP.cmdConnect.Visible = True frmSockFTP.cmdExit.Visible = True End If End Sub Процедура cmdCwdUp_Click Кнопка CWD Up заставляет SockFTP дать указание серверу изменить рабочий каталог на родительский. Если вы щелкаете мышью на кнопке команды CWD Up, Visual Basic производит событие Click, по которому выполняется процедура cmdCwdUp_Click. Как показано дальше, процедура cmdCwdUp_Click вызывает strDoCDUPCommand и сохраняет значение, возвращенное ей в текстовом окне рабочего каталога: Sub cmdCwdUp_Click () Dim intServerReplyCode As Integer txtWorkingDir.Text = strDoCDUPCommand() End Sub Процедура strDoCDUPCommand передает команду CDUP FTP-серверу и возвращает имя нового рабочего каталога. Процедура cmdDisconnect_Click Кнопка Disconnect заставляет SockFTP закрыть TCP-соединение с FTP-сервером (но не завершает SockFTP). Когда вы щелкаете мышью на кнопке команды Disconnect, Visual Basic генерирует событие Click, по которому выполняется процедура cmdDisconnect_Click, как показано ниже: Sub cmdDisconnect_Click () Dim intServerReplyCode As Integer Dim intWinsockReplyCode As Integer Dim strCommand As String frmSockFTP.cmdDisconnect.Visible = False frmSockFTP.cmdClear.Visible = False subShowBut tons (False) strCommand = "QUIT" & Chr(13) & Chr(10) 7 Добавляем CRLF subShowServerCommand (strCommand) 564 
Как работает SockFTP? i intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) intWinsockReplyCode = closesocket(glbintControlSocket) frmSockFTP.txtWorkingDir = "" frmSockFTP.txtSystemType = "" frmSockFTP.cmdConnect.Visible = True frmSockFTP.cmdExit.Visible = True frmSockFTP.IstServerFiles.Clear End Sub Процедура CmdDisconnect_Click скрывает кнопки Disconnect и Clear и запрещает кнопки команд сервера, вызывая subShowButtons. CmdDisconnect_Click использует функцию SendFTPCommand из QFTPLIB, чтобы передать команду QUIT серверу. После этого процедура CmdDisconnect_Click делает прямой запрос к Winsock DLL, чтобы закрыть управляющий сокет (используя функцию closesocket из Winsock). Перед выходом cmdDisconnect_Click очищает окна Working Dir и System Type, показывает кнопки команд Connect и Exit и очищает окно-список файлов. Однако cmdDisconnect_Click не очищает окно-список сообщений. Другими словами, когда вы закрываете соединение с FTP-сервером, SockFTP сохраняет для вас историю работы с сервером. Вы можете анализировать диалог с сервером автономно, чтобы идентифицировать любые проблемы, которые произошли в течение сеанса. Конечно, если вы планируете соединяться с различными FTPсерверами, вы можете не сохранять информацию о входе в систему, оставшуюся от предыдущего сеанса. В этом (случае вы можете щелчком мыши на кнопке Clear очистить окно-список сообщений. Процедура cmdExit_Click Для выхода из программы SockFTP вы можете щелкнуть на кнопке Exit. Когда вы щелкаете мышью на кнопке команды Exit, Visual Basic производит событие Click, по которому выполняется процедура cmdExit_Click. Так же, как уже было показано, SockFTP выполняет cmdExit__Click, когда Visual Basic посылает сигнал Form Unload. Как показано ниже, процедура cmdExit_Click вызывает функцию CloseWinsock QFTPLIB, освобождающую ресурсы, занятые в Winsock. Затем cmdExit_Click вызывает Visual Basic-команду End, которая завершает выполнение программы: Sub cmdExit_Click () Dim intSuccess As Integer intSuccess = CloseWinsock() End End Sub 565 
ш приложения Ш Процедура cmdListDir_Click Когда вы щелкаете мышью на кнопке команды List, Visual Basic производит событие Click, которое выполняет cmdListDir_Click. Процедура cmdListDir_Click вызывает процедуру intDoListCommand SockFTP, которая выполняет процедуры, передающие команду LIST серверу и показывающие результаты в окне-списке файлов: Sub cmdListDir__Click () Dim intServerReplyCode As Integer intServerReplyCode = intDoListCommand("LIST") End Sub Процедура cmdRetrieve_Click Когда вы щелкаете мышью на кнопке команды Retrieve, Visual Basic генерирует событие Click, которое выполняет процедуру cmdRetrieve__Click. Процедура cmdRetrieve_Click очень похожа на cmdChgWorkingDir_Click. Обе они используют метод Listlndex, чтобы получить индекс пункта, в настоящее время выбранного в окне-списке. Затем обе процедуры используют этот индекс, чтобы получить значение строки, которая соответствует выбранному пункту. Когда происходит событие, инициирующее процедуру cmdChgWorkingDir_Click, значение строки в окне-списке мржет содержать имя подкаталога или файла. Как уже отмечалось, SockFTP не проверяет значение, содержащееся в строке, извлеченной из окна-списка. Процедура CmdRetrieve_Click передает эту строку в процедуру SockFTP intDoRETRCommand. При использовании значения строки из окна-списка в качестве параметра процедура intDoRETRCommand начинает процесс передачи команды RETR серверу. В дополнение к передаче в intDoRETRCommand значения строки, выбранной из окна-списка (имя файла), cmdRetrieve__Click также передает значение True или False, в зависимости от того, какую кнопку вы выбрали — IMAGE или ASCII, прежде чем щелкнуть мышью на кнопке Retrieve. Если значение типа файла — True (значит, вы выбрали файл двоичного типа), intDoRETRCommand передаст FTP-команду ТУРЕ с параметром «I». Если значение типа файла — False (значит, вы выбрали файл типа ASCII), intDoRETRCommand будет автоматически передавать FTP-команду ТУРЕ со значением параметра «А N». Вы можете вспомнить, что параметр «А N» в команде ТУРЕ сообщает серверу использовать тип файла ASCII: Sub cmdRetrieve_Click () Dim intServerReplyCode As Integer Dim intListlndex, optlmage As Integer Dim strFile As String 566 
jw.va-a;.:.: t -aw.w v w • ♦ -v Как работает So< intListIndex = frmSockFTP.IstServerFiles.ListIndex strFile = frmSockFTP.IstServerFiles.List(intListIndex) optlmage = optImageFileType.Value intServerReplyCode = intDoRETRCommand(strFile, optlmage) End Sub Объекты SockFTP, запоминающие информацию Как вы узнали, многие объекты Visual Basic распознают и отвечают на различные типы действий, называемых событиями. Ответ Visual Basic на событие должен вызывать соответствующую процедуру обработки события подобно описанным в предыдущих разделах. Вы можете определить, что должно происходить каждый раз при возникновении определенного события. Несколько процедур обработки событий используют объекты, расположенные на форме SockFTP. В табл. 18.2 приведены различные объекты SockFTP, в которых сохраняется возвращенная сервером или введенная вами информация. Таблица 18.2. Объекты SockFTP, которые содержат данные, введенные пользователем или возвращенные FTP-сервером Название объекта Описание L IstServerDialog Содержит список переданных FTP-команд и кодов ответа сервера. IstServerFiles Содержит список файлов рабочего каталога на сервере. optAsciiFileType Позволяет сообщить серверу, что тип файла — ASCIIтекст. Этот объект и объект optlmageFileType взаимно исключающие. optlmageFileType Позволяет сообщить серверу, что тип файла изображение (двоичный). Этот объект и объект optAsciiFileType — взаимно исключающие. txtHostName SockFTP использует имя сетевого компьютера, записанное в этом текстовом окне, чтобы установить TCP-соединение по FTP-порту этого компьютера. txtPassword SockFTP использует значение из этого текстового окна как параметр FTP-команды PASS (пароль). txtSystemType Хранит тип системы сетевого компьютера, извлеченный из кода ответа, возвращенного сервером в ответ на команду SYST. txtUserlD SockFTP использует значение из этого текстового окна в качестве параметра к FTP-команде USER. txtWorkingDir Хранит рабочий каталог сервера, извлеченный из ответа, возвращенного сервером в ответ на команду PWD. 567 
Глава 18* Визуальные приложения Икгернрт Прочие объекты SockFTP Объекты, внесенные в табл. 18.3 (за исключением основной формы приложения FrmSockFTP), несущественны для программы SockFTP. Другими словами, они или являются ярлыками или добавляют привлекательности (например, иконки или изображения). Табл. 18.3 содержит краткое описание каждого объекта. Таблица 18.3. Прочие объекты SockFTP Тип объекта Название Описание Form frmSockFTP Представляет программу SockFTP в качестве объекта Form. Frame fraFileType Инкапсулирует две кнопки выбора типа файла, позволяя одновременно перемещать обе кнопки. Label lblFTP Показывает строку текста FTP. Label lblnternetProgramming Показывает текстовую строку Internet Programming. Label lblHostName Маркирует текстовое окно Host. Label lblPassword Маркирует текстовое окно PASSWORD. Label lblSystemType Маркирует текстовое окно System Туре. Label lblUserld Маркирует текстовое окно USER ID. Label lblWorkingDir Маркирует текстовое окно Working Directory. PictureBox picJPLogo Содержит изображение эмблемы Jamsa Press. PictureBox picSockman Содержит изображение иконки Sockman. Процедуры SockFTP Как вы узнали, основная активность в программе Visual Basic вызывается некоторыми типами событий, генерируемых пользователем, системой или операторами программы. В предыдущих частях этой главы мы рассмотрели пользовательские процедуры обработки событий, которые отвечают на события, генерируемые Visual Basic, представляющие интерес для программы SockFTP. Как вы узнали, много таких функций обработки событий определено в SockFTP. В следующих разделах обсуждаются оставшиеся процедуры, определенные в SockFTP. 568 
Процедуры SockFTP Процедура intDoListCommand Процедура intDoListCommand устанавливает соединение данных с FTP-сервером при подготовке к получению ответа сервера на команду LIST или NLST. (Строковый параметр, полученный intDoListCommand, определяет, какую команду передать.) Процедура intDoListCommand, чтобы исполнить эти действия, прежде всего использует функции, содержащиеся в QFTPLIB.DLL. На протяжении всей процедуры intDoListCommand использует subShowServerReplyCode, чтобы показывать команды, переданные серверу, и ответы сервера. При запуске intDoListCommand использует subShowButtons, чтобы запретить кнопки команд сервера. Затем intDoListCommand вызывает функцию CreateListenSocket QFTPLIB. Как вы помните, CreateListenSocket создает сокет, ожидающий запрос на установление соединения от FTP-сервера. Если CreateListenSocket успешно создает ожидающий сокет, intDoListCommand использует функцию SendFTPCommand из QFTPLIB, чтобы передать команду NLST или LIST серверу. Вызывающая процедура определяет, какая команда передается, внося их список команд, чтобы передать в строковом параметре, который вызывающая функция передает intDoListCommand в strListCommand. Если код ответа сервера на команду просмотра не указывает на ошибку (код должен быть меньше чем 400), intDoListCommand вызывает функцию AcceptDataConnection QFTPLIB, чтобы подтвердить запрос на установление соединения, поступающий от сервера. AcceptDataConnection возвращает дескриптор сокета данных, который указывает канал данных FTP-сервера. Если дескриптор сокета данных действителен, intDoListCommand вызывает функцию ReadDataChannel QFTPLIB для чтения канала данных. Иначе intDoListCommand закрывает ожидающий сокет и добавляет сообщение об ошибке в окно-список сообщений. Если все идет хорошо, intDoListCommand вызывает процедуру subReadListFile SockFTP, чтобы прочитать содержимое каталога сервера из файла на вашем жестком диске и добавить его в окно-список файлов. Как вы можете вспомнить, ReadDataChannel сохраняет поступающие данные в файле с именем, определяемом в вашей программе и передаваемом в одном из параметров ReadDataChannel. Программы QFTP и SockFTP используют команду FTP, которая начинает процесс передачи данных с именем файла в качестве параметра. Программные операторы, показанные ниже, и составляют процедуру intDoListCommand: Function intDoListCoimnand (strListCommand As String) As Integer Dim intListenSocket As Integer Dim intDataSocket As Integer Dim strCommand As String Dim intServerReplyCode, intWinsockReply As Integer subShowButtons (False) 569 
Глава 18. Визуальные приложения Интерна! intListenSocket = CreateListenSocket(glbintControlSocket) If intListenSocket <> INVALID_SOCKET Then strCommand = strListCommand & Chr(13) & Chr(10) # Добавляем CRLF subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode < 400 Then intDataSocket = AcceptDataConnection(intListenSocket) If intDataSocket <> INVALID_SOCKET Then intServerReplyCode = ReadDataChannel(glbintControlSocket, intDataSocket,strListCommand) subShowServerReplyCode (intServerReplyCode) intServerReplyCode = closesocket(intDataSocket) ' Считываем результаты выпЪянения команды и ' добавляем файлы к списку. subReadListFile (strListCommand) Else intWinsockReply = closesocket(intListenSocket) intServerReplyCode = 999 subShowServerReplyCode (intServerReplyCode) subShowServerCommand ("INVALID DATA SOCKET!") End If Else subShowServerCommand ("UNEXPECTED Reply Code " & intServerReplyCode) End If Else intServerReplyCode = 999 subShowServerReplyCode (intServerReplyCode) subShowServerCommand ("INVALID LISTEN SOCKET!") End If intDoListCommand = intServerReplyCode subShowButtons (True) End Function 570 
Процедуры SockFTP Процедура intDoPASSCommand Процедура intDoPASSCommand использует FTP-команду PASS, чтобы пользователь SockFTP мог войти в систему FTP-сервера. Чтобы это сделать, intDoPASSCommand вызывает функцию SendFTPCommand QFTPLIB, чтобы передать команду PASS серверу FTP. Процедура intDoPASSCommand передает значение, показанное в текстовом окне пароля (frmSockFTP.txtPassword), серверу как параметр для команды PASS. Как показано в нижеследующем тексте процедуры, intDoPASSCommand сообщает код ответа сервера вызывающей процедуре: Function intDoPASSCommand () As Integer Dim strCommand As String Dim intServerReplyCode As Integer strCommand = "PASS " & frmSockFTP.txtPassword & Chr(13) & Chr(10) subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode <> 230 Then subShowServerCommand ("EXPECTED Reply Code 230") End If intDoPASSCommand = intServerReplyCode End Function Процедура intDoRETRCommand Процедура intDoRETRCommand использует FTP-команду RETR, чтобы получить файл от FTP-сервера. Если вы исследуете процедуру intDoRETRCommand, показанную ниже, вы обнаружите, что она похожа на процедуру intDoListCommand описанную ранее. Однако процедура intDoRETRCommand передает две FTP-команды одновременно. Также для передачи данных intDoRETRCommand зызывает strTransferFile (процедура Visual Basic) вместо функции ReadDataChannel из QFTPLIB.DLL. Процедура intDoRETRCommand получает два параметра. Первый параметр strFileName) определяет имя получаемого файла. intDoRETRCommand объединяет это значение с командой RETR. Второй параметр (optlmage) — булево значение, которое сообщает intDoRETRCommand тип файла: изображение двоичный файл) или ASCII-текст. Если переменная optlmage истинна, intDoRETRCommand передает команду TYPE с параметром «I», чтобы сообщить ерверу, что тип передаваемого файла — изображение (двоичный). Если optImage ложна (а это означает, что выбрана кнопка ASCII), intDoRETRCommand 571 
Глава 18. Визуальные приложения Интернет передает команду TYPE с параметром «А N», которая задает тип файла ASCII. После того как функция AcceptDataConnection возвращает действительный дескриптор сокета, strTransferFile вызывает функцию intDoRETRCommand, которая фактически передает данные, выданные по FTP-команде RETR: Function intDoRETRCommand (strFileName As String, optlmage As Integer) As Integer Dim intListenSocket As Integer Dim intDataSocket As Integer Dim strCommand As String Dim strType As String Dim strServerReply As String Dim intServerReplyCode, intWinsockReply As Integer subShowButtons (False) intListenSocket = CreateListenSocket(glbintControlSocket) If intListenSocket <> INVALID_SOCKET Then If optlmage = True Then strType = "I" Else strType = "A N" End If strCommand = "TYPE " & strType & Chr(13) & Chr(10) ' Добавляем CRLF subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket,. strCommand) subShowServerReplyCode (intServerReplyCode) strCommand = "RETR " & strFileName & Chr(13) & Chr(10) ' Добавляем CRLF subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode < 400 Then intDataSocket = AcceptDataConnection(intListenSocket) If intDataSocket <> INVALID_SOCKET Then strServerReply = strTransferFile(intDataSocket, strFileName) subShowServerCommand (strServerReply) intServerReplyCode = CInt(Left$(strServerReply, 3)) 572 
Процедуры SockFTP Веер intWinsockReply = closesocket(intDataSocket) Else intWinsockReply = closesocket(intListenSocket) intServerReplyCode = 999 subShowServerReplyCode (intServerReplyCode) subShowServerCommand ("INVALID DATA SOCKET!") End If Else subShowS erverCommand ("UNEXPECTED Reply Code " & intServerReplyCode) End If Else intServerReplyCode = 999 subShowServerReplyCode (intServerReplyCode) subShowServerCommand ("INVALID LISTEN SOCKET!") End If intDoRETRCommand = intServerReplyCode subShowButtons (True) End Function Еще раз об intDollSERCommand Процедура intDoUSERCommand (показанная ниже) использует FTP-команду USER как часть процедуры SockFTP для входа в систему FTP-сервера. Кроме передачи команды USER вместо команды PASS и ожидания другого кода ответа от сервера, intDoUSERCommand идентична intDoPASSCommand. Вы могли бы легко объединить эти процедуры в одну и потребовать, чтобы вызывающая функция определила, какую команду передать: Function intDoUSERCommand () As Integer Dim strCommand As String Dim intServerReplyCode As Integer strCommand = "USER " & frmSockFTP.txtUserlD & Chr(13) & Chr(10) subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode <> 331 Then subShowServerCommand ("EXPECTED Reply Code 331") End If 573 
Глава 18. Визуальные приложения Интернет intDoUSERCommand = intServerReplyCode End Function Продолжая рассматривать процедуры SockFTP, вы обнаружите, что некоторые из них очень похожи. В данной книге подобные процедуры описываются, как отдельные программные модули для упрощения изложения. Другими словами, чтобы гарантировать, что программные тексты достаточно ясно иллюстрируют выполняемые в процедуре задачи, похожие процедуры обычно не объединяются. Однако если вы приспосабливаете SockFTP к собственным нуждам, нет никаких причин, запрещающих вам объединить процедуры типа intDoUSERCommand и intDoPASSCommand в одной функции общего назначения. Примечание: Процедура intDoListCommand иллюстрирует, как вы можете проектировать общую процедуру, обрабатывающую несколько команд. Как уже было показано, intDoListCommand может передать как команду NLST, так и LIST. Процедура strCutCrLf Как вы узнали, большинство серверов требуют, чтобы конец строки идентифицировался парой символов CRLF. Как вы видели при обсуждении функции Form_Load, SockFTP определяет глобальную переменную, которую модули SockFTP могут легко использовать для объединения значений строковых переменных для передачи FTP-серверу. Ответы сервера также обычно включают пары символов CRLF. К сожалению, эти символы появляются в текстовом окне Visual Basic. Хотя они не причиняют никакого вреда, они зрительно отвлекают. Процедура strCutCrLf обеспечивает удобный способ устранить CRLF из любой текстовой строки прежде, чем вы передадите эту строку функции Visual Basic для показа в окне. Как показано ниже, процедура strCutCrLf использует функцию InStr Visual Basic для поиска пар CRLF и возвращает значение без CRLF. Обратите внимание, что несмотря на название, strCutCrLf фактически не выполняет никаких разрушительных действий со строкой текста переданной ей как параметр. В действительности, вы можете использовать strCutCrLf, чтобы грамматически разобрать строки текста, заканчивающегося CRLF, неразрушающим способом: Function strCutCrLf (strText As String) As String Dim intCrLfLocation As Integer intCrLfLocation = InStr(1, strText, Chr(13) & Chr(10)) ' Добавляем CRLF If intCrLfLocation > 0 Then intCrLfLocation = intCrLfLocation - 1 Else 574 
Процедуры SpckFTP intCrLfLocation = Len(strText) End If strCutCrLf = Left(strText, intCrLfLocation) End Function Процедуры strDoCDUPCommand и strDoCWDCommand Как вы узнали, FTP-команды CWD и CDUP требуют от сервера изменить рабочий каталог текущего FTP-сеанса. CWD требует параметра, который сообщает серверу, какой каталог сделать текущим. CDUP не требует параметра. CDUP просто сообщает серверу сделать рабочим каталог, находящийся выше в дереве каталогов, относительно текущего. Как показано в следующем программном тексте, процедуры strDoCDUPCommand и strDoCWDCommand почти идентичны. Как показано ниже, strDoCDUPCommand передает команду CDUP без параметров: Function strDoCDUPCommand () As String Dim strCommand As String ' Строка с командой для передачи Dim intServerReplyCode As Integer ' Ответ сервера subShowButtons (False) ' Выключаем кнопки strCommand = "CDUP" & Chr(13) & Chr(10) subShowServerCommand (strCommand) ' Показываем команду intServerReplyCode = SendFTPCommand(glbintControlSocket, subShowServerReplyCode (intServerReplyCode) If intServerReplyCode = 250 Then ' Если все прошло успешно, intServerReplyCode = intDoListCommand("NLST") End If strDoCDUPCommand = strDoPWDCommand() subShowButtons (True) End Function Процедура strDoCWDCommand объединяет значение strDirectory, полученное как параметр вместе с командой CWD, и тогда следует той же самой процедуре, что и strDoCDUPCommand, для передачи строки команды: Function strDoCWDCommand (strDirectory As String) As String Dim strCommand As String Dim intServerReplyCode As Integer пользователю strCommand) ' выводим список файлов 575 
Главд IS. визуальные приложения Интернет subShowBu11 ons (False) strCommand = "CWD " & strDirectory & Chr(13) & Chr(10) ' Добавляем CRLF subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode = 250 Then intServerReplyCode = intDoListCommand("NLST") End If strDoCWDCommand = strDoPWDCommand() subShowBut tons (True) End Function Обе функции ожидают получить в ответ от сервера код 250, который показывает, что сервер успешно сменил рабочий каталог. Если функции получают в ответ код 250, они вызывают функцию intDoListCommand SockFTP, чтобы показать список файлов в новом рабочем каталоге сервера. Процедура strDoPWDCommand Процедура strDoPWDCommand (показанная ниже) использует FTP-команду PWD, чтобы получить название рабочего каталога сервера. Процедура strDoPWDCommand следует тем же самыми путем, который использован в большинстве других процедур SockFTP, передающих команду серверу, и для этого, вызывает функцию SendFTPCommand из QFTPLIB: Function strDoPWDCommand () As String Dim strCommand As String Dim strDirectory As String Dim intServerReplyCode As Integer subShowButtons (False) strCommand = "PWD" & Chr(13) & Chr(10) ' Добавляем CRLF subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode = 257 Then strDirectory = strGetServerReplyText() strDirectory = strCutCrLf(strDirectory) strDirectory = strExtractQuotedExpression(strDirectory) Else 576 
, Процедуры SockFTP; strDirectory = "" End If strDoPWDCommand = strDirectory subShowBu11 ons (True) End Function Как вы узнали, функции QFTPLIB обычно полагаются на числовое значение в кодах ответа сервера. Также в большинстве случаев функции QFTPLIB не исследуют текст, включенный в ответ сервера. Команда PWD заставляет сервер возвратить в качестве ответа код 257, который включает специально сформатированную строку, содержащую имя рабочего каталога. Сервер заключает имя каталога в двойные кавычки. Таким образом, программный модуль должен уметь исследовать фактическое содержимое строки ответа сервера. SockFTP определяет несколько функций, выполняющих это требование для программных модулей Visual Basic. Чтобы извлечь рабочий каталог из ответа сервера, сначала strDoPWDCommand вызывает strGetServerReplyText. Процедура strGetServerReplyText возвращает текст ответа сервера без кода ответа, другими словами, только часть текста ответа сервера. После того как strGetServerReplyText возвращает текст ответа сервера, strDoPWDCommand передает текст ответа сервера к strCutCrLf, которая, как уже говорилось, устраняет завершающие CRLF. Наконец, strDoPWDCommand передает строку, которая получается после обработки функции strExtractQuotedExpression. Процедура strExtractQuotedExpression отыскивает значение параметра, заключенное в двойных кавычках. Если она находит это значение, strExtractQuotedExpression возвращает только часть строки в кавычках. Если она не может найти значение, заключенное в кавычки, она возвращает полный текст строки, переданный ей в начале. Независимо от того, находит ли strExtractQuotedExpression значение в кавычках или нет, strDoPWDCommand передает вызывающей функции значение строки, возвращенное функцией strExtractQuotedExpression. Когда команда PWD терпит неудачу (от сервера не получен ответ с кодом 257), strDoPWDCommand возвращает пустую строку. Процедура strDoSYSTCommand Как показано ниже, извлечение типа системы из ответа сервера по команде SYST функцией strDoSYSTCommand работает подобно strDoPWDCommand. Однако strDoSYSTCommand ожидает код ответа 215, а не 257, и передает команду SYST вместо PWD. Также strDoSYSTCommand не вызывает strExtractQuotedExpression. Примечание: Сервер не заключает тип системы в кавычки. Тип системы сервера описывается всем текстом ответа целиком. 19 Зак. № 1949 577 
Глава 18. Визуальные приложения интернет Function strDoSYSTCommand () As String Dim strCommand As String Dim strSystemType As String Dim intServerReplyCode As Integer subShowButtons False strCommand = "SYST" & glbstrCRLF subShowServerCommand (strCommand) intServerReplyCode = SendFTPCommand(glbintControlSocket, strCommand) subShowServerReplyCode (intServerReplyCode) If intServerReplyCode = 215 Then strSystemType = strGetServerReplyText() strSystemType = strCutCrLf(strSystemType) Else strSystemType = "" End If strDoSYSTCommand = strSystemType subShowButtons True End Function Процедура strExtractQuotedExpression В некоторых случаях FTP-сервер отвечает на команду строкой, содержащей специально отмеченный текст, представляющий интерес для клиента. Например, FTP-серверы отвечают на команду PWD строкой, которая выглядит примерно так: 257 "PATHNAME" created. Процедура strExtractQuotedExpression, показанная в этой части, отыскивает в переданной ей строке значение в кавычках. Function strExtractQuotedExpression (strValue As String) As String Dim intlstQuoteLocation As Integer Dim int2ndQuoteLocation As Integer Dim strQuotedValue As String ' Если две кавычки не найдены, используем значение, ' переданное вызывавшим модулем strQuotedValue = strValue intlstQuoteLocation = InStr(l, strValue, Chr(34)) ' Двойная кавычка 578 
Процедуры SockFTf1 If intlstQuoteLocation > 0 Then int2ndQuoteLocation = InStr(intlstQuoteLocation + 1, strValue, Chr(34)) If int2ndQuoteLocation > 0 Then strQuotedValue = Mid$(strValue, intlstQuoteLocation + 1, int2ndQuoteLocation - intlstQuoteLocation - 1) End If End If strExtractQuotedExpression = strQuotedValue End Function Процедура использует функцию InStr, чтобы найти первое вхождение символа. Если процедура находит двойную кавычку, она использует функцию InStr еще раз начиная поиск со следующего, за только что найденным, символа. Если процедура находит вторую двойную кавычку, она использует функцию Mid, чтобы извлечь подстроку, находящуюся между первой и второй двойными кавычками. Если процедура не может найти обе кавычки, она возвращает переданную ей строку в неизменном виде. Вызывающая функция может использовать всю строку целиком или заключенное в кавычки значение, если оно существует. В зависимости от требований вашей программы, вы могли бы предпочесть иметь процедуру, возвращающую пустую строку, если она не может найти выделенного значения. Однако для программы SockFTP текущая реализация предпочтительнее. Процедуры strGetServerReply и st rGet Server Re plyText Процедура strGetServerReply использует технику, описанную ранее для запроса функции lstrcpy Windows, чтобы получить копию строки, которая находится вне контекста Visual Basic. Как вы можете вспомнить, пересмотр QFTPLIB для программ QFTP добавлял функцию GetFTPServerReplyText. GetFTPServerReplyText возвращает указатель LPSTR на глобальный буфер, в котором хранится текст ответа FTP-сервера. Следующие операторы определяют процедуру strGetServerReply: Function strGetServerReply () As String Dim IpReplyTextAddress As Long Dim strReply As String IpReplyTextAddress = GetFTPServerReplyText() strReply = Space$(2048) IpReplyTextAddress = lstrcpy(strReply, IpReplyTextAddress) strGetServerReply = strReply End Function 579 
Глава 18. Визуальные приложении Интерне! Процедура strGetServerReply вызывает GetFTPServerReplyText и сохраняет возвращенное значение указателя в 32-битной переменной с типом Long по имени IpReplyTextAddress. Затем strGetServerReply резервирует место (как требуется в Visual Basic) для хранения максимально возможного буфера ответов QFTPLIB.DLL. Наконец, strGetServerReply вызывает функцию lstrcpy Windows, чтобы копировать данные из буфера DLL в зарезервированное в Visual Basic место, указываемое strReply. Процедура strGetServerReply возвращает строку ответа сервера, которая теперь размещается в зарезервированном месте памяти, доступной Visual Basic. Примечание: Функция Space резервирует столько места памяти в байтах, сколько указано в ее числовом параметре. Как работает strGetServerReplyText? Как показано ниже, функция strGetServerReplyText зависит от strGetServerReply, которая исполняет основную работу. Ключевое различие между strGetServerReplyText и strGetServerReply в том, что strGetServerReply возвращает полную строку ответа сервера, a strGetServerReplyText выкидывает из строки код ответа: Function strGetServerReplyText () As String Dim strReplyText As String strReplyText = strGetServerReply() StrGetServerReplyText = Right(StrReplyText, Len(strReplyText) - 4) End Function Функция strGetServerReplyText использует Visual Basic функции Right и Len, чтобы возвратить только часть текста ответа сервера. Другими словами, strGetServerReplyText исключает первые четыре символа (которые содержат код ответа из трех цифр и пробел или знак переноса) из текста ответа сервера. Как работает strTransferFile? Функция strTransferFile исполняет операцию передачи данных для процедуры intDoRETRCommand SockFTP. SockFTP мог бы обойтись без функции strTransferFile и позволить intDoRETRCommand вызывать только функцию QFTPLIB ReadDataChannel так же, как это делает intDoListCommand. Однако операции по передаче файла потенциально требуют еще большего времени для работы, чем передача списка файлов каталога. Как вы помните, функция ReadDataChannel использует единственный цикл do-while для чтения из канала данных. Также, когда SockFTP использует функцию ReadDataChannel для передачи файла, SockFTP не имеет никакого 580 
Проиедуры Sos&Flp способа информировать пользователя относительно продвижения в передаче данных. Функция QFTPLIB TransferFile обеспечивает лучшей альтернативой. Функция TransferFile единственный раз вызывает recv и возвращает число байтов, полученных из канала данных. Таким образом, SockFTP может вызывать функцию TransferFile изнутри цикла do-while Visual Basic и показывать общее количество байтов, переданных от FTP-сервера. Вместо того чтобы добавлять дополнительное текстовое окно с информацией о состоянии передачи данных, функция strTransferFile временно использует текстовые окна Host, USER ID, PASSWORD и System Type. Фактически, большинство операторов программы strTransferFile или заменяют, или восстанавливают свойства и значения, связанные с четырьмя текстовыми окнами, которые отображают информацию о состоянии передачи файла. Следующие программные операторы показывают необходимые действия функции strTransferFile: Function strTransferFile (hDataSocket As Integer, strFileName As String) As String 7 Объявляем локальные переменные 7 ... еще операторы 7 Преобразуем имя файла, выбранного пользователем, в 7 формат DOS strLocalFile = Space$(13) IngPointerAddress = ExtractFileName(strFileName, strLocalFile) hFile = CreateTransferFile(strLocalFile) If hFile <> HFILE_ERROR Then 7 Сохраняем значения текстовых окон 7 и сбрасываем некоторые переменные 7 ... еще операторы 7 Инициализируем счетчик общего количества байтов 7 и входим в цикл чтения IngTotalBytes = О Do 7 Считываем данные из канала данных и обновляем 7 содержимое счетчика байтов intBytes = TransferFile(glbintControlSocket, hDataSocket, hFile) IngTotalBytes = IngTotalBytes + intBytes frmSockFTP.txtSystemType.Text = Str$(IngTotalBytes) Loop While intBytes > 0 7 Считываем ответ сервера strServerReply = strGetServerReply() MsgBox strServerReply, , "SockFTP" 581 
РЯЭВЙ IS. визуэлвммв приложвнив ИНТВрНВТ ' Восстанавливаем значения текстовых окон и свойства по ' умолчанию ' ... еще операторы strTransferFile = strServerReply Else MsgBox "Unable to create " & strLocalFile, , "SockFTP" strTransferFile = "File creation error." End If End Function Если вы рассмотрите полный текст strTransferFile (он находится в файле SOCKFTP.BAS), то обнаружите, что эта функция довольно длинна. Однако, как уже говорилось, большинство ее операторов просто сохраняют и восстанавливают значения текстовых окон. Процедура subCenterForm Процедура subCenterForm не связана с сетевой работой, она просто вычисляет размер экрана компьютера пользователя и размещает форму на экране. В SockFTP subCenterForm вызывается процедурой обработки события Form_Load прежде, чем Visual Basic показывает форму SockFTP. Другими словами, прежде чем Visual Basic показывает форму SockFTP, программа выполняет эту процедуру, чтобы разместить и отцентрировать себя на экране компьютера, независимо от его размера: Sub subCenterForm (frm As Form) ' Центрируем форму по горизонтали frm.Left = (Screen.Width - frm.Width) / 2 ' Центрируем форму по вертикали frm.Top = (Screen.Height - frm.Height) / 2 End Sub Процедура subCloselncompleteConnection SockFTP использует процедуру subCloselncompleteConnection, чтобы закрыть сокет канала контроля, если программа не смогла успешно войти в систему FTP-сервера. Единственное место в SockFTP, где используется эта процедура, — cmdConnect_Click. Однако cmdConnect_Click вызывает subCloselncompleteConnection в двух случаях, которые происходят в различных точках процедуры cmdConnect_Click. В одном случае SockFTP может успешно открыть сокет контроля, но функция CreateListenSocket будет не в состоянии открыть ожидающий сокет. В другом случае программа может открыть сокет контроля и ожидающий сокет успешно, но AcceptDataConnection не сможет возвратить действительный дескриптор 582 
Процедуры SockFTP сокета данных. В обоих случаях cmdConnect_Click вызывает функцию subCloselncompleteConnection, чтобы закрыть сокет контроля. Как видим в нижеследующем тексте процедуры, subCloselncompleteConnection также несет ответственность за переустановку Disconnect, Clear, Connect и кнопок Exit в их начальное состояние: Sub subCloselncompleteConnection () Dim intWinsockReplyCode As Integer intWinsockReplyCode = closesocket(glbintControlSocket) frmSockFTP.cmdDisconnect.Visible = False frmSockFTP.cmdClear.Visible = False frmSockFTP.cmdConnect.Visible = True frmSockFTP.cmdExit.Visible = True subShowServerCommand ("Socket closed—incompleteconnection!") End Sub Процедура subReadListFile Процедура subReadListFile — другая не связанная с Интернет процедура в SockFTP. Процедура subReadListFile использует стандартные для Visual Basic функции ввода-вывода файла, чтобы открыть и прочитать файл, содержащий значения для окна-списка файлов. Другими словами, subReadListFile открывает и читает файл, который создает ReadDataChannel. Процедура subReadListFile использует метод Addltem Visual Basic для добавления каждой строки из файла в окно-список. Sub subReadListFile (strFileName As String) Dim intFileHandle As Integer intFileHandle = FreeFile frmSockFTP.IstServerFiles.Clear On Error GoTo ErrorHandler Open strFileName For Input Access Read Lock Write As intFileHandle Do While Not EOF(intFileHandle) Line Input #intFileHandle, strFileName frmSockFTP.IstServerFiles.Addltem strFileName Loop Closefile: Close #intFileHandle Exit Sub ErrorHandler: Resume Closefile End Sub 583 
Глава 18. Визуальные приложения Интернет Процедуры subShowServerCommand и subShowServerReplyCode И subShowServerCommand и subShowServerReplyCode добавляют новые пункты в окно-список сообщений. Единственное различие состоит в том, что subShowServerReplyCode ожидает значение целого типа (числовое значение кода ответа), a subShowServerCommand ожидает строку. Процедура subShowServerReplyCode использует функцию Str, чтобы преобразовать ответ из числового значения в строку. Следующие программные операторы определяют процедуры subShowServerReplyCode и subShowServerCommand: Sub subShowServerCommand (ByVal strCommand As String) Dim strListBox As String strListBox = strCutCrLf(strCommand) frmSockFTP.IstServerDialog.Addltem strListBox, 0 End Sub Sub subShowServerReplyCode (intReplyCode As Integer) Dim strListBox As String strListBox = Str$(intReplyCode) frmSockFTP.IstServerDialog.Addltem strListBox, 0 End Sub Процедура subShowButtons Как мы уже говорили, процедура subShowButtons устанавливает свойство Enabled для пяти кнопок команд, которые управляют файловыми операциями FTP-сеанса. Другие процедуры SockFTP вызывают процедуру subShowButtons со значением True, чтобы разрешить кнопки, и со значением False, чтобы запретить их: Sub subShowButtons (intButtonToggle As Integer) frmSockFTP.cmdListDir.Enabled = intButtonToggle frmSockFTP.cmdRetrieve.Enabled = intButtonToggle frmSockFTP.cmdChgWorkingDir.Enabled = intButtonToggle frmSockFTP.cmdCwdUp.Enabled = intButtonToggle End Sub Подводя итоги Если вы внимательно изучите все функции Visual Basic и подпрограммы, рассмотренные в этой главе, то увидите, что большинство процедур состоят из одного или нескольких операторов. Другими словами, хотя в этой главе описано большое количество разнообразных процедур, каждая процедура, взятая сама 584 
Штщшт шшги по себе, вполне легка для понимания. Глава продемонстрировала, как вы можете использовать Winsock и пользовательские DLL в визуальной окружающей среде программирования. Процедуры SockFTP, определенные в этой главе, можно использовать, как модели функций и процедур, проектируемых для реального FTP-клиента. В следующей главе вы узнаете, как использовать подобные методы программирования для работы с Word Wide Web. Прежде чем вы приступите к чтению следующей главы, проверьте, хорошо ли вы усвоили эти ключевые концепции: S Оператор Visual Basic Declare позволяет использовать фактически любую функцию из Windows API или любой динамической библиотеки DLL, написанной для операционной системы Windows. S Визуальные инструменты программирования обеспечивают ценный механизм для быстрого макетирования и испытания. S Относительно простая Winsock-основанная динамическая библиотека DLL, объединенная с визуальной окружающей средой программирования, создает мощный инструмент для разработки приложений Интернет. 
Всемирная паутина World Wide Web В течение длительного времени ученые и инженеры прорабатывали идею создания универсальной информационной базы данных. Однако только недавно они получили средства, чтобы создать ее. Множество людей рассматривает Интернет и Всемирную Паутину (Word Wide Web, WWW или W3) как экспериментальный образец такой базы данных. Технология, разрабатываемая профессионалами для WWW, воплощает идею глобальной информационной базы данных, реализованную в пределах возможностей настоящего момента. На дискете, приложенной к книге, находится файл помощи (WEBHELP.HLP), в котором рассматриваются компоненты, входящие в WWW. В нем объясняются основные концепции WWW типа гипертекста и гиперсвязей. В нем также описывается язык разметки гипертекста (Hypertext Markup Language, HTML) и даны образцы его использования. Для использования help-файла WWW достаточно дважды щелкнуть мышью на иконке «Web Help» в окне группы «Internet Programming». 586 
Протокол передачи гипертекста В этой главе мы сосредоточимся на проблемах программирования WWW. Предполагается, что вы уже знакомы с WWW, по крайней мере, как пользователь. Если нет, то до того, как приступить к чтению этой главы, изучите его возможности при помощи help-файла. К тому времени, как вы закончите чтение, вы овладеете следующими ключевыми понятиями: ♦ Каким образом в WWW определяется местоположение и методы доступа для практически любого типа файла, который можно найти в Интернет. + Как гипертекстовые документы позволяют вам получить большое количество разнообразной информации, не думая о процессе ее поиска. ♦ Как гипертекстовые документы используют язык разметки гипертекста (HTML), чтобы определить гиперсвязи, объединяющие логически связанную информацию, хранимую в разных местах по всему миру. ♦ Как протокол передачи гипертекста (HTTP) позволяет программам-клиентам WWW получать информацию из Интернет. ♦ Как создать программу-клиент WWW — основу для написания автономной программы для поиска файлов в Интернет (web spider). ♦ Как создать простую и легко изменяемую программу-сервер WWW, которая могла бы действовать по протоколам, близким по структуре к HTTP. Протокол передачи гипертекста Подобно всему в Интернет действия WWW зависят от протокола передачи гипертекста (HTTP). Так же как в FTP, POP и SMTP, в HTTP задан набор команд, передающийся посредством строк текста в формате ASCII. Транзакция HTTP еще более проста, чем транзакции FTP, обсуждавшиеся в предыдущих трех главах. Транзакция HTTP состоит из четырех частей: установление соединения, запрос, ответ и завершение. Программа-клиент HTTP устанавливает TCP-соединение с официальным портом HTTP (80) на удаленном компьютере. Затем клиент посылает запрос к серверу HTTP. После того как сервер HTTP высылает ответ, клиент или сервер закрывают соединение. Каждая транзакция HTTP подчиняется вышеописанной схеме. 587 
Глава wSk Всемирная паутина World Wee Основанный на Winsock клиент HTTP использует ту же самую процедуру, чтобы установить HTTP-соединение. В большинстве случаев клиент HTTP будет запрашивать сервер HTTP, послать ли файл с гипертекстом (типа файла HTML) или файл с гипермедиа (например, изображение, видео, звук или файл с мультипликацией). Также в большинстве случаев ответ сервера состоит из передачи запрошенного файла потоком байтов в локальный порт протокола клиента. HTTP не требует никаких специальных действий по завершению соединения. Как клиент, так и сервер (или оба сразу) имеют право закрыть TCP-соединение. Запросы клиента HTTP Web обеспечивает пользователей сети свободным доступом к огромному количеству файлов — ресурсам Интернет. Как уже говорилось, после установления соединения клиент HTTP может послать HTTP-запрос. Запрос клиента HTTP состоит обычно из просьбы к HTTP-серверу передать файл (гипертекстовый документ, изображение, звуковой файл, мультипликацию или видео) от сервера к клиенту. Для получения файла требуется, чтобы программа-клиент WWW передала имя определенного файла, его местоположение в Интернет (адрес хоста) и метод передачи (обычно протокол типа HTTP или FTP). Комбинация этих элементов формирует нечто, названное универсальным идентификатором ресурса (Universal Resource Identifier, URI). Указатели ресурсов: URI и URL Чтобы получить файл из Интернет, броузер (browser, программа для просмотра Web) должен знать, где находится файл и как общаться с компьютером, на котором этот файл находится. На сегодняшний день броузеры WWW используют протокол HTTP и несколько других общих для Интернет протоколов типа FTP, GOPHER, ARCHIE, VERONICA и WAIS. В состав URI входит информация, требующаяся броузеру WWW, чтобы использовать любой из этих протоколов. Help-файл по WWW содержит дополнительную информацию относительно URI и унифицированных указателей ресурсов (Uniform Resource Locators, URL), тесно связанных между собой. В следующих абзацах приводится их краткий обзор. Различие между URI и URL достаточно сложно уловить. Вообще, вы можете рассматривать некоторый URI как идентификатор определенного объекта типа файла изображения или HTML-файла. URI является как бы обобщенным названием любого объекта в Интернет. Однако если вы хотите использовать существующие протоколы, чтобы получить объект из Интернет, вы должны знать IP-адрес компьютера, хранящего этот объект. URL является URI, который содержит информацию о местоположении (адрес), закодированную в URI. 588 
Указатели ресурсов: UR1 и URL Что такое URL? WWW проложил дорогу практическому применению URL, и профессионалы планируют переработать спецификацию URL в стандарт, который другие профессионалы могли бы плодотворно использовать. Читая следующие разделы (и особенно, если вы решите взглянуть на RFC 1738, «Унифицированные указатели ресурсов (URL)» (Uniform Resource Locators, URL, Berners-Lee, Masinter и McCahill, 1994)), имейте в виду, что цель создания спецификации URL состоит в том, чтобы определить общий синтаксис для ссылок и именования широкого разнообразия объектов Интернет. Синтаксис URL Как уже говорилось, URL содержит информацию об адресе или местоположении объекта. Цель, преследуемая URL, состоит в том, чтобы дать возможность другим программам найти объект в Интернет. Практически, URL можно рассматривать, как специальный тип сетевого адреса, который, однако, не просто идентифицирует сетевой компьютер, но и идентифицирует определенный объект на нем. Кроме того, в URL задается другая важная характеристика, отличающая его от других типов сетевых адресов, — метод доступа к сетевому объекту. В RFC 1738 методы доступа называются схемами. Другими словами, схема URL описывает, каким образом программа может получить определенный сетевой объект. На сегодняшний день методы доступа соответствуют сетевым протоколам. В табл. 19.1 содержится список определенных в настоящее время схем доступа. Как видим, некоторые схемы доступа URL — не что иное, как протоколы, изученные вами в предыдущих главах этой книги. Таблица 19.1. Определенные на настоящий момент схемы доступа URL Схема доступа (элемент URL) Описание ftp Протокол передачи файлов http Протокол передачи гипертекста gopher Протокол Gopher mailto Адрес электронной почты news Новости USENET nntp Новости USENET по протоколу NNTP telnet Сеанс telnet wais Сервер протокола wais file Имя файла в компьютере prospero Служба каталогов ргоьрего 589 
Глара 19. Всемирная паутина Worldwide Web Синтаксис URL прост, он состоит из двух частей, как показано ниже: <схема>:<часть-относящаяся-к-схеме> Первая часть URL содержит название схемы (обычно — название протокола), которая используется для доступа к объекту. Вторая часть содержит специфическую для данной схемы информацию типа названия и местоположения объекта, а также информацию для определенного метода доступа. Например, большинство пользователей Интернет знакомы с протоколом Telnet. Вам, вероятно, известно, что программу-клиент Telnet можно использовать для входа практически в любой компьютер Интернет. Однако в зависимости от того, в какую систему вы хотите войти, вам может понадобиться ввести имя пользователя и пароль. Схема URL Telnet обеспечивает необходимый для ввода пароля и имени синтаксис: telnet://<пользователь>:<пароль>@<компьютер>:<порт>/ Так как в этой главе мы сконцентрируем внимание на WWW, в большинстве примеров URL будет использоваться протокол передачи гипертекста (HTTP). Как уже упоминалось, HTTP — специальный протокол, предназначенный для WWW. Подробная информация относительно других схем доступа URL находится в документе RFC 1738. Определение схемы URL HTTP Схема доступа URL HTTP просто определяет объекты Интернет, для доступа к которым может использоваться протокол передачи гипертекста (HTTP). Синтаксис схемы доступа URL HTTP (показанный ниже) похож на синтаксис схемы доступа Telnet: http: / /<компьютер>: <порт>/<путь>?<информация__для__поиска> Как видим, схема URL — HTTP, а в специфической для схемы части задаются: сетевой компьютер (имя или адрес), номер порта, путь и дополнительная информация для поиска. Если элемент «порт» в URL не указывается, будет использован порт 80 (официальный порт протокола HTTP). Обратите внимание, что схема URL HTTP не позволяет вам задавать имя пользователя или пароль. Вы наверное знаете, что в WWW в настоящее время не предусмотрена возможность указывать имя пользователя или пароль. Также имейте в виду, что протокол HTTP в настоящее время не умеет обслуживать часть URL «информация_для_поиска». Хотя элемент «информация_для_поиска» и включен в синтаксис схем URL HTTP (в RFC 1738), вы нигде не сможете найти ему реального применения. Элемент «путь» схемы URL мы рассмотрим позже при обсуждении HTTP. Сейчас вам достаточно знать, что элемент «путь» задает каталог сетевого компьютера, в котором содержится интересующий нас объект (как правило, документ HTML), предназначенный для передачи по HTTP. 590 
Методы HTTP Методы HTTP Как уже говорилось, запрос клиента HTTP (обычно запрос на передачу файла) — вторая часть транзакции HTTP. Запросы HTTP-клиента делятся на две основные категории: простой запрос и полный запрос. Запросы HTTP называются методами. Единственный метод простого запроса — метод GET. Как видим, при простом запросе программа просто передает URI и комбинацию CRLF после команды GET: GET <uri> CRLF Этот простой запрос заставит сервер HTTP найти и передать объект, который указан в URI. Объект может быть документом HTML, изображением, звуком, видео или файлом мультипликации. Так как клиент запрашивает совершенно определенный тип объекта, он должен знать, как обращаться с полученным объектом. Например, клиент, который запрашивает файл изображения, должен знать, как показать этот файл на дисплее компьютера. Для простого НТТР-запроса сервер HTTP является не более, чем файловым сервером. Полный HTTP-запрос также начинается с метода HTTP. К сожалению, так как определения большинства предложенных методов HTTP для полных запросов несовершенны, эти методы в настоящее время не слишком полезны. После команды полного запроса следует URI и версия протокола HTTP. Полный запрос может также включать область запроса заголовка (header) (подобные областям заголовка MIME, связанным с SMTP). Однако каждый элемент передается отдельной строкой, оканчивающейся CRLF. Код ответа сервера HTTP подобен кодам, используемым серверами SMTP, POP и FTP. Однако код ответа (или код состояния, как он называется в спецификации HTTP) — не первый элемент в строке ответа. Вместо этого в качестве первого элемента в строке ответа печатается нфмер версии: <Bepcnn_http> <код__состояния> <текст> <CRLF> Подробности, касающиеся протокола HTTP, его методов и полей запроса, можно найти в его последней спецификации. Лучшее место для начала поисков свежей информацию о WWW — WWW-сервер в CERN. (CERN — Европейская лаборатория ядерной физики; находится в Швейцарии, где и родился проект WWW.) Если у вас есть WWW-броузер, начните с URL http://www.w3.org/ и следуйте по гиперсвязям к самой последней информации. Если у вас нет броузера, самая последняя информацию все равно доступна через анонимный FTP-сервер по адресу ftp.www.w3.org. Возможности WWW Документы HTML содержат много информации, которую WWW-клиент должен как-то интерпретировать. Однако действия, производимые WWW-клиентом, еще более просты, чем действия клиента FTP. Чтобы работать с WWW, 591 
Глава T9- Всемирная паутина World ЩЫш Iftteb программа должна управлять всего лишь одним TCP-соединением. Кроме того, программа должна держать эту связь открытой лишь на время передачи файла. В результате, разработка программ, работающих с WWW и получающих гипертекстовые документы, — вполне тривиальный процесс. Создание же программы-броузера, которая должным образом интерпретирует и показывает содержание HTML-документа, — процесс весьма и весьма трудоемкий. Создание броузера не связано с работой в сети. В нашей книге мы не будем обсуждать написание WWW-броузеров, а затронем лишь аспекты получения сетевых файлов. На сегодняшний день WWW является передним краем технологии Интернет. Поскольку число пользователей, подключающихся к Интернет, каждый день продолжает увеличиваться, WWW будет быстро развиваться. Если вы хотите стать участником этого процесса, вы можете приступить, например, к созданию собственного WWW-броузера. Хотя ряд общеизвестных превосходных броузеров уже существует, например Netscape Navigator или Mosaic, имеется, конечно, место и для чего-нибудь лучшего. Будем надеяться, что эта задача вам вполне по плечу. Не забывайте, однако, что с вашей продукцией будет конкурировать продукция некоторых крупных компаний, имеющих огромное преимущество в маркетинге. WWW предоставляет огромные возможности. Область, остающаяся фактически незатронутой, — инструменты автоматизирования поиска в гиперпространстве. (Инструменты автоматизирования поиска — программы, которые исследуют WWW, находя и записывая информацию без человеческого вмешательства.) Тем более автоматические инструменты поиска — это область, которая, вероятно, всегда будет требовать высокой степени настройки на нужды конкретных пользователей. Другими словами, независимо от того, самостоятельны ли вы или работаете для кого-то еще, потребность в новых развитых программах для исследования WWW возрастает, так как все больше и больше людей в мире обнаруживают для себя удивительный мир WWW. Проблемы К сожалению, всего за несколько лет существования WWW уже возник ряд проблем, связанных с его автоматизированным исследованием. К автоматизированным инструментам исследования WWW относятся так называемые spiders («пауки»), robots («роботы») и wanderers («странники»). Другими словами, spider WWW (или robot или wanderer) — программа, которая исследует WWW автоматически без человеческого вмешательства. Возможности, предоставляемые программой spider, открывают мир интереснейших сетевых приложений. Так как быстро возрастающее количество документов в WWW содержит огромное количество информации, WWW становится основным местом для поиска необходимых данных. Ну, а размер WWW и количество объединяемых им ресурсов, делают автоматизированное исследование очень желательным. Однако прежде чем садиться писать собственную программу spider, проведите небольшое исследование, основываясь на материалах следующих разделов. 592 
программирование WWW Краткий обзор Замечательное исследование по поводу программ автоматического поиска в WWW находится на домашней странице английской компании NEXOR по адресу http://web. пехог. со. uk/mak/doc/robots/robots. html. Домашняя страница NEXOR приведет вас к множеству полезной и важной информации. Хотя не вся информация размещается на этом сервере, гипертекстовые документы сервера NEXOR содержат связи с большинством мест, которые вы просто обязаны посетить для того, чтобы получить подготовительную информацию относительно программ spider. На странице NEXOR вы найдете гиперсвязи к списку существующих роботов WWW и других автоматических инструментов. Кроме того, вы обнаружите список почтовой рассылки, где обсуждаются технические аспекты автоматических программ поиска в WWW. Два наиболее важных документа вы можете получить через собственную страницу NEXOR — «Guidelines for Robot Writers» («Советы программистам, пишущим программы-роботы») и «Ethical Web Agents» («Этичные агенты Web»). Для начала прочитайте «Ethical Web Agents». Документ в форме HTML, написанный Давидом Эйхманном (Eichmann David), профессором-ассистентом по разработке программного обеспечения в университете Хьюстона, Clear Lake. Документ Эйхманна содержит ценную информацию для подготовки и предысторию, связанную с автоматическими инструментами поиска в WWW. Эйхманн описывает выгоды и опасности, связанные с такими программами. Затем прочитайте «Guidelines for Robot Writers» (также в форме HTML) Мартина Костера (Koster Martijn) в NEXOR. Документ Костера описывает философию проекта робота и указывает в общих чертах, что могут и чего не могут программисты, его разрабатывающие. К тому времени, когда вы закончите изучение этих и других документов, находящихся на сервере NEXOR (или связанных с ним), вы сами будете разбираться в большинстве проблем, встречающихся при создании автоматических инструментов поиска в WWW. Программирование WWW Дискета, приложенная к книге, содержит простую программу, названную SockWEB, которая умеет получать файлы из WWW. SockWEB — программа на Visual Basic, подобная программе SockFTP, рассмотренной в предыдущей главе, только намного проще. Подобно программе SockFTP SockWEB также использует простую DLL, чтобы исполнять функции сетевого ввода-вывода. Однако вместо QFTPLIB.DLL SockWEB использует динамическую библиотеку QWEBLIB.DLL. Далее обсуждается динамическая библиотека QWEBLIB и программа SockWEB. Если после изучения информации, рекомендованной выше, вы решаетесь спроектировать программу-spider, вы сможете использовать программу SockWEB, как основу для построения вашей собственной разработки 593 
Глава 19. Всемирная паутина Wori^ Wide Web Обзор динамической библиотеки QWEBLIB QWEBLIB — динамическая библиотека, которая содержит очень немного незнакомых по предыдущим примерам функций. В табл. 19.2 содержится список функций, определенных в динамической библиотеке QWEBLIB. Таблица 19.2. Функции, определенные в динамической библиотеке QWEBLIB Функция Описание ConnectWebServerSocket Создает и соединяет сокет сервером WWW. ExtractFileName Получает имя файла в формате DOS из URL. SendWebQuery Передает простой запрос (использующий команду GET), чтобы получить сетевой файл. RecvWebFile ^ Читает данные из сокета WWW-соединения и записывает данные в локальный файл. LibMain Инициализирует динамическую библиотеку QWEBLIB. WEP Предоставляет пустую оболочку, которую вы можете использовать, чтобы создать процедуру выхода. Функции WEP, LibMain и функция ExtractFileName в динамической библиотеке QWEBLIB идентичны одноименным функциям в QFTPLIB. Функция ConnectWebServerSocket создает и соединяет сокет с портом 80 удаленного компьютера (порт протокола HTTP). Функция SendWebQuery передает запрос удаленному хосту. Как показано ниже, функция SendWebQuery вызывает ExtractFileName и создает локальный файл данных, чтобы хранить данные, переданные WWW-сервером: extern "С" HFILE _export FAR PASCAL SendWebQuery (SOCKET nSocket, LPSTR IpszQuery) HFILE hFile; // OFSTRUCT openFileBuff; // // char szFileName[13]; // char szWebQuery[100]; // int nCharSent; // char szMsg[100]; // // Дескриптор файла данных Структура для открытия файла Windows Буфер для имени нового файла Буфер хранения запроса Web Количество переданных символов Буфер общего назначения для сообщений 594 
Программирование* WWW wsprintf(szWebQuery,"GET %s\n", (LPSTR)IpszQuery); nCharSent = send(nSocket, szWebQuery, lstrlen(szWebQuery), NO_FLAGS); if (nCharSent == SOCKET_ERROR) { nCharSent = WSAGetLastError(); wsprintf(szMsg,"%d Error occurred during send()!", nCharSent); MessageBox(NULL, szMsg, PROG_NAME, MB_OK|MB__ICONSTOP) ; hFile = SOCKET_ERROR; } else { ExtractFileName(IpszQuery, szFileName); l hFile = OpenFile(szFileName, (OFSTRUCT far *) &openFileBuf f, OF__CREATE) ; if (hFile == HFILE__ERROR) { wsprintf(szMsg,"Error occurred opening file: %s", (LPSTR)szFileName); MessageBox (NULL, szMsg, PROG_NAME, MB_OK | MB__I CONS TOP) ; } } return(hFile); } Функция SendWebQuery возвращает дескриптор локального файла данных. После того как функция RecvWebFile (показанная ниже) заканчивает передачу данных, она закрывает файл данных, созданный функцией SendWebQuery: PASCAL RecvWebFile(SOCKET nSocket, HFILE hFile) extern "C" UINT _export FAR { char szWeblnfo[5000]; int nCharRecv; char szMsg[1000]; static LONG lTotalData; // Буфер для хранения // поступившей информации // Количество принятых символов // Буфер общего назначения для // сообщений // Отслеживает общее количество // переданных данных nCharRecv = reev(nSocket, (LPSTR)&szWebInfо, sizeof(szWeblnfo), NO_FLAGS); 595 
Глава 19. Всемирная паутина World Wilde Web lTotalData += nCharRecv; if (nCharRecv > 0 ) { if (HFILE__ERROR == _lwrite (hFile, szWeblnfo, nCharRecv)) { lTotalData = 0; _lclose(hFile); wsprintf(szMsg, "%d Error occurred during recv()!", nCharRecv); MessageBox(NULL, szMsg, PROG_NAME, MB__OK|MB_ICONSTOP) ; return(HFILE_ERROR); } if (nCharRecv == lTotalData) { if (*(szWeblnfo+O) == f<f) { *(szWeblnfo+nCharRecv) = '\0'; wsprintf(szMsg,"%s", (LPSTR)szWeblnfo); MessageBox(NULL, szMsg, PROG_NAME, MB_OK | MB_ICONSTOP) ; } } } else if (nCharRecv == SOCKET_ERROR) { lTotalData = 0; _lclose(hFile); nCharRecv = WSAGetLastError(); wsprintf(szMsg,"%d Error occurred during recv()!", nCharRecv); MessageBox(NULL, szMsg, PROG_NAME, MB_OKIMB_ICONSTOP); return(SOCKET_ERROR); } if (nCharRecv == 0) { lTotalData = 0; _lclose(hFile); } return(nCharRecv); 596 
Щршрттщтттмт 1Ш Как видим, функция RecvWebFile считывает данные из сокета и записывает их в локальный файл данных практически так же, как в других примерах программ, рассмотренных ранее. Однако можно не уловить цели следующих операторов программы: if (nCharRecv == lTotalData) { if (*(szWeblnfo+O) == '<') { *(szWeblnfo+nCharRecv) = '\0'; wsprintf(szMsg,"%s", (LPSTR)szWeblnfo); MessageBox(NULL , szMsg, PROG_NAME, MB_OKIMB_ICONSTOP); } } Конструкция if проверяет первые данные, полученные от сокета, чтобы определить, являются ли они текстом в формате HTML (файлом HTML или основанным на HTML сообщением). Если поступающие данные в формате HTML, функция RecvWebFile показывает начальный блок данных в диалоговом окне. Таким образом, программа позволяет пользователю прерывать передачу, если поступают не те данные, которых он ожидал. ^ Чтобы понять, как работает конструкция if, обратите внимание на то, что функция RecvWebFile сохраняет число байтов, полученных от каждого вызова функции recv в переменной nCharRecv. Затем обратите внимание, что функция RecvWebFile использует статическую переменную lTotalData для хранения общего числа полученных байтов. (Так как переменная lTotalData статическая, переменная lTotalData сохраняет свое значение при многократных вызовах функции RecvWebFile.) Единственный раз, когда nCharRecv будет равна lTotalData (условие, которое проверяется оператором if) — это когда функция recv читает данные из сокета в первый раз. Программа SockWEB использует динамическую библиотеку QWEBLIB для сетевого ввода-вывода. Вы можете использовать программу SockWEB, чтобы считывать файлы гипертекста или файлы мультимедиа (изображения, звука, видео или файлов мультипликации). Как показано в help-файле на дискете, приложенной к книге, файлы HTML используют символы «больше, чем» (>) и «меньше, чем» (<), чтобы выделять тэги (знаки формата) HTML. Обычно первый символ в файле HTML будет «меньше, чем» (<), поскольку HTML-файл начинается с тэга. Оператор г/*, использованный в программе, и проверяет первый символ первого блока данных, полученных из сокета: if (*(szWeblnfo + 0) == '<') Если первый символ — «меньше, чем» (<), функция RecvWebFile предполагает, что поступающие данные являются файлом HTML, и показывает блок данных в окне сообщения. Независимо от того, являются ли поступающие данные гипертекстом или нет, функция RecvWebFile записывает их в локальный файл данных. 597 
Глава 19. Всемирная паутина World Wide Web Как работает SockWEB? Основная цель динамической библиотеки QWEBLIB, рассмотренной в предыдущем разделе, — обеспечивать сетевой ввод-вывод для программы SockWEB. Программа SockWEB — очень простой броузер для HTML-файлов. Как показано на рис. 19.1, в диалоговом окне броузера SockWEB (форма Visual Basic) первоначально присутствуют два окна текста и единственная командная кнопка. Рис. 19.1. Начальное диалоговое окно программы просмотра файлов SockWEB Когда пользователь щелкает мышью на кнопке команды Read, программа пробует установить связь с сетевым компьютером, имя которого задано в соответствующем текстовом окне. Если это удается, программа читает файл по имени, определенном в соответствующем текстовом окне. Программа скрывает кнопку команды Read, показывает кнопку Cancel и еще два дополнительных текстовых окна. Как показано на рис. 19.2, одно текстовое окно показывает число переданных байтов, а другое — имя локального файла, куда поступающие данные будут записываться. Когда передача данных закончится, окно диалога SockWEB возвращается обратно к состоянию, показанному на рис. 19.1. Далее мы объясним, каким образом программа SockWEB принимает данные и как управляет формой Visual Basic. [ДЦ WebSevvei: •*' , ' [ill Hi TRANSFERRED ’ Local Fife Name : www. whitehouse. gov 65304 ;• •• , • Ж-Шniyersa(fie^urce Locator (U R Lj. a 'l * . ’ .4: i‘“in ■■■■ ‘ m : I ;; /White_H ouse/images/ white_house_home. gif ^CANCEL 11 И ■ . 4 hmii:•Jv 1Ш 4K.. ■ . A Рис. 19.2. Диалоговое окно SockWEB во время приема данных 598 
Программирование WWW Составляющие программы SockWEB Как обсуждалось в главе 18, для вызова функций динамической библиотеки программами на Visual Basic используется оператор Declare — он определяет прототипы функций DLL. Программа SockWEB объявляет три функции Winsock DLL и четыре функции QWEBLIB DLL. В дополнение к определению формы SockWEB (она называется frmSockWEB), показанной в предыдущем разделе, программа SockWEB определяет три процедуры-обработчика событий. Сначала SockWEB определяет процедуру Form_load, которая просто размещает форму SockWEB на экране пользователя так же, как процедура subCenterForm из SockFTP. В предыдущем разделе также показано, что программа SockWEB определяет две командные кнопки: Read и Cancel. Вот как выглядит процедура для кнопки Cancel: Sub cmdCancel_Click () Dim intStatus As Integer intStatus = WSACancelBlockingCall() End Sub Как видим, процедура cmdCancel_Click вызывает Winsock^yHKtuno WSACancelBlockingCall, тем самым отменяя работу любых блокирующих функций. В следующем разделе мы рассмотрим процедуру для кнопки Read. Мы также объясним, что произойдет с файлом-приемником данных, если пользователь нажмет кнопку Cancel в процессе приема сетевого файла. Обработка события для кнопки Read Процедура, обрабатывающая нажатие на кнопку Read, содержит большинство функциональных возможностей программы SockWEB. Если вы исследуете файл с исходными текстами SOKWEB.FRM, то обнаружите, что процедура cmdRead_Click довольно длинна. Однако если вы внимательно рассмотрите операторы, из которых состоит процедура, то обнаружите, что она весьма проста. Так как процедура cmdRead_Click довольно длинна, мы обсудим ее пофрагментно. Для начала обратите внимание на то, что cmdRead Click объявляет несколько Статус, возвращаемый функциями DLL Дескриптор сокета для Web-соединения Дескриптор файла Счетчик общего количества переданных байт Счетчик байт при каждом обращении к recv Имя файла переменных, как показано ниже: Sub cmdRead_Click () Dim intStatus As Integer Dim nSocket As Integer Dim hFile As Integer Dim nTotalBytes As Long Dim nBytes As Integer Dim IpszLocalFileName As String 599 
Глава 19. Всемирная паутина World Wide Web Dim IpszPath As String ' Строка path для URL Dim PointerAddress As Long ' Указатели областей памяти функций DLL Назначение этих переменных станет более ясным в следующих абзацах. После объявления локальных переменных процедура cmdCancel_Click проверяет содержание текстовых окон URL и WWW-сервера, чтобы удостовериться, что пользователь ввел некоторые данные. Если любое из текстовых окон пусто (содержит текст нулевой длины), процедура показывает окно сообщения и заканчивается, как показано ниже: If Len(txtURL.Text) = 0 Or Len(txtWebServer.Text) = 0 Then MsgBox "Please enter a server name and a Web URL.", MB_ICONSTOP + MB_OK, "WEB File Reader" Exit Sub End If После проверки того, что пользователь ввел что-нибудь и в окно URL, и в окно WWW-сервера, процедура скрывает кнопку Read, как показано ниже: cmdRead.Enabled = О cmdRead.Visible = О и продолжает показывать другие текстовые окна SockWEB и кнопку Cancel: lblBytesTransferred.Visible = 1 txtBytesTransferred.Visible = 1 txtBytesTransferred.Text = "CONNECTING" cmdCancel.Visible = 1 txtLocalFileName.Visible = 1 txtLocalFileName.Enabled = 1 lblLocalFileName.Visible = 1 Затем в следующих операторах программы извлекается имя файла, допустимое в DOS, из URL, введенного пользователем. Как видим, процедура вызывает функцию ExtractFileName из динамической библиотеки QWEBLIB: IpszPath = txtURL.Text IpszLocalFileName = String$(13, " ") PointerAddress = ExtractFileName(IpszPath, IpszLocalFileName) Как уже говорилось, функция ExtractFileName сохраняет допустимое для DOS имя файла в буфере, на который указывает ее второй параметр (в данном случае переменная IpszLocalFileName). Процедура записывает это имя в текстовом окне имени файла (txtLocalFileName): txtLocalFileName.Text = IpszLocalFileName 600 
Программирование WWW В этой точке процедура выполнила все начальные действия по инициализации и может начать сетевую работу. Сначала она вызывает функцию ConnectWebServer Socket из QWEBLIB.DLL, чтобы создать и соединить сокет с удаленным сетевым компьютером, имя которого ввел пользователь: ' Устанавливаем соединение с WWW-сервером nSocket = ConnectWebServerSocket(txtWebServer.Text) Как мы уже говорили, если функция ConnectWebServerSocket успешно создаст и соединит сокет с WWW-сервером, она возвратит дескриптор сокета. Далее процедура проверяет значение, занесенное функцией ConnectWebServerSocket в переменную nSocket, чтобы удостовериться, что значение дескриптора действительно. Если сокет действителен (то есть находится в рабочем состоянии), процедура показывает текст «SENDING QUERY» в текстовом окне BYTES TRANSFERRED и вызывает функцию SendWebQuery из QWEBLIB.DLL: If nSocket о INVALIDJSOCKET Then txtBytesTransferred.Text = "SENDING QUERY" hFile = SendWebQuery(nSocket, txtURL.Text) Else ' Если соединение не установилось, управляющие кнопки ' сбрасываются и подпрограмма завершается cmdRead.Enabled = 1 cmdRead. Visible = 1 lblBytesTransferred.Visible = 0 txtBytesTransferred.Visible = 0 txtBytesTransferred.Text = "0" cmdCancel.Visible = 0 txtLocalFileName.Visible = 0 lblLocalFileName.Visible = 0 txtLocalFileName.Text = "" Exit Sub End If Если функция ConnectWebServerSocket возвращает недействительный дескриптор сокета, процедура сбрасывает текстовые окна программы и кнопки команд в значения по умолчанию и завершается. Программа SockWEB не выводит никаких сообщений об ошибках, если функция ConnectWebServerSocket терпит неудачу, потому что функция ConnectWebServerSocket показывает такие сообщения самостоятельно. После соединения сокета с WWW-сервером и успешного открытия локального файла данных программа обновляет текстовое окно BYTES TRANSFERRED, чтобы показать, что программа готова читать файл данных, как показано ниже: ' Считываем данные и записываем их на диск If hFile о SOCKET_ERROR And hFile <> HFILE_ERROR Then txtBytesTransferred.Text = "READING FILE" 601 
Ггзва 19. всемирная паутина World Wide Web nTotalBytes = О Do nBytes = RecvWebFile(nSocket, hFile) nTotalBytes = nTotalBytes + nBytes txtBytesTransferred.Text = Str$(nTotalBytes) Loop While nBytes > 0 End If Как видим, процедура входит в цикл do-while, вызывающий функцию RecvWebFile из QWEBLIB.DLL для чтения файла данных с WWW-сервера. Цикл будет завершаться в двух случаях. Первый, когда WWW-сервер заканчивает передачу, он закрывает соединение TCP. При этом функция recv возвращается с нулевым значением. Второй, если пользователь воспользуется кнопкой Cancel (при этом будет выполнена процедура обработки этого события, рассмотренная выше), функция recv возвратится со значением ошибки, и процедура также выйдет из цикла. Операторы, следующие за циклом, просто еще раз устанавливают текст в окнах программы и кнопки команд в их первоначальное состояние. После этого программа готова к следующему соединению. ' Сбрасываем управляющие кнопки и выходим txtBytesTransferred.Text = "DONE" intStatus = closesocket(nSocket) intStatus = WSACleanupO lblBytesTransferred.Visible = 0 txtBytesTransferred.Visible = 0 cmdCancel.Visible = 0 txtLocalFileName.Visible = 0 lblLocalFileName.Visible = 0 txtLocalFileName.Text = "" cmdRead.Enabled = 1 cmdRead. Visible = 1 End Sub Создание Web-сервера Как вы поняли из предыдущего материала, создание программы-клиента для получения файла из WWW не слишком сложно. Создание сервера Web не многим более трудная задача. В этом разделе мы покажем, как разработать очень простой WWW-сервер, который передает одно и то же HTML-сообщение каждый раз, когда программа-клиент соединяется с его портом. Программа сервера использует файл инициализации (SERVER.INI), чтобы определить номер своего порта. Вы можете использовать сервер с любым портом, а не только с портом 80, официальным портом протокола HTTP. 602 
:■=■-Создание-Web^cepeepa: Чтобы изменить порт протокола, просто измените номер 80 в файле SERVER.INI на порт протокола, который вы хотите использовать. Файл SERVER.INI, имеющийся на дискете, приложенной к книге, содержит следующие строки: [ProtocolPort] Port =80 ; Официальный номер порта протокола HTTP Программа-сервер, в том виде, в котором она описана в этом разделе, представляет собой «скелет» реальной программы. Например, наш сервер не читает никаких команд, получаемых от клиента, и всегда передает один и тот же ответ. Однако образец программы-сервера демонстрирует все шаги, требуемые для фактического осуществления любого типа сервера, который вы захотите спроектировать. Читая следующие разделы и рассматривая тексты программы-сервера, вы обнаружите, что уже встречались практически со всеми ее операторами. При разработке программы мы использовали окружение C++ из набора Microsoft Foundation Classes (MFC). Мы не использовали средства ObjectWindows фирмы Borland. Однако если вы тщательно изучите операторы программы, описанные далее, вы сможете реализовать программу и в этом окружении, внеся лишь незначительные изменения. При этом предполагается, конечно, что с ObjectWindows вы уже знакомы. Исходный текст программы Как вы уже знаете, компилятор Microsoft Visual C++ (версия 1.5) включает утилиту AppWizard (подобную AppExpert фирмы Borland), которая создает интерфейсную оболочку программы, построенную на основе MFC. AppWizard позволяет вам воспользоваться всей мощью предлагаемых вам визуальных сред программирования. За несколько секунд AppWizard в состоянии произвести на свет сотни строк свободного от ошибок кода, который поддерживает несколько классов, определенных структурой MFC. В то время как AppWizard может сильно повысить вашу производительность, просмотр всего произведенного AppWizard кода, чтобы найти изменения, внесенные другим программистом, быстро становится утомительным. Чтобы помочь вам более легко находить изменения, внесенные в код, созданный AppWizard, мы отмечали любые измененные или добавленные операторы программы комментариями, показанными ниже: V START CUSTOM CODE: Internet Programming V END MODIFICATIONS: Internet Programming Простым поиском указанных комментариев в исходном тексте программ вы можете быстро найти все внесенные нами модификации произведенного AppWizard кода. 603 
Глава ТЭ- Зсемирная паутина World Wide Web Классы программы-сервера В табл. 19.3 показан список классов, используемых в приложении-сервере. Класс CServerView выполняет основную часть его работы. Таблица 19.3. Классы, образованные при помощи AppWizard Базовый класс Производный класс Описание CWinApp CServerApp Приложение-сервер определяется, как объект Windows. CFrameWnd CMainFrame Определяет окно для приложениясервера. CDocument CServerDoc Определяет общую информацию о сетевом соединении сервера. CScrollView CServerView Определяет интерфейс между пользователем и сетью. В данной книге не объясняются классы MFC и код, произведенный AppWizard. Мы обсудим только модификации, внесенные в каждый из этих классов. Для более детального описания классов MFC и объяснения того, как эти классы взаимодействуют друг с другом, см. документацию по Microsoft Foundation Classes. Класс CServerApp Класс CServerApp определяет приложение-сервер как прикладной объект Windows. В файле-заголовке serverl.h находятся следующие операторы: #include "..\winsock.h" const int WINSOCK_VERSION = 0x0101; // Необходим Winsock // версии 1.1 const int DEFAULT_PROTOCOL =0; // Протокол по умолчанию const int NO_FLAGS =0; // Флаги не определены const int MAX_COLUMNS = 80; // Максимальное количество // столбцов для вывода текста const int QUEUE_SIZE =5; // Максимальное количество // запросов на соединение // для помещения во входную // очередь // Прикладные сообщения сервера const int WM_SERVER_ACCEPT = WM_USER+1; const int WM_CLIENT_CLOSE = WM_USER+2; const int WM_CLIENT__READ = WM_USER+3 ; 604 
class CServerApp : public CWinApp { private: char chMsgBuffer[100]; // Буфер общего назначения public: WSADATA wsaData; // Сведения о реализации // Winsock virtual BOOL Initlnstance() ; virtual BOOL Exitlnstance(); }; // Инициализация образца // Serverl // Образец Serverl // завершен Как видим, в файле-заголовке serverl.h объявляются и инициализируются несколько констант. Также в файле повторно объявляются используемые по умолчанию функции Initlnstance и Exitlnstance. Их определения находятся в файле SERVER1.CPP. Функция CServerApp::Initlnstance просто добавляет инициализацию DLL Winsock к функции Initlnstance, как показано ниже: BOOL CServerApp::Initlnstance() { if (WSAStartup (WINSOCK__VERSION, fcwsaData) ) { MessageBeep(MB_ICONSTOP); MessageBox(NULL,"Winsock could not be initialized!", AfxGetAppName(), MB_OK|MB_ICONSTOP); WSACleanup(); return(FALSE); } return(TRUE) ; } Аналогично, функция CServerApp::Exitlnstance добавляет выход из Winsock к функции Exitlnstance: BOOL CServerApp::Exitlnstance() { int iErrorCode; if ((iErrorCode = WSACleanup())) { wsprintf(chMsgBuffer, "Winsock error %d.", iErrorCode); MessageBeep(MB_ICONSTOP); MessageBox(NULL, chMsgBuffer, AfxGetAppName(), MB_OK|MB_ICONSTOP); } 605 
Глава 19. Всемирная паутина World Wide Web if (m_pMainWnd != NULL) VERIFY(m_pMainWnd->DestroyWindow()); return(CWinApp::ExitInstance()); } Класс CMainFrame Для программы-сервера делается лишь небольшая модификация в используемом по умолчанию классе CMainFrame. В файле MAINFRM1.CPP вы найдете операторы программы, приведенные ниже. Модификации, показанные между комментарием START CUSTOM CODE и END MODIFICATIONS заключаются в изменении идентификаторов команд, связанных с используемой по умолчанию панелью инструментов. static UINT BASED_CODE buttons[] = { // START CUSTOM CODE: Internet Programming // Кнопки Change File New, Open и Save меняются на // Server Close, Open и File Save As. I D__S ERVER_CLOSE, ID_S ERVER_OPEN, ID_FILE_SAVE_AS, // END MODIFICATIONS: Internet Programming ID_SEPARATOR, I D_ED I T__CUT, ID_EDIT_COPY, ID_EDIT_PASTE, ID_S EPARATOR, ID_FILE__PRINT, ID_APP_ABOUT, Приложение-сервер использует по умолчанию панель инструментов MFC, показанную ниже: Используемые по умолчанию команды, связанные с первыми тремя кнопками файлового меню: New, Open и Save. Программа-сервер заменяет первые две кнопки панели инструментов на пункты меню Close и Open. Другими словами, пользователь программы-сервера нажимает первые две кнопки на панели инструментов, чтобы открывать или закрывать сервер (реагировать на поступающие запросы или игнорировать их). Кроме того, в программе-сервере изменена третья кнопка. Она включается в файловое меню, чтобы пользователь мог 606 
Создание Щ^тщтщха сохранять файл протокола работы под новым именем. На рис. 19.3 изображено окно класса CMainFrame, основанное на MFC, которое появляется при выполнении программы-сервера. Как видим, окно сервера первоначально появляется в закрытом состоянии (он не реагирует на поступающие запросы). Рис. 19'3. Окно класса CMainFrame программы-сервера Открыть сервер (создать ожидающий сокет) вы можете простым щелчком мыши на кнопке Open или из меню Server, опция Open. При этом сервер создает ожидающий сокет и начинает принимать поступающие запросы. Как показано на рис. 19.4, когда вы открываете сервер, программа показывает простое сообщение о состоянии, в котором присутствует имя компьютера, определяется порт, с которого ожидается запрос, и описывается реализация Winsock вашего компьютера. Когда сервер обнаружит запрос на установление соединения, поступивший от клиента, и примет его, окно сервера изменится, как показано на рис. 19.5. Как уже говорилось, программа-сервер принимает запрос клиента, передает ответное сообщение в формате HTML й закрывает соединение. Если для доступа к программе-серверу вы используете WWW-броузер типа Mosaic, вы увидите ответ в формате HTML, показанный на рис. 19.6. Примечание: Получить WWW-броузер можно прямо из Интернет. О том, как это сделать, рассказано в help-файле с дискеты, приложенной к книге. 607 
Глава 19. Всемирная паутина ■ г-*1 SERVER - WAITING for CONNECTION File Edit View Server Help I ft Ш ДШШ1Ш SERVER READY on jamsa.com at PORT No. 80 Trumpet WINSOCK Version 2.0 Revision В Server socket initialized - waiting for connections. • 1=!': Puc. 19.4. Окно программы-сервера после создания ожидающего сокета Рис. 19.5. Окно программы-сервера после обнаружения запроса и установления соединения. 608 
Создание Web-сервера Рис. 19.6. HTML-ответ программы-сервера Как запустить программу-сервер' Программа установки с дискеты создает группу программ, включая иконку под названием «Web Server». Иконка Web Server запускает программу-сервер, описанную в этой главе. Запустите Web Server двойным щелчком мыши на иконке. Сервер начнет работу и покажет информацию о своем состоянии. Обратите внимание, что, подобно всем программам-примерам с дискеты, для работы WWW-сервера необходим файл Winsock DLL. Если вы получаете сообщение об ошибке типа «не могу найти WINSOCK.DLL», удостоверьтесь, что WINSOCK.DLL находится в зоне действия переменной path, или запустите Trumpet Winsock DLL. После того как вы запустили сервер (и, возможно, установили соединение с Интернет), щелкните мышью на меню Server и выберите пункт Open или нажмите кнопку Open на панели инструментов. После запуска сервера вы можете использовать ваш WWW-броузер, чтобы соединиться с сервером. В окне URL броузера задайте следующую информацию (заменив 000.000.000.000 на IP-адрес вашего компьютера): http://000.000.000.000/ Обратите внимание, что адрес должен заканчиваться прямым слэшем (/). Имя HTML-файла задавать не требуется. Программа-сервер передает один и тот же ответ на каждый запрос соединения. Как только броузер установит соединение с сервером, программа-сервер передаст строки данных в формате HTML, которые предписывают вашему броузеру показать заглавие, имена авторов и издателя книги «Internet Programming». 20 Зак. № 1949 609 
Глава 19. Всемирная паутина World Wide Web Класс CServerDoc Большинство модификаций класса CServerDoc состоит в добавлении переменных public, используемых другими классами во время работы сервера. Следующие операторы программы, добавленные нами, находятся в файле servldoc.h: const MAX_LINES = 1000; // Максимальное количество // строк в документе class CServerDoc : public CDocument { // Атрибуты public: CString m_csText[MAX_LINES]; // Массив CString — один //на строку LONG m_lLineNumber, m_lColuinnNurnber; // Текущая // позиция в документе UINT m_nServerPort; SOCKET rnJiServerSocket ; SOCKADDR_IN m_sockServerAddr; // Дескриптор сокета // Порт сервера // Структура адреса сервера SOCKADDR_IN m_sockClientAddr; LPHOSTENT mJLpHostEntry; LPSERVENT m__lpServEntry ; // // Структура адреса // // клиента Структура с // информацией о компьютере // Структура с информацией // о сетевой службе }; Вы уже использовали подобные переменные в примерах программ, обсуждавшихся ранее в книге. Единственная новая переменная, добавленная здесь, — массив m_csText класса CString. Класс CString позволяет управлять строковыми данными средствами, подобными средствам языка Basic. Другими словами, вы можете объединять переменные CString знаком плюс и сравнивать переменные CString при помощи знака равенства. Класс CString имеет встроенный механизм управления памятью, так что вам не нужно беспокоиться относительно выхода за границы переменной CString подобно тому, как это происходит с обычными строковыми переменными языка С. В нашем случае массив m_csText используется для хранения текста, показанного в главном окне сервера. Массив CString позволяет простыми средствами организовать прокрутку окна сервера. (Ранее, в программе Sockman, такой возможности не было.) Еще одно изменение класса CServerDoc внесено в функцию-конструктор класса: 610 
CServerDoc::CServerDoc() { m_lLineNumber = 0; m__lColumnNurnber = 0; } Как видим, программа-сервер инициализирует переменную номера строки и переменную номера колонки нулями. Класс CServerView использует эти две переменные, чтобы управлять выдачей текста в окно сервера. Класс CServerView Мы уже упоминали, что класс CServerView выполняет основную работу приложения-сервера. У нас определено двадцать функций-членов класса CServerView. Хотя некоторые из них весьма коротки (всего из нескольких строк), этот класс несет наибольшую нагрузку, обеспечивая функционирование сервера. Определения всех этих функций находятся в файле SERV1VW.CPP. Следующие операторы находятся в файле servlvw.h. Как видим, при определении класса CServerView объявляются несколько переменных private, четыре функции-члена public и несколько внутренних функций-членов protected. class CServerView : public CScrollView { private: char m_chMsgBuffer[100]; // Буфер для // внутренних сообщений SOCKET m_hClientSocket; // Дескриптор сокета // клиента SOCKADDR_IN m_sockClientAddr; // Структура адреса // клиента BOOL m___bServerIsOpen; // Флаг "закрыто" или // "открыто" CServerDoc *m_pDoc; // Родительский // документ CWnd *m_pParentWnd; // Родительское окно CMenu *m_pParentMenu; // Родительское меню // Операции public: virtual void ReportWinsockErr(LPSTR IpszErrorMsg); virtual void PrintChar(char chChar, BOOL bLastChar); virtual void Printstring(LPSTR IpszString); virtual void Printstring(CString csString); // Созданные функции-обработчики сообщений protected: //{{AFX_MSG(CServerView) 611 
Глава 19. Всемирная паутина World Wide Weti; ; afx_msg void OnServerClose(); afx_msg void OnServerOpen(); afx_msg void OnUpdateServerClose(CCmdUI* pCmdUI); afx_msg void OnUpdateServerOpen(CCmdUI* pCmdUI); afx_msg void OnUpdateFileNew(CCmdUI* pCmdUI); afx_msg void OnUpdateFileOpen(CCmdUI* pCmdUI); afx_msg void OnUpdateFileSave(CCmdUI* pCmdUI); afx_msg void OnUpdateFileSaveAs(CCmdUI* pCmdUI); //}}AFX_MSG afx_msg LRESULT OnServerAccept(WPARAM wParam, LPARAM 1Param); afx_msg LRESULT OnClientClose(WPARAM wParam, LPARAM ^ 1Param); afx_msg LRESULT OnClientClose(void); afx_msg LRESULT OnClientRead(WPARAM wParam, LPARAM 1Param); DECLARE__MES SAGE__MAP () } ; Приведенные ниже операторы программы показывают соответствие различных команд меню и сообщений функциям класса CServerView. В следующих разделах кратко описаны функции класса CServerView. BEGIN_MESSAGE_MAP(CServerView, CScrollView) //{{AFX_MSG_MAP(CServerView) ON_COMMAND (ID_SERVER_CLOSE, OnServerClose) ON_COMMAND(ID_SERVER_OPEN, OnServerOpen) ON_UPDATE_COMMAND_UI(ID_SERVER_CLOSE, OnUpdateServerClose) ON_UPDATE_COMMAND__UI (ID__SERVER__OPEN, OnUpdateServerOpen) ON_UPDATE_COMMAND_UI (ID_FILE_NEW, OnUpdateFileNew) ON_UPDATE_COMMAND_UI (ID_FILE_OPEN, OnUpdateFileOpen) ON_UPDATE_COMMAND_UI (ID_FILE_SAVE, OnUpdateFileSave) ON_UPDATE_COMMAND__UI (ID_FILE_SAVE_AS, OnUpdateFileSaveAs) //}}AFX_MSG_MAP // Стандартные команды печати ON_COMMAND(ID_FILE__PRINT, CScrollView::OnFilePrint) ON_COMMAND (ID_FILE_PRINT_PREVIEW, CScrollView: : OnFilePrint Preview) // START CUSTOM CODE: Internet Programming ON_MESSAGE(WM_SERVER_ACCEPT, OnServerAccept) ON_MESSAGE(WM_CLIENT_CLOSE, OnClientClose) ON_MESSAGE(WM_CLIENT_READ, OnClientRead) // END MODIFICATIONS: Internet Programming END_MESSAGE_MAP() 612 
: Создание We>b-4?epBepa Конструктор класса CServerView Как известно, программа C++ инициализирует класс, вызывая для него функцию-конструктор каждый раз, когда создается новый объект указанного класса. Класс CServerView определяет интерфейс между пользователем и сетью. Поэтому каждый раз, когда программа-сервер запускается, создается класс CServerView и вызывается его конструктор. Конструктор CServerView просто инициализирует две переменные-члены класса: m_bServerIsOpen и m_hClientSocket. Переменная m_bServerIsOpen — это булева переменная, имеющая значения True или False, которая показывает, открыт ли сервер (реагирует ли на поступающие запросы на установление соединения) или нет. Другие функции класса могут проверять переменную m_bServerIsOpen, чтобы определить состояние сервера и работать в соответствии с состоянием. Значение True в m_bServerIsOpen указывает, что сервер принимает поступающие запросы. Другими словами, ожидающий сокет существует и будет сообщать о поступающих запросах. Переменная m_hClientSocket хранит дескриптор сокета для любых программ-клиентов, связанных с сервером. Как показано ниже, CServerView инициализирует переменную m_bServerIsOpen значением False и переменную m_hClientSocket значением INVALID_SOCKET: CServerView::CServerView() { m_bServerIsOpen = FALSE; m_hClientSocket = INVALID_SOCKET; } Другими словами, сервер всегда запускается в закрытом состоянии. Сокетов клиента не может существовать, когда сервер закрыт. Определение CServerView Точно так же, как программа C++ вызывает конструктор, чтобы инициализировать класс, она вызывает функцию-деструктор для уничтожения класса. Как показано ниже, деструктор CServerView закрывает сокет сервера, если он был открыт. Деструктор проверяет сокет клиента, обращаясь к m_hClientSocket, чтобы узнать, существует ли какой-нибудь клиент. Если таковой существует, деструктор закрывает также и сокет клиента: CServerView::-CServerView() { if (m_bServerIsOpen) closesocket(m_pDoc->m_hServerSocket); if (m_hClientSocket != INVALID_SOCKET) closesocket(m_hClientSocket); } 613 
Глава 10. Всемирная паутина World Wide Web Определение OnlnitialUpdate MFC автоматически вызывает функцию OnlnitialUpdate для демонстрационного класса (типа CServerView) перед его показом. Вы можете переписать функцию OnlnitialUpdate для проведения любой разовой инициализации, которая требует информацию о классе документа, связанного с вашим броузером. Например, как показано дальше, функция CServerView::OnlnitialUpdate инициализирует три внутренние переменные класса: m__pDoc, m_pParentWnd и m_pParentMenu. Переменная m_pDoc — указатель на класс демонстрационного документа (CServerDoc). Вы увидите, что функции-члены класса CServerView используют m__pDoc как указатель на доступные переменные и функции в пределах класса CServerDoc. Аналогично, OnlnitialUpdate вызывает функцию GetParent, чтобы получить адрес родительского окна для класса. Функция OnlnitialUpdate немедленно использует этот указатель с функцией GetMenu, чтобы получить адрес меню программы и сохранить этот адрес в переменной-члене класса m_pParentMenu. После инициализации переменных-членов и выполнения только что рассмотренных действий функция OnlnitialUpdate получает контекст устройства для окна сервера и инициализирует окно. Сначала функция OnlnitialUpdate устанавливает положение полос прокрутки, вызывая функцию SetScrollSizes и тогда размещает окно, вызывая функцию GetParentFrameO-MoveWindow, как показано ниже: void CServerView::OnlnitialUpdate() { m_pDoc = GetDocument(); m_pDoc->SetTitle("CLOSED for BUSINESS"); m__pParentWnd = GetParent () ; m__pParentMenu = m__pParentWnd->GetMenu () ; // Формируем окно сервера TEXTMETRIC tm; CRect rectServerSize; CClientDC dc(this); dc.GetTextMetrics(&tm); SetScrollSizes( MM_TEXT, CSize(0,0), CSize(0,(MAX_LINES*tm.tmHeight)), CSize(0, tm.tmHeight)); GetWindowRect( rectServerSize ); GetParentFrame()->MoveWindow( rectServerSize.left, rectServerSize.top, (MAX_COLUMNS*tm. tmAveCharWidth) , (24*tm.tmHeight), FALSE); return; } 614 
If Сек- <ие Web-сервера Функция OnDraw Чтобы нарисовать текст в окне сервера, программа должна переопределить функцию OnDraw. MFC вызывает функцию OnDraw, чтобы обеспечить вывод на экран, печать и предварительный просмотр вывода на печать. Однако в MFC не определены действия, выполняемые по умолчанию. Как показано ниже, реализация WWW-сервера выводит текст в окно сервера, используя функцию TextOut, и после этого изменяет положение полос прокрутки, вызывая функцию SetScrollSizes: void CServerView::OnDraw(CDC* pDC) { TEXTMETRIC tm; int iYValue; pDC->GetTextMetrics(&tm); iYValue = 0; for (int iLine = 0; iLine <= m_pDoc->m_lLineNuinber; iLine++) { pDC->TextOut (0, iYValue, m__pDoc->m_csText [iLine] , m__pDoc->m_csText [iLine] .GetLength() ) ; iYValue += tm.tmHeight; } SetScrollSizes (MM_TEXT, CSize(0, ( (int) m__pDoc->m__lLineNuinber+l) *tm.tmHeight) ) ; return; } Обратите внимание, что функция OnDraw использует указатель m_pDoc (рассмотренный в функции OnlnitialUpdate) для доступа к тексту, находящемуся в массиве CServerDoc m_csText. Как вы помните, программа-сервер использует массив CString m_csText, чтобы хранить текст, показанный в главном окне сервера. Функция Printstring с параметром CString Как известно, C++ позволяет вам перезагружать определения функций. Когда вы перезагружаете функцию, вы определяете две или более функций с одинаковым именем, но с различными параметрами или типами возвращаемого значения. Например, программа-сервер перезагружает функцию Printstring. Функция Printstring, показанная ниже, принимает в качестве параметра переменную класса CString. В следующем разделе вы встретите функцию PrintString, у которой параметр типа LPSTR. Обе функции Printstring исполняют одно и то же действие — они вызывают функцию PrintChar, чтобы посимвольно напечатать в окне сервера строку 615 
глаза 19. всемирная паутина\мсн1с1 Wide Web текста. Как показано ниже, Printstring сначала удостоверяется, что имеются данные для печати, и затем входит в цикл посимвольного вывода: void CServerView::Printstring(CString csString) { if (csString.GetLength() == 0) return; // Печатаем каждый символ строки, за исключением // последнего for (int iChar = 0; iChar < (csString.GetLength()-1); iChar++) PrintChar(csString[iChar], FALSE); // Полосу // прокрутки не // обновляем // Обновляем полосу прокрутки, дойдя до последнего // символа [ PrintChar(csString[iChar], TRUE); m_pDoc->UpdateAllViews(NULL, 0L, 0); return; } После печати всей строки функция Printstring использует указатель m__pDoc и функцию UpdateAllViews, чтобы перерисовать окно сервера с внесенной строкой текста. Функция Printstring с параметром типа LPSTR Функция Printstring практически идентична функции Printstring из предыдущего раздела. Однако вместо параметра типа CString, функция Printstring, текст которой приведен ниже, принимает параметр типа LPSTR: void CServerView::Printstring(LPSTR IpszString) { if (*lpszString == NULL) return; // Печатаем каждый символ строки, за исключением // последнего for (int iChar = 0; IpszString[iChar+1] != NULL; iChar++) PrintChar(IpszString[iChar], FALSE); // Полосу // прокрутки //не обновляем // Обновляем полосу прокрутки, дойдя до последнего // символа PrintChar( IpszString[iChar], TRUE); 616 
' «I Создание Web-сервера m_pDoc->UpdateAllViews(NULL, OL, 0); return; } Определение PrintChar Как показано в двух предыдущих разделах, перезагружаемые функции PrintString вызывают функцию PrintChar, чтобы вывести строку в окно сервера по эдному символу. Функция PrintChar использует указатель m_pDoc на класс CServerDoc, чтобы управлять переменными m_lColumnNumber и m_lLineNumЬег и сохранять позицию текста, показанного в окне сервера. Хотя функция PrintChar довольно длинна, оначвызывает функции, общие для большинства приложений, использующих библиотеку классов MFC, и хотя делает немного, но сохраняет все переменные, связанные с выводом текста на экран сервера, должным образом: void CServerView::PrintChar(char chChar, BOOL bLastChar) { // Игнорируем перевод каретки (CR) if (chChar =='\r') return; CClientDC dc(this); TEXTMETRIC tm; CPoint pt, ptOrigin; CRect rectServerSize; OnPrepareDC(&dc); dc.GetTextMetrics(&tm); if (chChar =='\n') { // Перемещаемся на нулевую колонку, если начинается // новая строка m_pDoc->m_lColumnNumber = 0; if (m__pDoc->m_lLineNumber == (MAX__LINES-1) ) { for (int iLine = 0; iLine < MAX_LINES; iLine++) m_pDoc->m_csText[iLine] = m__pDoc->m_csText [iLine+1] ; m__pDoc->m_csText [iLine] .Empty; m__pDoc->UpdateAllViews (this, 0L, 0) ; } else m__pDoc->m_lLineNumber++ ; 617 
Глава!■ "fWorld Wide Web SetScrollSizes( MM_TEXT, CSize(0, ((int) m__pDoc->m_lLineNumber+l) * tm.tmHeight)); } else { if (m_pDoc->m_lColurnnNumber++ >= MAX_COLUMNS) { m_pDoc->m_lColumnNuinber = 1; if (m_pDoc->m_lLineNumber == (MAX_LINES-1)) { for (int iLine = 0; iLine < MAX__LINES; iLine ++) m__pDoc->m_csText [iLine] = m_pDoc->m_csText[iLine+1]; m_pDoc->m__csText[iLine].Empty; mjpDoc->UpdateAllViews(this, 0L, 0); } else m__pDoc->m__lLineNumber++ ; m__pDoc->m_csText [m__pDoc->m_lLineNumber] += chChar; if (bLastChar) dc. TextOut (0, (int) m__pDoc->m__lLineNumber * tm.tmHeight, m__pDoc->m__csText [m__pDoc->m__lLineNumber] , m__pDoc->m_csText [m__pDoc->m_lLineNumber] .GetLengthO ) ; // Если символ последний, позиция полосы прокрутки // обновляется if (bLastChar) { pt = GetScrollPosition(); if ((int) m__pDoc->m_lLineNumber * tm.tmHeight < pt.y) { pt.y = (int) m__pDoc->m_lLineNumber * tm.tmHeight; ScrollToPosition(pt); } else { CScrollView: -.GetClientRect ( rectServerSize) ; if ( ( ( (int)m_pDoc->m__lLineNumber * tm.tmHeight) 618 
Создание Web-сервера + tm.tmHeight) > rectServerSize.bottom) { ptOrigin = dc.GetViewportOrg(); pt.x = ptOrigin.x; pt.y = ((int) m__pDoc->m__lLineNumber * tm.tmHeight) + tm.thnHeight - rectServerSize.bottom; ScrollToPosition(pt ); } } } return; } Для того чтобы указать последний символ строки, функции Printstring в переменной bLastChar передается значение True. Когда Print Char получает параметр bLastChar со значением True, она вызывает функцию TextOut, чтобы отобразить текст в окне сервера. Как показано в последней конструкции г/*, функция PrintChar, в зависимости от количества выведенного текста, модифицирует окно прокрутки. Функция ReportWinsockErr Чтобы последовательно обращаться с сообщениями об ошибках, программа-сервер определяет функцию ReportWinsockErr. Функция ReportWinsockErr просто формирует внутренний массив символов m_chMsgBuffer из кода ошибки, возвращаемого функцией Winsock WSAGetLastError. Подобно другим функциямчленам CServerView функция ReportWinsockErr вызывает функцию Printstring для вывода сообщения об ошибке в окно сервера. Как показано ниже, функция ReportWinsockErr также уведомляет пользователя о случившейся ошибке, выводя окно сообщения Windows: VOID CServerView::ReportWinsockErr(LPSTR IpszErrorMsg) { wsprintf(m_chMsgBuffer, "\nWinsock error %d: %s\n\n", WSAGetLastError(), IpszErrorMsg); Printstring((LPSTR)IpszErrorMsg); MessageBeep(MB_ICONSTOP); MessageBox(m_chMsgBuffer, AfxGetAppName(), MB__OK | MB_I CONS TO P) ; return; } Функция OnServerAccept Программа-сервер передает используемый по умолчанию ответ в формате HTML (изображенный на рис. 19.6) для каждого соединения с клиентом. Функция, 619 
мирная паутина World Wipe которая выполняет эту задачу в программе-сервере, — OnServerAccept. Как показано в следующих операторах программы, функция OnServerAccept принимает поступающие соединения и передает ответ. Вы уже сталкивались с большинством операторов, используемых в функции OnServerAccept в предыдущих программах, рассмотрениях в этой книге: LRESULT CServerView: : OnServerAccept (WPARAM wParam, LPARAM 1Param) { int iErrorCode; int nLength = sizeof(SOCKADDR); if (WSAGETSELECTERROR(lParam)) { ReportWinsockErr( "Error detected on entry intoOnServerAccept."); return(OL); } if (WSAGETSELECTEVENT(lParam) == FD_ACCEPT) { Printstring("Connection has arrived!\n"); m_hClientSocket = accept (m_pDoc->m_hServerSocket, (LPSOCKADDR) &m_sockClientAddr, (LPINT)&nLength); if (m_hClientSocket == INVALID_SOCKET) { ReportWinsockErr( "Server socket failed to accept connection."); return(OL); } CString csDottedDecimal = "CONNECTED to "; csDottedDecimal += inet_ntoa (m_sockClientAddr. sin_addr) ; m_pDoc->SetTitle(csDottedDecimal); csDottedDecimal += "\n"; Printstring(csDottedDecimal); CString csText = "<Title>Internet Programming</Title>"; csText += "<Hl>Internet Programming</HlxHR>" ; csText += "<H3>Kris Jamsa, Ph.D., and Ken Cope</H3xP>" ; csText += "<H5>Published by Jamsa Press, 1995</H5xHR>" ; LPSTR IpszResponse = csText.GetBuffer(1000); 620 
*sw Shi i eliEh iErrorCode = send( m_hClientSocket, IpszResponse, lstrlen(IpszResponse), NO_FLAGS); if (iErrorCode == SOCKET_ERROR) ReportWinsockErr("Error sending response to client."); else Printstring("Response sent!\n"); OnClientClose(); } return(OL); } Для максимального упрощения программы-сервера функция OnServerAccept формирует HTML-ответ в памяти (это быстрее, чем чтение его с диска). Как показано ниже, для формирования ответа функция OnServerAccept использует локальную переменную класса CString по имени csText. Конкатенации объектов класса CString в функции OnServerAccept формирует HTML-документ в памяти: <Title>Internet Programming</Title> <Hl>Internet Programming</Hl><PxHR> <H3>Kris Jamsa, Ph.D., and Ken Cope</H3xP> <H5>Published by Jamsa Press, 1995</H5xPxHR> Как показано на рис. 19.6, текст и тэги HTML формируют ответ в формате HTML, состоящий из заглавия этой книги и имен авторов. После имен авторов WWW-броузер покажет названия издательства и дату издания. Операторы конкатенации CString можно изменить так, чтобы создать любое подходящее сообщение-ответ. Аналогично, если вы используете программу-сервер из нашей книги как базу для разработки полнофункционального WWW-сервера, вы можете изменить программу, чтобы считывать файлы HTML с жесткого диска. Поместив данные на жесткий диск, вы сможете легко изменять содержимое сообщений вашего WWW-сервера. Функция OnServerClose Когда пользователь программы-сервера закрывает сокет сервера, выбирая опцию Close меню Server или щелкая мышью на кнопке Close, Windows посылает сообщение, приводящее к вызову функции OnServerClose. Как показано ниже, функция OnServerClose закрывает сокеты клиента и сервера, вызывая функцию closesocket из набора Winsock с соответствующим дескриптором сокета: void CServerView::OnServerClose() { if (m_hClientSocket != INVALIDJ30CKET) { if (WSAAsyncSelect(m_hClientSocket, m_hWnd, NO_FLAGS, NO_FLAGS)) 621 
■ГлWorld Wide Web ReportWinsockErr("WSAAsyncSelect error in OnServerClose."); closesocket(m_hClientSocket); m__hClient Socket = INVALID_SOCKET; } closesocket (m_pDoc->m__hServerSocket) ; m_bServerIsOpen = FALSE; m_pDoc->SetTitle("CLOSED for BUSINESS"); Printstring("\nServer CLOSED for BUSINESS.\n"); return; } Функция OnServerClose также использует указатель m_pDoc для вызова функции SetTitle (определенной в библиотеке классов MFC), чтобы изменить заголовок программы-сервера на «CLOSED for BUSINESS». Аналогично, функция OnServerClose вызывает функцию Printstring, чтобы вывести сообщение в окно программы. Функция OnClientClose без параметров Программа-сервер перезагружает функцию OnClientClose — одна функция OnClientClose предназначена для функций-членов и другая функция OnClientClose для остальных функций прикладного окружения. Функция OnClientClose закрывает только сокет клиента. Функции-члены могут вызывать функцию OnClientClose, показанную ниже: LRESULT CServerView: -.OnClientClose (void) { int iErrorCode = closesocket(m_hClientSocket); m_hClient Socket = INVALID_SOCKET; if (iErrorCode == SOCKET_ERROR) ReportWinsockErr("Error closing client socket!"); else Printstring( "Client socket closed, ready for next client!\n\n"); m__pDoc->SetTitle("'WAITING for CONNECTION"); return(OL); } 622 
Создание We ,-се;- Как видим, функция OnClientClose вызывает функцию closesocket, чтобы закрыть сокет клиента, и проверяет возвращаемое значение, чтобы определить, не было ли ошибок. После закрытия сокета функция OnClientClose изменяет текст заголовка окна, чтобы сообщить пользователю, что сервер готов принять следующий запрос на установление соединения. Функция OnClientClose с параметрами Функция OnClientClose, показанная ниже, также закрывает сокет клиента. Однако так же, как в функции обратного вызова, ее аргументами являются параметры сообщения Windows: LRESULT CServerView::OnClientClose(WPARAM wParam, LPARAM lParam) { if (WSAGETASYNCERROR(lParam)) ReportWinsockErr( "Error detected on entry intoOnClientClose"); int iErrorCode = closesocket (m__hClientSocket) ; m_hClientSocket = INVALID_SOCKET; if (iErrorCode == SOCKET_ERROR) ReportWinsockErr("Error closing client socket!"); else Printstring( "Client socket closed successfully, readyfor next client!\n\n"); m_pDoc->SetTitle("WAITING for CONNECTION"); return(OL); } Наша реализация сервера фактически не использует этот вариант функции OnClientClose. Однако программа-сервер определяет сообщение и связывает сообщение WM_CLIENT_CLOSE с функцией OnClientClose. Следующий фрагмент программы показывает, как при помощи MFC связать функцию OnClientClose с сообщением WM_CLIENT_CLOSE. Другими словами, если Windows посылает сообщение WM_CLIENT_CLOSE, будет вызываться функция OnClientClose. Если вы решите изменить программу сервера, чтобы использовать другой протокол (не HTTP), вы сможете использовать это сообщение и функцию OnClientClose, чтобы закрывать сокеты клиента, связанные с сервером, как это требуется для нормальной работы программы. BEGIN_MESSAGE_MAP(CServerView, CScrollView) ON_MESSAGE(WM_CLIENT_CLOSE, OnClientClose) END_ME S S AGE__MAP () 623 
4..V.W.^^WVW; . ..у.. .... Глава 19. Всемирная паутина World Wide Web ... : Функция OnServerOpen Как вам известно (из примеров QFTP), чтобы принимать запросы на установление соединения, необходимо создать ожидающий сокет. Функция OnServerOpen создает сокет сервера и переводит его в режим пассивного прослушивания так же, как это происходило в программах QFTP. Хотя функция OnServerOpen, показанная ниже, довольно длинна, вы уже сталкивались практически со всеми ее операторами в предыдущих главах этой книги: void CServerView::OnServerOpen() { WSADATA wsaData; int iErrorCode; char chLocallnfo[64]; // Сведения о реализации // Winsock // Код ошибки Winsock // Буфер для описания Winsock // Выполняем WSAStartup, чтобы получить описание // Winsock DLL. // Класс CServerApp уже инициализировал Winsock, // поэтому, получив описание, мы должны сразу же вызвать // WSACleanup. if (WSAStartup (WINSOCK__VERSION, fcwsaData) ) { MessageBeep(MB_ICONSTOP); MessageBox("Winsock could not be initialized!", Af xGetAppName () , MB__OK | MB_ICONSTOP) ; WSACleanup(); return; } else WSACleanup(); // Преобразуем локальное имя хоста, чтобы // удостовериться, что сеть функционирует if (gethostname(chLocallnfo, sizeof(chLocallnfo))) { ReportWinsockErr( "\nCould not resolve local host!\nAre you on-line?\n"); return; } // Выводим текущее состояние сервера, включая информацию //о Winsock DLL CString csWinsockID = "\nSERVER READY on "; csWinsockID += chLocallnfo; csWinsockID += " at PORT No. "; csWinsockID += itoa(m_pDoc->m_nServerPort, chLocallnfo, 10); csWinsockID += "\n"; 624 
Создание Weh-o&Dbepa csWinsockID += wsaData.szDescription; csWinsockID += "\n"; Printstring(csWinsockID); // Инициализируем сервер, используя знакомые по учебным // программам процедуры m_pDoc->m_hServerSocket = socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL); if (m_pDoc->m_hServerSocket == INVALID_SOCKET) { ReportWinsockErr("Could not create server socket."); return; } m__pDoc->m_sockServerAddr. sin_family = AF__INET; m__pDoc->m__sockServerAddr. sin_addr. s_addr = INADDR__ANY; m__pDoc->m_sockServerAddr. sin__port = htons (m__pDoc->m_nServerPort) ; // Связываем сокет сервера так же, как в учебных // программах QFTP if (bind(m_pDoc->m_hServerSocket, (LPSOCKADDR)&m_pDoc->m_sockServerAddr, sizeof (m__pDoc->m__sockServerAddr) ) == SOCKET_ERROR) { ReportWinsockErr("Could not bind server socket."); return; } // Здесь используются асинхронные функции Winsock iErrorCode = WSAAsyncSelect (m__pDoc->m_hServerSocket, m_hWnd, WM_SERVER_ACCEPT, FD_ACCEPT); if (iErrorCode == SOCKET__ERROR) { ReportWinsockErr( "WSAAsyncSelect failed on server socket."); return; } if (listen (m__pDoc->m__hServerSocket, QUEUE_SIZE) == SOCKET_ERROR) { ReportWinsockErr("Server socket failed to listen."); m_pParentMenu->EnableMenuItem (ID_SERVER_OPEN, MF_ENABLED); return; } 625 
Глава 19, Всемирная паутина World Wide Web Printstring( "Server socket initialized — waiting for connections.\n\n"); m_bServerIsOpen = TRUE; m_pDoc->SetTitle("WAITING for CONNECTION"); return; } Определение OnClientRead Наша реализация программы-сервера не использует функцию OnClientRead. Однако функция OnClientRead, показанная ниже, иллюстрирует процесс чтения данных из сокета сервера. Если вы решите изменить программу-сервер, поступайте следующим образом: LRESULT CServerView: :OnClientRead(WPARAM wParam, LPARAM IParam) { if (WSAGETASYNCERROR(IParam)) ReportWinsockErr( "Error detected on entry into OnClientRead."); int iBytesRead; int' iBuf f erLength; int iEnd; int iSpaceRemaining; char chlncomingDataBuffer[1024]; iBufferLength = iSpaceRemaining = sizeof(chlncomingDataBuffer); iEnd = 0; iSpaceRemaining -= iEnd; iBytesRead = recv(m_hClientSocket, (LPSTR)(chlncomingDataBuffer+iEnd), iSpaceRemaining, NO_FLAGS); iEnd+=iBytesRead; if (iBytesRead == SOCKET_ERROR) ReportWinsockErr( "OnClientRead recv reported a socket error."); chlncomingDataBuffer[iEnd] = '\0'; if (lstrlen(chlncomingDataBuffer) != 0) Printstring(chlncomingDataBuffer); else OnClientClose(); return(0L); } 626 
Подводя итоги Изменение меню сервера Оставшиеся функции класса CServerDoc управляют выбором меню программысервера. Функции OnUpdateServerClose и OnUpdateServerOpen переключают состояние меню, по мере того как пользователь открывает и закрывает ожидающий сокет сервера. Другие четыре функции (OnUpdateFileNew, OnUpdateFileOpen, OnUpdateFileSave и OnUpdateFileSaveAs) просто запрещают вывод опций меню File, так как программа-сервер не имеет функций файлового ввода-вывода. Однако такие функции вполне можно использовать. Эти опции уже связаны с соответствующими сообщениями — вам остается определить, что должно происходить при их выборе. Подводя итоги Эта глава заканчивает ваше путешествие по Интернет. К концу прочтения вы должны иметь общее понятие относительно функционирования сетей TCP/IP и работы Интернет. Авторы надеются, что вы наслаждались этим введением в программирование Интернет. Вы должны преисполниться уверенностью в том, что сможете изучить то, чего еще не знаете, вооруженные знаниями, полученными из этой книги. И прежде, чем продолжить свой путь, проверьте, хорошо ли вы усвоили следующие ключевые концепции: S Унифицированный указатель ресурса, URL, сообщает программе-клиенту, как получить требуемые файлы, задавая адрес сервера, каталог на нем, содержащий эти файлы, и протокол, используемый для их получения. S Внутри HTML-тэгов гипертекстовых документов находятся URL, унифицированные указатели ресурсов, определяя связи между файлами в Интернет. S Протокол передачи гипертекста (HTTP) позволяет программам-клиентам получать информацию из Интернет, используя команды формата NVT ASCII, подобные командам протоколов SMTP, POP и FTP. S Программа-сервер WWW очень похожа на FTP-сервер. Для передачи файлов программе-клиенту она использует протокол передачи гипертекста. 
Ком п ьютеры-бастион ы и безопасность в Интернет Если мы спросим пользователей Интернет о том, какое слово в первую очередь приходит им в голову, когда они слышат о безопасности в Интернет, скорее всего это будет «firewall»1. Те из них, кто не назовет «firewall», скорее всего вспомнят «шифрование» (encryption). В наши дни, соединяя корпоративную локальную сеть с Интернет, менеджер больше всего надеется, что безопасность сети и сохранность данных от несанкционированного доступа будет обеспечена компьютерами-бастионами. Система-бастион состоит из нескольких компонентов. Можно сказать, что она выглядит как преграда, В современной литературе слово «firewall» чаще всего переводится, как «брандмауэр». Что такое «брандмауэр», переводчик не знает, поэтому он предпочел перевести «firewall» как «бастион». Представляется, что это более знакомое читателю понятие. — Примеч. перев. 628 
Определяем цели возведенная между внутренней, или локальной, сетью (LAN) и внешними, глобальными сетями, например Интернет. За обеспечение сетевой безопасности, как правило, отвечает администратор сети. Однако и программист, разрабатывающий прикладные программы, тоже обязан приложить усилия, чтобы его разработки соблюдали принципы безопасности. Это то, чем он может здорово помочь администратору сети. Например, в состав прикладных программ могут включаться шлюзы, обеспечивающие контроль и авторизацию пользователей. Они сами по себе могут предохранять систему от несанкционированного доступа к важной и критической для жизнедеятельности предприятия информации. Если при создании программы вопросы защиты не рассматривались, компьютерные хакеры (люди, специализирующиеся на взломе систем защиты данных) причинят массу беспокойства; вы потратите значительное время (и деньги), восстанавливая разрушенные данные, уничтожая вирусы и заново устанавливая испорченные программы. Как правило, хакерами являются квалифицированные программисты. Хакеры знают не только, как программировать сетевые операции. Они знакомы с особенностями работы различных сетевых операционных систем. Большая часть информации в этой книге для настоящего хакера уже давно — лишь пройденный этап. Сетевые профессионалы написали целые книги, посвященные различным аспектам сетевой безопасности. И в нашем небольшом приложении невозможно охватить все целиком. Однако тем, кто ничего не знает о безопасности в сети или не совсем понимает, каким образом системы-бастионы обеспечивают сетевую безопасность, будет полезно прочитать это приложение. Определяем цели До того как приступить к созданию сетевой информационной системы, руководителю необходимо тщательно взвесить все плюсы и минусы, оценить потенциальный риск и, с другой стороны, ожидаемые преимущества. Предположим, например, что менеджер решает выставить на сеть важную для функционирования фирмы информацию и вместе с тем не хочет, чтобы ею смогли воспользоваться в целях промышленного шпионажа. С одной стороны, он понимает, что оперативное получение информации о счетах фирмы ее представителями позволит увеличить скорость обработки и, соответственно, объем продаж. С другой стороны, очевидно, что если конкурирующие фирмы получат эту информацию, наступит крах всего бизнеса. Опасность, связанная с утечкой данных в этом случае настолько велика, что, какой надежной бы ни была сетевая система защиты данных, размещать столь критическую информацию в сети нельзя. Безусловно, наличие определенной политики сетевой безопасности, лояльность сотрудников и надежное программное и аппаратное обеспечение во много раз снижают вероятность нежелательного проникновения в сеть извне. Но, к сожалению, ни одно из средств защиты данных никогда не даст стопроцентной гарантии. Вывод: любая хорошо поставленная защита данных начинается с грамотного управления риском. 629 
Приложение А Для создания эффективной системы защиты необходимо четко представлять, какие именно данные нуждаются в защите и от кого. Некоторые методы защиты работают эффективно только против определенного способа нападения, некоторые являются универсальными. Если администратор знает, с какой стороны ждать нападения, он сможет лучше подготовиться к нему. Программист-профессионал должен предусматривать три типа возможного нападения на сетевую систему. Существуют три области, по которым распределяются факторы риска (угрозы нападения) и стоимости (защиты данных): целостность, доступ и конфиденциальность информации. Защита целостности данных Когда кто-нибудь, все равно умышленно или случайно, вносит несанкционированные изменения в сетевые данные, он нарушает их целостность. Таким образом, система сетевой безопасности должна препятствовать попыткам не имеющих полномочий пользователей подменить или уничтожить ценную сетевую информацию. Для этих целей во многих сетях применяется авторизация доступа и установление прав доступа на файлы, содержащие ценную информацию. Эти элементарные средства защиты позволяют снизить риск и ограничить объем и тип информации, к которой пользователь может получить доступ. Защита доступа к сети Термин «доступность» означает здесь, насколько легко и быстро авторизованный пользователь получает доступ к сетевым ресурсам. Наибольшую угрозу в этом аспекте представляют компьютерные вирусы. С точки зрения воздействия на сетевые устройства, вирусы можно разделить на две группы: вирусы первой группы {virus) повреждают данные, а второй {worm) — снижают производительность системы, поглощая рабочее время центрального процессора, заставляя его выполнять ненужные действия. Вирусы не обязательно уничтожают данные. С другой стороны, они часто перегружают системы, тем самым снижая производительность остальных пользователей сети. Для того чтобы обеспечить устойчивое функционирование сети, необходимо периодически или постоянно проверять ее на наличие вирусов специальными диагностическими и антивирусными программами. Конфиденциальность данных Если в сети не обеспечена конфиденциальность данных, кто-нибудь может с легкостью получить копию важной информации. Конфиденциальность достигается при помощи шифрования передаваемых и хранимых сетевых данных. В мире существует масса технических и юридических моментов, связанных с шифрованием данных, и в этом приложении мы не можем рассмотреть их все. Если вы хотите ознакомиться с шифрованием сообщений электронной почты, взгляните на описание системы под названием Pretty Good Privacy (PGP), разработанную Филиппом Циммерманом (Philip Zimmerman). Это самый попу¬ 630 
Что такое сетевой бастион? лярный и широко используемый в Интернет пакет для шифрования сообщений. Описание PGP в форме часто задаваемых вопросов и ответов на них (FAQ) называется pgpfaq?.asc и находится в каталоге /pub/gb/gbe анонимного ftp-сервера ftp.netcom.com. PGP посвящена телеконференция (группа новостей) Интернет под названием alt. security.рдр. Что такое сетевой бастион? Системы-бастионы чаще всего используются там, где нужно обеспечить целостность данных и их доступность пользователям. Бастионы защищают сеть, воздвигая следующие электронные преграды: • Шлюзы-фильтры пакетов данных • Шлюзы прикладного программного обеспечения • Шлюзы сетевых соединений Другими словами, бастион не является физическим понятием, таким, как, например, компьютер (хотя компьютер обычно является главным компонентом бастиона). Скорее бастион — это система или даже метод защиты. В следующих абзацах мы обсудим значения и ограничения всех трех перечисленных компонентов, из которых состоит бастион. Вы узнаете, что каждый тип электронного заграждения функционирует на своем собственном сетевом уровне. Что такое фильтрация пакетов? Один из наиболее популярных типов электронной защиты — фильтрация пакетов данных. Барьер (фильтрующий пакеты шлюз) исследует каждый поступающий в сеть и выходящий из нее пакет. Из четвертой главы этой книги вам известно, что каждый пакет содержит информацию в IP-заголовке, необходимую для его маршрутизации. Метод фильтрации пакетов пользуется способностями маршрутизаторов анализировать содержимое пакетов данных (IP-датаграмм). Маршрутизатор перемещает пакеты между двумя различными сетями. Для того чтобы выбрать правильное направление, маршрутизатор анализирует сетевой адрес назначения в заголовке IP-датаграммы. Точно так же фильтр пакетов анализирует содержимое полей адресов отправителя и получателя пакета, а затем, основываясь на критериях фильтрации, заданных сетевым администратором, отбрасывает или передает пакет дальше. Главное преимущество системы фильтрации пакетов состоит в ее дешевизне. Программное обеспечение любого маршрутизатора заранее содержит практически все, необходимое для работы фильтра пакетов. Один из недостатков фильтра пакетов заключается в том, что необходимо много времени, чтобы заставить его работать так, как вам этого хочется. В начале сетевой администратор должен составить список приемлемых и неприемлемых сочетаний адресов и портов получателей и отправителей. Далее, маршрутизатор настраивается таким образом, чтобы все пакеты, не попадающие в определенный 631 
Приложение A заранее список, отбрасывались. Чтобы сконфигурировать фильтр пакетов, сетевой администратор должен следовать основным принципам политики по защите данных своей фирмы. Так, например, должен существовать список удаленных сетевых компьютеров, которым будет позволено входить во внутрифирменную локальную сеть. Например, руководство может установить, что определенный сетевой компьютер должен принимать все входящие соединения от любого компьютера Интернет, но только по порту SMTP (25). Другими словами, решено, что один сетевой компьютер фирмы будет функционировать, как сервер электронной почты. В таком случае сетевой администратор настраивает фильтр пакетов, чтобы он пропускал пакеты с любым адресом отправителя и адресом сервера электронной почты в качестве получателя. При этом номер порта во всех пропускаемых пакетах должен равняться 25. Пакеты, не соответствующие данным требованиям, отбрасываются. Фильтр пакетов может быть настроен на отбрасывание всех пакетов, пришедших с определенных сетевых компьютеров. Предположим, что местный колледж имеет дурную репутацию, как место, где обучаются студенты-любители взламывать системы компьютерной защиты. Руководство может решить перекрыть доступ к локальной сети любым пакетам, пришедшим с компьютера колледжа, подключенного к Интернет. На первый взгляд, фильтр пакетов представляется хорошей системой защиты. На самом деле мало-мальски квалифицированный программист всегда может написать программу, которая попросту обманет такой электронный барьер. Даже вы, прочитав данную книгу, в состоянии написать программу, которая будет записывать в заголовок пакета ту информацию, которую пожелаете вы, а не ту, что заносится туда при нормальной работе сетевых протоколов. На профессиональном жаргоне такое поведение называется «spoofing» (обман, надувательство). Это «надувательство» необычайно популярно в хакерской среде. В борьбе против «надувательства» могут использоваться различные средства. Нужно отметить, что фильтрация пакетов не может служить панацеей против него и здесь должны применяться иные методы защиты. В добавок ко всему, сетевой администратор должен составлять разнообразные таблицы для маршрутизаторов, в которых написано, кто и какие сетевые манипуляции может производить. Чем сложнее и комплекснее подход к организации взаимодействия с Интернет у руководства фирмы, тем сложнее и запутаннее становятся эти таблицы. А в этом случае увеличивается вероятность появления ошибок. Шлюзы сетевого соединения Фильтры пакетов функционируют на сетевом уровне сети TCP/IP. Шлюзы сетевых соединений функционируют на транспортном уровне и имеют дело исключительно с TCP. Как уже отмечалось, фильтры пакетов являются электронными барьерами — они исследуют содержимое пакетов при их прохождении через систему-бастион. Шлюз сетевого соединения защищает локальную сеть, когда между двумя сетевыми хостами устанавливается TCP-соединение. Когда программа-клиент соединяется с защищенным сервером, на самом деле она 632 
что такое сетевой бастион? соединяется не с сервером, а с программным обеспечением бастиона-шлюза сетевого соединения. При этом используется специальный тип программ, взаимодействующих между настоящим сервером и бастионом. Шлюз сетевого соединения как бы сидит между программой-клиентом из внешнего мира и программой-сервером в локальной сети. Вы уже наверное догадались, что при такой схеме работы только те клиенты, которые знают, как общаться с защищенным сервером, могут связаться с ним. Другими словами, для работы с бастиономшлюзом соединения необходимы специальные программы-клиенты. Клиенты, желающие обмениваться данными с сервером, должны договориться о соединении с хостом-бастионом. Программное обеспечение бастиона перехватывает все запросы на установление соединения и позволяет обращаться только к заданным администратором сети портам. После того как соединение установлено, шлюз соединения становится прозрачным для обеих сторон — он больше никак не влияет на сценарий соединения. Другими словами, защита локальной сети обеспечивается только на стадии установления соединения. После того как соединение разрешено и установлено, защитный барьер как бы исчезает. Программное обеспечение шлюза никак не проверяет содержимое передаваемых пакетов. Оно превращается в некое подобие провода — еще одного носителя сигналов на пути пакетов от передатчика к приемнику. Вы помните, что программное обеспечение шлюза соединения обрабатывает начальный запрос на установление соединения, а далее становится «невидимым». На момент, когда соединение установлено, шлюз соединения уже никак не участвует в процессе. Ваш шлюз соединения предполагает, что используется специальные клиентские программы, способные установить соединение через него. Если у клиента установлено специальное программное обеспечение, процесс установления соединения с защищенным сервером «невидим» для него. Одна из проблем, связанных с использованием шлюзов соединения, состоит в том, что кто-либо, находясь внутри защищенной сети, может открыть неучтенный порт протокола, которым, в свою очередь, может воспользоваться недоброжелатель снаружи. Против такого злоупотребления доверием сложно бороться при помощи шлюза соединения, поскольку он не может уследить за всеми открытыми для соединения портами. Не стоит огорчаться — мы знаем, что никакая система, принадлежи она местному банку или учебному заведению, никогда не обеспечит стопроцентной гарантии. Шлюзы приложения Вы знаете, что фильтры пакетов исследуют содержимое каждого пакета с целью предотвратить прохождение нежелательных по сети. Точно так же шлюзы соединений устанавливают электронные барьеры на пути нежелательных входящих TCP-соединений. Прикладные шлюзы (третий компонент бастионов) создают барьеры на пути нежелательных сетевых приложений. Шлюзы-фильтры пакетов работают на сетевом уровне. Шлюзы соединения — на транспортном. Прикладные шлюзы работают на верхнем сетевом уровне — прикладном. Структура прикладного шлюза зависит от конкретного приложения, которое он 633 
Приложение А обслуживает. Для каждого приложения пользователя требуется специальный шлюз. Так же как шлюз соединения требует наличия специальных программклиентов, для прикладного шлюза необходимо иметь специальные приложения. Однако поскольку прикладные шлюзы функционируют на прикладном уровне, их работа не требует наличия каких-либо специальных программ-клиентов. Необходимо понимать, что работа прикладных шлюзов основывается на совершенно иной концепции защиты данных, нежели работа фильтров пакетов или шлюзов соединения. Защита данных в последних двух трактуется наиболее общим образом. Например, фильтр пакетов исследует содержимое каждого пакета. Несмотря на то, что шлюзы соединения требуют наличия специальных клиентских программ, любой желающий может воспользоваться копией такой программы, чтобы обмануть систему-бастион. (Сетевые администраторы не ограничивают распространение этих специальных программ.) Другими словами, и фильтры пакетов, и шлюзы соединения в принципе позволяют любому приложению использовать любой порт и любой протокол, доступный на данном сетевом компьютере без того, чтобы проходить процедуру авторизации доступа. По сравнению с остальными, прикладные шлюзы не являются системой защиты общего назначения. Вместо этого они представляют собой специальный код, предназначенный для защиты совершенно конкретных программ. Для того чтобы спроектировать такой шлюз, вначале анализируется назначение программы, а затем определяется риск, связанный с несанкционированным доступом к программе. Далее разработчик решает, какой из механизмов защиты будет наиболее подходящим для этого приложения. Каждое новое приложение требует отдельного анализа, связано с другими рисками и, возможно, нуждается в ином механизме защиты. Поскольку одни приложения оказываются более чувствительными к несанкционированному доступу, нежели иные, сетевой администратор должен уметь правильно определить степень риска и установить прикладной шлюз в первую очередь в той прикладной программе, которой он необходим больше всего. Для организаций, нуждающихся в очень надежной защите, прикладные шлюзы являются наиболее действенным средством. Например, поскольку к каждому приложению применяется индивидуальный подход, снижается вероятность ошибок и случайных упущений при разработке шлюза. (Это, однако, не значит, что таких ошибок не бывает вовсе. Ошибаться, к сожалению, свойственно всем.) Другими словами, работа прикладного шлюза не зависит от того, насколько правильно сетевой администратор сконфигурирует таблицы доступа и все возможные комбинации при настройке шлюза соединения или фильтра пакетов. Кроме того, разработчик прикладного шлюза может с легкостью заложить в него возможность вести запись в системных журналах учета, всех происшествий и транзакций, имевших место при работе данного приложения. Системные журналы учета сетевых операций (в них записывается информация о заранее определенном наборе операций, происходящих в сети) широко используются при последующем анализе различных сетевых происшествий и событий. Ведение системных журналов обычно является частью внутрифирменной политики по защите данных и используется в различных сетях, в том числе и военных. 634 
Выводы Сетевой администратор или другой сотрудник, ответственный за сохранность сетевых данных, должен тщательно подойти к проблеме выбора системы-бастиона. В большинстве случаев сетевой администратор имеет набор требований, предъявляемых к такой системе. Вы, как прикладной программист Интернет, можете реализовать некоторые из них на уровне разрабатываемого программного обеспечения. В любом случае, разрабатывая приложение Интернет, необходимо иметь в виду соображения безопасности. К сожалению, часто администратор спохватывается слишком поздно, когда сеть уже пострадала от неожиданного нападения и в ее системе защиты обнаружилась брешь. Чтобы лучше ознакомиться с системами-бастионами и общими проблемами безопасности в Интернет, попробуйте обратиться к книге «Бастионы и безопасность в Интернет» (Firewalls and Internet Security, Cheswick and Bellovin, Addision-Wesley, 1994). Документ RFC 1579 (Firewall-Friendly FTP, Bellovin, 1994) посвящен совместной работе FTP и систем-бастионов. Наконец, если у вас есть возможность приобрести экземпляр журнала «Мг/р Интернет» (Internet World) за февраль 1995 года, вы сможете найти там несколько статей, посвященных проблемам безопасности Интернет вообще и проблемам бастионов и шифрования данных в частности. 
Пособие по работе с учебными программами Исходные тексты всех обсуждаемых здесь учебных программ вы найдете на приложенной к книге дискете. В приложении Б рассматриваются общие концепции, применявшиеся при создании этих программ. Если вам понятна хотя бы общая структура учебных программ, можете считать, что первый шаг к мастерству программирования в Интернет уже сделан. Вполне естественно, что мы сфокусировали внимание не на программировании как таковом, а на программировании сетевых приложений для среды Интернет. Мы не ставили перед собой задачи обучить вас программированию на C/C++ или прикладному интерфейсу Windows. В расчет принимались лишь два важных соображения. Во-первых, то, что вы программист и имеете опыт в разработке Windows-приложений. Во-вторых, подразумевается, что вы знакомы с языком программирования C/C++. Тем не менее вам не потребуется большого опыта и знаний, чтобы разобраться в наших примерах. Мы старались как можно 636 
Различные виды программ нагляднее продемонстрировать описываемые в книге принципы, поэтому исходный текст программ прост и доступен пониманию даже неискушенного програмчиста. Различные виды программ Программы на дискете делятся на четыре категории. К первой относятся так называемые учебные (quick) программы. Все имена учебных программ начинается на букву Q (например, Q Lookup, QFinger и т. д.). Как правило, каждая иллюстрирует один из протоколов Интернет и несколько функций для работы : этим протоколом. Очевидно, что практической пользы в повседневной жизни гни принести не могут. С другой стороны, они свободны от избыточных подробностей обыкновенных программ, отвлекающих от восприятия смысла исходного текста. Ко второй категории относятся программы, разработанные при помощи SDK (Software Development Kit, пакет разработчика программ). В пашей книге они называются программами Sockman. Основное отличие программ Sockman в том, что они объединяют несколько сетевых утилит в пакет гбщего назначения. Пакет производит различные сетевые операции, например поиск адреса сетевого компьютера или доставку информации о пользователях удаленного хоста. Кроме того, Sockman позволяет запускать другие Интернетпрограммы из собственных меню. Для целей нашей книги, в общем, программы Sockman не имеют особого значения. Они приведены в качестве образца того, пак можно объединить множество протоколов и сетевых функций в рамках f линственной программы-приложения. Третьим видом примеров являются программы на Visual Basic. Все Visual Basic программы имеют собственные динамические библиотеки (DLL), с помощью которых и выполняют сетевые операции протокола передачи файлов (FTP) и протокола передачи гипертекста (HTTP). Каждая библиотека DLL содержит дункции, описанные в учебных программах. При этом демонстрируют подход, з соответствии с которым функции на C/C++ помещаются в модуль-библиотеку DLL, а интерфейсом пользователя служит программа на Visual Basic. Это добно, поскольку язык Visual Basic предназначен для быстрого и эффективного написания удобных интерфейсов. Добавим, что на дискете есть исходные тексты scex библиотек DLL. Четвертый вид программ — исходный текст сервера Интернет, написанный на гбъектном C++ с использованием классов из библиотеки классов Microsoft Microsoft Foundation Classes, MFC). Реализация этого сервера предназначена для работы по протоколу HTTP, однако его можно легко настроить на любой другой протокол. Для начальной конфигурации сервера предназначен INI-файл. Сервер считывает его содержимое при запуске. Если вам знакома концепция классов и связанные с ней понятия, например наследование (inheritance), вы поймете, что исходные тексты сервера являются моделью, на базе которой чожно создать реальные программы, обслуживающие множество различных дротоколов. 637 
. Приложение Б ^ Учебные программы Строго говоря, учебные программы на дискете вовсе не являются Windows-программами. Скорее, они — некий костяк Windows-программы, из которой выброшено все, не относящееся непосредственно к исследуемой теме. Как правило, они состоят всего лишь из нескольких функций. Две первые учебные программы вызывают всего лишь одну функцию WinMain. Ни одна из них не регистрирует класс window и, следовательно, не пользуется сообщениями Windows. Учебная программа иллюстрирует шаги, предпринимаемые с целью выполнить конкретную операцию в Интернет. Каждая учебная программа основывается на предыдущих. Как правило, учебные программы демонстрируют применение одного из протоколов Интернет (например, FTP, SMTP и т. д.). Чтобы разобраться в работе учебной программы, во-первых, рекомендуется прочесть соответствующую главу. Во-вторых, необходимо изучить все операторы, из которых составлена программа. В-третьих, запустите программу и ознакомьтесь с результатами. Наконец, попробуйте пропустить код через отладчик, приложенный к вашему компилятору. Это поможет отследить поведение каждого оператора и преобразования данных в процессе работы. Пользовательский интерфейс учебных программ основан на панелях сообщений Windows. Поскольку программы не создают собственных окон и не шлют сообщений Windows, проследить за их выполнением при помощи отладчика очень просто. Отладчик — превосходный инструмент. С его помощью можно видеть подробности пошагового выполнения программы. Кроме того, если вы еще не знакомы с отладчиком, учебные программы — прекрасная возможность научиться им пользоваться. Например, настроив отладчик на выдачу содержимого структуры данных сокета, вы сможете постепенно, шаг за шагом, понаблюдать, как оно изменяется при вызове различных функций. Кроме того, это возможность изучить, как интерфейс Windows Sockets взаимодействует с различными протоколами. Разработка программ Sockman В то время как учебные программы облегчают понимание основных принципов работы протоколов Интернет, программы Sockman (Socket Manager) демонстрируют, как из простейших программ-утилит конструируется полноценное Windows-приложение. Программы Sockman (Sockmanl, Sockman2 и т. д.) на дискете представляют собой различные стадии создания одного Windows-приложения Интернет. В результате постепенно получается полнофункциональная Windows-программа. Процесс построения Sockman поделен на пять стадий. На каждой стадии к программе добавляется новая утилита Интернет или новая возможность. Программа Sockman сложнее, чем учебные программы. Тем не менее Sockman остается простой по сравнению с настоящими Windows-приложениями. Например, в ней не используются механизмы управления памятью Windows. Вместо этого данные хранятся в глобальных переменных. При 638 
Разрабитка программ Sockman ~ необходимости Sockman регистрирует класс window и выводит на монитор окна и панели, однако функции, управляющие выводом текста в окно Windows, не используются. Вместо этого каждый раз, когда необходимо вывести сообщение в главное окно, Sockman очищает содержимое текущего окна и посылает туда содержимое глобального массива — в нем целиком хранится все сообщение. Имена модулей Sockman Схема именования файлов Sockman весьма легка для запоминания. Она отражает изменения, вносимые в программу на каждой стадии ее развития. Основная программа (Sockman 1, она выполняет основные функции сетевого ввода-вывода) состоит из двух модулей на C/C++ (.СРР), двух заголовков (.Н), файла ресурсов Windows (.RC) и файла определений (.DEF). Каждая из программ Sockman имеет свой собственный файл пиктограммы (.ICO). На каждой стадии к Sockman добавляется еще один файл СРР, а в некоторые функции основных модулей вносятся необходимые изменения. На каждой стадии разработки модули переименовываются, отмечая этим внесенные изменения (если, разумеется, они вносились). Модуль COMMON.CPP, например, называется COMMON1.CPP на первой стадии, COMMON2.CPP на второй, COMMON3.CPP на третьей и т. д. При этом во всех пяти модулях описаны одни и те же функции. Точно так же файлы SOCKMAN.CPP, sockman.h и global.h содержат почти одно и то же на всех стадиях проектирования. На каждой стадии к файлам добавляется новая информация. Например, в файле global.h описываются новые переменные. То есть файл globall.h содержит несколько переменных. Файл global2.h, содержит те же переменные плюс несколько новых. global3.h содержит те же, что и global2.h, плюс еще несколько переменных, необходимых для работы протокола Finger. В этом приложении мы не будем обсуждать модули LOOKUP.СРР, FINGER.CPP, TIME.СРР, PING.CPP и sockraw.h, поскольку это уже сделано в предыдущих главах книги. Вместо этого мы обсудим другие модули Sockman. Имейте в виду, что, несмотря на то, что вносимые в исходный текст изменения обсуждаются в соответствующих главах на протяжении всей книги, целиком модули рассматриваются только в этом приложении. Модуль SOCKMAN.CPP Модуль SOCKMAN.CPP в основном состоит из функций по управлению сообщениями Windows. В дополнение к функциям WinMain и WndProc SOCKMAN.CPP включает еще две: первая обслуживает сообщения для меню Sockman (DoMenuCommand), а вторая (DoWinsockProgram) обрабатывает сообщения, относящиеся к сетевым операциям Winsock. От версии к версии операторы, определяющие функции SOCKMAN.CPP, могут видоизменяться, однако назначение функций всегда остается прежним. Все функции, за исключением AsyncGetServicelnfo, остаются в программе на протяжении всего процесса проектирования. Функция AsyncGetServicelnfo появля¬ 639 
Приложение В ется в SOCKMAN.CPP только начиная с третьей версии. Комбинируя функции WndProc, DoMenuCommand и DoWinsockProgram можно создать процедуруобработчик сообщений для окон Windows. Все три определяют процедуры для обработки сообщений класса window. Когда процедуры разделены на три отдельных функции, нам проще отсылать вас к конкретным операторам при рассмотрении сообщений Sockman. Функция WinMain На каждой стадии развития программы Sockman к функции WinMain добавляются новые инициализирующие операторы. За исключением этих операторов, на протяжении всех пяти версий структура WinMain (и DoMenuCommand), заданная в SOCKMAN.CPP, остается прежней. В следующем листинге приведены операторы функции WinMain из файла SOCKMAN1.CPP. Структура WinMain в общем виде весьма похожа на аналогичные функции из других программ: int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdLine, int nCmdShow) { MSG msg ; WNDCLASS wndclass ; hlnstanceSockman = hlnstance; lstrcpy(szAppName, "SockMan"); szPrintBuffer[0] = '\0'; if (!hPrevInstance) { wndclass.style wndclass.lpfnWndProc wndclass.cbClsExtra wndclass.cbWndExtra wndclass.hlnstance wndclass.hlcon wndclass.hCursor wndclass.hbrBackground wndclass.IpszMenuName wndclass.IpszClassName = CS__HREDRAW I CS_VREDRAW; = WndProc; = 0; = 0; = hlnstance; = Loadlcon(hlnstance, szAppName) ; = LoadCursor (NULL, IDC_ARROW); = GetStockObj ect (WHITE_BRUSH) ; = szAppName; = s zAppName; if (!RegisterClass(&wndclass)) return FALSE; } hwndSockman = CreateWindow ( szAppName, "SockMan rev. 1", WS_OVERLAPPEDWINDOW I WS_VSCROLL I WS_HSCROLL, 640 
Разработка программ Sockmart cw_usedefault, cw_usedefault, cw_usedefault7 cw_usedefault7 NULL, NULL, hlnstance, NULL if (IhwndSockman) return FALSE; ShowWindow(hwndSockman, nCmdShow) ; UpdateWindow(hwndSockman); if( StartWinsock()) { while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } WSACleanup(); return(msg.wParam) ; } Каждая последующая версия WinMain отличается от предыдущей только несколькими инициализирующими операторами. Например, в добавок к операторам hlnstanceSockman, szAppName и szPrintBuffer, WinMain пятой версии (из SOCKMAN5.CPP) содержит следующие операторы (они следуют сразу за инициализирующим оператором szPrintBuffer): szHostName [0] = '\0'; szPingHost[0] = ' \0'; szFingerHost[0] = ' \0'; szTimeServer[0] = ' \0'; r.FingerTask = 0; hrimeServerTask = 0; r.PingTask = 0 ; После инициализации нескольких глобальных переменных WinMain регистрирует класс window для модуля Sockman. Функция WndProc объявляется принадлежащей классу window-процедур и обрабатывает все сообщения этого класса: wndclass.lpfnWndProc = WndProc; 21 Зак. № 1949 641 
Приложение € Функция WndProc В программе SOCKMAN.CPP функция WndProc обрабатывает два стандартных сообщения класса window. WM_PAINT (сообщение для перерисовки окна) и WM_DESTROY (для уничтожения окна). WndProc содержит оператор case для обработки сообщений WM_COMMAND. Однако на самом деле WndProc просто передает все сообщения WM_COMMAND функции DoMenuCommand. Следующие операторы демонстрируют определение функции WndProc в модуле SOCKMAN1.CPP: long FAR PASCAL _export WndProc(HWND hwnd, UINT iMessage, UINT wParam, LONG lParam) { switch (iMessage) { case WM_PAINT: PAINTSTRUCT ps; HDC hdc; RECT rect; hdc = BeginPaint( hwnd, &ps) ; GetClientRect(hwndSockman, &rect); DrawText(hdc, szPrintBuffer, -1, &rect, DT_EX PANDTAB S I DT_WORDBREAK) ; EndPaint(hwnd, &ps); return(0); case WM_COMMAND: if (DoMenuCommand(hwnd, iMessage, wParam, lParam)) return(0); else break; case WM_DESTROY: PostQuitMessage(0); return(0); } return (DefWindowProc (hwnd, iMessage, wParam, lParam) ) ; } Операторы конструкции case одни и те же на протяжении всего цикла разработки программы Sockman. Сама функция модифицируется, только чтобы добавить новые операторы case для обработки сообщений о сетевых событиях, поступающие в процессе работы Sockman. Все сообщения WM_COMMAND перенаправляются функции DoMenuCommand. 642 
Функция DoMenuCommand Функция DoMenuCommand одинакова во всех пяти версиях модуля SOCKMAN.CPP. Она обрабатывает сообщения для всех не относящихся к сетевым операций. Сообщения, получаемые DoMenuCommand, производятся выбором в меню Sockman, например Clear, Print, Save As или Exit: longDoMenuCommand(HWND hwnd, UINT iMessage, UINT wParam, LONG 1Param) { switch (wParam) { case IDM_FILE_CLEAR: szPrintBuffer[0]='\0'; InvalidateRect(hwndSockman, NULL, TRUE); UpdateWindow(hwndSockman) ; return(TRUE); case IDM__FILE_PRINT: case IDM_FILE_SAVEAS: PaintWindow ( (LPSTR)"Selected function is not yet implemented!"); MessageBeep(0) ; MessageBox(hwnd, "Sorry! You have to implement this yourself.", SzAppName, MB_ IC ONEXC L AMATI ON | MB_OK); return(TRUE); case IDM_FILE_EXIT: SendMessage (hwnd, WM__CLOSE, 0, 0L); return(TRUE); case IDM__HELP__HELP: PaintWindow ( (LPSTR)"Selected function is not yet implemented!"); MessageBeep(0); MessageBox(hwnd, "Help is not yet implemented!", SzAppName, MB_ICONEXCLAMATION I MB_OK) ; return(TRUE); case IDM__HELP__ABOUT: MessageBox(hwnd, "A programming shell for Windows Sockets programmers.", "SockMan - Winsock Program Manager rev. 5", MB_I CON INFORMAT I ON I MB_OK) ; return(TRUE); 643 
Приложение 6 default: return(DoWinsockProgram(hwnd, wParam, lParam)); } } Оператор default функции DoMenuCommand передает все неопознанные в конструкции case сообщения функции DoWinsockProgram. Функция DoWinsockProgram Эта функция так же, как и DoMenuCommand, занимается обработкой сообщений, вызывающихся выбором в меню Sockman. DoWinsockProgram модуля SOCKMAN1.CPP только выводит соответствующие текстовые сообщения для каждого пункта меню. По мере разработки Sockman вместо сообщений появляются сетевые функции, вызываемые операторами case: long DoWinsockProgram(HWND hwnd, UINT wParam, LONG lParam) { switch (wParam) { case IDM_APP_MAIL: case IDM_APP__FTP: MessageBeep(0); MessageBox(hwnd, "No APPLICATIONS are currently implemented!", szAppName, MB_I CONEXCLAMATI ON I MB_OK) ; return(TRUE); case IDM_LOOKUP_ASYNC: case IDM_LOOKUP_BLOCKING: MessageBeep(0); MessageBox(hwnd, "The LOOKUP utility is not yet implemented!", szAppName, MB_I CONEXCLAMAT I ON | MB_OK) ; return(TRUE); case IDM__FINGER_ASYNC: case IDM_FINGER_J3LOCKING: MessageBeep(0); MessageBox(hwnd, "The FINGER utility is not yet implemented!", szAppName, MB_I CONEXCLAMAT I ON I MB_OK) ; return(TRUE); case IDM__TIME_UTIL: MessageBeep(0) ; MessageBox(hwnd, "The TIME utility is not yet implemented!", szAppName, MB_I CONEXCLAMAT I ON I MB_OK); return(TRUE); 644 
Разработка программ ,£<*с|рЩ| case IDM_PING_UTIL: MessageBeep(0); MessageBox(hwnd, "The PING utility is not yet implemented!", SzAppName, MB_I CONEXCLAMATI ON I MB_OK) ; return(TRUE); } return(FALSE); } Если посланное сообщение WM_COMMAND не опознано ни DoMenuCommand, ни DoWinsockProgram, DoMenuCommand возвращает значение false в функцию WndProc. В следующем фрагменте показано, как, получив значение false от DoWinsockProgram, WndProc передает сообщение функции Windows DefWindowProc. Функция DefWindowProc, в свою очередь, выполняет процедуру для сообщений по умолчанию. case WM_COMMAND: if (DoMenuCommand(hwnd, iMessage, wParam, IParam)) return(0); else break; case WM_DESTROY: PostQuitMessage(0); return(0); } return (DefWindowProc (hwnd, iMessage, wParam, IParam) ) ; Еще одна функция модуля SOCKMAN.CPP — AsyncGetServiceinfo. Она использует функцию Winsock под названием WSAAsyncGetServByName, чтобы получить информацию о сетевых службах из базы данных. Функция AsyncGetServicelnfo обсуждается подробнее в главе 12. Модуль Common.СРР Модуль COMMON.СРР состоит из трех функций общего назначения. К ним относится инициализация Winsock, центровка окон и прорисовка текста. Эти функции одинаковы во всех пяти версиях модуля COMMON. СРР (COMMON1.CPP, COMMON2.CPP, COMMON3.CPP и т. д.). В следующих разделах рассматривается исходный текст каждой функции COMMON.СРР. Ни одна из них не осуществляет операций сетевого ввода-вывода. Функция StartWinsock Функция StartWinsock вызывается из WinMain непосредственно перед вхождением в главный цикл обработки сообщений Sockman. Как известно, до вызова любой, входящей в состав Winsock функции, необходимо инициализировать 645 
Приложение Б модуль при помощи WSAStartup. Кроме прочего, WSAStartup загружает динамическую библиотеку Winsock, а затем, возможно, возвращает код ошибки. Если ошибок не было, функция StartWinsock вызывает функцию PaintWindow для вывода сообщения о Winsock DLL: BOOL StartWinsock(VOID) { WSADATA wsaData; // Сведения о реализации Winsock UINT iErr; // Код ошибки char szErrMessage[MAX_ERR_MESSAGE] ; // Буфер сообщения //об ошибке if (iErr = WSAStartup(WINS0CK_VER_11, &wsaData)) { MessageBeep(O); switch(iErr) { case WSASYSNOTREADY: lstrcpy(szErrMessage, WSASYSNOTREADY_MSG); break; case WSAVERNOTSUPPORTED: lstrcpy(szErrMessage, WSAVERNOTSUPPORTED__MSG); break; case WSAEINVAL: lstrcpy(szErrMessage, WSAEINVAL_MSG); break; } MessageBox(NULL, szErrMessage, "SockMan", MB_OK|MB_ICONSTOP) ; return (FALSE) ; } else PaintWindow(wsaData.szDescription); return(TRUE); } Если WSAStartup удалось инициализировать Winsock DLL, функция StartWinsock вернет результат true функции WinMain. Значение true от StartWinsock заставит WinMain войти в главный цикл по обработке сообщений Sockman. Если инициализировать Winsock DLL не удастся, StartWinsock выведет сообщение об ошибке в панель Windows и вернет значение false вызывавшей функции WinMain. Как только произошла неудачная инициализация, WinMain вызывает функцию WSACleanup и Sockman корректно заканчи- 646 
: зает работу. Поскольку ни одна сетевая операция Sockman не обходится без участия Winsock DLL, эта динамическая библиотека должна быть правильно инициализирована. Функция CenterWindow До того как отобразить диалоговое окно, Windows посылает сообщение WM_INITDIALOG функции, описанной как обработчик сообщений для диалоговых окон. Сообщение WM_INITDIALOG позволяет программе выполнить процедуры по инициализации диалогового окна. Любая процедура инициализации диалогового окна в Sockman включает вызов функции CenterWindow. Ее задача — центрировать положение окна относительно окна Sockman до того, как оно отобразится на экране. Функция CenterWindow вызывает стандартные функции прикладного интерфейса Windows, позволяющие правильно центрировать окно, независимо от его размеров. Функция GetWindowRect возвращает размеры (в прямоугольных координатах) диалоговых окон Sockman. Функция GetClientRect возвращает координаты клиента для окна Sockman. Следующие операторы вычисляют ширину и высоту диалогового окна: nWidth = rect.right - rect.left; nHeight = rect.bottom - rect.top; Поскольку координаты клиента заданы относительно верхнего левого угла оконной области клиента, для верхнего левого угла они равны (0,0). Следующие два оператора вычисляют х-у координаты для центра окна Sockman: point.х = (rectMainWindow.right - rectMainWindow.left) / 2; point.у = (rectMainWindow.bottom - rectMainWindow.top) / 2; Функция ClientToScreen преобразует х-у координаты центра окна клиента Sockman в экранные координаты. В результате получаются х-у координаты для верхнего левого угла диалогового окна, центрированного по отношению к окну Sockman: point.х = point.х - (nWidth / 2); point.у = point.у - (nHeight / 2); Наконец, функция Move Window присваивает диалоговому окну только что вычисленные х-у координаты: MoveWindow(hWnd, point.х, point.у, nWidth, nHeight, FALSE); Шестой параметр функции, FALSE, запрещает Windows перерисовывать сдвинутое окно до завершения остальных инициализирующих его операций. 647 
Приложение Б Функция PaintWindow Большинство программистов имеют собственные методы прорисовки текста в окне. Программа Sockman отличается в этом смысле особенной простотой. Каждый раз, когда функции нужно вывести текст в окно Sockman, она очищает его содержимое и выводит туда глобальный массив с новым текстом. Для обновления содержимого массива и вывода его в окно и предназначена функция PaintWindow. Ее текстовый параметр (IpszTxt) копируется в глобальный массив по указателю szPrintBuffer, затем окно Sockman стирается и вызывается функция UpdateWindow для генерации сообщения WM_PAINT. Компиляция учебных программ После того как учебные программы с дискеты установлены на жесткий диск, в каждом каталоге можно найти файл с расширением МАК для компилятора Microsoft Visual C++ версии 1.5 и файл с расширением IDE для компилятора Borland C++ версии 4.0. В зависимости от ваших предпочтений и от того, каким компилятором вы пользуетесь, выберите один из этих файлов для управления проектом соответствующей учебной программы. 
Алфавитный указатель А АСК (acknowledged), сообщение-подтверждение 49 ANSI, Американский национальный институт стандартов 40 AppWizard, утилита 603 ARCNET, технология 29 ARP, протокол 82, 101 ARPA, американское правительственное агентство 180 ASCII NVT 469, 472 код 15, 469 расширенный набор 16 символы, служебные 165 символы, управляющие 274 стандартный набор 274 в Base-64 декодирование данных 451 кодирование данных 450 реализация метода 448 С CALLBACK, макрос 304, 305 COMM.DRV, драйвер Windows 219 CSLIP, протокол предпосылки к появлению 157 требования 161 CSMA/CD, методика обнаружения столкновений 85 ctime, функция 371 D Daytime Protocol, протокол 349 Е EBCDIC, код 469 ESC, управляющий символ 473 ESMTP (расширенный SMTP), протокол 441 Ethernet адрес 465 кадр 84 технология 29, 83, 93, 94, 101 F Finger, протокол 296 Frame Check Sequence, термин 164 FTP, программа 466, 468 FTP, протокол еще раз о кодах возврата 499 интерактивный сеанс 462 краткий обзор 498 программа-клиент 462 что такое 464 FYI, «К вашему сведению» 464 649 
Алфавитный указатель н HDLC, протокол применение с РРР 164 HTML, язык разметки гипертекста 587 HTTP, протокол броузеры 588 запрос клиента 588 запрос, полный 591 запрос, простой 591 и WWW 587 схемы доступа (методы) URL 589 транзакция 587 универсальный идентификатор ресурса, URI 588 унифицированный указатель ресурса, URL 588 I IAC, управляющий символ 473, 474 ICMP, протокол доставка сообщений 389 инкапсуляция сообщений в 391 назначение 390 ограничения 391 поле контрольной суммы 393 сообщения «эхо» 404 сообщения об ошибках 394 сообщения-запросы 401 составляющая сетевого уровня 92 типы сообщений 392, 394 формат сообщений 393 что такое 389 IGMP, протокол 92, 100, 389 IP, протокол датаграмма 102 заголовок пакета 105 контрольная сумма заголовка 111 опции 112 пакет 102 программный модуль 92 составляющая сетевого уровня 92 термин 76 IPCP, протокол RFC 1332 177 IPCP, протокол (продолжение) варианты конфигурации 178 чем отличается от LCP 177 ISO, Международный институт стандартов 40 ISO/OSI, модель 40, 55, 71, 72, 78, 80 L LCP, протокол варианты конфигурации соединения 173 задачи 169 пакеты конфигурации 170 структура пакетов 172 local time, функция 385 LPSTR, тип 555 м MIME, протокол поля заголовка 443 способы кодирования 446 что такое 443 MSS, максимальная длина сегмента 143 MTU, блок данных максимальной длины 109, 114, 295 N NAK (not acknowledged), сообщение о недоставке 50 NCP, протокол 163, 166 NDIS драйвер 220 протокол 220 Network Time Protocol, протокол 349 О ODI (Open Data Link Interface) драйвер 220 интерфейс 220 OLE, стандарт 550 650 
Алфавитный указатель Р Ping, программа 187, 405 PING5.CPP, файл 417, 418 POP, протокол 453 POP2, протокол 453 POP3, протокол 453 РРР, протокол Esc-последовательность 164 инкапсуляция данных в 164 кадр данных 164 магическое число 176 протокол управления сетью (IPCP) 177 протокол управления соединением (LCP) 169 соединение-петля (loop) 176 специальный флаг 164 тип кадра данных 165 типы пакетов 169 фаза неактивности 167 фаза прекращения соединения 169 фаза проверки полномочий 168 фаза управления сетью 168 фаза установления соединения 167 что такое 163 Q QFinger, учебная программа 276 QFTP1, учебная программа канал управления 507 обработка ошибок 500 открытие канала управления 503 посылка команд серверу 505 чтение ответов сервера 504 QFTP2, учебная программа исходный текст 508 установление соединения с сервером 517 функция DemonstrateCommand 508, 510 функция WinMain 508 чтение из канала данных 518 QFTPLIB.CPP, файл 524 QLookup, учебная программа значения констант 263 значения переменных 263 исходный текст 260 QPing, учебная программа вычисление контрольной суммы 409 исходный текст 406 процедура Ping 410 структуры данных 408 функция DoPingOperation 413 QPOP3, учебная программа 458 массив команд для 458 QSMTP, учебная программа 458 массив команд для 458 QTime, учебная программа 358 диалоговое окно Time Server 375 запуск утилиты сервера времени 374 обмен данными с сервером 366 определения констант 363 преобразование порядка байтов 369 создание диалогового окна 376 управление диалоговым окном 378 функция WinMain 365 QTIME.CPP, файл 358 Quoted printable, метод кодирования 446 R RARP, протокол 82, 101, 102, 465 RFC «Присвоенные номера» (Assigned Numbers) 101, 170, 476, 492 архив документов 463, 543 RS-232, интерфейс передача данных в 147 реализация в персональных компьютерах 147 физические характеристики 147 что такое 147 S SERVER.INI, файл инициализации 602 SERVICES, файл 284, 286 SFTP, протокол 466 651 
Алфавитным указатель SLIP, протокол инкапсуляция данных в 153 коррекция ошибок в 154 недостатки 155 перенос UDP-датаграмм 155 со сжатием (CSLIP) 157 соединение по 150 что такое 149, 153 SMTP, протокол 427 SNTP, протокол 349 SockFTP, учебная программа как работает 555 объекты, запоминающие информацию 567 оператор #IFDEF 547 процедуры обработки событий 559 прочие объекты 568 Sockman, учебная программа 299 маршрут движения сообщений в 301 SOCKMAN.CPP, файл 302 Sockman2, учебная программа асинхронный поиск в DNS 313 блокирующий поиск в DNS 310 диалоговая функция 303 диалоговое окно 302 модификация функции WndProc 319 составляющие 300 SOCKMAN2.CPP, файл 302 SOCKMAN2.RC, файл 302, 308, 309 Sockman3, учебная программа Finger-операция, асинхронная 339 Finger-операция, блокирующая 340 диалоговое окно Finger 326 добавление утилиты Finger в 323 изменение функции DoWinsockProgram 324 изменение функции WndProc 334 исходный текст программы 323 функция AsyncGetServicelnfo 330 SOCKMAN3.CPP, файл 302, 324 SOCKMAN3.RC, файл 323 SOCKMAN4.H, файл 378 SOCKMAN4.RC, файл 376 Sockman5, учебная программа алгоритм работы утилиты Ping 419 исходный текст программы 417 SOCKMAN5.H, файл 4178 652 SOCKRAW.H, файл 417 Sock Web, учебная программа 593 как работает 598 создание сервера 602 т TCP, протокол 76, 86, 103 длина заголовка 140 заголовок 139 закрытие «наполовину» 138 как передает данные 129 контрольная сумма 141 надежность 130 начальный номер последовательности 135 номер подтверждения 139 номер последовательности 136, 139 окончание соединения 138 опции 143 подтверждение доставки данных 135 размер окна 141 сегмент 132 скользящее окно 130 сообщение 132 флаг АСК 135, 140 флаг FIN 138, 141 флаг PSH 140 флаг RST 140 флаг SYN 135, 141 флаг URG 140 что такое 129 Telnet, программа 108 Telnet, протокол 273 TFTP, протокол 123, 285, 464 Time Protocol, протокол использование в Интернет 349 официальный номер порта 351 представление времени 352, 358 сервер 351 Token Ring, технология 29, 84 и UDP, протокол длина заголовка 128 
Алфавитный указатель UDP, протокол (продолжение) заголовок 128 как передает данные 128 термин 86 что такое 128 V VBX (Visual Basic Extensions) 549 Visual Basic, язык программирования 542 соглашение о вызовах 553 тип LPSTR в 555 типы параметров 554 W WINDOWS.Н, файл 230, 262 WINSOCK.H, файл 203, 234, 236, 249, 382 World Wide Web (WWW) домашняя страница компании NEXOR 593 программы автоматического поиска на 592 WSAGETASYNCBUFLEN, макрос 317 WSAGETASYNCERROR, макрос 317, 346 х XNS, протокол 185 А агент передачи почты 426 пользователя 426 промежуточный 437 раздельные (split UA) 454 термин 426 адрес «десятичное с точкой» (dotted-decimal) 95 Ethernet 94, 101 IP 94 в Интернет 94 групповой 100 длина 95 класса А 97 класса В 97, 99 класса С 97 класса D 98 класса Е 98 классы 96 подсети 99 семейство 289 широковещательный 96 электронной почты 425 алгоритм сжатия данных FTP 470 аналоговые сигналы 18 Б Бастион 631 Безопасность в Интернет 628, 630, 632, 634 бит стартовый 148 стоповый 148 четности 148 блокирование в Windows 3.1 243 в Windows 95 248 в Windows NT 248 ловушка (hook routine) 246 что такое 236 бод, термин 149 653 
Алфавитный указатель В видимость (visibility), свойство объекта 557 виртуальный сетевой терминал назначение 70 протокол 273 формат 274 что такое 273 Вирусы 630 время протоколы 349 сервер 351 точка отсчета в протоколе Time Protocol 358, 363 точка отсчета на персональных компьютерах 358, 363, 371 г групповая передача 100 Д данные «вне диапазона» (out of band) 53, 197, 227, 412, 471, 476, 477 защита целостности 630 инкапсуляция в РРР 164 инкапсуляция в SLIP 153 конфиденциальность 630 маршрутизация 60 отображение 69 поток 35 сжатие 68 управление 61, 468 фрагментация 54 целостность 59 шифрование 68 датаграмма IP 102 UDP, термин 103 термин 102 двоичные цифры применение 42 демультиплексирование 64 654 дескриптор сокета системная таблица 189 что такое 188 дескриптор файла 182 динамическая библиотека (DLL) интерфейс с Visual Basic 551 разработка 523 динамическая библиотека QFTPLIB исходные тексты 524 Динамическая библиотека QWEBLIB список функций 594 домен Агра 255 верхнего уровня 255 термин 254 драйвер пакетный 220 что такое 219 3 загрузка, по протоколу TFTP 465 задание дескриптор асинхронного 315 запрос, широковещательный 465 затухание что такое 30 зона, в системе имен доменов 256 И имена доменов 251 инкапсуляция данных в CSLIP 152 в РРР 164 в SLIP 153 что такое 93, 143 интерактивность системы 159 интерпретация «данные вне диапазона» 477 DATA MARK 477 интерфейс пользователя 466, 468 прикладной программы 180 протокола пользователя 466 сокетов TCP/IP 180 сокетов Беркли 181 141, 
Алфавитный интерфейс Winsock происхождение 210 реализация 211 состав функций 210 интерфейс сокетов модель 190 интерфейс, графический пользователя (GUI) 542 Информационный центр Интернет, InterNIC 96, 98, 99, 256 к кадр данных 58-60 данных РРР 164 данных SLIP 153 термин 55 канал «точка-точка» 35 коммуникационный 35 тип 35 широковещательный 35 канал данных еще раз о 508 использование 509 открытие 510 класс window 300 клиент не ориентированный на соединение 194 клиент-сервер, модель зачем нужна 72 и TCP/IP 80 определение 71 типичное соединение 466 что такое 71 код ответов SMTP 433 кодирование по методу quoted printable 446 команда FTP контроля доступа 483 описание 483 сервиса 488 установки параметров передачи 486 команды протокола SMTP 428 комиссия по присвоению номеров Интернет, IANA 100 Компилятор Borland C++ 648 Microsoft Visual C++ 648 конечная точка соединения 210 контроль ошибок 48 последовательности 48 четности 147, 148 контрольная сумма TCP 141 алгоритм вычисления 409 обычная 49 циклическая (CRC) 49, 164 м маршрутизатор что такое 32 маршрутизация динамическая 391 таблицы 61, 391 микросхема UART 219 мини-компьютер 469 многозадачность 237 кооперативная 237 приоритетная 237 модели памяти 235 модем 19 внешний 219 внутренний 219 параметры связи 147 модификатор _export 525 мост надежность 32 производительность 31 секретность 32 что такое 31 мультиплексирование 64 мэйнфрейм 469 н нить (thread) 202 655 
Алфавитный указатель О образец (instance) 300 общая ошибка защиты Windows, GPF 552 октет, термин 89, 468 ошибки возникновение 48 и TCP/IP 112 обработка 53 сообщение об 114 п пакет коммутатор 51 переключение 23, 51 термин 55, 102 фильтрация 631, 632 панель инструментов MFC 606 переключатель пакетов 35 повторитель (repeater) что такое 30 подсеть 99 адреса 99 подтверждение доставки 49 полоса пропускания фактическая 160 эффективная 160 порт TCP 126 UDP 126 использование номера в программе^? источника и получателя 139 официальный номер 125 параллельный 20 персональных компьютеров 125 понятие 124 последовательный 125 с точки зрения сети 188 порядок байтов «с конца» 355 «с начала» 354 различных компьютерных платформ 356 сетевой 357 что такое 352 последовательность соблюдение правильной 53 поток (thread) 202 данных 78 контроль 61, 476 управление 51, 53, 66 поток (thread) 202 блокирующий 238 почтовая транзакция 431 почтовый ящик (mailbox) 425 преобразователь (resolver), программа 257 прикладной уровень описание 57 реализация 68 что такое 70, 144 приложение клиент 73 сервер 73, 74 сетевые 34 программа многопоточная 202 стиль написания 307 сценарий выполнения в DOS 237 пролог, функция 305 пропускная способность, увеличение 64 пространство имен доменов, виды 252 протокол адреса 101 выбор 187 интерпретатор (PI) 466, 482 надежность 50, 86 не ориентированный на соединение 86, 102 ориентированный на соединение 86 последовательного соединения 148 семейство 289 семейство TCP/IP 76 сетевой 43 стек 77, 80 что такое 42 процедура термин 552 процесс легковесный (thread) 202 передачи данных 466 равноправный (peer) 44, 62 656 
путь обратный (reverse path) 432 передачи сообщения (forward path) 432 разложение на уровни (layering) 40 разъем, последовательного порта DB-25 147 DB-9 147 распределенная база данных DNS, как пример 252 режим передачи FTP блочный 470 как параметр 470 потоковый 470, 471 со сжатием 470 связывание (binding), процесс активизации сеанса 66 сеансовый уровень еще раз об 67 описание 57 что такое 66 сегмент данных TCP 103, 111 составляющие 93, 132 термин 103, 132 сервер параллельной обработки запросов 74, 202 последовательной обработки запросов 73, 202 сервер DNS 256 вторичный (secondary) 256 запрос методом итераций к 258 корневой 256 надежность функционирования 258 определение 256 первичный (primary) 256 передача зоны от первичного ко вторичному 258 рекурсивный запрос к 258 сетевая служба база данных по 287 не ориентированная на соединение 48 ориентированная на соединение 47 псевдонимы 284 режимы 47 что такое 45 элементы структуры с информацией о 2843 сетевой уровень в подробностях 60 описание 56 что такое 92 сеть архитектура 38-74 глобальная (WAN) 12 контроль за использованием ресурсов 65 краткая история 40 локальная (LAN) 12 объединения 30 основные компоненты 34 перегрузка (broadcast storm) 393 передача сообщений в 42 пропускная способность 69 разработка и проектирование 52 с переключением пакетов 34 структура 33 терминология 39 сигнал Synch 476 прерывания 476 электрический 14 символьная константа AF INET 186, 290 AFNS 186 AF UNIX 186 INADDR_ANY 514 IPPROTO_TCP 187 IPPROTOJJDP 187 MSGJDONTROUTE 2932 MSG_OOB 227 MSG_PEEK 228 PF_INET 185, 290 PF NS 186 657 
Алфавитный указатели символьная константа ( ) PFJJNIX 186 SOCK_DGRAM 187 SOCK_RAW 405 SOCK_STREAM 187 система почтовая 425 имен доменов 251 зона 256 иерархическая структура 254 кто отвечает за 255 структура 254 что такое 253 система, открытая документация на 40 распространение разработок с использованием 40 термин 40 скользящее окно TCP 130 служба конечной доставки 51 потоковая 51 промежуточной доставки 51 соединение «точка-точка» 72 в режиме терминала 150 виртуальное 44, 62 данных 466 дуплексное 21, 135 не ориентированное на соединение 186 ориентированное на соединение 186 параллельное 20 переключение 21 полудуплексное 21 последовательное 20 симплексное 21 транспортного уровня 62 управление 52, 66 управляющее 466 сокет ICMP 405 абстракция 183 адрес 290 данных 510 для записи 206 сокет (продолжение) для чтения 206 имя 512 как используется в программе 191 конфигурация 191 ожидающий 510 передача данных через 194 простой (raw) 187, 405 соединение 192, 291 сторона ТСР-соединения 139 сторона виртуального соединения 72 структура данных 190 управления 510 элементы адресной структуры 289 сокет Windows блокирование в 244 дескриптор 232 информация о 211 как пользоваться 226 концепция программирования 221 настройка 223 не блокирующий 240 не ориентированный на соединение 228 ориентированный на соединение 227 по сравнению с сокетом Беркли 229 соединение через 223 создание нового 221 функции 212 что такое 210 сообщение параметры асинхронного 316 прикладное 103 термин 55 транспортное 103 стартовая подпрограмма компилятора 526 столкновение, данных обнаружение 28 т топология «звезда» 24 «кольцо» 26 «шина» 26 какая лучше 27 что такое 24 658 
Алфавитный указатель точка деления (breaking point) 115 транспортный уровень в подробностях 62 описание 56 трафик подсчет 61 термин 61 управление 63 У уровень представления еще раз об 272 описание 57 что такое 68 уровень соединения в подробностях 58 как устроен 82 описание 55 реализация 58 Ф файл «изображение» 468 ASCII 468, 469 EBCDIC 468, 469 двоичный 468 дескриптор 182 помощи по WWW 587 преобразование имени в формат DOS 537 проблемы с именами 519 с описанием сетевых служб 284 тип 468 формат в FTP 469 файл-контейнер 425 физический уровень в подробностях 58 как устроен 82 описание 55 флаг MSGJDONTROUTE 292, 412 MSG_OOB 293, 412 MSGJPEEK 535 URG 477 формат управление 469 фрагментация что такое 114 функции асинхронные 240 функция Abort Output (АО) 476 DOS для работы с временем 384 Interrupt Process (IP) 476 блокирующая 236 модификатор _export 525 не блокирующая 236, 238 обратного вызова (call-back) 304, 526 термин 552 функция Windows GetTickCount 413 модификатор 236 соглашение о вызовах 236 функция Winsock bind 225 closesocket 233 connect 224 gethostbyaddr 259, 269, 290 gethostbyname 259, 265, 266 getservbyname 285 inet_addr 268 inet ntoa 267 ioctlsocket 233 listen 513 ntohl 369 recv 226, 227, 233 recvfrom 226, 229 select 234 send 226, 227, 233 sendto 226, 228 socket 222 WSAAsyncSelect 241, 242 WSACancelBlockingCall 247 WSACleanup 231, 232 WSAGetLastError 234 WSAIsBlocking 247 WSAStartup 231 асинхронные 215 в стиле Беркли 213 для работы с базами данных 214 659 
Алфавитный указатель функция Winsock (продолжение) обозрение библиотеки 212 описание 213 расширения Windows 216 функция Winsock, асинхронная WSAAsyncGetHostByAddr 315 WSAAsyncGetHostByName 316 WSAAsyncGetServByName 331 ошибки 317 функция интерфейса сокетов accept 204 bind 193 connect 192 listen 203 select 206 send 197 sendmsg 198 sendto 197 socket 184, 185 write 195 writev 196 передачи данных 194 приема данных 198 часы персонального компьютера 350 синхронизация 350 человеческий фактор в связи с протоколом CSLIP 159 числа двоичные 15 десятичные 15 шестнадцатиричные 15 ш шинный арбитраж 27, 36 шлюз прикладного обеспечения 33, 84 прикладного программного обеспечения 631, 633 сетевых соединений 631, 632 типы 33 фильтр пакетов данных 631 что такое 33 Ц цепь виртуальная 72, 88, 124, 195 переключение 22 термин 35 ч частота, определение 19 частотная модуляция 19 Э электронная почта основные компоненты 425 сообщение 440 терминальная 430 транзакция 428 формат сообщений 445 хаб 437 Эпилог, функция 305 эффективность линии 158 
программирование lUTEDklET•** ШН 111 I L!llH I WIDOWS Книга, которую вы держите в руках, является прекрасным пособием для изучения TCP/IP и Winsock API. В ней рассматриваются все основные службы Internet и соответствующие протоколы (в том числе SMTP, POP, HTTP и FTP). Приводятся примеры программ на языках С, C++ и Visual Basic, в которых реализованы различные протоколы Internet. Эта книга поможет вам: разрабатывать приложения Windows З.хх/95/NT для доступа в Internet писать свои собственные программы-клиенты создавать несложный Web-сервер разбираться в тонкостях программирования TCP/IP строить приложения архитектуры «клиент-сервер» использовать интерфейс прикладного программирования Winsock API Прочитав эту книгу, вы сможете не только досконально разобраться в протоколах Internet, но и научитесь создавать свои собственные приложения Windows для работы с электронной почтой, World Wide Web и другими сетевыми ресурсами. Сопроводительная дискета содержит исходные тексты рассмативаемых в книге программ, включая Web-сервер, клиент FTP, утилиту преобразования имени DNS в IP-адрес и многое другое. Дискету можно приобрести за дополнительную плату. \ JAMSA Секреты программирования для Internet — в ваших руках! С&гттЕР