Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS© Александр Фролов, Григорий ФроловТом 4, М.: Диалог-МИФИ, 1993, 160 стр. 2.4. Простая система "клиент-сервер"В качестве примера рассмотрим две программы. Первая из них является сервером, вторая - клиентом. После запуска сервер ожидает пакет от клиента. В свою очередь, клиент после запуска посылает пакет одновременно всем станциям данной сети, поэтому на какой бы станции ни работал сервер, он обязательно примет пакет от клиента. Когда сервер примет пакет от клиента, в поле ImmAddress блока ECB сервера окажется непосредственный адрес клиента. Поэтому сервер сможет ответить клиенту индивидуально, по его адресу в текущей сети. Клиент, в свою очередь, получив пакет от сервера, сможет узнать его сетевой адрес (по содержимому поля ImmAddress блока ECB). Следующий пакет клиент отправит серверу используя полученный непосредственный адрес сервера. Начиная с этого момента сервер знает адрес клиента, а клиент знает адрес сервера. Они могут обмениваться пакетами друг с другом не прибегая к посылке пакетов по адресу FFFFFFFFFFFFh. Исходный текст программы-сервера приведен в листинге 4. Вначале с помощью функции ipx_init() сервер проверяет наличие драйвера IPX и получает адрес его API. Затем с помощью функции IPXOpenSocket() программа открывает короткоживущий сокет с номером 0x4567. Этот номер мы выбрали произвольно из диапазона сокетов, распределяемых динамически. Далее программа-сервер подготавливает блок ECB для приема пакета от клиента (RxECB). Сперва весь блок расписывается нулями. Затем заполняются поля номера сокета, счетчик фрагментов (всего используются два фрагмента) и дескрипторы фрагментов. Первый фрагмент предназначен для заголовка пакета, второй - для принятых данных. Подготовленный блок ECB ставится в очередь на прием пакета при помощи функции IPXListenForPacket(). Затем программа в цикле опрашивает содержимое поля InUse блока ECB, дожидаясь прихода пакета. В цикл ожидания вставляется вызов функции IPXRelinquishControl() и функция опроса клавиатуры getch(). С помощью последней вы можете прервать ожидание, если нажмете на любую клавишу. После того, как сервер примет пакет от клиента, содержимое поля данных (переданное клиентом в виде текстовой строки, закрытой двоичным нулем) будет выведено на консоль. Приняв пакет, сервер подготавливает еще один блок ECB для передачи ответного пакета. Фактически сервер будет использовать тот же самый блок ECB, что и для приема. Поле непосредственного адреса в блоке ECB уже содержит адрес клиента, так как когда драйвер IPX принял пакет, он записал фактическое значение непосредственного адреса в соответствующее поле блока ECB. Для того, чтобы использовать блок ECB для передачи, нам достаточно изменить дескрипторы фрагментов - они должны указывать на заголовок передаваемого пакета и на буфер, содержащий передаваемые данные. В качестве передаваемых данных сервер использует буфер TxBuffer с записанной в него текстовой строкой "SERVER *DEMO*". Эта строка будет выведена клиентом на консоль после приема от сервера ответного пакета. Подготовив блок ECB для передачи, программа ставит его в очередь на передачу при помощи функции IPXSendPacket(), после чего закрывает сокет и завершает свою работу. // =================================================== // Листинг 4. Сервер IPX // // Файл ipxserv.c // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.h" #define BUFFER_SIZE 512 void main(void) { // Используем сокет 0x4567 static unsigned Socket = 0x4567; // Этот ECB мы будем использовать и для приема // пакетов, и для их передачи. struct ECB RxECB; // Заголовки принимаемых и передаваемых пакетов struct IPX_HEADER RxHeader, TxHeader; // Буферы для принимаемых и передаваемых пакетов unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE]; printf("\n*Сервер IPX*, (C) Фролов А., 1993\n\n"); // Проверяем наличие драйвера IPX и определяем // адрес точки входа его API if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); } // Открываем сокет, на котором мы будем принимать пакеты if(IPXOpenSocket(SHORT_LIVED, &Socket)) { printf("Ошибка при открытии сокета\n"); exit(-1); }; // Подготавливаем ECB для приема пакета memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &RxHeader; RxECB.Packet[0].Size = sizeof(RxHeader); RxECB.Packet[1].Address = RxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE; IPXListenForPacket(&RxECB); printf("Ожидание запроса от клиента\n"); printf("Для отмены нажмите любую клавишу\n"); while(RxECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); RxECB.CCode = 0xfe; break; } } if(RxECB.CCode == 0) { printf("Принят запрос от клиента '%s'\n", RxBuffer); printf("Для продолжения нажмите любую клавишу\n"); getch(); // Подготавливаем ECB для передачи пакета // Поле ImmAddress не заполняем, так как там // уже находится адрес станции клиента. // Это потому, что мы только что приняли от клиента пакет // данных и при этом в ECB установился непосредственный адрес // станции, которая отправила пакет RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &TxHeader; RxECB.Packet[0].Size = sizeof(TxHeader); RxECB.Packet[1].Address = TxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE; // Подготавливаем заголовок пакета TxHeader.PacketType = 4; memset(TxHeader.DestNetwork, 0, 4); memcpy(TxHeader.DestNode, RxECB.ImmAddress, 6); TxHeader.DestSocket = IntSwap(Socket); // Подготавливаем передаваемые данные strcpy(TxBuffer, "SERVER *DEMO*"); // Передаем пакет обратно клиенту IPXSendPacket(&RxECB); } // Закрываем сокет IPXCloseSocket(&Socket); exit(0); } Программа-клиент (листинг 5) после проверки наличия драйвера IPX/SPX и получения адреса его API подготавливает блок ECB и передает первый пакет по адресу FFFFFFFFFFFFh. Его принимают все станции в текущей сети, но откликается на него только та станция, на которой запущена программа-сервер. Послав первый пакет, клиент подготавливает ECB для приема пакета и ожидает ответ от сервера, вызывая в цикле функции IPXRelinquishControl и getch(). После прихода ответного пакета клиент закрывает сокет и завершает свою работу. // =================================================== // Листинг 5. Клиент IPX // // Файл ipxclien.c // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.h" // Максимальный размер буфера данных #define BUFFER_SIZE 512 void main(void) { // Будем работать с сокетом 0x4567 static unsigned Socket = 0x4567; // ECB для приема и передачи пакетов struct ECB RxECB, TxECB; // Заголовки принимаемых и передаваемых пакетов struct IPX_HEADER RxHeader, TxHeader; // Буфера для принимаемых и передаваемых данных unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE]; printf("\n*Клиент IPX*, (C) Фролов А., 1993\n\n"); // Проверяем наличие драйвера IPX и определяем // адрес точки входа его API if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); } // Открываем сокет, на котором будем принимать и передавать пакеты if(IPXOpenSocket(SHORT_LIVED, &Socket)) { printf("Ошибка при открытии сокета\n"); exit(-1); }; // Подготавливаем ECB для передачи пакета memset(&TxECB, 0, sizeof(TxECB)); TxECB.Socket = IntSwap(Socket); TxECB.FragmentCnt = 2; TxECB.Packet[0].Address = &TxHeader; TxECB.Packet[0].Size = sizeof(TxHeader); TxECB.Packet[1].Address = TxBuffer; TxECB.Packet[1].Size = BUFFER_SIZE; // Пакет предназначен всем станциям данной сети memset(TxECB.ImmAddress, 0xff, 6); // Подготавливаем заголовок пакета TxHeader.PacketType = 4; memset(TxHeader.DestNetwork, 0, 4); memset(TxHeader.DestNode, 0xff, 6); TxHeader.DestSocket = IntSwap(Socket); // Записываем передаваемые данные strcpy(TxBuffer, "CLIENT *DEMO*"); // Передаем пакет всем станциям в данной сети IPXSendPacket(&TxECB); // Подготавливаем ECB для приема пакета от сервера memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &RxHeader; RxECB.Packet[0].Size = sizeof(RxHeader); RxECB.Packet[1].Address = RxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE; IPXListenForPacket(&RxECB); printf("Ожидание ответа от сервера\n"); printf("Для отмены нажмите любую клавишу\n"); // Ожидаем прихода ответа от сервера while(RxECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); RxECB.CCode = 0xfe; break; } } if(RxECB.CCode == 0) { printf("Принят ответ от сервера '%s'\n", RxBuffer); } // Закрываем сокет IPXCloseSocket(&Socket); exit(0); } Исходные тексты функций для обращения к API драйвера IPX приведены в листинге 5.1. Здесь же определена функция IntSwap(), переставляющая местами байты в слове. // =================================================== // Листинг 5.1. Функции IPX. // // Файл ipx.c // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include "ipx.h" /** * .Name IntSwap * * .Title Обмен байтов в слове * * .Descr Функция меняет местами байты в слове, * которое передается ей в качестве параметра * * .Params unsigned i - преобразуемое слово * * .Return Преобразованное слово **/ unsigned IntSwap(unsigned i) { return((i>>8) | (i & 0xff)<<8); } /** * .Name IPXOpenSocket * * .Title Открыть сокет * * .Descr Функция открывает сокет, тип которого * передается ей через параметр SocketType. * Перед вызовом необходимо подготовить в памяти * слово и записать в него значение открываемого * сокета (или нуль, если нужен динамический сокет). * Адрес слова передается через параметр Socket. * Если открывается динамический сокет, его * значение будет записано по адресу Socket. * * .Params int SocketType - тип сокета: * 0x00 - короткоживущий; * 0xFF - долгоживущий. * * unsigned *Socket - указатель на слово, * в котором находится номер * открываемого сокета или нуль, * если нужен динамический сокет. * * .Return 0 - сокет открыт успешно; * 0xFE - переполнилась таблица сокетов; * 0xFF - такой сокет уже открыт. **/ int IPXOpenSocket(int SocketType, unsigned *Socket) { struct IPXSPX_REGS iregs; iregs.bx = IPX_CMD_OPEN_SOCKET; iregs.dx = IntSwap(*Socket); iregs.ax = SocketType; ipxspx_entry( (void far *)&iregs ); *Socket = IntSwap(iregs.dx); return(iregs.ax); } /** * .Name IPXCloseSocket * * .Title Закрыть сокет * * .Descr Функция закрывает сокет. * Перед вызовом необходимо подготовить в памяти * слово и записать в него значение закрываемого * сокета. Адрес слова передается через параметр Socket. * * .Params unsigned *Socket - указатель на слово, в котором * находится номер закрываемого сокета. * * .Return Ничего **/ void IPXCloseSocket(unsigned *Socket) { struct IPXSPX_REGS iregs; iregs.bx = IPX_CMD_CLOSE_SOCKET; iregs.dx = IntSwap(*Socket); ipxspx_entry( (void far *)&iregs ); } /** * .Name IPXListenForPacket * * .Title Принять пакет * * .Descr Функция подготавливает ECB для приема * пакета из сети. Указатель на ECB передается * через параметр RxECB. * * .Params struct ECB *RxECB - указатель на ECB, * заполненное для приема пакета. * * .Return Ничего **/ void IPXListenForPacket(struct ECB *RxECB) { struct IPXSPX_REGS iregs; iregs.es = FP_SEG((void far*)RxECB); iregs.si = FP_OFF((void far*)RxECB); iregs.bx = IPX_CMD_LISTEN_FOR_PACKET; ipxspx_entry( (void far *)&iregs ); } /** * .Name IPXSendPacket * * .Title Передать пакет * .Descr Функция подготавливает ECB для передачи * пакета. Указатель на ECB передается через * параметр TxECB. * * .Params struct ECB *TxECB - указатель на ECB, * заполненное для передачи пакета. * * .Return Ничего **/ void IPXSendPacket(struct ECB *TxECB) { struct IPXSPX_REGS iregs; iregs.es = FP_SEG((void far*)TxECB); iregs.si = FP_OFF((void far*)TxECB); iregs.bx = IPX_CMD_SEND_PACKET; ipxspx_entry( (void far *)&iregs ); } /** * .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 ); } Листинг 6 содержит include-файл, в котором определены необходимые константы, структуры данных и прототипы функций. // =================================================== // Листинг 6. Include-файл для работы с IPX // Файл ipx.h // // (C) A. Frolov, 1993 // =================================================== // ----------------------- // Команды интерфейса 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 // ----------------------- // Команды интерфейса SPX // ----------------------- #define SPX_CMD_INSTALL_CHECK 0x10 // ----------------------- // Коды ошибок // ----------------------- #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 // Внешние процедуры для инициализации и вызова драйвера IPX/SPX void far ipxspx_entry(void far *ptr); int ipx_init(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; }; // =========================================================== // Заголовок пакета IPX // =========================================================== 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; }; // ============================================================ // ECB // ============================================================ 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]; }; unsigned IntSwap(unsigned i); int IPXOpenSocket(int SocketType, unsigned *Socket); void IPXCloseSocket(unsigned *Socket); void IPXListenForPacket(struct ECB *RxECB); void IPXRelinquishControl(void); void IPXSendPacket(struct ECB *TxECB); |