Локальные сети персональных компьютеров Использование протоколов IPX, SPX, NETBIOS

Пример программы


Приведем пример программы, которая демонстрирует способ определения конфигурации текущей сети, т. е. той сети, в которой работает данная программа. Программа создает 20 блоков ECB для приема пакетов конфигурации от рабочих станций, ставит их в очередь на прием пакетов и посылает диагностический пакет в текущую сеть по адресу FFFFFFFFFFFFh. Затем после небольшой задержки программа анализирует блоки ECB, поставленные в очередь на прием. Если был принят пакет конфигурации, он расшифровывается и в текстовом виде выводится в стандартный выходной поток.

Приводимая ниже программа - первая программа в серии "Библиотека системного программиста", составленная на языке С++. В ней мы использовали только некоторые возможности языка С++. Для более глубокого понимания объектно-ориентированного подхода в программировании вам необходимо ознакомиться с литературой, список которой приведен в конце книги.

В функции main() создается объект класса IPX_CLIENT, который по своим функциям является клиентом. В нашем случае задача клиента - послать диагностический запрос всем станциям сети и получить от них пакет конфигурации. Можно считать, что программа диагностики, работающая на каждой станции, является сервером. Принимая запросы от клиентов на диагностическом сокете, она посылает им в ответ пакеты конфигурации.

При создании объекта класса IPX_CLIENT вызывается конструктор, выполняющий все необходимые инициализирующие действия. Конструктор инициализирует драйвер IPX, получает его точку входа и открывает динамический сокет. Соответствующий деструктор автоматически закрывает полученный сокет при завершении работы программы.

Описание класса IPX_CLIENT находится в файле ipx.hpp (см. ниже).

После того как отработает конструктор объекта IPX_CLIENT, для созданного объекта вызывается функция go(), которая и выполняет все необходимые действия. В теле функции определен массив ECB*RxECB[20] из 20 указателей на объекты класса ECB. Эти объекты используются для приема пакетов конфигурации.
Кроме того, определен один объект ECB TxECB для посылки пакета с диагностическим запросом.
Программа в цикле создает 20 объектов класса ECB и с помощью функции ListenForPacket() ставит их в очередь на прием пакетов. Затем программа посылает в сеть пакет с диагностическим запросом и ждет одну секунду. За это время станции сети присылают пакеты конфигурации.
Полученные пакеты конфигурации выводятся в стандартный поток функцией PrintDiagnostics(), определенной в классе ECB. Эта функция выводит для каждой ответившей станции версию используемой диагностики, номер сокета для работы с SPX-диагностикой (не описана в нашей книге), количество программных компонентов, работающих на станции, номер сети и сетевой адрес станции (узла). Для файл-серверов и мостов дополнительно выводится количество подключенных к ним сетей. Для каждой сети выводится ее номер и сетевой адрес соответствующего адаптера.
Для упрощения программы мы ограничились диагностикой только одной сети. Кроме того, мы послали только один диагностический запрос, в котором не исключали ни одной станции. Если вам нужно определить конфигурацию всей сети, вам надо сделать следующее.
Во-первых, после приема пакетов конфигурации запишите адреса ответивших станций в список станций, исключаемых из диагностического запроса. Не забудьте проставить количество исключаемых станций. Затем выполните повторную передачу диагностического запроса. Выполняйте описанную процедуру до тех пор, пока в ответ на диагностический запрос не будет передан ни один пакет конфигурации. В этом случае адреса всех станций, имеющихся в текущей сети, будут записаны в список станций, исключаемых из диагностического запроса.
Во-вторых, выполните анализ пришедших пакетов конфигурации. Найдите пакеты, которые пришли от файл-серверов и мостов. Определите номера сетей, подключенных к ним. Затем выполните предыдущую процедуру многократной посылки диагностических пакетов в остальные сети. Для этого при передаче диагностического пакета в заголовке укажите номер сети, которую вы желаете проверить.





