7.1. Описание структур и формат данных протокола IPX

7.1.1. Структура и формат данных

Протокол IPX (Internetwork Packet Exchange Межсетевой Обмен Пакетов) принадлежит к классу так называемых датаграммных протоколов транспортного уровня. Передача сообщений датаграммами осуществляется без подтверждения. Датаграмма состоит из заголовка и собственно блока данных. В заголовке указывается адрес назначения и свой собственный. Поэтому датаграмма может существовать автономно, продвигаясь по сети.

В протоколе IPX сетевой адрес состоит из трех полей:

- Первое поле (4 байта) содержит номер сети. Он задается при генерации файлового сервера или межсетевого моста. Если в локальной сети сервер (мост) выключен или отсутствует, то в этом поле должны стоять нули.

- Второе поле ( 6 байт) содержит адрес сетевой платы станции. В платах Ethernet этот адрес, как правило, прожигается на заводе.

- Третье поле (2 байта) содержит номер сокета (англ. Socket гнездо, розетка). Оно используется для задания адреса процесса внутри узла.

Сетевой адрес описан в виде структуры IPXAddress в файле IPX.H:


typedef struct
{
    unsigned char netadd[4];      // "Старший-младший"
    unsigned char nodeadd[6];     // "Старший-младший"
    unsigned short socket;        // "Старший-младший"
} IPXADDRESS;


"Старший-младший" определяет порядок следования байтов. Нормальный порядок следования байт "младший-старший", поэтому при работе с этими полями нужно быть особенно внимательным и для извлечения информации из них необходимо использовать функции типа IntSwap() или revresword().

Взаимодействие прикладной программы с IPX.COM происходит через блок обработки события(Event Control Block), сокращенно "ECB". Он представляет непрерывный участок памяти длиной 48 байтов. Структура блока определена в файле IPX.H как struct ECB и состоит из следующих полей:


typedef struct
{
    void far *linkaddress;
    void (far *esraddress)();
    unsigned char inuse;
    unsigned char cc;
    unsigned short socket;          /* старший-младший */
    unsigned char workspace[16];
    unsigned char immedaddr[6];     /* старший-младший */
    unsigned short fragcount;
    void far *fragaddr1;
    unsigned short fragsize1;
    void far *fragaddr2;
    unsigned short fragsize2;
} IPXECB;

IPX записывает в поле inuse inUseFlag (флаг использования) текущее состояние события. Прикладная программа может задействовать это поле для определения факта завершения события.

Поле cc (СompletionCode) – код завершения, содержит код завершения для события, который обозначает, был ли пакет отправлен успешно, или пакет имеет неправильный формат и т.п.

Поле socket ( SocketNumber ) – номер гнезда, определяет отправляющий или получающий сокет, к которому подстыковывается данный ECB.

Поля IPXWorkspace (рабочая область IPX) и driverWorkspace (рабочая область драйвера) используются для внутренних целей протоколом и драйвером сетевой платы. Эти поля нельзя изменять.

Поле immedaddr (immediateAddress) – непосредственный адрес, содержит адрес узла, которому отправляется пакет, или узла, от которого он получается. Если пакет не получен от узла и не отправлен узлу в местной сети, то этим адресом будет адрес межсетевого моста в местной сети.

Для заполнения этого поля можно использовать функцию IPXGetLocalTarget.

Поле fragcount (fragmentCount) – обозначает количество буферов (не менее двух), из которых будет построен исходящий (отправляемый) пакет, или на которые будет разбит принимаемый пакет. Прикладная программа дает список дескрипторов фрагментов в конце блока ECB, с буфером адреса и размера, обозначенным посредством поля fragmentCount. Первый фрагмент обязательно в начале должен содержать место под протокольный заголовок пакета (struct IPXHeader или struct SPXHeader). Суммарная длина двух фрагментов не должна превышать 576 байтов.

Заголовок пакета IPX IPXHeader определен в файле IPX.H и состоит из следующих полей:


 typedef struct
{
    unsigned short checksum;      /* старший-младший */
    unsigned short length;            /* старший-младший */
    unsigned char tc;
    unsigned char type;
    IPXADDRESS dest;
    IPXADDRESS source;
} IPXHEADER;

7.1.2. Список команд протокола IPX

Все ниже перечисленные функции, за исключением IPXGetDataAddress, являются непосредственно функциями протокола IPX-SPX.

Обращения к ним формируются как вызов функции (*IPXSPX_entry)() с соответствующим кодом:

7.1.3. Описание программной реализации функций IPX

В самом начале работы прикладная программа должна проверить наличие IPX в оперативной памяти и получить адрес входа для интерфейса IPX, вызвав функцию ipx_init() - IPXInitialize.

Данная функция обращаясь к мультиплексному прерыванию с кодом 0x7A определяет наличие IPX.COM в памяти и получает адрес точки входа в IPX (IPXLocation). IPXInitialize() обязательно должна присутствовать перед первым вызовом любой другой функции. В случае отсутствия IPX.COM в памяти функция возвращает значение (0x00).

