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;
}
}
Рассмотрим пример программирования обмена данными. Программа пересылает по локальной сети пакет, содержащий текст " 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); }