В качестве сетевого адреса станции используйте значение FFFFFFFFFFFFh. В блоке ECB в поле непосредственного адреса укажите сетевой адрес моста, через который можно получить доступ в исследуемую сеть.
А теперь приведем текст основной программы (листинг 9):
// =================================================== // Листинг 9. Вызов диагностики и определение // конфигурации текущей сети // // Файл ipxdiagn.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.hpp"
// Вход в программу. // Создаем объект - программу-клиент. Затем запускаем ее.
void main(void) { IPX_CLIENT NetView; NetView.Go(); }
// Функция определяет и распечатывает конфигурацию текущей сети.
void IPX_CLIENT::Go(void) {
// Создаем 20 ECB для приема ответов от станций
ECB *RxECB[20];
// Создаем ECB для передачи диагностического запроса.
ECB TxECB(this->Socket, 0x456);
// Ставим заказанные ECB в очередь на прием пакетов.
for(int i=0; i<20; i++) { RxECB[i] = new ECB(this->Socket); RxECB[i]->ListenForPacket(); }
// Посылаем диагностический пакет всем станциям текущей сети.
TxECB.SendPacket();
printf("*NetView* v1.0, (C) Фролов А.В., 1993\n" "Подождите немного...\n\n");
// Ждем примерно одну секунду
sleep(1);
// Распечатываем конфигурацию сети
printf("Конфигурация сети:\n\n"); printf("Версия\tСокет\tКомпоненты\tСеть\t\tУзел\n"); printf("------\t-----\t----------\t----\t\t----\n");
for(i=0; i<20; i++) { RxECB[i]->PrintDiagnostics(); } }
Файл ipx.hpp содержит определения классов для приведенной выше программы (листинг 10):
// =================================================== // Листинг 10. Include-файл для работы с IPX // Файл ipx.hpp // // (C) A. Frolov, 1993 // ===================================================
#include <mem.h> #include <dos.h>


