Программа SLIST
Мы подготовили для вас программу, которая, пользуясь протоколом 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; } };