Для некоторых приложений (например, для программ, передающих файлы между рабочими станциями) удобнее использовать сетевой протокол более высокого уровня, обеспечивающий гарантированную доставку пакетов в правильной последовательности. Разумеется, ваша программа может сама следить за тем, чтобы все переданные пакеты были приняты. Однако в этом случае вам придется делать собственную надстройку над протоколом IPX - собственный протокол передачи данных.
Прежде чем принять решение о создании собственного протокола, изучите протокол SPX - протокол последовательного обмена пакетами (Sequenced Packet Exchange Protocol), разработанный Novell. Возможно, что протокол 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 |
Зарезервировано |
10h |
End-of-Message. Этот бит может использоваться
программой для сигнализации окончания передачи
данных. Драйвер SPX передает этот бит программе в
неизменном виде, причем сам драйвер протокола SPX
этот бит игнорирует |
20h |
Attention. Этот бит игнорируется драйвером SPX
и передается в неизменном виде программе |
40h |
Acknowledgement Required. Бит используется
драйвером SPX. Вам не следует модифицировать его
значение |
80h |
System Packet. Этот бит устанавливается
драйвером SPX при передаче системных пакетов,
которые используются самим драйвером и не
передаются в программу пользователя |
Поле DataStreamType также состоит из однобитовых
флагов, которые используются для классификации
данных, передаваемых или принимаемых при помощи
протокола SPX. Приведем возможные значения поля
DataStreamType:
Биты |
Назначение |
00h-FDh |
Эти значения игнорируются драйвером SPX и
могут быть использованы программой произвольным
образом |
FEh |
End-of-Connection. Когда программа вызывает
функцию, закрывающую SPX-канал, драйвер SPX посылает
партнеру по связи последний пакет, в поле DataStreamType
которого записано значение FEh. Это служит
требованием завершить связь и закрыть канал |
FFh |
End-of-Connection-Acknowledgement. Это значение
отмечает пакет, подтверждающий завершение связи.
Такой пакет является системным и не передается в
программу пользователя |
Поле SourceConnID содержит номер канала связи передающей программы, присвоенный драйвером SPX при создании канала связи. Этот номер должен указываться функции передачи пакета средствами SPX.
Поле DestConnID содержит номер канала связи принимающей стороны. Так как все пакеты приходят на один номер сокета и могут принадлежать разным каналам связи (на одном сокете можно открыть несколько каналов связи), вам необходимо классифицировать приходящие пакеты по номеру канала связи.
Поле SeqNumber содержит счетчик пакетов, переданных по каналу в одном направлении. На каждой стороне канала используется свой счетчик. После достижения значения FFFFh счетчик сбрасывается в нуль, после чего процесс счета продолжается.
Содержимым этого поля управляет драйвер SPX, поэтому программа не должна менять его значение.
Поле AckNumber содержит номер следующего пакета, который должен быть принят драйвером SPX.
Содержимым этого поля управляет драйвер SPX, поэтому программа не должна менять его значение.
Поле AllocNumber содержит количество буферов, распределенных программой для приема пакетов.
Содержимым этого поля управляет драйвер SPX, поэтому программа не должна менять его значение.
Для протокола SPX используется точно такой же
блок ECB, что и для протокола IPX.
На входе: |
BX |
= |
10h. |
AL |
= |
00h. |
|
На выходе: |
AL |
= |
Код завершения: |
BH |
= |
Верхний (major) номер версии SPX. |
|
BL |
= |
Нижний (minor) номер версии SPX. |
|
CX |
= |
Максимальное количество каналов SPX,
поддерживаемых драйвером SPX. |
|
DX |
= |
Количество доступных каналов SPX. |
Прежде чем использовать функции SPX, программа должна вызвать функцию SPXCheckInstallation для того, чтобы убедиться в наличии драйвера SPX.
На входе: |
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.
На входе: |
BX |
= |
11h. |
AL |
= |
Счетчик повторов попыток создать канал
связи. |
|
AH |
= |
Флаг включения системы периодической
проверки связи (Watchdog Supervision Required Flag). |
|
ES:SI |
= |
Указатель на блок ECB. |
|
На выходе: |
AL |
= |
Промежуточный код завершения: |
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.
На входе: |
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.
На входе: |
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.
На входе: |
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.
На входе: |
BX |
14h. |
|
DX |
Номер канала связи. |
||
На выходе: |
Регистры не используются. |
Функция разрывает канал связи без "согласования" с партнером. Данная функция должна использоваться только в катастрофических случаях, когда невозможно выполнить нормальную процедуру закрытия канала.
После вызова этой функции во всех ECB, относящихся к данному каналу в поле CCode проставляется значение EDh.
На входе: |
BX |
= |
15h. |
DX |
= |
Номер канала связи. |
|
ES:SI |
= |
Указатель на буфер размером 44 байта. |
|
На выходе: |
AL |
= |
Код завершения: |
С помощью функции 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 для их приема.
Приведем простейший пример, демонстрирующий использование основных функций 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);
В разделе, посвященном настройке параметров драйвера IPX, мы говорили о том, что при запуске программы ipxodi.com можно указывать параметры. Если указывается параметр "d", на рабочей станции не загружается диагностический сервис. Если же указывается параметр "a", в память не загружаются драйвер протокола SPX и диагностический сервис.
В документации на вашу программу следует указать о том, какие параметры можно использовать при загрузке ipxodi.com. В частности, если ваша программа использует протокол SPX, параметр "a" задавать нельзя.
Для изменения режима работы драйвера SPX в первых строках файла net.cfg, расположенного в каталоге C:\NET (см. предыдущий том "Библиотеки системного программиста"), можно указывать параметры:
SPX ABORT TIMEOUT |
Время в тиках системного таймера, в
течение которого драйвер SPX будет ожидать
прихода ответа от партнера по каналу, прежде чем
будет сделан вывод о невозможности работы с
каналом. После истечения этого времени канал
будет закрыт. |
SPX CONNECTIONS |
Параметр определяет максимальное
количество каналов, которые могут быть созданы
на рабочей станции. |
SPX LISTEN TIMEOUT |
Параметр задает время, в течение
которого драйвер SPX будет ждать прихода пакета от
партнера. Если за это время пакет не придет,
драйвер будет посылать пакеты для проверки
работоспособности канала. |
SPX VERIFY TIMEOUT |
Этот параметр задает период времени, с
которым драйвер SPX передает пакеты для проверки
работоспособности канала связи. |
Например, для увеличения числа доступных каналов до 25 добавьте в начало файла net.cfg строку:
SPX CONNECTIONS=25