Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

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



[Назад] [Содеожание] [Дальше]