В этой главе мы рассмотрим процедуры получения списка активных серверов в сети и подключения пользователей к серверам. Вы научитесь составлять программы, выполняющие действия, аналогичные сетевым утилитам slist.exe, login.exe и attach.exe. Эти утилиты были подробно описаны нами в томе "Библиотеки системного программиста", посвященном установке аппаратного и программного обеспечения локальных сетей компьютеров.
Пользователь подключается к файл-серверу обычно при помощи утилит login.exe и attach.exe, которые поставляются в комплекте с сетевой операционной системой Novell NetWare. Программа login.exe подключает пользователя только к одному серверу. Если необходимо подключиться сразу к нескольким серверам, это можно сделать при помощи программы attach.exe.
Программа login.exe прежде всего создает канал с сервером, записывая данные о сервере в две внутренние таблицы сетевой оболочки - в таблицу номеров каналов (Connection ID Table) и в таблицу имен серверов (Server Name Table). Для этой процедуры не нужны имя пользователя и пароль, запрашиваемые утилитой login.exe. Заметим, что сама сетевая оболочка netx.exe при запуске создает канал с ближайшим сервером. Этот сервер с точки зрения сетевой оболочки становится первичным (Primary) сервером. После создания канала на рабочей станции появляется новый диск, отображаемый на сетевой каталог SYS:LOGIN. В этом каталоге есть две программы - slist.exe и login.exe, предназначенные соответственно для получения списка серверов и для подключения пользователя к серверу.
Затем утилита login.exe пытается подключить пользователя к серверу, проверяя имя пользователя и пароль. Если база данных объектов сервера содержит пользователя с введенным именем и паролем, пользователь получает доступ к файл-серверу.
Файл-сервер имеет таблицу каналов (File Server Connection Table) и таблицу паролей (Password Table). После того как сетевая оболочка создает канал с сервером, в таблицу каналов файл-сервера записывается номер канала, используемого сервером для связи с рабочей станцией. Разумеется, номер канала на рабочей станции не равен номеру канала на сервере. Рабочая станция может создать до 8 каналов с серверами, а сервер - до 250 (в зависимости от версии сетевой операционной системы).
Если подключение пользователя к файл-серверу завершилось успешно, Netware заносит идентификатор пользователя в таблицу паролей.
Программа attach.exe может создать новый канал с другим сервером, отличным от первичного, записав его номер в таблицу номеров каналов, а также подключить пользователя к еще одному серверу.
Подключив пользователя к файл-серверу, программа login.exe переходит в каталог SYS:PUBLIC и считывает системный файл автоматической настройки System Login Script, который находится в файле с именем net$log.dat. Программа login.exe интерпретирует все команды, записанные в этом файле.
Далее программа login.exe извлекает из базы данных BINDERY идентификатор пользователя и ищет в каталоге SYS:MAIL каталог с именем, совпадающим с десятичным представлением идентификатора пользователя, - личный каталог пользователя. В личном каталоге пользователя находится личный файл автоматической настройки Login Script с именем login (имя не имеет расширения). Если этот файл есть, программа login.exe считывает и интерпретирует его.
Только что мы рассмотрели упрощенный алгоритм работы программы login.exe, подключающей пользователя к файл-серверу. Если вам потребуется создать собственную программу подключения пользователей, вы должны выполнить все или некоторые из описанных выше действий.
Прежде чем обращаться к функциям сетевой оболочки рабочей станции, ваша программа должна убедиться, что эта оболочка загружена. Следует также узнать ее версию, так как состав функций сетевой оболочки может меняться от версии к версии.
Для проверки присутствия сетевой оболочки и определения ее версии проще всего воспользоваться функцией GetShellVersionInformation(), входящей в состав библиотеки NetWare C Interface. Приведем прототип указанной функции:
int GetShellVersionInformation(BYTE *MajorVersion, BYTE *MinorVersion, BYTE *RevisionLevel);
Тип BYTE описан в include-файле prolog.h, входящем в состав NetWare C Interface:
#define BYTE unsigned char
Если функция возвратила значение 0xFF, поля MajorVersion, MinorVersion, RevisionLevel будут содержать соответственно верхний (major) номер версии, нижний (minor) номер версии и номер изменения (revision). Если функция GetShellVersionInformation() вернула нулевое значение, версия сетевой оболочки слишком стара (номер версии меньше 2.1) и не поддерживает данную функцию.
Для того чтобы определить наличие сетевой оболочки, перед вызовом функции GetShellVersionInformation() запишите нулевое значение в переменную MajorVersion. Если после возврата из функции переменная MajorVersion не изменила своего значения, сетевая оболочка не загружена.
К сожалению, функция GetShellVersionInformation() не сохраняет содержимое регистра SI, поэтому у нас возникли проблемы с использованием этой функции в среде сетевой оболочки версии 3.26. Мы вышли из положения простым способом - сохранили содержимое этого регистра в стеке сами перед вызовом функции, а после возврата из функции восстановили его.
Приведем программу, определяющую версию сетевой оболочки (листинг 1):
// =================================================== // Листинг 1. Программа для обнаружения сетевой // оболочки и определения ее версии // Файл version\version.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h> #include <stdio.h> extern "C" int GetNetWareShellVersion(char *,char *, char *); void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); return; } printf("\nВерсия оболочки NetWare: %d.%d.%d\n",MajorVersion, MinorVersion, Revision); }
Приведенная программа составлена на языке программирования С++, поэтому внешняя функция GetNetWareShellVersion() должна быть описана как внешняя функция, использующая соглашения об именах и передаче параметров в стандарте С:
extern "C" int GetNetWareShellVersion(char *,char *, char *);
Если бы программа была составлена на языке С, можно было бы использовать описание этой функции, приведенное в одном из include-файлов библиотеки Netware C Interface. Для включения всех include-файлов библиотеки Netware C Interface вы должны добавить в вашу программу следующую строку:
#include <nit.h>
Для программ, составленных на языке С++, вам придется создавать собственные include-файлы на базе поставляемых вместе с библиотекой Netware C Interface.
Если у вас нет библиотеки Netware C Interface, вы можете узнать номер версии, вызвав непосредственно функцию 0xEA01 прерывания INT 21h.
Перед вызовом функции вам нужно соответствующим образом загрузить регистры:
На входе: |
AX |
= |
EA01h. |
ES:DI |
= |
Указатель на буфер размером 40 байт, в
который будет записано текстовое описание среды
рабочей станции. Это описание состоит из четырех
строк:- название операционной системы; |
|
На выходе: |
BH |
= |
Верхний (major) номер версии или 0, если
сетевая оболочка не загружена или ее версия
меньше 2.1. |
BL |
= |
Нижний (minor) номер версии. |
|
CL |
= |
Номер изменения (revision). |
Приведем вариант предыдущей программы, не использующий библиотеку NetWare C Interface (листинг 2). Кроме версии сетевой оболочки программа выводит содержимое буфера с текстовым описанием среды рабочей станции.
// ================================================================ // Листинг 2. Программа для обнаружения сетевой оболочки, определе- // ния ее версии и вывода строк описания среды рабочей станции // Файл version1\version1.cpp // // (C) A. Frolov, 1993 // ================================================================ #include <stdlib.h> #include <stdio.h> #include <dos.h> #include <string.h> void PrintBuffer(char*); void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; char Buffer[40]; union REGS regs; struct SREGS sregs; regs.x.ax = 0xea01; regs.x.di = FP_OFF(Buffer); sregs.es = FP_SEG(Buffer); intdosx(®s, ®s, &sregs); MajorVersion = regs.h.bh; MinorVersion = regs.h.bl; Revision = regs.h.cl; printf("\nВерсия оболочки NetWare: %d.%d.%d\n",MajorVersion, MinorVersion, Revision); printf("\nСтроки описания среды: "); PrintBuffer(Buffer); } void PrintBuffer(char *Buffer) { char *ptr; for(ptr = Buffer; *ptr != '\0';) { printf("'%s' ", ptr); ptr = ptr + strlen(ptr) + 1; } }
Если в сети имеется более одного сервера, то, прежде чем подключиться к файл-серверу, вам необходимо узнать его имя, заданное супервизором при запуске сервера. Для этого предназначена утилита slist.exe, которая находится в каталоге SYS:LOGIN и всегда доступна, если на рабочей станции загружена сетевая оболочка.
Однако при создании собственных утилит вам может потребоваться сделать меню активных серверов в сети, поэтому следующим этапом после определения версии сетевой оболочки будет определение списка активных файл-серверов.
Для поиска серверов вы можете воспользоваться диагностическим сервисом, описанным в предыдущем томе "Библиотеки системного программиста". Однако существует более удобный протокол, позволяющий средствами IPX найти все активные серверы и, что самое главное, определить их имена. Этот протокол называется протоколом объявления сервиса в сети (Service Advertising Protocol - SAP).
Использование протокола SAP основано на том факте, что все серверы в сети идентифицируют себя периодической посылкой пакета IPX специального типа - пакета объявления сервиса (Servise Advertising Packet). Кроме того, рабочие станции и серверы могут посылать пакеты запроса (Service Query) по адресу 0xFFFFFFFFFFFF, в ответ на который все серверы присылают запросившей станции пакеты объявления сервиса. Последнее обстоятельство роднит сервис SAP с диагностическим сервисом.
Для того чтобы найти все активные серверы в сети, ваша программа должна подготовить массив буферов и блоков ECB для приема IPX-пакетов объявления сервиса и послать по адресу 0xFFFFFFFFFFFF пакет запроса на сокет 0x452. Через некоторое время программа получит пакеты объявления сервиса. Просмотрев их, она сможет определить имена серверов, а также другую информацию об активных серверах.
Пакет запроса состоит из стандартного IPX-заголовка и блока данных, который может быть описан структурой следующего вида:
struct QPacket { unsigned QueryType; unsigned ServerType; };
Поле QueryType задает тип запроса и может содержать одно из двух значений: 1 или 3. Значение 1 соответствует общим запросам и позволяет получить информацию о всех серверах во всех сетях. Значение 3 позволяет найти ближайший сервер нужного типа.
Тип сервера, который нужно найти, задается в поле ServerType. Для определения значения, соответствующего файл-серверу, можно воспользоваться списком типов объектов, хранящихся в базе данных объектов сервера:
Значение |
Описание |
0 |
Не классифицируемый (неизвестный)
объект |
1 |
Пользователь |
2 |
Группа пользователей |
3 |
Очередь печати |
4 |
Файл-сервер |
5 |
Сервер заданий |
6 |
Шлюз |
7 |
Сервер печати |
8 |
Очередь для архивирования |
9 |
Сервер для архивирования |
A |
Очередь заданий |
B |
Администратор |
24 |
Сервер удаленного моста |
Номера типов объектов назначаются фирмой Novell; при необходимости вы можете создавать объекты своих типов, если получите в Novell номер специально для создаваемого вами объекта.
Для поиска всех файловых серверов вам надо указать в поле ServerType значение 4, а в поле QueryType - значение 1.
После посылки пакета запроса вы получите несколько пакетов объявления типа, состоящих из обычного IPX-заголовка и блока данных в следующем формате (описанном в файле sap.h, входящем в библиотеку NetWare C Interface):
typedef struct SAPHeader { WORD checksum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; WORD SAPPacketType; WORD serverType; BYTE serverName[48]; IPXAddress serverAddress; WORD interveningNetworks; } SAPHeader;
Тип WORD описан в include-файле prolog.h, входящем в состав NetWare C Interface:
#define WORD unsigned int
Поля checksum, length, transportControl, packetType, destination и source представляют собой заголовок IPX-пакета. Тип IPXAddress описывает сетевой адрес и также определен в файле sap.h:
typedef struct IPXAddress { BYTE network[4]; BYTE node[6]; WORD socket; } IPXAddress;
Все эти поля мы подробно описали в предыдущем томе "Библиотеки системного программиста".
Поле SAPPacketType содержит значение 2, если пакет пришел в ответ на общий запрос, или 4 - для ближайших запросов.
Поле serverType содержит номер типа сервера, описываемого данным пакетом. Если мы запрашивали информацию о файл-серверах, в этом поле должно быть значение 4.
В поле serverName расположена текстовая строка имени сервера, закрытая двоичным нулем. Именно это поле нам и нужно для получения списка имен активных серверов в сети.
Поле serverAddress является структурой, в которой находится сетевой адрес сервера. Именно по этому адресу сервер принимает запросы от сетевых оболочек рабочих станций. Вам не следует использовать в своих программах сокет, номер которого возвращается в поле serverAddress.socket, если вы не знаете точно, что и как собираетесь с ним делать.
Поле interveningNetworks отражает количество мостов, которые прошел пакет на своем пути от сервера до рабочей станции. Если значение в этом поле равно 1, сервер и рабочая станция находятся в одном сегменте сети.
Таким образом, самое интересное для нас в пакете объявления типа - это поля типа сервера serverType и имени сервера serverName. Для подключения к сети нам потребуются только имена файловых серверов.
В разделе "Программа SLIST" мы приведем исходный текст программы, выводящей на экран список активных серверов и другую интересную информацию о серверах, например серийные номера операционных систем. Для того чтобы вы смогли в ней разобраться, в мы расскажем вам в следующем разделе о каналах, создаваемых между сетевыми оболочками рабочих станций и серверами.
Каналы, создаваемые между сетевыми оболочками рабочих станций и файл-серверами, похожи на каналы, создаваемые протоколом SPX (или протоколом NETBIOS). Однако для повышения производительности эти каналы сделаны на базе протокола IPX, а не на базе протокола SPX, как это можно было бы предположить.
И сервер, и каждая рабочая станция имеют таблицы номеров каналов, в которых находятся различные характеристики партнеров, такие, как имена или сетевые адреса. Таблица каналов рабочей станции содержит 8 элементов, поэтому каждая рабочая станция может подключиться не более чем к 8 различным серверам. Размер таблицы каналов файл-сервера может меняться в зависимости от версии операционной системы Novell NetWare в пределах от 5 до 250. Этот размер определяет максимальное количество пользователей, которые могут подключиться к файл-серверу.
Подробно формат таблицы номеров каналов и других таблиц сетевой оболочки мы рассмотрим в разделе "Отображение дисков рабочей станции" этой главы. Сейчас для нас важно, что при создании канала с файл-сервером в таблице номеров каналов появляется новая запись. При уничтожении канала соответствующая запись также удаляется.
Для создания канала с файл-сервером следует использовать функцию AttachToFileServer(), определенную в библиотеке Novell NetWare C Interface следующим образом:
int AttachToFileServer(char *ServerName, WORD *ConnectionID);
Функции надо передать указатель на текстовую строку с именем файл-сервера и адрес переменной типа WORD, в которую будет записан номер созданного канала. При успешном создании канала функция возвращает нулевое значение, в противном случае - код ошибки:
Код ошибки |
Значение |
0xF8 |
Рабочая станция уже подключена к этому
серверу |
0xF9 |
Нет места в таблице номеров каналов
рабочей станции |
0xFA |
Нет места в таблице номеров каналов
сервера |
0xFC |
Сервера с указанным именем нет в сети |
0xFE |
База объектов сервера заблокирована |
0xFF |
Сервер не отвечает на запрос |
Для уничтожения канала вы можете использовать функцию DetachFromFileServer():
void DetachFromFileServer(WORD ConnectionID);
В качестве параметра вы должны передать функции номер канала, распределенного серверу, от которого вы собираетесь отключиться.
Таким образом, все, что вам нужно знать для
создания канала с файл-серве-
ром, - это имя файл-сервера. Пользователь может
ввести имя нужного файл-сервера, спросив его у
супервизора. Однако вы можете предоставить
пользователю меню активных файл-серверов. Для
получения меню можно воспользоваться методикой
обнаружения файл-серверов, изложенной нами ранее
и основанной на протоколе SAP. Соответствующая
программа, иллюстрирующая использование
SAP-протокола, приведена дальше в разделе
"Программа SLIST" этой главы.
Заметим, что сетевая оболочка сразу после своего запуска создает канал с ближайшим файл-сервером. Этот файл-сервер становится первичным (Primary).
Диски рабочей станции могут отображаться на каталоги файл-сервера. Если на рабочей станции текущим (т. е. используемым по умолчанию) является диск, отображенный на каталог файл-сервера, то этот файл-сервер называется текущим или используемым по умолчанию (Default).
Кроме того, существует понятие предпочтительного (Preferred) файл-сервера. Этот сервер должен быть задан явно специальной функцией.
Когда программа, запущенная на рабочей станции, обращается к файл-серверу, вначале проверяется, был ли задан предпочтительный файл-сервер. Если он задан не был, запрос адресуется текущему серверу. Если же текущий диск рабочей станции локальный (т. е. текущий сервер не определен), запрос адресуется первичному серверу.
В библиотеке NetWare C Interface есть несколько функций, позволяющих определить номера каналов первичного, текущего и предпочтительного сервера, задать предпочтительный сервер и изменить первичный сервер.
Функция GetPrimaryConnectionID() возвращает номер канала первичного сервера:
WORD GetPrimaryConnectionID(void);
Функция GetDefaultConnectionID() возвращает номер канала для текущего сервера:
WORD GetDefaultConnectionID(void);
Функция GetPreferredConnectionID() возвращает номер канала предпочтительного сервера или 0, если предпочтительный сервер не был задан.
Напомним, что номер канала соответствует индексу в таблице номеров каналов и лежит в пределах от 1 до 8.
Функция SetPreferredConnectionID() предназначена для определения предпочтительного сервера. Номер канала для сервера, который должен стать предпочтительным, передается функции в качестве параметра:
void SetPreferredConnectionID(BYTE ConnectionID);
Если у вас нет библиотеки NetWare C Interface, вы можете создать канал с сервером или удалить его с помощью функции F1h прерывания INT 21h.
Перед вызовом функции вам нужно загрузить регистры следующим образом:
На входе: |
AH |
= |
F1h; |
AL |
= |
0 - создать канал с файл-сервером,
использовать номер канала, заданный в регистре DL; |
|
DL |
= |
Номер канала. |
|
На выходе: |
AL |
= |
Код ошибки или 0, если операция выполнена
без ошибок. |
При помощи функции F0h прерывания INT 21h вы сможете определить первичный и текущий сервер, а также задать новый первичный или предпочтительный сервер:
На входе: |
AH |
= |
F0h; |
AL |
= |
0 - установить предпочтительный
файл-сервер, номер канала которого задан в
регистре DL; |
|
DL |
= |
Номер канала. |
|
На выходе: |
AL |
= |
Код ошибки или 0, если операция выполнена
без ошибок. |
Создав канал с файл-сервером, программа еще не получила доступ к томам сервера и другому сервису. Следующим после создания канала этапом должно быть подключение пользователя к файл-серверу.
Для подключения пользователя к файл-серверу вы должны использовать функцию LoginToFileServer() из библиотеки Novell NetWare C Interface:
int LoginToFileServer(char *ObjectName, WORD ObjectType, char *ObjectPassword);
Первый параметр ObjectName - указатель на имя пользователя, под которым его зарегистрировал супервизор сети или руководитель группы. Второй параметр определяет тип объекта. Для пользователя вы долны задать значение 1. Последний параметр - указатель на текстовую строку, содержащую пароль пользователя. Учтите, что и имя пользователя, и его пароль должны задаваться заглавными буквами.
Функция LoginToFileServer() выполняет достаточно сложную процедуру шифровки пароля, поэтому без использования библиотеки NetWare C Interface или аналогичных средств вы только с большим трудом сможете выполнить процедуру подключения к серверу без этой функции. Кстати, в отличие от других функций, исходный текст функции LoginToFileServer() и некоторых других не входит в комплект поставки библиотеки NetWare C Interface.
Есть функции и для отключения пользователя от одного или сразу ото всех файл-серверов.
С помощью функции Logout() вы можете отключиться сразу ото всех файл-серверов:
void Logout(void);
Функция LogoutFromFileServer() предназначена для отключения только от одного сервера, номер канала которого задается в качестве единственного параметра функции:
void LogoutFromFileServer(WORD ConnectionID);
В разделе "Программа LOG" мы приведем программу, которая умеет подключать пользователя к файл-серверу, а сейчас займемся тем, что определим список активных файл-серверов.
Мы подготовили для вас программу, которая, пользуясь протоколом SAP, определяет список активных серверов и запоминает имена серверов. Затем для всех активных серверов программа получает дополнительную информацию и выводит ее в стандартный поток вывода.
Программа создает объект класса SLIST. Конструктор этого объекта получает всю необходимую информацию, которая при помощи функции SLIST::PrintServersName(), определенной в классе SLIST, выводится в стандартный поток (листинг 3).
// ================================================================ // Листинг 3. Просмотр списка активных серверов и вывод в стандарт- // ный поток имен и другой информации об активных серверах // Файл slist!\slist.cpp // // (C) A. Frolov, 1993 // ================================================================ #include <stdlib.h> #include <stdio.h> #include <mem.h> #include <string.h> #include <dos.h> #include <direct.h> #include "sap.hpp" void main(void) { SLIST *ServerList; int ccode = 0; printf("\n*SLIST!*, v.1.0, (C) Фролов А.В., 1993\n"); // Создаем объект класса SLIST. Конструктор этого объекта // получает всю необходимую информацию о серверах и // записывает ее в область данных объекта ServerList = new SLIST(GENERAL_SERVICE); // Если при создании объекта были ошибки, завершаем // выполнение программы ccode = ServerList->Error(); if(ccode) { printf("Ошибка %d\n", ccode); return; } // Выводим список серверов printf("\nОбнаружены серверы:\n"); printf( "---------------------------------------------" "------------------------------\n"); ServerList->PrintServersName(); printf( "---------------------------------------------" "------------------------------\n"); }
Файл slist.cpp содержит определения функций-членов класса SLIST (листинг 4).
Конструктор SLIST() проверяет наличие сетевой оболочки, проверяет и запоминает тип запроса (получить сведения о ближайшем сервере или о всех серверах сети) и запоминает его. Затем конструктор инициализирует драйвер протокола IPX и открывает динамический короткоживущий сокет для работы с протоколом SAP. Далее в цикле создаются блоки ECB и ставятся в очередь на прием пакетов. Эти блоки ECB будут использованы для приема SAP-пакетов. После подготовки ECB конструктор посылает пакет запроса, ожидает примерно одну секунду и при помощи функций SLIST::GetServersName() и SLIST::GetServersInfo() получает и запоминает имена серверов и другую информацию.
Для работы с IPX-пакетами мы использовали функции из библиотеки NetWare C Interface. Назначение этих функций вам будет понятно из их названия, если вы прочитали предыдущий том "Библиотеки системного программиста".
Функция IPXInitialize() проверяет наличие драйвера протокола IPX и выполняет все инициализирующие действия, необходимые для использования протокола IPX.
Функция IPXOpenSocket() предназначена для открытия сокета. Первый параметр функции - указатель на переменную типа WORD, содержащую значение открываемого сокета или ноль, если надо получить динамический сокет. Байты в этой переменной расположены в обратном порядке, т. е. старший байт расположен по младшему адресу. Второй параметр функции IPXOpenSocket() определяет тип открываемого сокета - долгоживущий или короткоживущий. В нашем случае мы открываем динамический короткоживущий сокет.
После открытия сокета конструктор с помощью функции SLIST::ReceiveSAPPacket() подготавливает массив блоков ECB для приема ответных пакетов и, вызывая функцию IPXListenForPacket(), ставит эти блоки в очередь на прием. Функция IPXListenForPacket() имеет в качестве единственного параметра указатель на блок ECB.
Далее конструктор вызывает функцию SLIST::SendSAPPacket(), которая подготавливает блок ECB и заголовок IPX-пакета для SAP-запроса. При этом с помощью функции IPXGetInternetworkAddress() программа определяет свой собственный сетевой адрес. Функция IPXGetInternetworkAddress() имеет в качестве параметра указатель на структуру, в которую будет записан номер сети и сетевой адрес узла в сети.
Подготовив пакет, функция SLIST::SendSAPPacket() ставит его в очередь на передачу при помощи функции IPXSendPacket(), передавая ей в качестве параметра указатель на соответствующий блок ECB.
Когда пакет будет передан, конструктор ждет примерно одну секунду. В течение этого времени приходят ответные пакеты от серверов. После ожидания вызываются функции SLIST::GetServersName() и SLIST::GetServersInfo(), получающие соответсвенно имена серверов и дополнительную информацию.
Функция SLIST::GetServersName() переписывает имена откликнувшихся на запрос серверов из принятых SAP-пакетов во внутренний массив объекта класса SLIST.
Функция SLIST::GetServersInfo() выполняет более сложные действия.
Вначале с помощью функций GetPrimaryConnectionID() и GetDefaultConnectionID() она получает номера каналов первичного и текущего серверов, записывая их во внутренние переменные объекта класса SLIST. Затем запускается цикл по всем обнаруженным в сети серверам.
Внутри этого цикла для каждого сервера функция получает его номер канала при помощи функции GetConnectionID(). Если канала нет, рабочая станция создает его, подключаясь к серверу. Для подключения используется функция AttachToFileServer().
Затем сервер делается предпочтительным, для чего вызывается функция SetPreferredConnectionID(). Теперь все запросы будут идти к предпочтительному серверу. Внутри цикла мы по очереди будем делать все имеющиеся серверы предпочтительными и, направляя запросы, получать от серверов интересующую нас информацию.
Далее функция SLIST::GetServersInfo() вызывает функцию GetServerInformation(), которая записывает сведения о сервере в структуру ServerInfo. Первый параметр функции GetServerInformation() задает размер этой структуры, а второй является указателем на нее.
Перед возвратом функция SLIST::GetServersInfo() пытается получить серийный номер операционной системы Novell NetWare, работающей на предпочтительном файл-сервере, вызывая функцию GetNetworkSerialNumber(). Этой функции в качестве первого параметра необходимо передать указатель на переменную типа long, в качестве второго - указатель на переменную типа WORD. В первую переменную функция запишет серийный номер операционной системы, во вторую - серийный номер приложения, работающего на файл-сервере.
Надо заметить, что данная функция возвращает серийный номер только для тех серверов, к которым было выполнено подключение пользователя функцией LoginToFileServer(). Поэтому перед вызовом функции GetNetworkSerialNumber() мы записываем в поле серийного номера и номера приложения нулевое значение. Если содержимое этих полей останется нулевым, значит, пользователь не подключился к данному файл-серверу. Для сокращения размера листинга мы не проверяем код ошибки, возвращаемый функцией GetNetworkSerialNumber().
Функция SLIST::PrintServersName() в цикле для всех обнаруженных серверов выводит в стандартный поток вывода имя сервера, напротив которого указывается, является ли он первичным (Primary) или текущим (Default). Затем выводится версия Novell NetWare, взятая из полей netwareVersion и netwareSubVersion структуры ServerInfo. Для подключенных серверов выводится серийный номер и номер приложения.
Далее для всех серверов выводится номер канала, используемого сервером и записанного ранее в массив ConnID[].
После этого для каждого сервера выводится содержимое полей maxConnectionsSupported и connectionsInUse структуры ServerInfo, которые содержат максимальное количество каналов для сервера и количество каналов, используемых в данный момент.
Перед окончанием работы программы вызывается деструктор, который отменяет все ожидающие приема блоки ECB и закрывает динамический сокет. Для отмены блоков ECB используется функция IPXCancelEvent(), которой в качестве параметра передается указатель на отменяемый блок ECB. Сокет закрывается при помощи функции IPXCloseSocket(). Номер закрываемого сокета передается этой функции в качестве параметра.
// =================================================== // Листинг 4. Функции для программы SLIST.CPP // Файл slist!\sap.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h> #include <stdio.h> #include <mem.h> #include <string.h> #include <dos.h> #include "sap.hpp" // ==================================================== // Конструктор класса SLIST // ==================================================== SLIST::SLIST(int ServiceType) { // Проверяем наличие сетевой оболочки и определяем ее версию MajorVersion = 0; asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si // Если оболочка не загружена, завершаем работу // программы с сообщением об ошибке if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); errno = 0xff; return; } // Проверяем тип SAP-запроса if (ServiceType != 1 && ServiceType != 3) { errno = NOT_SUPPORTED; return; } // Запоминаем тип запроса QueryType = ServiceType; // Инициализируем драйвер протокола IPX IPXInitialize(); // Открываем короткоживущий динамический сокет SrcSocket = 0x00; errno = IPXOpenSocket(&SrcSocket, SHORT_LIVED); // Заполняем таблицу имен серверов нулями memset(ServerName,0,sizeof(ServerName)); // Подготавливаем блоки ECB для приема // пакетов от SAP-протокола for(int i=0;i<MAX_SERVERS;i++) { // Заполняем блок ECB ReceiveSAPPacket(&Query[i]); // Ставим в очередь на прием пакета IPXListenForPacket(&Query[i].theECB); } // Если не было ошибок, посылаем запрос if (!errno) { SendSAPPacket(); // Ждем примерно одну секунду sleep(1); // Переписываем имена серверов и другую информацию GetServersName(); GetServersInfo(); } } // ==================================================== // Деструктор класса SLIST // ==================================================== SLIST::~SLIST() { // Отменяем ожидающие блоки ECB for(int i=0;i<MAX_SERVERS;i++) { IPXCancelEvent(&Query[i].theECB); } // Закрываем сокет IPXCloseSocket(SrcSocket); } // ==================================================== // Посылка SAP-запроса // ==================================================== void SLIST::SendSAPPacket(void) { // Сбрасываем поле inUseFlag и ESRAddress, устанавливаем тип пакета 0 SendPacket.theECB.inUseFlag = 0; SendPacket.theECB.ESRAddress = 0; SendPacket.SAPq.packetType = 0; // SAP-пакет состоит из одного фрагмента. Записываем в ECB // количество фрагментов, адрес и размер буфера SendPacket.theECB.fragmentCount = 1; SendPacket.theECB.fragmentDescriptor[0].address = &SendPacket.SAPq; SendPacket.theECB.fragmentDescriptor[0].size = sizeof(SAPQueryPacket); // Записываем в ECB номер своего сокета SendPacket.theECB.socketNumber = SrcSocket; // Устанавливаем адрес назначения - все станции в текущей сети, // сокет SAP_SOCKET. Устанавливаем поле непосредственного адреса memset(SendPacket.SAPq.destination.network, '\x00', 4); memset(SendPacket.SAPq.destination.node, '\xFF', 6); SendPacket.SAPq.destination.socket = IntSwap(SAP_SOCKET); memset(SendPacket.theECB.immediateAddress, '\xFF', 6); // Устанавливаем свой адрес в заголовке запроса IPXGetInternetworkAddress(SendPacket.SAPq.source.network); SendPacket.SAPq.source.socket = IntSwap(SrcSocket); // Заполняем передаваемый пакет. Устанавливаем тип запроса // и тип сервера SendPacket.SAPq.queryType = IntSwap(QueryType); SendPacket.SAPq.serverType = IntSwap(0x0004); // Посылаем SAP-пакет IPXSendPacket(&SendPacket.theECB); // Ожидаем завершения процесса передачи пакета while (SendPacket.theECB.inUseFlag) IPXRelinquishControl(); // Сохраняем код возврата errno = SendPacket.theECB.completionCode; } // ==================================================== // Прием SAP-пакетов // ==================================================== void SLIST::ReceiveSAPPacket(RECEIVE_PACKET *Query) { // Сбрасываем поле inUseFlag и ESRAddress Query->theECB.inUseFlag = 0; Query->theECB.ESRAddress = 0; // Записываем в ECB количество фрагментов, адрес и размер буфера Query->theECB.fragmentCount = 1; Query->theECB.fragmentDescriptor[0].address = &Query->SB; Query->theECB.fragmentDescriptor[0].size = sizeof(Query->SB); // Устанавливаем в ECB свой номер сокета Query->theECB.socketNumber = SrcSocket; } // ==================================================== // Процедура переписывает имена серверов из тех // блоков ECB, для которых пришли пакеты // ==================================================== void SLIST::GetServersName(void) { for(int i=0,j=0; i<MAX_SERVERS; i++) { if(!Query[i].theECB.inUseFlag) { strcpy(ServerName[j],Query[i].SB.ServerName); j++; } } } // ==================================================== // Процедура получает информацию о серверах // ==================================================== void SLIST::GetServersInfo(void) { // Получаем номера каналов первичного сервера // и сервера по умолчанию PrimaryConnID = GetPrimaryConnectionID(); DefaultConnID = GetDefaultConnectionID(); // Цикл по всем обнаруженным в сети активным серверам for(int i=0; i<MAX_SERVERS; i++) { if(ServerName[i][0]) { // Получаем номер канала сервера errno = GetConnectionID(ServerName[i], &ConnID[i]); // Если канала нет, создаем его, подключаясь к серверу if(errno) { AttachToFileServer(ServerName[i], &ConnID[i]); } // Делаем текущий сервер предпочтительным, так как // именно к нему должны поступать запросы errno = SetPreferredConnectionID(ConnID[i]); // Получаем информацию о текущем сервере if(!errno) errno = GetServerInformation(sizeof(ServerInfo[i]), &ServerInfo[i]); // Получаем серийный номер и номер приложения SerialNumber[i]=ApplicationNumber[i]=0L; errno = GetNetworkSerialNumber(&SerialNumber[i], &ApplicationNumber[i]); errno = 0; } } } // ============================================================ // Процедура распечатывает имена и другую информацию о серверах // ============================================================ void SLIST::PrintServersName(void) { // Цикл по всем обнаруженным в сети активным серверам for(int i=0; i<MAX_SERVERS; i++) { if(ServerName[i][0]) { // Выводим имя сервера printf("%s",ServerInfo[i].serverName); // Если номер канала текущего сервера совпадает с // номером канала первичного сервера, выводим строку "\t[Primary]" if(ConnID[i] == PrimaryConnID) printf("\t[Primary]"); else printf("\t[ ]"); // Если номер канала текущего сервера совпадает с // номером канала сервера по умолчанию, выводим строку " [Default]" if(ConnID[i] == DefaultConnID) printf(" [Default]"); else printf(" [ ]"); // Выводим версию сетевой операционной системы, // работающей на текущем сервере printf(" v.%d.%d, ", ServerInfo[i].netwareVersion, ServerInfo[i].netwareSubVersion); // Для подключенных серверов выводим серийный // номер и номер приложения if(SerialNumber[i] != 0L) printf("s/n %08.8lX/%04.4X", SerialNumber[i], ApplicationNumber[i]); else printf("- Not Logged In -"); // Выводим номер канала, используемого для связи с текущим сервером printf("\tConnID: %d,",ConnID[i]); // Выводим максимальное число каналов, поддерживаемых // сервером, и количество используемых каналов printf(" (%d-%d)\n", ServerInfo[i].maxConnectionsSupported, ServerInfo[i].connectionsInUse); } } }
Файл sap.hpp содержит все определения констант и описания структур, необходимые для программы SLIST. В частности, в этом файле описан класс SLIST.
// =================================================== // Листинг 5. Include-файл для программы SLIST.CPP // Файл slist!\sap.hpp // // (C) A. Frolov, 1993 // =================================================== // Максимальное количество серверов, для которых выполняется опрос #define MAX_SERVERS 8 // Типы сервиса SAP #define GENERAL_SERVICE 1 #define NEAREST_SERVICE 3 #define NOT_SUPPORTED 1 // Короткоживущий сокет #define SHORT_LIVED 0x00 // Сокет для SAP-протокола #define SAP_SOCKET 0x452 // Тип пакета SAP #define SAP_PACKET_TYPE 2 // Определения используемых типов данных #define BYTE unsigned char #define WORD unsigned short // Сетевой адрес typedef struct IPXAddress { BYTE network[4]; BYTE node[6]; WORD socket; } IPXAddress; // Заголовок IPX-пакета typedef struct IPXHeader { WORD checkSum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; } IPXHeader; // Заголовок SAP-пакета typedef struct SAPHeader { WORD checksum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; WORD SAPPacketType; WORD serverType; BYTE serverName[48]; IPXAddress serverAddress; WORD interveningNetworks; } SAPHeader; // Пакет для посылки SAP-запроса typedef struct SAPQueryPacket { WORD checksum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; WORD queryType; WORD serverType; } SAPQueryPacket; // Структуры для описания блока ECB typedef struct ECBFragment { void far *address; WORD size; } ECBFragment; typedef struct ECB { void far *linkAddress; void (far *ESRAddress)(); BYTE inUseFlag; BYTE completionCode; WORD socketNumber; BYTE IPXWorkspace[4]; BYTE driverWorkspace[12]; BYTE immediateAddress[6]; WORD fragmentCount; ECBFragment fragmentDescriptor[2]; } ECB; // SAP-пакет typedef struct { IPXHeader Header; WORD ResponseType; WORD ServerType; BYTE ServerName[48]; BYTE Network[4]; BYTE Node[6]; WORD Socket; WORD InterveningNetworks; } SAP; // Структура для передачи SAP-пакета typedef struct { ECB theECB; SAPQueryPacket SAPq; } SEND_PACKET; // Структура для приема SAP-пакета typedef struct { ECB theECB; SAP SB; } RECEIVE_PACKET; // Информация о файл-сервере typedef struct { char serverName[48]; BYTE netwareVersion; BYTE netwareSubVersion; WORD maxConnectionsSupported; WORD connectionsInUse; WORD maxVolumesSupported; BYTE revisionLevel; BYTE SFTLevel; BYTE TTSLevel; WORD peakConnectionsUsed; BYTE accountingVersion; BYTE VAPversion; BYTE queingVersion; BYTE printServerVersion; BYTE virtualConsoleVersion; BYTE securityRestrictionLevel; BYTE internetBridgeSupport; } FILE_SERV_INFO; // Описания функций библиотеки NetWare C Interface extern "C" int IPXInitialize(void); extern "C" int IPXOpenSocket(WORD *, BYTE); extern "C" int IPXListenForPacket(ECB *); extern "C" int IPXCancelEvent(ECB *); extern "C" int IPXCloseSocket(WORD); extern "C" WORD IntSwap(WORD); extern "C" void IPXGetInternetworkAddress(BYTE *); extern "C" void IPXSendPacket(ECB *); extern "C" void IPXRelinquishControl(void); extern "C" IPXGetLocalTarget(BYTE *, BYTE *, int*); extern "C" WORD IPXGetIntervalMarker(void); extern "C" long LongSwap(long); extern "C" int AttachToFileServer(char *, WORD *); extern "C" int SetPrimaryConnectionID(int); extern "C" int GetServerInformation(int, FILE_SERV_INFO *); extern "C" WORD GetPreferredConnectionID(void); extern "C" WORD GetPrimaryConnectionID(void); extern "C" WORD GetDefaultConnectionID(void); extern "C" int SetPreferredConnectionID(WORD); extern "C" int GetConnectionID(char *, WORD *); extern "C" void DetachFromFileServer(WORD); extern "C" int GetNetWareShellVersion(BYTE *,BYTE *, BYTE *); extern "C" int IsConnectionIDInUse(WORD); extern "C" int GetNetworkSerialNumber(long *, int*); // Класс SLIST class SLIST { private: WORD QueryType; // тип запроса WORD SrcSocket; // сокет // Массив для приема SAP-пакетов RECEIVE_PACKET Query[MAX_SERVERS]; // Передаваемый SAP-пакет SEND_PACKET SendPacket; // Таблицы имен файл-серверов, серийных // номеров и номеров приложений char ServerName[MAX_SERVERS][48]; long SerialNumber[MAX_SERVERS]; int ApplicationNumber[MAX_SERVERS]; // Таблица информации о файл-серверах FILE_SERV_INFO ServerInfo[MAX_SERVERS]; // Таблица номеров каналов файл-серверов WORD ConnID[MAX_SERVERS]; // Функции для приема и передачи SAP-пакетов void ReceiveSAPPacket(RECEIVE_PACKET *Query); void SendSAPPacket(void); // Функции для получения имен файл-серверов и // другой информации о файл-серверах void GetServersName(void); void GetServersInfo(void); public: int errno; // код ошибки WORD PreferredConnID; // предпочтительный сервер WORD PrimaryConnID; // первичный сервер WORD DefaultConnID; // сервер по умолчанию BYTE MajorVersion; // верхний номер версии BYTE MinorVersion; // нижний номер версии BYTE Revision; // номер изменений SLIST(int); // конструктор ~SLIST(); // деструктор // Функция для вывода имен серверов void PrintServersName(void); // Проверка ошибок int Error(void) { return errno; } };
В этом разделе мы приведем исходный текст программы, выполняющей подключение пользователя к файл-серверу. Возможности этой программы ограничены по сравнению со стандартной утилитой login.exe: она, например, не выполняет интерпретацию файлов Login Script и System Login Script. После подключения к файл-серверу диск "S:" рабочей станции отображается на том SYS:. Вы можете использовать нашу программу как прототип собственной процедуры подключения к файл-серверу.
После проверки присутствия сетевой оболочки программа LOG с помощью функции GetConnectionNumber() получает номер канала текущего файл-сервера и затем, вызвав функцию GetFileServerName(), определяет имя текущего файл-сервера. Имя и номер канала текущего сервера выводятся в стандартный поток вывода.
Далее программа запрашивает имя сервера, имя пользователя и его пароль, при помощи функции AttachToFileServer() создает канал с указанным файл-сервером. Если канал уже есть или его удалось создать, новый сервер делается предпочтительным, для чего вызывается функция SetPreferredConnectionID().
Затем вызывается функция LoginToFileServer(). Она пытается подключить пользователя к предпочтительному серверу.
После подключения программа с помощью функции CheckConsolePrivileges() проверяет, имеет ли данный пользователь права оператора консоли, и выводит соответствующее сообщение.
Для того чтобы получить информацию о сервере, к которому только что подключился пользователь, программа LOG вызывает функцию GetFileServerDescriptionStrings(), которая записывает в четыре буфера имя фирмы-изготовителя, изменения и дату изменений, права на сетевую операционную систему. Содержимое всех этих буферов выводится в стандартный поток вывода.
Затем вызывается функция GetServerInformation(). С ее помощью определяется максимальное количество пользователей для данного сервера.
Так как мы только что подключились к файл-серверу, он должен стать первичным, поэтому на следующем шаге программа LOG вызывает функцию SetPrimaryConnectionID() и делает новый сервер первичным.
Подключившись к файл-серверу, вы еще не имеете доступа к его томам. Для того чтобы вы могли работать с дисками файл-сервера, вам необходимо отобразить один или несколько локальных дисков на сетевые каталоги. В нашей программе мы отображаем диск "S:" на корневой каталог тома SYS: нового первичного сервера. Для этого мы вызываем функцию AllocPermanentDirectoryHandle(). Эту функцию, а также все, что связано с дисками сервера, мы рассмотрим в следующей главе.
// =================================================== // Листинг 6. Подключение к серверу // Файл log\log.c // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h> #include <stdio.h> #include <string.h> #include "nit.h" // include-файлы из библиоткеи #include "niterror.h" // NetWare C Interface // Эта функция не описана в include-файлах // библиотеки NetWare C Interface, поэтому опишем ее сами. void GetServerInformation(int, FILE_SERV_INFO*); void main(void) { int ccode; char ServerName[48]; char UserName[48]; char Password[128]; WORD ConnID, ConnNumber; char companyName[80], revision[80]; char revisionDate[24], copyrightNotice[80]; FILE_SERV_INFO serverInfo; BYTE newDirectoryHandle, effectiveRightsMask; char driveLetter; char MajorVersion=0; char MinorVersion=0; char Revision=0; printf("NetWare Login, (C) Фролов А.В., 1993\n"); asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); return; } // Получаем номер канала, используемого сервером // по умолчанию (default) для связи с рабочей станцией, на // которой была запущена эта программа ConnNumber = GetConnectionNumber(); // Получаем имя файл-сервера, используемого по умолчанию (default) GetFileServerName(0, ServerName); // Выводим имя и номер канала для // сервера, используемого по умолчанию if(ConnNumber) printf("Сервер по умолчанию '%s', ConnNumber=%04.4X\n", ServerName, ConnNumber); // Вводим имя сервера, имя пользователя и пароль. // Преобразуем все введенные буквы в заглавные. printf("\nВведите имя сервера: "); gets(ServerName); strupr(ServerName); printf("\nВведите ваше имя: "); gets(UserName); strupr(UserName); printf("\nВведите пароль: "); gets(Password); strupr(Password); // Создаем канал с сервером ccode = AttachToFileServer(ServerName, &ConnID); // Если канал удалось создать или он уже был создан раньше, // выводим имя сервера и номер канала, используемого // на рабочей станции для идентификации сервера. if(ccode == 0 || ccode == ALREADY_ATTACHED_TO_SERVER) { printf("\nServerName='%s', ServerID=%04.4X", ServerName, ConnID); // Делаем данный сервер предпочтительным для того, // чтобы все запросы направлялись к нему в первую очередь SetPreferredConnectionID(ConnID); // Подключаем пользователя к файл-серверу ccode = LoginToFileServer(UserName,OT_USER,Password); if(!ccode) { // Если подключение произошло успешно, проверяем, есть ли // у подключившегося пользователя права оператора консоли if(!CheckConsolePrivileges()) printf("Вы оператор консоли\n"); // Получаем строки описания сервера и выводим их // в стандартный поток GetFileServerDescriptionStrings(companyName, revision, revisionDate, copyrightNotice); printf("Описание сервера:\n%s\n%s\n\n%s\n%s\n", companyName,revision, revisionDate, copyrightNotice); // Получаем информацию о сервере, выводим максимальное количество // пользователей, которые могут подключиться к // данному файл-серверу. GetServerInformation(sizeof(serverInfo), &serverInfo); printf("Версия на %d пользователей\n", serverInfo.maxConnectionsSupported); // Делаем данный сервер первичным. SetPrimaryConnectionID(ConnID); // Отображаем диск S: рабочей станции на // корневой каталог тома SYS: сервера driveLetter = 'S'; ccode = AllocPermanentDirectoryHandle(0,"SYS:\\", driveLetter, &newDirectoryHandle,&effectiveRightsMask); printf("Диск отображен, код CCode = %d\n",ccode); } } else { printf("Ошибка при подключении: %04.4X\n",ccode); return; } }