15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту

Библиотека Системного Программиста. Том 8
Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS

3. ПРОТОКОЛ SPX

3.1. Формат пакета SPX

3.2. Блок ECB

3.3. Функции SPX

3.4. Простая система "клиент-сервер" на базе SPX

3.5. Настройка параметров SPX

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

Прежде чем принять решение о создании собственного протокола, изучите протокол SPX - протокол последовательного обмена пакетами (Sequenced Packet Exchange Protocol), разработанный Novell. Возможно, что протокол SPX удовлетворит потребности вашей программы в гарантированной передаче данных по сети.

3.1. Формат пакета SPX

Пакет, передаваемый при помощи протокола SPX, имеет более длинный заголовок. Дополнительно к 30 байтам стандартного заголовка пакета IPX добавляется еще 12 байт (рис. 4).

Рис. 4. Формат заголовка пакета SPX

Приведем структуру заголовка пакета 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];
};

Поле ConnControl можно рассматривать как набор битовых флагов, управляющих передачей данных по каналу SPX:

БитыНазначение
01h-08hЗарезервировано
10hEnd-of-Message. Этот бит может использоваться программой для сигнализации окончания передачи данных. Драйвер SPX передает этот бит программе в неизменном виде, причем сам драйвер протокола SPX этот бит игнорирует
20hAttention. Этот бит игнорируется драйвером SPX и передается в неизменном виде программе
40hAcknowledgement Required. Бит используется драйвером SPX. Вам не следует модифицировать его значение
80hSystem Packet. Этот бит устанавливается драйвером SPX при передаче системных пакетов, которые используются самим драйвером и не передаются в программу пользователя

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

БитыНазначение
00h-FDhЭти значения игнорируются драйвером SPX и могут быть использованы программой произвольным образом
FEhEnd-of-Connection. Когда программа вызывает функцию, закрывающую SPX-канал, драйвер SPX посылает партнеру по связи последний пакет, в поле DataStreamType которого записано значение FEh. Это служит требованием завершить связь и закрыть канал
FFhEnd-of-Connection-Acknowledgement. Это значение отмечает пакет, подтверждающий завершение связи. Такой пакет является системным и не передается в программу пользователя

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

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

Поле SeqNumber содержит счетчик пакетов, переданных по каналу в одном направлении. На каждой стороне канала используется свой счетчик. После достижения значения FFFFh счетчик сбрасывается в нуль, после чего процесс счета продолжается.

Содержимым этого поля управляет драйвер SPX, поэтому программа не должна менять его значение.

Поле AckNumber содержит номер следующего пакета, который должен быть принят драйвером SPX.

Содержимым этого поля управляет драйвер SPX, поэтому программа не должна менять его значение.

Поле AllocNumber содержит количество буферов, распределенных программой для приема пакетов.

Содержимым этого поля управляет драйвер SPX, поэтому программа не должна менять его значение.

3.2. Блок ECB

Для протокола SPX используется точно такой же блок ECB, что и для протокола IPX.

3.3. Функции SPX

3.3.1. Инициализация SPX

SPXCheckInstallation

На входе:BX= 10h.
AL= 00h.
На выходе:AL= Код завершения:
00h - SPX не установлен;
FFh - SPX установлен.
BH= Верхний (major) номер версии SPX.
BL= Нижний (minor) номер версии SPX.
CX= Максимальное количество каналов SPX, поддерживаемых драйвером SPX.
DX= Количество доступных каналов SPX.

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

3.3.2. Образование канала связи

SPXListenForConnection

На входе:BX= 12h.
AL= Счетчик повторов попыток создать канал связи.
AH= Флаг включения системы периодической проверки связи (Watchdog Supervision Required Flag).
ES:SI= Указатель на блок ECB.
На выходе: Регистры не используются.

Эта функция используется в паре с функцией SPXEstablishConnection для образования канала связи.

Программа-сервер вызывает SPXListenForConnection, передавая ей адрес блока ECB. Этот блок будет использован для образования канала связи. Когда программа-клиент вызовет функцию SPXEstablishConnection, произойдет образование канала связи и в поле InUse блока ECB будет записано нулевое значение. Будет также вызвана соответствующая программа ESR, если задан ее адрес.

В блоке ECB необходимо определить значение поля ESRAddress и указать номер сокета. Для каждого сокета вы можете образовать несколько каналов.