Ее можно использовать следующим образом:


        if (!ipx_init()) {
        printf(" протокол IPX не загружен\n");
	      exit(-1);
}

Перед тем, как начать обмен пакетами с помощью IPX, прикладные программы на обеих сетевых станциях должны открыть сокеты функцией IPXOpenSocket. Данная функция открывает сокет (гнездо IPX):

int IPXOpenSocket( BYTE socketType, BYTE *socketNum)

Возвращаемые значения:

(0x00) SUCCESSFUL - Успешное выполнение

(0xF0) IPX_NOT_INSTALLED - IPX не установлен

(0xFE) SOCKET_TABLE_FULL - Таблица сокетов заполнена

(0xFF) SOCKET_ALREADY_OPEN Сокет уже открыт

Прикладная программа должна использовать эту функцию для открытия сокета перед тем, как получить пакет.

Параметр socketNumber содержит номер открываемого сокета. Передача величины 0000h в это поле позволяет IPX открыть доступные гнезда по своему выбору в диапазоне от 0x4000 до 0x5000. Это называется ДИНАМИЧЕСКИМ открытием гнезда.

Oткрываем сокет на котором будем передавать:


if(IPXOpenSocket(0x00, &Socket)) 
	{
	      printf("Ошибка при открытии сокета\n");
	      exit(-1);
	};


Номер сокета, открытого на станции А должен быть известен прикладной программе на станции Б и наоборот.

По окончании передачи и приема пакетов, прикладные программы на обеих станциях должны закрыть гнезда, вызвав функцию IPXCloseSocket(void). Для отправки пакета используется функция IPXSendPacket. Данная функция инициализирует отправку пакета IPX и имеет формат:

void IPXSendPacket(ECB *eventControlBlock)

Функция IPXSendPacket передает IPX адрес блока ECB для отправки пакета IPX. Эта функция возвращает управление вызывающей прикладной программе, не дожидаясь окончанияпроцесса отправки. С этого момента IPX пытается отправить пакет. Перед тем, как вызвать эту функцию, прикладная программа должна инициализировать следующие поля блока ECB:

- socketNumber

- immediateAddress

- fragmentCount

- fragmentDescriptor

Пример подготовки ECB для передачи пакетов:

memset(TxECB.immeaddr, 0xff, 6); // адрес получателя - всем

TxECB.esraddress=NULL; // п/п обpаботки события - отсутсвует

TxECB.socket = IntSwap(Socket);

TxECB.fragcount = 2;

TxECB.fragaddr1 = &TxHeader;

TxECB.fragsize1 = sizeof(TxHeader);

TxECB.fragaddr2 = TxBuffer;

TxECB.fragsize1 = BUFFER_SIZE;

Поле immediateAddress содержит адрес узла, которому отправляется пакет, или узла, от которого он получается.

Пример подготовки заголовка для передачи пакетов предназначенных всем станциям данной сети:


       TxHeader.type = 4;                    /* тип пакета */
       for(i=0;i<4;i++)
       TxHeader.dest.netadd[i]=0x00;  /* адpес сети */
       for(i=0;i<6;i++)
       TxHeader.dest.nodeadd[i]=0xFF; /* адpес компьютеpа */
	/* адpес 00 00 00 00 FF FF FF FF FF FF - означает , что
		пакет пойдет на все компьютеpы сети */
       TxHeader.dest.socket=IntSwap(Socket); /* сокет пpиемника */


Для приема пакетов служит функция IPXListenForPacket():

IPXListenForPacket(&RxECB);

IPX не переходит в состояние ожидания окончания отправки или получения пакета, эти операции только инициализируются. Реальная отправка и прием происходят в фоновом режиме. Ход этих операций можно отслеживать двумя способами: либо прикладная программа периодически проверяет признак завершения операции inUseFlag, либо передает IPX адрес подпрограммы (ESRAddress), которая будет выполнена при завершении события.

Сначала IPX устанавливает поле блока ECB inuse – “inUseFlag” как (0xFF), это означает, что блок ECB отправляет пакет. После попытки отправить пакет, IPX устанавливает поле cc "completionCode" блока ECB до соответствующей величины и устанавливает поле "inUseFlag" как (0x00), и вызывает подпрограмму ESR (подпрограмму обслуживания события), на которую указывает поле "ESRAddress ".

Ниже приводятся возможные величины для поля cc "completionCode" блока ECB:

(0x00) SUCCESSFUL Успешное выполнение

(0xFC) REQUEST_CANCELLED Запрос отменен

(0xFD) BAD_PACKET Неправильный пакет (Данный пакет не имеет 30-байтового заголовка пакета в качестве первого фрагмента, или же общая длина пакета превышает 576 байт)

(0xFE) PACKET_NOT_DELIVERABLE Пакет не может быть доставлен

(0xFF) HARDWARE_FAILURE Сбой в аппаратуре

