Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS
© Александр Фролов, Григорий Фролов Том 4, М.: Диалог-МИФИ, 1993, 160 стр. 3.4. Простая система "клиент-сервер" на базе SPXПриведем простейший пример, демонстрирующий использование основных функций SPX. Этот пример сделан на базе предыдущего, в котором две програм-мы - клиент и сервер - общались между собой с помощью протокола IPX. После определения наличия драйвера IPX и получения адреса его API программа-сервер с помощью функции SPXCheckSPXInstallation() определяет присутствие драйвера протокола SPX. Затем открывается сокет для протокола IPX, подготавливается ECB для приема пакета от клиента. Этот пакет будет передаваться с помощью протокола IPX и предназначен для определения адреса клиента. Аналогично предыдущему примеру программа-клиент посылает пакет в текущую сеть с номером 00000000 по адресу FFFFFFFFFFFFh, т. е. всем станциям текущей сети. После того, как программа-сервер примет этот пакет, она сохранит в области памяти ClientImmAddress непосредственный адрес станции, на которой работает программа-клиент. После этого программа-сервер, пользуясь полученным непосредственным адресом, посылает клиенту ответный IPX-пакет, сообщая о том, что сервер принял пакет от клиента. Далее программа-сервер открывает еще один сокет, который будет использоваться протоколом SPX. Напомним, что для работы с протоколами IPX и SPX необходимо выделять разные сокеты. Открыв сокет, сервер подготавливает блок ECB для приема SPX-пакета от клиента. В поле непосредственного адреса копируется непосредственный адрес клиента, полученный после приема от него IPX-пакета. Запрос на создание канала выдает функция SPXListenForSequencedPacket(). Далее программа-сервер подготавливает в структуре ConnECB блок ECB для создания канала и вызывает функцию создания канала с принимающей стороны SPXListenForConnection(). После создания канала программа-сервер ожидает прихода SPX-пакета, проверяя в цикле содержимое поля InUse блока LsECB, распределенного ранее функцией SPXListenForSequencedPacket() для приема SPX-пакетов. После прихода SPX-пакета сервер закрывает оба сокета и завершает свою работу. // =================================================== // Листинг 12. Сервер SPX // // Файл spxserv.c // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.h" #include "spx.h" #define BUFFER_SIZE 512 void main(void) { // Используем сокет 0x4568 static unsigned IPXSocket = 0x4567; static unsigned SPXSocket = 0x4568; // Этот ECB используется для приема пакетов и для их передачи. struct ECB RxECB; struct ECB ConnECB, LsECB; // Заголовки принимаемых и передаваемых пакетов struct IPX_HEADER RxHeader, TxHeader; struct SPX_HEADER ConnHeader, LsHeader; // Буферы для принимаемых и передаваемых пакетов unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE]; struct SPXParams Params; unsigned char ClientImmAddress[6]; printf("\n*Сервер SPX*, (C) Фролов А., 1993\n\n"); // Проверяем наличие драйвера IPX и определяем // адрес точки входа его API if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); } if( SPXCheckSPXInstallation(&Params) != 0xFF) { printf("SPX не загружен!\n"); exit(-1); } // Открываем сокет, на котором мы будем принимать пакеты if(IPXOpenSocket(SHORT_LIVED, &IPXSocket)) { printf("Ошибка при открытии сокета IPX\n"); exit(-1); }; // Подготавливаем ECB для приема пакета memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(IPXSocket); 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(); memcpy(ClientImmAddress, RxECB.ImmAddress,6); // Подготавливаем ECB для передачи пакета // Поле ImmAddress не заполняем, так как там уже находится адрес // станции клиента. Это потому, что мы только что приняли от // клиента пакет данных и при этом в ECB установился непосред- // ственный адрес станции, которая отправила пакет RxECB.Socket = IntSwap(IPXSocket); 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(IPXSocket); // Подготавливаем передаваемые данные strcpy(TxBuffer, "SPX SERVER *DEMO*"); // Передаем пакет обратно клиенту IPXSendPacket(&RxECB); printf("Связь с сервером установлена\n"); printf("Создаем SPX-канал\n\n"); // Открываем сокет для работы с протоколом SPX if(IPXOpenSocket(SHORT_LIVED, &SPXSocket)) { printf("Ошибка при открытии сокета SPX\n"); exit(-1); }; // Подготавливаем ECB для приема пакета memset(&LsECB, 0, sizeof(LsECB)); LsECB.Socket = IntSwap(SPXSocket); memcpy(LsECB.ImmAddress, ClientImmAddress,6); LsECB.FragmentCnt = 2; LsECB.Packet[0].Address = &LsHeader; LsECB.Packet[0].Size = sizeof(LsHeader); LsECB.Packet[1].Address = RxBuffer; LsECB.Packet[1].Size = BUFFER_SIZE; SPXListenForSequencedPacket(&LsECB); // Подготавливаем заголовок пакета ConnHeader.PacketType = 5; ConnHeader.TransportControl = 0; memset(ConnHeader.DestNetwork, 0, 4); memcpy(ConnHeader.DestNode, ClientImmAddress, 6); ConnHeader.DestSocket = IntSwap(SPXSocket); memset(&ConnECB, 0, sizeof(ConnECB)); ConnECB.Socket = IntSwap(SPXSocket); ConnECB.FragmentCnt = 1; ConnECB.Packet[0].Address = &ConnHeader; ConnECB.Packet[0].Size = sizeof(ConnHeader); // Ожидаем запрос на создание канала SPXListenForConnection(&ConnECB,0,0); while(ConnECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); ConnECB.CCode = 0xfe; break; } } if(ConnECB.CCode == 0) { printf("Канал %04.4X создан\n", (unsigned)ConnECB.ConnectionId); } // Ожидаем прихода SPX-пакета от клиента while(LsECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); LsECB.CCode = 0xfe; break; } } if(LsECB.CCode == 0) { printf("Пакет принят: '%s'\n", RxBuffer); } } // Закрываем сокеты IPXCloseSocket(&IPXSocket); IPXCloseSocket(&SPXSocket); exit(0); } Программа-клиент после проверки наличия драйверов IPX и SPX открывает два сокета для использования с протоколами IPX и SPX. Затем подготавливается блок ECB для передачи "широковещательного" пакета по адресу FFFFFFFFFFFFh в текущую сеть с номером 000000. На этот пакет должна откликнуться программа-сервер, если она работает в текущей сети. После передачи пакета программа-клиент ожидает прихода пакета от сервера. Затем она подготавливает блок ECB для приема SPX-пакета и ставит его в очередь на прием при помощи функции SPXListenForSequencedPacket(). Затем программа-клиент подготавливает блок ECB для создания канала с программой-сервером и вызывает функцию создания канала с передающей стороны SPXEstablishConnection(). После того, как канал будет создан, в область памяти ConnID копируется идентификатор канала для использования при приеме и передаче SPX-пакетов. Далее программа-клиент подготавливает SPX-пакет и блок ECB для передачи программе-серверу и при помощи функции SPXSendSequencedPacket() передает пакет. После передачи SPX-пакета программа-клиент закрывает оба сокета и завершает свою работу. // =================================================== // Листинг 13. Клиент SPX // // Файл spxclien.c // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.h" #include "spx.h" // Максимальный размер буфера данных #define BUFFER_SIZE 512 void main(void) { // Будем работать с сокетом 0x4567 static unsigned IPXSocket = 0x4567; static unsigned SPXSocket = 0x4568; // ECB для приема и передачи пакетов struct ECB RxECB, TxECB; struct ECB ConnECB, LsECB, SndECB; // Заголовки принимаемых и передаваемых пакетов struct IPX_HEADER RxHeader, TxHeader; struct SPX_HEADER ConnHeader, LsHeader, SndHeader; // Буферы для принимаемых и передаваемых данных unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE]; struct SPXParams Params; unsigned char ServerImmAddress[6]; unsigned MyConnID, ConnID; unsigned rc; printf("\n*Клиент SPX*, (C) Фролов А., 1993\n\n"); // Проверяем наличие драйвера IPX и определяем // адрес точки входа его API if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); } if( SPXCheckSPXInstallation(&Params) != 0xFF) { printf("SPX не загружен!\n"); exit(-1); } // Открываем сокет, на котором мы будем // принимать и передавать пакеты if(IPXOpenSocket(SHORT_LIVED, &IPXSocket)) { printf("Ошибка при открытии сокета\n"); exit(-1); }; // Открываем сокет для протокола SPX if(IPXOpenSocket(SHORT_LIVED, &SPXSocket)) { printf("Ошибка при открытии сокета SPX\n"); exit(-1); }; // Подготавливаем ECB для передачи пакета memset(&TxECB, 0, sizeof(TxECB)); TxECB.Socket = IntSwap(IPXSocket); 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(IPXSocket); // Записываем передаваемые данные strcpy(TxBuffer, "CLIENT *DEMO*"); // Передаем пакет всем станциям в данной сети IPXSendPacket(&TxECB); // Подготавливаем ECB для приема пакета от сервера memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(IPXSocket); 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); } // Копируем сетевой адрес сервера memcpy(ServerImmAddress, RxECB.ImmAddress, 6); // Подготавливаем ECB для приема пакета memset(&LsECB, 0, sizeof(LsECB)); LsECB.Socket = IntSwap(SPXSocket); memcpy(LsECB.ImmAddress, ServerImmAddress,6); LsECB.FragmentCnt = 2; LsECB.Packet[0].Address = &LsHeader; LsECB.Packet[0].Size = sizeof(LsHeader); LsECB.Packet[1].Address = RxBuffer; LsECB.Packet[1].Size = BUFFER_SIZE; SPXListenForSequencedPacket(&LsECB); // Подготавливаем заголовок пакета ConnHeader.PacketType = 5; ConnHeader.TransportControl = 0; memset(ConnHeader.DestNetwork, 0, 4); memcpy(ConnHeader.DestNode, ServerImmAddress, 6); ConnHeader.DestSocket = IntSwap(SPXSocket); memset(&ConnECB, 0, sizeof(ConnECB)); ConnECB.Socket = IntSwap(SPXSocket); ConnECB.FragmentCnt = 1; ConnECB.Packet[0].Address = &ConnHeader; ConnECB.Packet[0].Size = sizeof(ConnHeader); // Устанавливаем SPX-канал с сервером rc = SPXEstablishConnection(&ConnECB, &MyConnID, 0, 0); printf("Ожидание SPX-соединения с сервером\n"); printf("Для отмены нажмите любую клавишу\n"); if(rc == 0) { while(ConnECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); ConnECB.CCode = 0xfe; break; } } } // Копируем идентификатор канала для передачи пакета в сервер memcpy(&ConnID, &(ConnHeader.SourceConnID), 2); printf("Канал с сервером установлен, ConnID=%d\n", IntSwap(ConnID)); // Подготавливаем ECB для передачи SPX-пакета memset(&SndECB, 0, sizeof(SndECB)); SndECB.Socket = IntSwap(SPXSocket); SndECB.FragmentCnt = 2; SndECB.Packet[0].Address = &SndHeader; SndECB.Packet[0].Size = sizeof(SndHeader); SndECB.Packet[1].Address = TxBuffer; SndECB.Packet[1].Size = BUFFER_SIZE; memcpy(SndECB.ImmAddress, ServerImmAddress, 6); // Подготавливаем заголовок пакета SndHeader.PacketType = 5; memset(SndHeader.DestNetwork, 0, 4); memcpy(SndHeader.DestNode, ServerImmAddress, 6); SndHeader.DestSocket = IntSwap(SPXSocket); SndHeader.TransportControl = 0; SndHeader.DataStreamType = 1; // Записываем передаваемые данные strcpy(TxBuffer, "SPX/CLIENT *DEMO*"); // Передаем SPX-пакет SPXSendSequencedPacket(&SndECB, ConnID); // Закрываем сокеты IPXCloseSocket(&IPXSocket); IPXCloseSocket(&SPXSocket); exit(0); } В файле spx.c определены функции для работы с протоколом SPX (листинг 14): // =================================================== // Листинг 14. Функции SPX. // // Файл spx.c // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include "ipx.h" #include "spx.h" /** * .Name SPXCheckSPXInstallation * * .Title Проверить присутствие протокола SPX * * .Descr Функция проверяет, загружен ли драйвер SPX * и возвращает его параметры. * * .Params struct *SPXParams - указатель на структуру, * в которую будут записаны параметры SPX. * * .Return FFh - протокол SPX загружен * 00h - протокол SPX не загружен **/ int SPXCheckSPXInstallation(struct SPXParams *Params) { struct IPXSPX_REGS iregs; iregs.bx = SPX_CMD_INSTALL_CHECK; iregs.ax = 0; ipxspx_entry( (void far *)&iregs ); Params->SPXVersion = iregs.bx; Params->SPXMaxConnections = iregs.cx; Params->SPXAvailableConnCount = iregs.dx; return(iregs.ax & 0xFF); } /** * .Name SPXListenForConnection * * .Title Ожидание соединения с клиентом * * .Descr Функция выдает запрос на соединение * с клиентом, который должен для выполнения * соединения вызвать функцию SPXEstablishConnection(). * * .Params struct ECB *ConnECB - указатель на ECB, * заполненное для установления соединения. * unsigned char RetryCount - счетчик повторов; * unsigned char WatchdogFlag - проверка связи. * * .Return Ничего. **/ void SPXListenForConnection(struct ECB *ConnECB, unsigned char RetryCount, unsigned char WatchdogFlag) { struct IPXSPX_REGS iregs; iregs.bx = SPX_CMD_LISTEN_FOR_CONNECTION; iregs.ax = RetryCount | ((unsigned)(WatchdogFlag << 8) & 0xff00); iregs.es = FP_SEG((void far*)ConnECB); iregs.si = FP_OFF((void far*)ConnECB); ipxspx_entry( (void far *)&iregs ); } /** * .Name SPXEstablishConnection * * .Title Установление соединения с клиентом * * .Descr Функция устанавливает соединение * с клиентом, который должен для выполнения * соединения вызвать функцию SPXListenForConnection(). * * .Params struct ECB *ConnECB - указатель на ECB, * заполненный для установления соединения. * unsigned char RetryCount - счетчик повторов; * unsigned char WatchdogFlag - проверка связи. * * .Return Ничего. **/ int SPXEstablishConnection(struct ECB *ConnECB, unsigned *ConnID, unsigned char RetryCount, unsigned char WatchdogFlag) { struct IPXSPX_REGS iregs; iregs.bx = SPX_CMD_ESTABLISH_CONNECTION; iregs.ax = RetryCount | ((unsigned)(WatchdogFlag << 8) & 0xff00); iregs.es = FP_SEG((void far*)ConnECB); iregs.si = FP_OFF((void far*)ConnECB); ipxspx_entry( (void far *)&iregs ); *ConnID = iregs.dx; return(iregs.ax & 0xff); } /** * .Name SPXListenForSequencedPacket * * .Title Прием пакета SPX * * .Descr Функция выдает запрос на прием пакета SPX. * * .Params struct ECB *LsECB - указатель на ECB, * заполненный для приема SPX-пакета. * * .Return Ничего. **/ void SPXListenForSequencedPacket(struct ECB *LsECB) { struct IPXSPX_REGS iregs; iregs.bx = SPX_CMD_LISTEN_FOR_SEQUENCED_PACKET; iregs.es = FP_SEG((void far*)LsECB); iregs.si = FP_OFF((void far*)LsECB); ipxspx_entry( (void far *)&iregs ); } /** * .Name SPXSendSequencedPacket * * .Title Передача пакета SPX * * .Descr Функция выдает запрос на передачу пакета SPX. * * .Params struct ECB *TxECB - указатель на ECB, * заполненный для передачи SPX-пакета. * * .Return Ничего. **/ void SPXSendSequencedPacket(struct ECB *TxECB,unsigned ConnID) { struct IPXSPX_REGS iregs; iregs.bx = SPX_CMD_SEND_SEQUENCED_PACKET; iregs.es = FP_SEG((void far*)TxECB); iregs.si = FP_OFF((void far*)TxECB); iregs.dx = ConnID; ipxspx_entry( (void far *)&iregs ); } В файле spx.h (листинг 15) определены константы, структуры данных и прототипы функций для работы с протоколом SPX: // =================================================== // Листинг 15. Include-файл для работы с SPX // Файл spx.h // // (C) A. Frolov, 1992 // =================================================== #include <dos.h> #include <string.h> #include <stdio.h> #include <stdlib.h> // ----------------------- // Команды интерфейса SPX // ----------------------- #define SPX_CMD_INSTALL_CHECK 0x10 #define SPX_CMD_ESTABLISH_CONNECTION 0x11 #define SPX_CMD_LISTEN_FOR_CONNECTION 0x12 #define SPX_CMD_TERMINATE_CONNECTION 0x13 #define SPX_CMD_ABORT_CONNECTION 0x14 #define SPX_CMD_GET_CONNECTION_STATUS 0x15 #define SPX_CMD_SEND_SEQUENCED_PACKET 0x16 #define SPX_CMD_LISTEN_FOR_SEQUENCED_PACKET 0x17 struct SPXParams { unsigned SPXVersion; unsigned SPXMaxConnections; unsigned SPXAvailableConnCount; }; // ========================================================= // Заголовок пакета SPX // ========================================================= struct SPX_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; // ------------Специфическая для SPX часть --------- unsigned char ConnControl; unsigned char DataStreamType; unsigned char SourceConnID[2]; unsigned char DestConnID[2]; unsigned char SequenceNumber[2]; unsigned char AckNumber[2]; unsigned char AllocationNumber[2]; }; int SPXCheckSPXInstallation(struct SPXParams *Params); void SPXListenForConnection(struct ECB *ConnECB, unsigned char RetryCount, unsigned char WatchdogFlag); int SPXEstablishConnection(struct ECB *ConnECB, unsigned *ConnID, unsigned char RetryCount, unsigned char WatchdogFlag); void SPXListenForSequencedPacket(struct ECB *LsECB); void SPXSendSequencedPacket(struct ECB *TxECB, unsigned MyConnID); |