Блок ECB, адрес которого задан в регистрах ES:SI, ставится драйвером SPX во внутреннюю очередь блоков ECB, ожидающих прихода пакетов от функций SPXEstablishConnection, выдаваемых другими станциями, желающими образовать канал связи.

После образования канала связи, когда в поле InUse будет записано нулевое значение, поле CCode блока ECB будет содержать код завершения:

00канал связи создан, ошибок нет;
FFhуказанный в ECB сокет не был открыт;
FChзапрос SPXListenForConnection был отменен функциями IPXCancelEvent или IPXCloseSocket (ESR не вызывается);
EFhпереполнилась внутренняя таблица номеров каналов связи; до тех пор, пока какой-нибудь канал не будет закрыт, вы не сможете образовать новые каналы.

Перед вызовом функции SPXListenForConnection программа должна выделить хотя бы один ECB для приема SPX-пакета. Это нужно сделать при помощи функции SPXListenForSequencedPacket (см. ниже описание функции).

Вам также надо задать в регистре AL cчетчик повторов попыток создания канала связи и в регистре AH - флаг включения системы периодической проверки связи. Вы можете задать от 1 до 255 попыток или использовать значение по умолчанию, если запишете в регистр AL нулевое значение.

Если в регистр AH будет записано ненулевое значение, драйвер SPX будет периодически проверять работоспособность канала связи, передавая специальные тестовые пакеты. Если канал вдруг перестает работать, он разрывается и в ECB, подготовленный для приема пакетов функцией SPXListenForSequencedPacket, в поле InUse проставляется нулевое значение. Поле CCode при этом будет содержать код ошибки EDh, а номер "испорченного" канала будет записан в первых двух байтах поля IPXWorkspace блока ECB.

SPXEstablishConnection

На входе:BX= 11h.
AL= Счетчик повторов попыток создать канал связи.
AH= Флаг включения системы периодической проверки связи (Watchdog Supervision Required Flag).
ES:SI= Указатель на блок ECB.
На выходе:AL= Промежуточный код завершения:
00h - выполняется попытка создать канал;
FFh - указанный в блоке ECB сокет закрыт;
FDh - сбойный пакет: либо счетчик фрагментов не равен 1, либо размер фрагмента не равен 42;
EFh - переполнение локальной таблицы номеров каналов связи.
DX= Присвоенный номер канала.

Функция устанавливает канал связи с программой, предварительно вызвавшей функцию SPXListenForConnection.

Для функции необходимо подготовить блок ECB и пакет в формате SPX, состоящий из одного заголовка. В блоке ECB необходимо заполнить поля ESRAddress, Socket, счетчик количества фрагментов (нужен один фрагмент) и указатель на фрагмент размером 42 байта. В заголовке SPX-пакета необходимо заполнить поля DestNetwork, DestNode, DestSocket.

Кроме того, перед вызовом функции SPXListenForConnection программа должна выделить хотя бы один ECB для приема SPX-пакета. Это нужно сделать при помощи функции SPXListenForSequencedPacket (см. ниже описание функции).

Канал создается в два приема.

На первом этапе проверяется возможность образования канала - проверяется наличие свободного места в таблице номеров каналов, проверяется таблица сокетов, размер пакета. Если все хорошо, с целью попытки создать канал удаленному партнеру посылается пакет, после чего функция возвращает управление вызвавшей ее программе. Регистр AL при этом содержит промежуточный код завершения. Если этот код равен нулю, можно переходить к ожиданию приема ответного пакета от партнера по созданию канала. Регистр DX при этом содержит номер присвоенного канала.

Если партнер отвечает соответствующим образом, в поле InUse блока ECB устанавливается нулевое значение. Если при этом в поле CCode также находится нулевое значение, канал считается созданным.

Номер канала удаленного партнера, который вы будете использовать для передачи ему пакетов функцией SPXSendSequencedPacket, находится в поле SourceConnID блока ECB. Сохраните его для дальнейшего использования.

Если по каким-либо причинам канал создать не удалось, в поле CCode будет записан код ошибки:

00hканал связи создан, ошибок нет;
FChзапрос SPXListenForConnection был отменен функциями IPXCancelEvent или IPXCloseSocket (ESR не вызывается);
FDhсбойный пакет: либо счетчик фрагментов не равен единице, либо размер фрагмента не равен 42;
FFhуказанный в ECB сокет не был открыт;
EFhпереполнилась внутренняя таблица номеров каналов связи; до тех пор, пока какой-нибудь канал не будет закрыт, вы не сможете образовать новые каналы;
EDhадресат не отвечает или сообщает, что он не может создать канал; этот код может возникнуть либо как результат неисправности сетевого аппаратного обеспечения, либо если функция SPXEstablishConnection была отменена при помощи функции SPXAbortConnection.

