Лекция № 5. Сетевая служба получения информации о

advertisement
Лекция № 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).
Download