Лекция № 5. Сетевая служба получения информации о пользователе 1. Понятие виртуального сетевого терминала. 2. База данных по сетевым службам. 3. Протокол Finger В девятой главе вы научились искать имена компьютеров и соответствующие им IP-адреса при помощи Winsock API. Точнее, вы научились вызывать функции API для работы с базой данных имен доменов Интернет (DNS). Учебная программа QLookup выполняет жизненно важную для любого приложения Интернет функцию и необходима практически каждому пользователю. Без нее прикладные программы Winsock могли бы работать только с IP-адресами компьютеров. В этой главе вы продвинетесь дальше, за рамки простого получения информации о сетевом компьютере. Программа Finger, рассмотренная ниже, позволяет получать информацию о пользователях, подключенных к удаленному компьютеру (например, jamsa.com). Более того, Finger позволяет получать общедоступную информацию о конкретном лице, работающем в системе (например, о kcope@jamsa.com). В процессе разработки программы вы познакомитесь с сетевым протоколом Finger, предназначенным для получения конкретной информации о пользователях от удаленного компьютера. Для этого в программе создается сокет, затем он соединяется с удаленным компьютером. После этого программа производит обмен данными и получает информацию о работающих в системе пользователях. Усвоив механизм работы протокола Finger, вы получите представление о том, как связаться и воспользоваться услугами любой другой информационной службы Интернет. Разумеется, форматы сообщений и способы взаимодействия варьируются от протокола к протоколу, однако принцип остается тем же самым. Так что главное — уловить идею. А уловив идею, вы окажетесь на расстоянии вытянутой руки от того, чтобы начать разрабатывать собственные сетевые службы Интернет. Прочитав эту главу, вы овладеете следующими ключевыми понятиями: ♦ Каким образом протокол виртуального терминала делает не заметными различия между операционными системами. ♦ Как запросить и получить информацию из базы данных, описывающей сетевые службы. ♦ Как посылать запросы и получать ответы при помощи протокола Finger. ♦ Где искать и как воспользоваться самыми важными для Интернет информационными источниками — документами RFC (Request for Comments). Еще раз о сетевом уровне представления Вы знаете, что далеко не все компьютерные системы трактуют один и тот же набор символов (например, перевод каретки, перевод строки, символы табуляции и забоя) одинаково. В Unix, например, строчку текста принято заканчивать символом перевода каретки (CR), а в некоторых системах — переводом строки (LF). В DOS, как известно, строка оканчивается и переводом каретки, и переводом строки. Прервать выполняющуюся программу в некоторых системах можно нажатием комбинации Ctrl-С, а в некоторых — клавишей Esc. Другими словами, для одинаковых целей в различных ОС используются различные символы. Все компьютеры (точнее, операционные системы) выполняют похожие операции. Однако несовместимость управляющих символов может привести к тому, что корректно работающая на одном компьютере сетевая программа выдаст непредсказуемые результаты на другом. Хорошо спроектированная сеть обязана сама помогать программам устранять такие недоразумения. К задачам уровня представления относится устранение несовместимости между сетевыми компьютерами. Функции уровня представления, как правило, выполняются протоколами виртуального сетевого терминала. При этом с точки зрения сетевой программы дисплеи и принтеры всех удаленных компьютеров выглядят и управляются одинаково. Предположим, что сетевая программа шлет закодированные виртуальным протоколом данные по Интернет. Символы окончания строки в этих данных соответствуют требованиям протокола виртуального терминала. Протокол, например, может кодировать окончание строки комбинацией символов перевода каретки и перевода строки. Компьютер-получатель данных преобразует их из протокола виртуального терминала в собственный формат. То есть комбинация «перевод каретки-перевод строки» преобразуется в локальное представление, позволяющее правильно выдать текст на экран. Стек протоколов 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», сдвигающая курсор в крайнюю левую позицию следующей строки. Сама функция называется по-прежнему: «перевод каретки» (CR). В спецификации виртуального терминала указывается, какими символами нужно кодировать те или иные действия. То есть какие символы представляют перевод каретки, забой, очистку экрана и прочие специальные действия. Определив единый для всех сетевых программ стандарт, NVT тем самым скрыл различия в реализации этих функций в разных сетевых компьютерах. На рис. 10.1 показано, как NVT вписывается в схему сетевого соединения между двумя компьютерами в Интернет. Рис. 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 управляющих широко применяются в устройствах вывода данных, а некоторые — нет. Хорошо известен, например, символ с кодом ASCII 7 или, по-другому, BEL. Его появление в тексте приводит к генерации звукового сигнала. Некоторые из управляющих символов ASCII используются и в NVT. Они перечислены в табл. 10.1. Таблица 10.1. Управляющие ASCII-коды в представлении NVT Код управляющего символа NUL BEL Шестнадцатиричное значение Совершаемое действие 0x00 0x07 Не производит никакой операции. Звуковой (звонок) или визуальный сигнал. BS 0x08 Сдвиг влево на одну позицию (backspace). НТ 0x09 LF ОхОА VT ОхОВ FF ОхОС CR OxOD Сдвиг вправо на одну позицию горизонтальной табуляции. Сдвиг вниз на одну строку (перевод строки, line-feed). Сдвиг вниз на следующую позицию вертикальной табуляции. Перемещение на начало следующей страницы (formfeed). Сдвиг к левой границе текущей строки (возврат каретки, carriage-return). Кроме того, в NVT определена стандартная комбинация символов для обозначения конца строки: CRLF (перевод каретки и перевод строки). Как только пользователь нажимает клавишу Enter (или Return), NVT преобразует нажатие в символы CRLF. Определения символов NVT называются также NVT ASCII — они полностью описаны в спецификации протокола TELNET. NVT ASCII используется: многими сетевыми программами. Примечание: Кроме вышеописанных символов, NVT определяет и некоторые другие. Правда, они очень редко встречаются на практике. В RFC 854, «Спецификация протокола TELNET», NVT описывается как часть протокола TELNET. NVT обеспечивает стандартный сетевой интерфейс, подобный виртуальному сетевому протоколу, призванный скрыть различия между компьютерами в интерпретации таких символов, как перевод каретки, строки, маркеров конца строки и т. п. Для кодирования цифр, букв и знаков пунктуации NVT использует 7-битную кодировку. Из 33 управляющих символов ASCI! используются только восемь (они перечислены в табл. 10.1). Комбинация CRLF (перевод каретки и перевод строки) используется в качестве маркера конца строки. Набор ASCII, определенный в NVT, часто так и называется: NVT ASCII. Множество программ Интернет пользуются набором NVT ASCII при передаче сетевых данных. Учебная программа 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 // Необходим Winsock версии 1.1 #define PF_INET_LENGTH 4 // Длина адреса в протоколах // Интернет всегда равна // 4 байтам #define FINGER_QUERY "lonetech" // Настоящее имя пользователя // или имя для входа в систему #define DEFAULT_PROTOCOL 0 // Протокол не задан, поэтому // используем протокол "по // умолчанию" #define SEND_FLAGS 0 // Флаги для функции send() //не заданы #define RECV_FLAGS 0 // Флаги для функции recv() //не заданы int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow) { WSADATA wsaData; // Сведения о реализации // Winsock LPHOSTENT lpHostEnt; // Структура с информацией о // сетевом компьютере SOCKET nSocket; // Номер сокета, используемого //в данной программе SOCKADDR_IN sockAddr; // Структура адреса сокета LPSERVENT lpServEnt; // Структура с информацией о // сетевой службе // Официальный номер порта // протокола — 79 char szFingerlnfo[5000]; // Буфер для хранения // информации Finger char szFingerQuery[100]; // Буфер для хранения запроса // Finger int nCharSent; // Количество переданных // символов int nCharRecv; // Количество принятых символов int nConnect; // Результат соединения // (дескриптор) сокета short iFingerPort; if (WSAStartup(WINSOCK_VERSION, &wsaData)) MessageBox(NULL, "Could not load Windows Sockets DLL.", PROG_NAME, MB_OKIMB_ICONSTOP); else // Преобразуем имя хоста { lpHostEnt = gethostbyname(HOST_NAME); if (UpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OKIMB_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 lpServEnt = getservbyname("Finger", NULL); if (lpServEnt == NULL) iFingerPort = IPPORT_FINGER; // Официальный // порт протокола else iFingerPort = lpServEnt->s_port; // Определяем адрес сокета sockAddr.sin_family = AF_INET; // Семейство // адресов Интернет sockAddr.sin_port = iFingerPort; sockAddr.sin_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_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 send()!", PROG_NAME, MB_OKIMB_ICONSTOP); else // Принимаем информацию Finger { do { nCharRecv = recv(nSocket, (LPSTR)&szFingerInfо[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); MessageBox(NULL, szFingerlnfo, szFingerQuery, MB_OK|MB_ICONINFORMATION); } } } } } } WSACleanup(); // Программа освобождает занятые ресурсы //и завершается return NULL; } Константы Так же как и другие программы Winsock API, QFinger включает в себя файл winsock.h. После оператора include следуют операторы, задающие константы: #include "..\winsock.h" #define PROG_NAME "Simple Finger Query" #define HOST_NAME "cerfnet.com" // Может быть любым // (настоящим) именем // компьютера ttdefine WINSOCK_VERSION 0x0101 // Необходим Winsock версии 1.1 #define PF_INET_LENGTH 4 // Длина адреса в протоколах // Интернет всегда равна // 4 байтам #define FINGER_QUERY "lonetech" // Настоящее имя // пользователя или имя для // входа в систему #define DEFAULT_PROTOCOL 0 // Протокол не задан, // поэтому используем // протокол "по умолчанию" ttdefine SEND_FLAGS 0 // Флаги для функции send() //не заданы #define RECV_FLAGS 0 // Флаги для функции recv() //не заданы Константы PROG_NAME, HOST_NAME и WINSOCK_VERSION здесь играют ту же роль, что и в QLookup. HOST_NAME должна являться именем реально существующего в Интернет компьютера. В качестве имени пользователя QFinger передает серверу константу FINGER_QUERY. В нашем случае HOST_NAME задает имя компьютера «cerfnet.com». Константа FINGERQUERY задает пользователя по имени «lonetech». Вместо этих значений или идентификатора пользователя можно подставить любые другие — лишь бы они существовали в реальности. Константа DEFAULT_PROTOCOL, равная нулю, говорит о том, что нам нужен стандартный протокол Finger, то есть протокол по умолчанию. Работа некоторых функций Winsock контролируется при помощи специальных флагов, о которых вы узнаете позже. В программе QFinger они не используются, поэтому значения SEND_FLAGS и RECV_FLAGS равны нулю. Переменные В следующих строках исходного текста объявляются некоторые переменные и описывается функция WinMain: int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow) { WSADATA wsaData; // Сведения о реализации Winsock LPHOSTENT lpHostEnt; // Структура с информацией о // сетевом компьютере SOCKET nSocket; // Номер сокета, используемого // в данной программе SOCKADDR_IN sockAddr; // Структура адреса сокета LPSERVENT lpServEnt; // Структура с информацией о // сетевой службе short iFingerPort; // Официальный номер порта // протокола — 79 char szFingerlnfo[5000]; // Буфер для хранения // информации Finger char szFingerQuery[100]; // Буфер для хранения запроса // Finger int nCharSent; // Количество переданных // символов int nCharRecv; // Количество принятых символов int nConnect; // Результат соединения сокета Переменная wsaData, так же, как и в QLookup, содержит сведения о реализации Winsock, полученные при вызове WSAStartup. lpHostEnt также указывает на структуру данных с информацией об удаленном компьютере. Структура заполняется при вызове функции gethostbyname. В табл. 9.3 девятой главы приведены составляющие структуры данных об удаленном компьютере. Перед тем как создать и соединить сокет, из этой структуры необходимо извлечь IP-адрес компьютера. Переменная nSocket имеет тип SOCKET, определенный в winsock.h как беззнаковое целое. Назначение переменной типа SOCKET соответствует назначению дескриптора файла. Программа Winsock может создавать множество сокетов, подобно тому, как программа DOS или Windows может одновременно открывать множество файлов. Переменная типа SOCKET указывает на сокет, созданный нашей программой для обмена данными с сервером. В переменной iFingerPort хранится номер порта службы Finger. Переменная szFingerlnfo является вместилищем данных, полученных от удаленного компьютера. Переменная szFingerQuery служит буфером запроса к службе Finger. Переменные целого типа, nCharSent и nCharRecv подсчитывают количество байтов, переданное или полученное через сокет нашей программой. В переменной nConnect хранится результат операции по соединению сокета. QFinger проверяет ее значение, чтобы выяснить, было ли соединение успешным. Назначение переменных sockAddr и lpServEnt будет объяснено в следующих разделах. С самого начала Несколько первых операторов в QFINGER.CPP в точности повторяют операторы из QLookup из девятой главы: if (WSAStartup(WINSOCK_VERSION, &wsaData)) MessageBox(NULL, "Could not load Windows Sockets DLL.", PROG_NAME, MB_OKIMB_ICONSTOP); else // Преобразуем имя сетевого компьютера { lpHostEnt = gethostbyname(HOST_NAME); if (!lpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OKIMB_ICONSTOP); else { // Продолжаем выполнение QFinger } } WSACleanup(); // Программа освобождает занятые ресурсы и // завершается return NULL; В начале, как видим, вызывается WSAStartup. Ее задача — инициализировать модуль WINSOCK.DLL. Далее вызывается gethostbyname, чтобы получить из DNS информацию о сетевом компьютере, который нас интересует. Параметр gethostbyname — имя компьютера, а результатом при успешном выполнении является указатель на структуру (lpHostEnt), содержащую данные о компьютере. Если возвращаемый указатель равен NULL (запрос был неудачным), программа выведет соответствующее сообщение на дисплей. Поскольку остальные операторы программы заключены в фигурные скобки ({}) конструкции else, следующим после неудачного выполнения gethostbyname будет оператор WSACleanup, и программа корректно завершится. Вы помните, что WSACleanup освобождает ресурсы Winsock, связанные с вызывающей программой. WSACleanup должен вызываться всегда перед завершением программы. Создание сокета Если переменная-указатель lpHostEnt не равна NULL, QFinger вызывает функцию socket и создает новый сокет для сетевого соединения: if (!lpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OKIMB_ICONSTOP); else // Создаем сокет { nSocket = socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL); if (nSocket == INVALID_SOCKET) MessageBox(NULL, "Invalid socket!!", PROG_NAME, MB_OK|MB_ICONSTOP); Функция 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) сокет, обозначаемый как SOCK_RAW. Спецификация Winsock версии 1.1 не требует поддержки этого типа сокетов, поэтому он не обязательно присутствует во всех реализациях Winsock. Сетевая служба SOCK_STREAM обеспечивает надежную, двухстороннюю, ориентированную на соединение передачу данных. Транспортный протокол для этой службы — TCP. Служба SOCK_DGRAM, наоборот, ненадежна, поскольку использует датаграммы и ориентирована на протокол UDP. Если вы не хотите определять протокол, четвертый параметр функции socket равен нулю. При этом используется стандартный протокол или протокол по умолчанию. В случае QFinger четвертый параметр функции, DEFAULT_PROTOCOL, как раз и равен нулю. После того как сокет создан и программа получила его дескриптор, он настраивается на адрес удаленного компьютера. Первый шаг на этом пути — получение информации о сетевой службе из базы данных. В случае QFinger необходима информация о сетевой службе Finger. Что такое база данных по сетевым службам? В базе данных по сетевым службам хранится информация о службах типа Finger, Ftp, Mail, Telnet и многих других. Для каждой службы задается номер порта, доступные протоколы и псевдонимы (другие имена для той же самой службы). На персональных компьютерах эта информация, как правило, находится в текстовом файле формата ASCII под названием SERVICES. Файл SERVICES обычно находится в том же каталоге, что и WINSOCK.DLL. Переменная lpServEnt программы QFinger указывает на структуру с информацией о сетевой службе: LPSERVENT lpServEnt; // сетевой службе // Структура с информацией о Структура 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 s_proto Список псевдонимов данной сетевой службы. Номер порта протокола данной службы, равный 79 для Finger. Название протокола, работающего с этой службой, например TCP или UDP. При вызове функции getservbyname указываются два параметра — указатель на имя службы и указатель на название протокола. Указатель на название протокола может равняться NULL. В этом случае будет выбран протокол по умолчанию. В случае QFinger первый параметр указывает на строку «finger», а второй равен NULL — программе нужен стандартный протокол. Функция getservbyname исследует базу данных с информацией о сетевых службах. На персональных компьютерах это обычно ASCII-файл под названием SERVICES. Его фрагмент приводится ниже. Файл состоит из трех колонок. Первая содержит наименование сетевой службы, вторая определяет номер порта и протокол, а в третьей приведены соответствующие службе псевдонимы: # Network services, Internet style # aliases #name port/protocol 41 # 7/tcp echo echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users daytime 13/tcp dayt 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 nameserver 42/tcp name # resource locat # IEN 116 who is domain server domain mtp tftp rje finger link supdup hostnames sri-nic ns 43/tcp 53/tcp nicname nameserver 53/udp 57/tcp 69/udp 77/tcp 79/tcp 87/tcp 95/tcp 101/tcp nameserver рор2 рорЗ sunrpc sunrpc auth 109/tcp 110/tcp 111/tcp 111/udp 113/tcp # name-domain # deprecated netrjs ttylink hostname 105/tcp # usually from # ph name server postoffice2 postoffice portmapper portmapper authentication sftp 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_proto[] = "udp"; He все сетевые службы позволяют использовать оба протокола. Ftp, например, может выполняться только с протоколом TCP. Точно так же протокол TFTP (Trivial FTP, «простой протокол передачи файлов») выполняется только вместе с протоколом UDP. Если вы внимательнее посмотрите на файл 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"; Точно так же эквивалентны два этих вызова: lpServEnt = getservbyname("whois", NULL); lpServEnt = getservbyname("nicname", NULL); Оба они вернут следующие результаты: s_name[] = "whois"; s_aliases[] = "nicname"; s_port = 43; s_proto[] = "tcp"; База данных по сетевым службам База данных по сетевым службам содержит список часто встречающихся сетевых служб, таких как Ftp, Finger или Telnet. Для каждой службы задается официальный номер порта и ее транспортный протокол. Некоторые службы используют более одного протокола (TCP и UDP, например). Также в базе данных находятся псевдонимы сетевых служб. На персональных компьютерах, как правило, база данных находится в текстовом файле SERVICES в том же каталоге, что и WINSOCK.DLL. Для доступа к базе данных необходимо вызвать функцию getservbyname. Получение информации о сетевой службе Как показано ниже, QFinger вначале проверяет переменную lpHostEnt, указатель на структуру с информацией об удаленном компьютере. Если информация действительно получена, программа продолжает выполнение и пробует получить информацию о сетевой службе Finger, вызывая функцию getservbyname. Указатель на структуру с информацией о службе Finger хранится в переменной lpServEnt. Далее QFinger проверяет содержимое lpServEnt: if (!lpHostEnt) MessageBox(NULL, "Could not get IP address!", HOST_NAME, MB_OK|MB_ICONSTOP) ; else // Получаем информацию о службе Finger { lpServEnt = getservbyname("Finger", NULL); if (lpServEnt == NULL) iFingerPort = IPPORT_FINGER; // Используем // официальный номер порта else iFingerPort = lpServEnt->s_port; Если lpServEnt действителен (то есть не равен NULL), iFingerPort получает значение lpServEnt->s_port, то есть номера порта Finger из базы данных. Если lpServEnt равен NULL, номер порта (iFingerPort) принимается равным константе IPPORT_FINGER. В файле winsock.h определены номера портов для чаще всего встречающихся сетевых служб, в том числе и для Finger, номер порта которого равен 79: // Официальные номера портов различных сетевых служб #define IPPORT_ECHO 7 #define IPPORT_DISCARD 9 #define IPPORT_SYSTAT 11 #define IPPORT_DAYTIME 13 #define IPPORT_NETSTAT 15 #define IPPORT_FTP 21 #define IPPORT_TELNET 23 #define IPPORT_SMTP 25 #define IPPORT_TIMESERVER 37 #define IPPORT_NAMESERVER 42 #define IPPORT_WHOIS 43 #define IPPORT_MTP 57 #define IPPORT_TFTP 69 #define IPPORTJRJE 77 #define IPPORT_FINGER 7 9 #define IPPORT_TTYLINK 87 #define IPPORT_SUPDUP 95 Другими словами, QFinger пытается получить номер порта Finger из базы данных при помощи getservbyname. Если это ему не удается и lpServEnt равен 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_famil Назначение Тип адреса сокета. В случае TCP/IP всегда равен AF_INET. sin_port sin_addr Номер порта протокола. IP-адрес удаленного компьютера, записанный в структуре in addr Winsock. В настоящее время не используется и равен нулю. y sin_zero Адресная структура sockaddr_in содержит информацию об адресе в формате Windows Sockets. Адрес компьютера необходим всегда, когда вы обращаетесь к удаленному компьютеру. Он должен быть доступен всем программам Windows Sockets API, поскольку требуется при соединении сокета. IP-адрес Winsock содержится в элементе sin_addr адресной структуры сокета. Элемент sin_addr на самом деле находится в структуре in_addr. Если вы помните материал девятой главы, там сказано, что в структуре in_addr содержится объединение (union), служащее для размещения IP-адреса в трех разных форматах: как четыре 8-битных, как два 16-битных и как одно 32битное значение. Элемент sin_port просто содержит номер порта протокола. Как известно из пятой главы, номер порта по своему назначению похож на IP-адрес. Только связан он не с номером компьютера, а с номером протокола. Большинство протоколов имеют официально назначенные номера портов. Как мы уже писали, официальный номер порта протокола 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. Разработчики 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 типа SOCKADDR_IN. SOCKADDR_IN описывает структуру-адрес сокета Интернет. Следующие несколько операторов из QFINGER. CPP заносят необходимую для соединения сокета с удаленным компьютером информацию в переменную 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-адрес, извлеченный из структуры host-entry. Соединяем сокет Как только сокету присвоен адрес, он готов к соединению. Соединение происходит при участии функции 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 socket!!", PROG_NAME, MB_OKIMB_ICONSTOP); Если все прошло успешно, функция connect возвращает ноль. Результат connect отправляется в переменную nConnect, затем ее значение проверяется QFinger. Если значение nConnect свидетельствует о случившейся ошибке, программа завершается,, выдав предупреждающее сообщение на экран. Вы знаете, что при создании сокета функцией socket, необходимо указывать тип протокола. Если программе нужно надежное, двухстороннее соединение, указывается SOCK_STREAM, если нужен датаграммный сервис — указывается SOCKDGRAM. Функция socket возвращает дескриптор сокета. Функция connect проверяет переданный ей дескриптор, выясняя, с каким типом протоколов она имеет дело. Если выбран потоковый протокол (SOCK_STREAM), connect соединяется с удаленным компьютером. Для датаграммного сокета (SOCKDGRAM) это излишне, поэтому адрес пункта назначения не устанавливается. Если функция connect отработала успешно, сокет готов к передаче и приему данных от удаленного компьютера. Как уже неоднократно говорилось, чтобы получить доступ к различным сетевым службам, программе необходимо выполнить последовательность определенных действий. Причем эта последовательность одинакова для большинства программ. После того как вы усвоите, что нужно предпринять, чтобы получить доступ к сетевым службам, вам станет проще создавать свои собственные варианты протоколов и сетевого сервиса. Возможно, лучший способ узнать, что же все-таки требуется — пройти весь процесс задом наперед, начав с соединенного сокета. Другими словами, чтобы соединить сокет, ему нужно предоставить определенную информацию. Следовательно, нужно рассмотреть эту информацию подробнее и выяснить, откуда она берется. Ниже приведены шаги, которые нужно предпринять, чтобы соединить сокет с удаленным приложением. Шаги приведены в обратном порядке. Вы начинаете с соединенного сокета и следуете назад: 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 определено два таких флага: MSG_DONTROUTE и MSG_OOB. Флаг MSG_DONTROUTE указывает, что сообщение нельзя маршрутизировать. Другими словами, блок передаваемых с этим флагом данных не должен обрабатываться низлежащим сетевым уровнем, ответственным за маршрутизацию обычных сообщений. В спецификации Winsock указано, что этот флаг может игнорироваться реализациями Winsock. Поэтому вам его лучше не употреблять в целях иных, нежели простое тестирование. Флаг MSG_OOB требует, чтобы данные передавались (и, соответственно, принимались) как данные «вне диапазона». Как вам известно из пятой главы, данные вне диапазона или данные для неотложной обработки представляют собой мощный инструмент сетевого управления. Но, поскольку пока так и не удалось прояснить ситуацию относительно правильности их обработки, лучше избегать их употребления в ваших программах. Если флаг MSG_OOB установлен с датаграммным сокетом, функция вернет сообщение об ошибке. Функция send в программе QFinger вызывается с третьим параметром, равным SEND_FLAGS. Он, в свою очередь, равен нулю. Как уже отмечалось, сервер протокола Finger ожидает получить запрос в формате NVT ASCII с порта 79. Чтобы запросить информацию обо всех работающих в данный момент пользователях, достаточно передать запрос в виде пустой строки. Чтобы получить информацию о конкретном пользователе, нужно передать его имя, либо идентификатор. Каждый запрос должен заканчиваться маркером конца строки. В случае NVT ASCII, это будет комбинация CRLF, возврат каретки плюс перевод строки. Текст запроса QFinger находится в константе FINGER_QUERY. Константа может содержать любое имя или идентификатор, лишь бы он был известен удаленному компьютеру по имени 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 = sendfnSocket, szFingerQuery, lstrlen(szFingerQuery), SEND_FLAGS); if (nCharSent == SOCKET_ERROR) MessageBox(NULL, "Error occurred during send()!", PROG_NAME, MB_OK|MB_ICONSTOP); Если все прошло успешно, функция send возвращает количество переданных байтов. Если нет, значение функции будет равно SOCKET_ERROR. Результат send присваивается переменной nCharSent, и ее значение проверяется. Если оно равно SOCKET_ERROR, выводится соответствующее сообщение и программа заканчивает работу. Примечание: Благополучное окончание функции 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()!", 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); Рассмотрим одно очень важное соображение относительно длины буфера принимаемых данных. Поскольку служба 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, MB_OK|MB_ICONINFORMATION); } Если ошибки не было, в буфере szFingerQuery формируются результаты ответа Рис. 10.2. Панель сообщения QFinger сервера, а именно туда помещается идентификатор пользователя и имя сетевого компьютера, выводимые на панель сообщения Qfinger в формате nsername@hos-tname. На рис. 10.2 приведен образец этого сообщения. Что такое протокол Finger? Сервер Finger не нуждается в большом количестве информации. Если запрос состоит из пустой строки, он предполагает, что необходима информация обо всех работающих в данный момент в системе пользователях. Если строка-запрос не пуста, предполагается, что ее содержимое идентифицирует известного серверу пользователя, о котором запрашивается информация. В этом случае сервер выдаст всю общедоступную информацию о пользователях, имена или идентификаторы которых совпадают с содержимым запроса. Самое трудное в программе QFinger — создать и соединить сокет. Сама по себе информация Finger (и ее формат) устроена просто. Однако помимо Finger, на свете существует большое количество не столь простых протоколов. Как мы уже отмечали, иногда самый легкий способ что-либо изучить — это начать с конца и следовать к началу. Теперь, когда вы знаете, как работает программа Finger, вам будет легко понять, как устроен сам протокол. Подводя итоги В предыдущей главе мы изучали, как работать с системой имен доменов, DNS. В большинстве программ Winsock, до того как воспользоваться тем или иным протоколом, нужно вызвать функцию DNS, чтобы узнать адрес компьютера. Учебная программа QFinger демонстрирует, как это сделать эффективно. В этой главе вы узнали, как используется протокол Finger для получения информации об удаленных пользователях. Как вы позднее увидите, многие прикладные протоколы очень похожи на Finger. Хотя формат данных этих протоколов и различается, большинство шагов, предпринимаемых для работы с ними, одинаковы. Одинаковы и функции сетевого ввода-вывода, с которыми вы познакомились в процессе обсуждения QFinger. До того как продолжить чтение, проверьте, хорошо ли вы усвоили следующие ключевые понятия: * Виртуальный сетевой терминал (NVT) представляет данные в качестве семибитных символов, а для конца строки используется комбинация CRLF. * NVT используется большим количеством прикладных сетевых протоколов, например протоколом Finger. * База данных по сетевым службам на персональных компьютерах представлена файлом SERVICES формата ASCII. В нем хранится информация о часто встречающихся сетевых службах типа Finger, Mail, Ftp, Telnet и т. д. * База данных по сетевым службам содержит официальные номера портов и протоколы для каждой сетевой службы. * Протокол Finger предназначен для получения сведений обо всех находящихся в системе пользователях или о конкретном пользователе удаленного компьютера. * Чтобы послать запрос серверу Finger, необходимо соединить сокет с портом протокола номер 79 (официальный номер порта службы Finger) и послать запрос в формате NVT ASCII, заканчивающийся комбинацией CRLF. * Чтобы получить информацию обо всех пользователях указанного компьютера, строка-запрос должна быть пустой (и заканчиваться CRLF). * Чтобы получить информацию о конкретном пользователе, строказапрос должна содержать его идентификатор или имя (и заканчиваться CRLF).