Обратим ваше внимание на то, что для отмены создания канала необхо-
димо пользоваться специально предназначенной для этого функцией SPXAbortConnection, а не функцией IPXCancelEvent.

3.3.3. Прием и передача пакетов

SPXListenForSequencedPacket

На входе:BX= 17h.
ES:SI= Указатель на блок ECB.
На выходе: Регистры не используются.

Функция обеспечивает прием пакетов средствами протокола SPX. При этом она ставит блок ECB, адрес которого передается через регистры ES:SI, в очередь на прием, после чего немедленно возвращает управление вызвавшей программе.

После того как пакет будет принят, в поле InUse блока ECB устанавливается нулевое значение, а в поле CCode - код завершения:

00hпакет принят без ошибок;
FChзапрос SPXListenForSequencedPacket был отменен функциями IPXCancelEvent или IPXCloseSocket (ESR не вызывается);
FDhпереполнение пакета - принятый пакет имеет длину, которая превосходит размер буферов, указанных в дескрипторах фрагментов;
EDhсистема периодической проверки связи обнаружила разрыв канала, номер разрушенного канала записан в первых двух байтах поля IPXWorkspace блока ECB;
FFhуказанный в ECB сокет не был открыт.

Перед вызовом функции необходимо заполнить в ECB поля ESRAddress, Socket, счетчик фрагментов и дескрипторы фрагментов. При этом первый фрагмент передаваемого пакета должен иметь длину не менее 42 байт - это буфер для приема стандартного заголовка SPX-пакета. Необходимо также открыть используемый сокет при помощи функции IPXOpenSocket.

Обычно для приема пакетов используется несколько блоков ECB. Все они по-
следовательно ставятся в очередь функцией SPXListenForSequencedPacket. Для приема пакета драйвером SPX может быть использован любой свободный блок ECB. Не гарантируется, что блоки ECB будут использованы именно в том порядке, в котором они ставились в очередь функцией SPXListenForSequencedPacket.

Если принимается системный пакет, использованный блок ECB автоматически возвращается в очередь для приема пакетов.

Так как для обработки системных пакетов протокол гарантированной доставки SPX использует те же блоки ECB, что и для приема прикладных пакетов, ваша программа должна обеспечить достаточное количество блоков ECB в очереди на прием пакетов.

Если принимается пакет, у которого в заголовке в поле DataStreamType находится значение FEh, это означает, что передающая программа собирается завершить передачу и закрыть канал. При этом все блоки ECB, стоящие в очередь на передачу пакетов (в которую они ставятся функцией SPXSendSequencedPacket, описанной ниже), отмечаются нулевым значением в поле InUse и соответствующим кодом завершения в поле CCode.

Программа может отменить ожидание завершения приема пакета для блока ECB при помощи функции IPXCancelEvent, при этом она должна заново проинициализировать поле ESRAddress перед повторным использованием этого блока ECB.

SPXSendSequencedPacket

На входе:BX= 16h.
ES:SI= Указатель на блок ECB
DX= Номер канала связи.
На выходе:--- Регистры не используются.

Функция ставит блок ECB, адрес которого указан в регистрах ES:SI, в очередь на передачу, после чего немедленно возвращает управление вызвавшей программе.

Перед вызовом функции программа должна заполнить поле ESRAddress, счетчик фрагментов и дескрипторы фрагментов блока ECB, а также бит End-Of-Message в поле ConnControl и поле DataStreamType в заголовке передаваемого пакета. Разумеется, заголовок должен иметь длину 42 байта.

В регистр DX необходимо загрузить номер канала, используемый партнером.

В отличие от средств передачи пакета протокола IPX успешное завершение передачи пакета, инициированной функцией SPXSendSequencedPacket, гарантирует доставку пакета партнеру. Если партнер не успевает принимать передаваемые пакеты, они ставятся в очередь на передачу, чем обеспечивается правильная последовательность доставки пакетов.

После завершения передачи пакета поле InUse блока ECB имеет нулевое значение. Если определена программа ESR, она вызывается. В поле CCode находится код завершения:

00hпакет был передан и успешно принят партнером;
FChуказанный в ECB сокет был закрыт, программа ESR не вызывается;
FDhсбойный пакет: либо счетчик фрагментов равен нулю, либо размер первого фрагмента меньше 42 байт, либо размер всего пакета больше 576 байт;
EEhнеправильное значение в регистре DX;
EDhлибо система периодической проверки связи обнаружила разрыв канала, либо канал был уничтожен функцией SPXAbortConnection (номер разрушенного канала записан в первых двух байтах поля IPXWorkspace блока ECB);
EChудаленный партнер закрыл канал без подтверждения приема этого пакета, при этом SPX не может гарантировать, что переданный пакет был успешно принят партнером перед тем, как канал был закрыт.

Для отмены передачи пакета нельзя использовать функцию IPXCancelEvent. Вместо нее необходимо использовать функцию SPXAbortConnection.

3.3.4. Разрыв канала связи

SPXTerminateConnection

На входе:BX= 13h.
ES:SI= Указатель на блок ECB.
DX= Номер канала связи.
На выходе: Регистры не используются.

Функция посылает удаленному партнеру пакет, который состоит из одного заголовка. В поле DataStreamType этого заголовка находится значение FEh, которое говорит партнеру о том, что необходимо закрыть канал. Сразу после вызова функция возвращает управление вызывавшей ее программе.

Перед вызовом функции программа должна заполнить поле ESRAddress, счетчик фрагментов (в пакете должен быть один фрагмент размером 42 байта) и дескриптор фрагмента блока ECB.

В регистр DX необходимо загрузить номер канала, используемый партнером.

После завершения процесса закрытия канала в поле InUse блока ECB проставляется нулевое значение и вызывается программа ESR (если она была задана). В поле CCode проставляется код завершения:

00hканал был успешно закрыт;
FDhсбойный пакет: либо счетчик фрагментов не равен единице, либо размер фрагмента меньше 42 байт;
EEhнеправильное значение в регистре DX;
EDhканал закрылся с ошибкой, при этом удаленный партнер не прислал пакет, подтверждающий закрытие канала. При этом SPX не гарантирует, что партнер успешно закрыл канал со своей стороны;
EChудаленный партнер закрыл канал без подтверждения команды закрытия канала, при этом SPX не может гарантировать, что партнер вызвал функцию, закрывающую канал.

После закрытия канала освобождается место в таблице номеров каналов. Программа может открывать новые каналы.

Заметим, что для отмены ожидания завершения процесса закрытия канала необходимо использовать функцию SPXAbortConnection, а не IPXCancelEvent.

SPXAbortConnection

На входе:BX 14h.
 DX Номер канала связи.
На выходе: Регистры не используются.

Функция разрывает канал связи без "согласования" с партнером. Данная функция должна использоваться только в катастрофических случаях, когда невозможно выполнить нормальную процедуру закрытия канала.

После вызова этой функции во всех ECB, относящихся к данному каналу в поле CCode проставляется значение EDh.

3.3.5. Проверка состояния канала

SPXGetConnectionStatus

На входе:BX= 15h.
DX= Номер канала связи.
ES:SI= Указатель на буфер размером 44 байта.
На выходе:AL= Код завершения:
00h - канал активен;
EEh - указанный канал не существует.

С помощью функции SPXGetConnectionStatus программа может проверить состояние канала. Если канал существует, в буфер, адрес которого задан в регистрах ES:SI, записывается информация о состоянии канала.

Приведем формат буфера в виде структуры:

struct CSB {
        unsigned char ConnectionState;
        unsigned char ConnectionFlags;
        unsigned char SrcConnectionID[2];
        unsigned char DestConnectionID[2];
        unsigned char SeqNumber[2];
        unsigned char AckNumber[2];
        unsigned char AllocNumber[2];
        unsigned char RemoteAckNumber[2];
        unsigned char RemoteAllocNumber[2];
        unsigned char ConnectionSocket[2];
        unsigned char ImmAddress[6];
        unsigned char DestNetwork[4];
        unsigned char DestNode[6];
        unsigned char DestSocket[2];
        unsigned char RetransmissionCount[2];
        unsigned char EstimatedRoundtripDelay[2];
        unsigned char RetransmittedPackets[2];
        unsigned char SuppressedPackets[2];
};

Все поля в этой структуре имеют "перевернутый" формат, в котором младшие байты записаны по старшему адресу.