// ----------------------- // Команды интерфейса IPX // -----------------------
#define IPX_CMD_OPEN_SOCKET 0x00 #define IPX_CMD_CLOSE_SOCKET 0x01 #define IPX_CMD_GET_LOCAL_TARGET 0x02 #define IPX_CMD_SEND_PACKET 0x03 #define IPX_CMD_LISTEN_FOR_PACKET 0x04 #define IPX_CMD_SCHEDULE_IPX_EVENT 0x05 #define IPX_CMD_CANCEL_EVENT 0x06 #define IPX_CMD_GET_INTERVAL_MARKER 0x08 #define IPX_CMD_GET_INTERNETWORK_ADDRESS 0x09 #define IPX_CMD_RELINQUISH_CONTROL 0x0a #define IPX_CMD_DISCONNECT_FROM_TARGET 0x0b
// ----------------------- // Коды ошибок // -----------------------
#define NO_ERRORS 0 #define ERR_NO_IPX 1 #define ERR_NO_SPX 2 #define NO_LOGGED_ON 3 #define UNKNOWN_ERROR 0xff
// ----------------------- // Константы // -----------------------
#define SHORT_LIVED 0 #define LONG_LIVED 0xff #define IPX_DATA_PACKET_MAXSIZE 546
// Максимальный размер буфера данных
#define BUFFER_SIZE 512
// Внешние процедуры для инициализации и вызова драйвера IPX/SPX
extern "C" void far ipxspx_entry(void far *ptr); extern "C" int ipx_init(void); extern unsigned IntSwap(unsigned i);
void IPXRelinquishControl(void);
// Структура для вызова драйвера IPX/SPX
struct IPXSPX_REGS { unsigned int ax; unsigned int bx; unsigned int cx; unsigned int dx; unsigned int si; unsigned int di; unsigned int es; };
// Класс динамических сокетов
class DYNAMIX_SOCKET { public: unsigned errno; unsigned Socket; struct IPXSPX_REGS iregs;
// Конструктор динамического сокета. // Открывает сокет и запоминает его номер.
DYNAMIX_SOCKET() { iregs.bx = IPX_CMD_OPEN_SOCKET; iregs.dx = 0; iregs.ax = 0; ipxspx_entry( (void far *)&iregs ); Socket = iregs.dx; errno = iregs.ax; };
// Деструктор. Закрывает ранее открытый сокет.
~DYNAMIX_SOCKET() { iregs.bx = IPX_CMD_CLOSE_SOCKET; iregs.dx = Socket; ipxspx_entry( (void far *)&iregs ); };
};
// Класс программ-клиентов IPX
class IPX_CLIENT { public:
unsigned errno;
// Сокет, с которым работает программа-клиент
DYNAMIX_SOCKET *Socket;


// Конструктор. Выполняет инициализацию клиента: // инициализирует драйвер IPX и открывает динамический сокет. IPX_CLIENT() { if(ipx_init() != 0xff) { errno = 0xff; return; } Socket = new DYNAMIX_SOCKET; } // Деструктор. Автоматически закрывает // сокет при завершении работы программы.
~IPX_CLIENT() { delete Socket; }
// Функция, определяющая конфигурацию сети
void Go(void); };
// Класс заголовков IPX-пакетов.
struct IPX_HEADER {
// Структура, описывающая заголовок
struct _IPX_HEADER { unsigned int Checksum; unsigned int Length; unsigned char TransportControl; unsigned char PacketType; unsigned char DestNetwork[4]; unsigned char DestNode[6]; unsigned int DestSocket; unsigned char SourceNetwork[4]; unsigned char SourceNode[6]; unsigned int SourceSocket; } _ipx_header;
// Конструктор. Записывает в заголовок тип пакета, // нулевой номер сети, в которую будет отправлен пакет, // адрес 0xFFFFFFFFFFFF в качестве адреса назначения, // номера сокетов адресата и отправителя пакета,
IPX_HEADER(unsigned Socket, unsigned SrcSocket) { _ipx_header.PacketType = 4; memset(_ipx_header.DestNetwork, 0, 4); memset(_ipx_header.DestNode, 0xff, 6); _ipx_header.DestSocket = Socket; _ipx_header.SourceSocket = SrcSocket; }
// Конструктор. Записывает в заголовок тип пакета, // нулевой номер сети, в которую будет отправлен пакет, // адрес 0xFFFFFFFFFFFF в качестве адреса назначения.
IPX_HEADER() { _ipx_header.PacketType = 4; memset(_ipx_header.DestNetwork, 0, 4); memset(_ipx_header.DestNode, 0xff, 6); } };
// Класс блоков ECB.
struct ECB {
// Сам блок ECB в стандарте IPX/SPX.
struct _ECB { void far *Link; void far (*ESRAddress)(void); unsigned char InUse; unsigned char CCode; unsigned int Socket; unsigned int ConnectionId; unsigned int RrestOfWorkspace; unsigned char DriverWorkspace[12]; unsigned char ImmAddress[6]; unsigned int FragmentCnt; struct { void far *Address; unsigned int Size; } Packet[2]; } _ecb;
// Указатель на заголовок пакета, связанного с данным ECB.
struct IPX_HEADER *IPXHeader;


// Структура для приема ответа от станции // после посылки диагностического пакета.
struct Reply { unsigned char MajVer; unsigned char MinVer; unsigned Socket; unsigned char NumberOfComponents; unsigned char Buffer[512]; } Rep;
// Структура для хранения диагностического пакета.
struct DiagnRequest { unsigned char Exclusions; unsigned char List[80][6]; } DReq;
struct IPXSPX_REGS iregs;
// Конструктор. Создается заголовок пакета, // в блок ECB записывается номер сокета, используемого клиентом, // инициализируются счетчик фрагментов и дескрипторы фрагментов. // В качестве непосредственного адреса указывается // адрес 0xFFFFFFFFFFFF.
ECB(DYNAMIX_SOCKET *Socket) { IPXHeader = new IPX_HEADER; memset(&_ecb, 0, sizeof(_ecb)); _ecb.Socket = Socket->Socket; _ecb.FragmentCnt = 2; _ecb.Packet[0].Address = &(IPXHeader->_ipx_header); _ecb.Packet[0].Size = 30; _ecb.Packet[1].Address = &Rep; _ecb.Packet[1].Size = sizeof(Rep); memset(_ecb.ImmAddress, 0xff, 6); }
// Конструктор. Создается заголовок пакета, в блок ECB записывается // номер сокета, используемого клиентом, а также номер сокета // адресата, инициализируются счетчик фрагментов и дескрипторы // фрагментов. В качестве непосредственного адреса указывается // адрес 0xFFFFFFFFFFFF.
ECB(DYNAMIX_SOCKET *Socket, unsigned DstSocket) {
IPXHeader = new IPX_HEADER(IntSwap(DstSocket), Socket->Socket);
// Запрос адресуется всем станциям без исключения.
DReq.Exclusions = 0;
memset(&_ecb, 0, sizeof(_ecb)); _ecb.Socket = Socket->Socket; _ecb.FragmentCnt = 2; _ecb.Packet[0].Address = &(IPXHeader->_ipx_header); _ecb.Packet[0].Size = 30; _ecb.Packet[1].Address = &DReq; _ecb.Packet[1].Size = sizeof(DReq); memset(_ecb.ImmAddress, 0xff, 6); }
// Прием IPX-пакета.
void ListenForPacket(void) { iregs.es = FP_SEG((void far*)&_ecb); iregs.si = FP_OFF((void far*)&_ecb); iregs.bx = IPX_CMD_LISTEN_FOR_PACKET; ipxspx_entry( (void far *)&iregs ); }
// Передача IPX-пакета.
void SendPacket(void) { iregs.es = FP_SEG((void far*)&_ecb); iregs.si = FP_OFF((void far*)&_ecb); iregs.bx = IPX_CMD_SEND_PACKET; ipxspx_entry( (void far *)&iregs ); }


// Распечатать принятый пакет конфигурации
void PrintDiagnostics(void); };
В файл ipx.cpp ( листинг 11) мы вынесли остальные используемые программой функции, в частности функцию PrintDiagnostics(). Кроме того, программа вызывает функции, определенные в файле ipxdrv.asm, содержимое которого уже было приведено нами раньше.
// =================================================== // Листинг 11. Функции IPX. // // Файл ipx.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include "ipx.hpp"
/** * .Name IntSwap * * .Title Обмен байтов в слове * * .Descr Функция меняет местами байты в слове, * которое передается ей в качестве параметра * * .Params unsigned i - преобразуемое слово * * .Return Преобразованное слово **/
unsigned IntSwap(unsigned i) { return((i>>8) | (i & 0xff)<<8); }
/** * .Name IPXRelinquishControl * * .Title Передать управление IPX при ожидании * * .Descr Функция используется при ожидании * завершения приема через опрос поля InUse * блока ECB. * * .Params Не используются * * .Return Ничего **/
void IPXRelinquishControl(void) {
struct IPXSPX_REGS iregs;
iregs.bx = IPX_CMD_RELINQUISH_CONTROL; ipxspx_entry( (void far *)&iregs ); }
// Функция для печати содержимого принятого пакета конфигурации.
void ECB::PrintDiagnostics(void) { int i, j, k, networks, component;
// Печатаем конфигурацию только для тех ECB,в поле InUse которых // стоит нулевое значение, т.е. если был принят пакет.
if(!_ecb.InUse) { // Распечатываем версию диагностической поддержки, номер сокета для // SPX-диагностики и количество компонентов программного //обеспечения, работающего на станции.
printf("\n%d.%d\t%d\t%d\t\t", Rep.MajVer, Rep.MinVer, Rep.Socket, Rep.NumberOfComponents);
// Распечатываем номер сети, из которой пришел пакет конфигурации.
for(i=0;i<4;i++) { printf("%02.2X",(unsigned char) IPXHeader->_ipx_header.SourceNetwork[i]); } printf("\t"); // Распечатываем сетевой адрес станции, из // которой пришел пакет конфигурации.


for(i=0;i<6;i++) { printf("%02.2X",(unsigned char) IPXHeader->_ipx_header.SourceNode[i]); } printf("\n\n");
// Для каждого программного компонента распечатываем его название.
for(i=0;i<Rep.NumberOfComponents;) { switch(component=Rep.Buffer[i]) { case 0: printf("\tДрайвер IPX/SPX\n"); i++; break; case 1: printf("\tДрайвер моста\n"); i++; break; case 2: printf("\tДрайвер сетевой оболочки\n"); i++; break; case 3: printf("\tСетевая оболочка\n"); i++; break; case 4: printf("\tОболочка VAP\n"); i++; break;
// Для мостов и серверов дополнительно выводим количество // подключенных к ним сетей,тип каждой сети, номера подключенных // сетей и сетевые адреса адаптеров.
case 5: case 6: case 7: switch(component) { case 5: printf("\tВыделенный мост\n"); break; case 6: printf("\tФайл-сервер/внутренний мост\n"); break; case 7: printf("\tНевыделенный сервер\n"); break; } i++; // Количество подключенных сетей
printf("\t\tПодключено сетей: %d", (unsigned char)Rep.Buffer[i]); networks = Rep.Buffer[i]; i++;
// Для каждой сети печатаем ее тип, // номер сети и сетевой адрес адаптера.
for(j=0;j<networks;j++) {
// Тип сети
printf("\n\t\t\tТип сети: %d\t", (unsigned char) Rep.Buffer[i++]);
// Номер сети
for(k=0;k<4;k++,i++) { printf("%02.2X",(unsigned char) Rep.Buffer[i]); } printf("\t"); // Сетевой адрес адаптера
for(k=0;k<6;k++,i++) { printf("%02.2X",(unsigned char) Rep.Buffer[i]); } } printf("\n"); break; } }
} }
Приведем образец листинга, выдаваемого программой в стандартный поток вывода:
*NetView* v1.0, (C) Фролов А.В., 1993 Подождите немного...
Конфигурация сети:
Версия Сокет Компоненты Сеть Узел ------ ----- ---------- ---- ----
1.0 576 3 00000010 000000000001
Драйвер IPX/SPX Драйвер моста Файл-сервер/внутренний мост Подключено сетей: 3 Тип сети: 1 00000010 000000000001 Тип сети: 0 00000013 48450000456C Тип сети: 0 00000012 4845000047C7


1.0 576 3 0000000E 000000000001
Драйвер IPX/SPX Драйвер моста Файл-сервер/внутренний мост Подключено сетей: 2 Тип сети: 1 0000000E 000000000001 Тип сети: 0 00000012 008428801E9D
1.1 320 3 00000012 008058801C82
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 484500004666
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.0 320 3 00000012 484500004889
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 008058801F0F
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 000561E5D284
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 008058801E1D
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 484506004726
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 008058801EB5
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.0 320 3 00000012 484556004705
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
Из приведенного листинга видно, что в сети имеются два файл-сервера и 9 рабочих станций. Все рабочие станции находятся в сети 00000012. К этой же сети подключены оба файл-сервера. Первый в списке файл-сервер подключен к сетям 00000012 и 00000013, следовательно, этот файл-сервер является внутренним мостом между сетью 00000012 и 00000013.
Обратите внимание, что для двух файл-серверов, имеющихся в сети, указан тип сети 1 и номера сетей 10h и 0Eh. Это так называемые внутренние номера сетей, которые задавались при генерации Novell NetWare версии 3.11. Физические сети имеют в нашем случае номера 12 и 13.

Содержание раздела