Пример определения завершения обмена с возможностью выхода из "зависания" следующий:


while(RxECB.inuse) 
	{
		IPXRelinquishControl();
		if(kbhit()) 
		{
		      getch();
		      RxECB.cc = 0xfc;
		IPXCloseSocket(Socket);
		break;
		}
}


7.2. Пример программы обмена данными

Рассмотрим пример программирования обмена данными. Программа пересылает по локальной сети пакет, содержащий текст " HELLO!". Необходимые программы для данного примера:

TEST.C

IPX.OBJ

IPX.H

TEST.C базовая программа.

Базовая программа, используемая в данном примере, открывает сокет, посылает пакет в этот сокет, принимает этот пакет и закрывает этот сокет.

IPX.ASM вспомогательная программа, содержащая системные вызовы функций IPX.

IPX.OBJ объектный код программы IPX.ASM

IPX.H включаемый файл, содержащий основные структуры, используемые в программировании обмена данными на базе IPX.

Рассмотрим пример передачи данных из буфера sbuffer и прием в буфер rbuffer.


// TEST.C  
#include 
#include 
#include 
#include "ipx.h"
// Определяем структуры
IPXADDRESS myaddr;
IPXECB recb;
IPXECB secb;
IPXHEADER rheader;
IPXHEADER sheader;
// Определяем входной и выходной буферы
unsigned char rbuffer[80];
unsigned char sbuffer[“HELLO!”];
int i;

void main ( void )
{
    if(!ipxinit()) // Проверяем наличие драйвера IPX
    {
        printf("IPX not installed\n");  exit(1);
    }
    printf("ipxentry %08lX\n",ipxentry);
    printf("max packet size %u\n",ipxgetmaxpacketsize());

    ipxgetaddress(&myaddress);
// Выводим на экран свой сетевой адрес
    printf("%02X:%02X:%02X:%02X %02X:%02X:%02X:%02X:%02X:%02X\n",myaddr.netadd[0], myadr.netadd[1], 
    myaddr.netadd[2], myaddr.netadd[3], myadr.nodeadd[0], myaddr.nodeadd[1],myaddr.nodeadd[2], 
    myadr.nodeadd[3], myaddress.nodeadd[4], myaddress.nodeadd[5]);
    myaddr.socket=0;
// Открываем сокет
    switch(ipxopensocket(0x00,&myaddr.socket))
    {
        case 0x00: // Success
            printf("Socket %04X\n",reverseword(myaddr.socket));
            break;
        case 0xFE:
            printf("Socket Table Full\n");
            exit(1);
        case 0xFF:
            printf("Socket Already Open\n");
            exit(1);
    }
// Заполняем блок управления для приема
    recb.socket=myaddr.socket;
    recb.esraddress=NULL;
    recb.fragcount=2;
    recb.fragaddr1=&rheader;
    recb.fragsize1=sizeof(IPXHEADER);
    recb.fragaddr2=rbuffer;
    recb.fragsize2=sizeof(rbuffer);
// Установим режим приема из линии связи
    ipxlistenforpacket(&recb);

// Заполняем заголовок для передачи
    for(i=0;i<4;i++) sheader.dest.netadd[i]=0x00;
    for(i=0;i<6;i++) sheader.dest.nodeadd[i]=0xFF;
    for(i=0;i<6;i++) secb.immedaddr[i]=0xFF;
    sheader.dest.socket=myaddr.socket;
// Заполняем блок управления  передачей
    secb.socket=myaddr.socket;
    secb.esraddress=NULL;
    sheader.type=4;
    secb.fragcount=2;
    secb.fragaddr1=&sheader;
    secb.fragsize1=sizeof(IPXHEADER);
    secb.fragaddr2=sbuffer;
    secb.fragsize2=sizeof(sbuffer);
// Выполняем команду передачи данных
    ipxsendpacket(&secb);

// Ожидаем прием
    printf("Waiting for packet\n");
// Пока не нажата какая-либо клавиша для выхода из программы
    while(!kbhit())
    {
        ipxrelenquishcontrol();
//Проверяем флаг окончания приема в блоке управления приема
        if(!recb.inuse) 
// Если прием завершен выводим содержимое входного буфера заголовка и данных
      {
       printf("Packet Received\n");
       printf("  from %02X:%02X:%02X:%02X  %02X:%02X:%02X :%02X:%02X:%02X\n", rheader.source.netadd[0],
       rheader.source.netadd[1], rheader.source.netadd[2], rheader.source.netadd[3], 
       rheader.source.nodeadd[0], rheader.source.nodeadd[1],rheader.source.nodeadd[2], 
       rheader.source.nodeadd[3],rheader.source.nodeadd[4], rheader.source.nodeadd[5]);
       printf("from socket %04X\n", reverseword (rheader.source.socket));
       printf(“%s”, rbuffer);
        break;
        }
    }
    while(kbhit()) getch();
    ipxclosesocket(myaddress.socket);
}