Простая система "клиент-сервер" на базе 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);