Поле ConnectionState отображает текущее состояние канала:

01hдрайвер SPX находится в состоянии ожидания приема пакета, посылаемого функцией SPXEstablishConnection;
02hдрайвер SPX пытается создать канал с удаленной рабочей станцией после вызова функции SPXEstablishConnection;
03hканал создан;
04hканал закрыт.

Поле ConnectionFlags содержит флаги, которые используются драйвером SPX для управления каналом. Бит 02h, в частности, управляет использованием системы периодической проверки связи. Если этот бит установлен в единицу, для данного канала выполняется периодическая проверка связи.

Поле SrcConnectionID содержит номер канала, присвоенный локальной станции. Это тот самый номер канала, который надо загружать в регистр DX перед использованием функций SPX.

Поле DestConnectionID содержит номер канала, присвоенный программе, работающей на удаленной станции.

Поле SeqNumber содержит последовательный номер, который SPX будет использовать для пересылки следующего пакета по каналу.

Поле AckNumber содержит последовательный номер следующего пакета, который должен быть принят по каналу от удаленной станции.

Поле AllocNumber используется драйвером SPX для контроля за пакетами, которые были переданы, но для которых еще не пришло подтверждение о приеме. В нем содержится количество свободных буферов, распределенных для приема пакетов.

Поле RemoteAckNumber содержит номер следующего пакета, который должен быть принят на удаленной станции от локальной станции.

Поле RemoteAllocNumber имеет назначение, аналогичное назначению поля AllocNumber, но относится к удаленной станции.

Поле ConnectionSocket содержит номер сокета, который используется драйвером SPX для приема и передачи пакетов по данному каналу.

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

Поля DestNetwork, DestNode, DestSocket содержат компоненты полного сетевого адреса удаленной станции, с которой локальная станция работает по данному каналу, - номер сети, физический адрес станции в сети и номер сокета.

В поле RetransmissionCount находится максимальное значение количества повторных передач пакетов, по достижении которого SPX делает вывод о невозможности завершения передачи.

Поле EstimatedRoundtripDelay содержит время (в тиках таймера), в течение которого SPX ждет прихода подтверждения приема пакета от удаленной станции. По истечении этого времени SPX начинает выполнять повторную передачу пакета.

Поле RetransmittedPackets содержит количество выполненных повторных передач пакета.

Поле SuppressedPackets содержит количество отвергнутых пакетов. Пакеты могут быть отвергнуты, если они уже были приняты ранее или в настоящий момент нет свободных ECB для их приема.

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);

3.5. Настройка параметров SPX

В разделе, посвященном настройке параметров драйвера IPX, мы говорили о том, что при запуске программы ipxodi.com можно указывать параметры. Если указывается параметр "d", на рабочей станции не загружается диагностический сервис. Если же указывается параметр "a", в память не загружаются драйвер протокола SPX и диагностический сервис.

В документации на вашу программу следует указать о том, какие параметры можно использовать при загрузке ipxodi.com. В частности, если ваша программа использует протокол SPX, параметр "a" задавать нельзя.

Для изменения режима работы драйвера SPX в первых строках файла net.cfg, расположенного в каталоге C:\NET (см. предыдущий том "Библиотеки системного программиста"), можно указывать параметры:

SPX ABORT TIMEOUTВремя в тиках системного таймера, в течение которого драйвер SPX будет ожидать прихода ответа от партнера по каналу, прежде чем будет сделан вывод о невозможности работы с каналом. После истечения этого времени канал будет закрыт.
По умолчанию драйвер ждет 540 тиков, что соответствует примерно 30 с
SPX CONNECTIONSПараметр определяет максимальное количество каналов, которые могут быть созданы на рабочей станции.
По умолчанию можно создавать максимально 15 каналов.
SPX LISTEN TIMEOUTПараметр задает время, в течение которого драйвер SPX будет ждать прихода пакета от партнера. Если за это время пакет не придет, драйвер будет посылать пакеты для проверки работоспособности канала.
По умолчанию это время равно 108 тикам, что составляет примерно 6 с
SPX VERIFY TIMEOUTЭтот параметр задает период времени, с которым драйвер SPX передает пакеты для проверки работоспособности канала связи.
По умолчанию проверочные пакеты передаются с интервалом 54 тика (примерно 3 с).

Например, для увеличения числа доступных каналов до 25 добавьте в начало файла net.cfg строку:

  SPX CONNECTIONS=25