|
Локальные сети персональных компьютеров. Использование протоколов 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);
|

