Локальные сети персональных компьютеров Использование протоколов IPX, SPX, NETBIOS

         

Датаграммы


Передача пакетов данных между рабочими станциями без подтверждения - это тип связи между рабочими станциями на уровне датаграмм (datagram). Уровень датаграмм соответствует сетевому уровню (Network Layer) семиуровневой модели OSI, описанной нами в предыдущем томе.

Что значит "передача без подтверждения"? Это означает, что не гарантируется доставка пакета от передающей станции к принимающей. В результате, например, перегрузки сети или по каким-либо другим причинам принимающая сторона может так и не дождаться предназначенного ей пакета данных. Причем, что характерно для уровня датаграмм, передающая сторона так и не узнает, получила ли принимающая сторона пакет или не получила.

Более того, на уровне датаграмм не гарантируется также, что принимающая сторона получит пакеты в той последовательности, в какой они посылаются передающей станцией!

Казалось бы, зачем нужна такая передача данных, которая не гарантирует доставки? Однако программы, обменивающиеся данными, могут сами организовать проверку. Например, принимающая программа может сама посылать подтверждение передающей программе о том, что получен пакет данных.

Протокол передачи данных IPX - межсетевой протокол передачи пакетов (Internetwork Packet Exchange) - используется в сетевом программном обеспечении Novell и является реализацией уровня датаграмм. Протокол NETBIOS, разработанный фирмой IBM, также может работать на уровне датаграмм.

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

Одно из преимуществ уровня датаграмм - возможность посылки пакетов данных одновременно всем станциям в сети. Если же для программ необходима гарантированная доставка данных, можно использовать протокол более высокого уровня - уровня сеанса связи.



ПЕРЕДАЧА ДАННЫХ В ЛОКАЛЬНОЙ СЕТИ


1.1.

1.2.

1.3.

В локальной сети данные передаются от одной рабочей станции к другой блоками, которые называют пакетами данных. Каждый пакет состоит из заголовка и собственно блока данных. Станция, которая желает передать пакет данных другой станции, указывает в заголовке адрес назначения и свой

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

Продолжая аналогию с письмами, вспомним, что на почте существует такая услуга, как отправка письма или телеграммы с уведомлением о вручении. Когда адресат получит ваше письмо, вам отправляется уведомление о вручении. Вэтом случае вы можете убедиться, что письмо дошло до адресата и не потерялось по дороге.

В локальной сети программы также имеют возможность отправлять "обычные письма", а также "письма с уведомлением о вручении". И, разумеется, в локальной сети имеется своя система адресов.



Сеансы связи




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

На уровне сеансов связи при необходимости выполняются повторные передачи пакетов данных, которые по каким-либо причинам "не дошли" до адресата. Кроме того, гарантируется, что принимающая станция получит пакеты данных именно в том порядке, в котором они были переданы.

При использовании уровня сеансов связи невозможно организовать "широковещательную" передачу пакетов одновременно всем станциям - для передачи данных необходимо организовать канал связи между одной и другой станцией. Следовательно, в процессе передачи данных могут участвовать одновременно только две станции.

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

Протокол NETBIOS реализует наряду с уровнем датаграмм уровень сеансов связи.

В сети Novell NetWare есть эмулятор протокола NETBIOS. Этот эмулятор использует протокол IPX для реализации как уровня датаграмм, так и уровня сеансов связи.



Сетевой адрес


Подобно почтовому адресу, сетевой адрес состоит из нескольких компонентов. Это номер сети, адрес станции в сети и идентификатор программы на рабочей станции - сокет (рис. 1).

Рис. 1. Сетевой адрес

Номер сети (network number) - это номер сегмента сети (кабельного хозяйства), определяемого системным администратором при установке Novell NetWare. Не путайте этот номер с внутренним номером сети файл-сервера. Напомним, что если в одном сегменте сети имеется два файл-сервера NetWare, то они оба имеют одинаковый номер сети, но разные внутренние номера сети. Если в общей сети есть мосты, каждая отдельная сеть, подключенная через мост, должна иметь свой, уникальный номер сети.

Адрес станции (node address) - это число, которое является уникальным для каждой рабочей станции. При использовании адаптеров Ethernet уникальность обеспечивается изготовителем сетевого адаптера (адрес станции записан в микросхеме постоянного запоминающего устройства, которая находится внутри самого адаптера). Для адаптеров ArcNet адрес станции необходимо устанавливать при помощи перемычек или переключателей на плате сетевого адаптера. Устанавливая в сети адаптеры ArcNet, позаботьтесь о том, чтобы все они имели в сети разные адреса. Как установить сетевой адрес адаптера ArcNet, вы сможете узнать из документации, поставляющейся вместе с адаптером.

Специальный адрес FFFFFFFFFFFFh используется для посылки пакета данных всем станциям данной сети одновременно. Пакет с таким адресом напоминает открытое письмо с опубликованием в печати.

Идентификатор программы на рабочей станции

- сокет (socket) - число, которое используется для адресации конкретной программы, работающей на станции. В среде мультизадачных операционных систем, к которым можно отнести OS/2 и Microsoft Windows в расширенном режиме, на каждой рабочей станции в сети одновременно могут быть запущены несколько программ.

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



Диагностический сервис IPX


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

Для посылки диагностического запроса программа должна подготовить IPX-пакет, состоящий из обычного заголовка размером 30 байт и блока данных, имеющего следующую структуру:

struct _REQ { unsigned char Exclusions; unsigned char List[80][6]; };

Заголовок пакета подготавливается обычным образом. В качестве номера сети можно указывать либо действительный номер сети, либо нулевое значение.

В качестве сетевого адреса можно указывать либо адрес конкретной станции, либо адрес FFFFFFFFFFFFh. В поле сокета необходимо проставить значение 0456h.

В поле Exclusions блока данных необходимо проставить количество станций, от которых не требуется получать диагностику. Адреса таких станций должны быть перечислены в массиве List. Если вам надо получить диагностику от всех станций, укажите в поле Exclusions нулевое значение. В любом случае, если диагностика должна быть получена от нескольких станций, в качестве адреса в заголовке пакета необходимо указывать значение FFFFFFFFFFFFh.

Блок ECB для передачи диагностического запроса также подготавливается обычным образом. При первом диагностическом запросе в поле ImmAddress указывается значение FFFFFFFFFFFFh. В дальнейшем при определении конфигурации сети, подключенной через мост, в этом поле вы будете указывать сетевой адрес моста.

Важное замечание относительно сокета 0456h: вы не должны открывать или закрывать этот сокет. Диагностический сокет уже открыт, вы должны использовать его для формирования адреса при передаче диагностического запроса. Для приема ответных пакетов конфигурации (а также для передачи запроса) вам следует динамически получить от драйвера IPX другой сокет.

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

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

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

Приведем структуру первой части:

struct _RESPONSE { unsigned char MajorVersion; unsigned char MinorVersion; unsigned SPXDiagnosticSocket; unsigned char ComponentCount; };

В полях MajorVersion и MinorVersion находится соответственно верхний и нижний номер версии диагностического сервиса.

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

Самое интересное поле - ComponentCount. В нем находится количество компонентов программного и аппаратного обеспечения, информация о которых имеется в принятом пакете конфигурации.

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

Простая структура и в самом деле несложна. Она состоит всего из одного байта идентификатора компонента:

struct _SIMPLE_COMPONENT { unsigned char ComponentID; };

Значениями поля ComponentID для простой структуры могут быть числа 0, 1, 2, 3 или 4:

Значение поля ComponentID Компонент
0 Драйвер IPX/SPX
1 Драйвер программного обеспечения моста
2 Драйвер сетевой оболочки рабочей станции
3 Сетевая оболочка
4 Сетевая оболочка в виде VAP-процесса
<


Расширенная структура сама по себе состоит из двух частей, имеющих соответственно, фиксированную и переменную структуру.

Приведем формат фиксированной части:

struct _EXTENDED_COMPONENT { unsigned char ComponentID; unsigned char NumberOfLocalNetworks; };

Поле ComponentID может содержать значения 5, 6 или 7:

Значение поля ComponentID Компонент
5 Внешний мост
6 Файл-сервер с внутренним мостом
7 Невыделенный файл-сервер
Для определения конфигурации сети важно исследовать компоненты с типом 5, 6 и 7, так как именно они имеют отношение к соединениям сетей через мосты.

Переменная часть описывает сети, подключенные к компонентам с типом 5, 6 или 7. Количество таких сетей находится в поле NumberOfLocalNetworks фиксированной части.

Для описания сетей используется массив структур (размерностью NumberOfLocalNetworks):

struct _NETWORK_COMPONENT { unsigned char NetworkType; unsigned char NetworkAddress[4]; unsigned char NodeAddress[6]; };

Поле NetworkType описывает тип сети:

Содержимое поля NetworkType Тип сети
0 Сеть, к которой подключен сетевой адаптер
1 Сеть с виртуальным сетевым адаптером (невыделенный файл-сервер)
2 Переназначенная удаленная линия (связь сетей через модемы)
Поле NetworkAddress содержит номер сети, к которой подключен соответствующий адаптер, а поле NodeAddress - сетевой адрес адаптера. Именно эти поля вам и нужны для определения номеров сетей, подключенных к мостам, и сетевых адресов самих мостов.


Другие функции IPX и AES


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

Мы рассмотрим также функции асинхронного планировщика событий AES (Asynchronous Event Scheduer), выполняющегося как процесс внутри драйвера IPX.



Формат блока ECB


Формат блока ECB представлен на рис. 3.

Рис. 3. Формат блока ECB

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

struct ECB { void far *Link; void far (*ESRAddress)(void); unsigned char InUse; unsigned char CCode; unsigned int Socket; unsigned int ConnectionId; unsigned int RrestOfWorkspace; unsigned char DriverWorkspace[12]; unsigned char ImmAddress[6]; unsigned int FragmentCnt; struct { void far *Address; unsigned int Size; } Packet[2]; };

Рассмотрим назначение отдельных полей блока ECB.

Поле Link предназначено для организации списков, состоящих из блоков ECB. Драйвер IPX использует это поле для объединения переданных ему блоков ECB в списки, записывая в него полный адрес в формате [сегмент:смещение]. После того, как IPX выполнит выданную ему команду и закончит все операции над блоком ECB, программа может распоряжаться полем Link по своему усмотрению. В частности, она может использовать это поле для организации списков или очередей свободных или готовых для чтения блоков ECB.

Поле ESRAddress содержит полный адрес программного модуля (в формате [сегмент:смещение]), который получает управление при завершении процесса чтения или передачи пакета IPX. Этот модуль называется программой обслуживания события ESR (Event Service Routine). Если ваша программа не использует ESR, она должна записать в поле ESRAddress нулевое значение. В этом случае о завершении выполнения операции чтения или передачи можно узнать по изменению содержимого поля InUse.

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

рация передачи данных, связанная с данным ECB, не завершилась, поле InUse содержит ненулевые значения:


FFh ECB используется для передачи пакета данных;
FEh ECB используется для приема пакета данных, предназначенного программе с определенным сокетом;
FDh ECB используется функциями асинхронного управления событиями AES (Asynchronous Event Sheduler), ECB находится в состоянии ожидания истечения заданного временного интервала;
FBh пакет данных принят или передан, но ECB находится во внутренней очереди IPX в ожидании завершения обработки.
Функции асинхронного управления AES будут рассмотрены позже.

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

Поле CCode после выполнения функции IPX (после того, как в поле InUse будет нулевое значение) содержит код результата выполнения.

Если с данным ECB была связана команда приема пакета, в поле CCode могут находиться следующие значения:

00 пакет был принят без ошибок;
FFh указанный в ECB сокет не был предварительно открыт программой;
FDh переполнение пакета: либо поле количества фрагментов в пакете FragmentCnt равно нулю, либо буферы, описанные дескрипторами фрагментов, имеют недостаточный размер для записи принятого пакета;
FCh запрос на прием данного пакета был отменен специальной функцией драйвера IPX.
Если ECB использовался для передачи пакета, в поле CCode после завершения передачи могут находиться следующие значения:

00 пакет был передан без ошибок (что, кстати, не означает, что пакет был доставлен по назначению и успешно принят станцией-адресатом, так как протокол IPX не обеспечивает гарантированной доставки пакетов);
FFh пакет невозможно передать физически из-за неисправности в сетевом адаптере или в сети;
FEh пакет невозможно доставить по назначению, так как станция с указанным адресом не существует или неисправна;
FDh сбойный: либо имеет длину меньше 30 байт, либо первый фрагмент пакета по размеру меньше размера стандартного заголовка пакета IPX, либо поле количества фрагментов в пакете FragmentCnt равно нулю;
FCh запрос на передачу данного пакета был отменен специальной функцией драйвера IPX.
<


Поле Socket содержит номер сокета, связанный с данным ECB. Если ECB используется для приема, это поле содержит номер сокета, на котором выполняется прием пакета. Если же ECB используется для передачи, это поле содержит номер сокета передающей программы (но не номер сокета той программы, которая должна получить пакет).

Поле IPXWorkspace зарезервировано для использования драйвером IPX. Ваша программа не должна инициализировать или изменять содержимое этого поля, пока обработка ECB не завершена.

Поле DriverWorkspace зарезервировано для использования драйвером сетевого адаптера. Ваша программа не должна инициализировать или изменять содержимое этого поля, так же как и поля IPXWorkspace, пока обработка ECB не завершена.

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

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

данные и заголовок пакета. Механизм фрагментации позволяет вам избежать пересылок данных или непроизводительных потерь памяти. Вы можете указать отдельные буферы для приема данных и заголовка пакета. Если сами принимаемые данные имеют какую-либо структуру, вы можете рассредоточить отдельные блоки по соответствующим буферам.

Значение, записанное вами в поле FragmentCnt, не должно быть равно нулю. Если в этом поле записано значение 1, весь пакет вместе с заголовком записывается в один общий буфер.

Сразу вслед за полем FragmentCnt располагаются дескрипторы фрагментов, состоящие из указателя в формате [сегмент:смещение] на фрагмент Address и поля размера фрагмента Size.

Если программе надо разбить принятый пакет на несколько частей, она должна установить в поле FragmentCnt значение, равное количеству требуемых фрагментов. Затем для каждого фрагмента необходимо создать дескриптор, в котором указать адрес буфера и размер фрагмента. Аналогичные действия выполняются и при сборке пакета перед передачей из нескольких фрагментов.

Отметим, что самый первый фрагмент не должен быть короче 30 байт, так как там должен поместиться заголовок пакета IPX.


Формат пакета IPX


Формат передаваемых по сети пакетов представлен на рис. 2.

Рис. 2. Структура пакета IPX

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

Особенностью формата пакета является то, что все поля заголовка содержат значения в перевернутом формате, т. е. по младшему адресу записывается старший байт данных, а не младший, как это принято в процессорах фирмы Intel. Поэтому перед записью значений в многобайтовые поля заголовка необходимо выполнить соответствующее преобразование. Представление данных в заголовке пакета соответствует, например, формату целых числел в компьютере IBM-370 (серия ЕС ЭВМ).

Рассмотрим подробнее назначение отдельных полей пакета.

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

Поле Length определяет общий размер пакета вместе с заголовком. Длина заголовка фиксирована и составляет 30 байт. Размер передаваемых в поле Data данных может составлять от 0 до 546 байт, следовательно, в поле Length в зависимости от размера поля Data могут находиться значения от 30 до 576 байт. Если длина поля Data равна нулю, пакет состоит из одного заголовка. Как это ни странно, такие пакеты тоже нужны! При формировании собственных пакетов вам не надо проставлять длину пакета в поле Length, протокол IPX сделает это сам (вернее, программный модуль, отвечающий за реализацию протокола IPX, вычислит длину пакета на основании длины поля Data).

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

Поле PacketType определяет тип передаваемого пакета. Программа, которая передает пакеты средствами IPX, должна установить в поле PacketType значение 4. Протокол SPX, реализованный на базе IPX, использует в этом поле значение 5.

Поле DestNetwork определяет номер сети, в которую передается пакет. При формировании собственного пакета вам необходимо заполнить это четырехбайтовое поле. Напомним, что номер сети задается сетевым администратором при установке Novell NetWare на сервер.

Поле DestNode определяет адрес рабочей станции, которой предназначен пакет. Вам необходимо определить все шесть байт этого поля.

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

Поля SourceNetwork, SourceNode и SourceSocket содержат соответственно номер сети, из которой посылается пакет, адрес передающей станции и сокет программы, передающей пакет.

Поле Data в пакете IPX содержит передаваемые данные. Как мы уже говорили, длина этого поля может быть от 0 до 546 байт. Если длина поля Data равна нулю, пакет состоит из одного заголовка. Такой пакет может использоваться программой, например, для подтверждения приема пакета с данными.

Для формирования заголовка пакета можно воспользоваться, например, следующей структурой:

struct _IPXHeader { unsigned char Checksum[2]; unsigned char Length[2]; unsigned char TransportControl; unsigned char PacketType; unsigned char DestNetwork[4]; unsigned char DestNode[6]; unsigned char DestSocket[2]; unsigned char SourceNetwork[4]; unsigned char SourceNode[6]; unsigned char SourceSocket[2]; } IPXHeader;

Обращаем ваше внимание на то, что все многобайтовые поля описаны как массивы. Даже те, которые состоят из двух байт и могли бы быть описаны как unsigned int.Это связано с тем, что все значения в заголовке пакета IPX хранятся в перевернутом виде, а для такого типа данных в языке Си нет подходящего описания.


Функции AES


Если вашей программе требуется измерять временные интервалы, она может воспользоваться асинхронным планировщиком событий AES, реализованным

в рамках драйвера IPX.

Для функций AES можно использовать тот же формат ECB, что и для функций IPX. Однако поля используются немного по-другому:

struct AES_ECB { void far* Link; void (far *ESRAddress)(); unsigned char InUse; unsigned char AESWorkspace[5]; };

Поле AESWorkspace используется планировщиком AES. Назначение остальных полей полностью аналогично соответствующим полям обычного ECB.



Функции для работы с сокетами


В этом разделе мы опишем функции IPXOpenSocket и IPXCloseSocket, предназначенные для получения и освобождения сокетов.



Инициализация сервера и клиента


Для инициализации программ сервера и клиента, работающих на базе IPX, недостаточно убедиться в наличии соответствующего драйвера и получить точку входа в его API. Вам необходимо выполнить некоторые подготовительные действия для того, чтобы программа могла принимать и передавать пакеты данных.

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

Для хранения сокета используется двухбайтовое слово, так что диапазон возможных значений простирается от 0 до FFFFh. Однако вы не можете использовать произвольные значения.

Некоторые значения зарезервированы для использования определенными программами. Это так называемые "хорошо известные" сокеты ("well-known" sockets).

Так как протокол IPX является практической реализацией протокола Xerox Internetwork Packet Protocol, первоначальное распределение сокетов выполняется фирмой Xerox. Согласно этому распределению сокеты от 0 до 3000 зарезервированы статически за определенным программным обеспечением. В частности, фирма Novell получила от фирмы Xerox диапазон сокетов для своей

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

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

Распределение сокетов в сети Novell NetWare несколько отличается от

распределения, установленного фирмой Xerox. Сокеты от 0 до 4000h зарезервированы и не должны использоваться в программном обеспечении пользователей. Сокеты от 4000h до 8000h распределяются динамически. Диапазон "хорошо известных" сокетов, распределяемых Novell персонально разработчикам программного обеспечения, расположен выше значения 8000h.

Вы, как разработчик программного обеспечения для сетей NetWare, можете получить у Novell для своей программы персональный сокет (если сумеете это сделать) или воспользоваться сокетом, полученным динамически.
Можно задавать сокет в качестве параметра при запуске программы. Если вы обнаружите, что используемое вами значение сокета конфликтует с другим программным обеспечением, вы легко сможете изменить его, просто задавая новое значение для соответствующего параметра.

При реализации схемы обмена данными "клиент-сервер" сервер обычно принимает пакеты на сокете, значение которого известно программам-клиентам. Сами же программы-клиенты могут использовать либо то же самое значение сокета, либо получать свой сокет динамически. Клиент может сообщить серверу свой сокет просто передав его в пакете данных (так как мы предполагаем, что сокет сервера известен программе-клиенту).

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

Если программа-клиент знает только сокет программы-сервера, но не знает его сетевой адрес, последний можно запросить у сервера, послав запрос во все станции одновременно. Такой запрос в пределах одного сегмента сети можно выполнить, если в качестве адреса рабочей станции указать специальное значение FFFFFFFFFFFFh. Это так называемый "широковещательный" (broadcast) адрес.

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

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


IPXCancelEvent


На входе: BX = 06h.
ES:SI = Указатель на блок ECB.
На выходе: AL = Код завершения:

00h - функция выполнена без ошибок;

F9h - обработка ECB не может быть отменена;

FFh - указанный ECB не используется.

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

После отмены ECB поле CCode в нем устанавливается в соответствующее состояние, поле InUse устанавливается в нуль. Для отмененного ECB программа ESR не вызывается.



IPXCloseSocket


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

Функция закрывает заданный в регистре DX сокет, короткоживущий или долгоживущий.

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

Для отмененных ECB программы ESR не вызываются.

Функцию IPXCloseSocket нельзя вызывать из программы ESR.



IPXDisconnectFromTaget


На входе: BX = 0Bh.
ES:SI = Указатель на структуру, содержащую сетевой адрес станции:

struct NetworkAddress { unsigned char Network[4]; unsigned char Node[6]; unsigned char Socket[2]; };

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

Эта функция используется программой для того, чтобы сообщить сетевому коммуникационному драйверу, что она (программа) больше не будет посылать пакеты на указанную станцию. Соответствующий драйвер освобождает виртуальный канал на уровне платы сетевого адаптера для указанного сетевого адреса.

Функцию IPXDisconnectFromTaget нельзя вызывать из программы ESR.



IPXGetInternetworkAddress


На входе: BX = 09h.
ES:DI = Указатель на буфер длиной 10 байт; в него будет записан адрес станции, на которой работает данная программа. Адрес состоит из номера сети Network и адреса станции в сети Node.
На выходе: Регистры не используются.

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

Формат буфера аналогичен представленному на рис. 4, за исключением того, что в буфер не записывается сокет. Считается, что сокет программа знает, так как она его открывала.



IPXGetIntervalMarker


На входе: BX = 08h.
На выходе: AX = Интервальный маркер.

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

Возвращаемое значение - интервальный маркер - это значение, лежащее в интервале от 0000h до FFFFh и представляющее собой время в тиках таймера (следуют с интервалом примерно 1/18 секунды).

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

Отметим, что вместо использования этой функции можно опрашивать значение двойного слова в области данных BIOS по адресу 0000h:046Ch. В этом слове хранится счетчик тиков таймера, значение которого обновляется каждые 55 миллисекунд.



IPXGetLocalTaget


На входе: BX = 02h.
ES:SI = Указатель на буфер длиной 12 байт, содержащий полный сетевой адрес станции, на которую будет послан пакет.
ES:DI = Указатель на буфер длиной 6 байт, в который будет записан непосредственный адрес, т. е. адрес той станции, которой будет передан пакет. Это может быть адрес моста.
На выходе: AL = Код завершения:

00h - непосредственный адрес был успешно вычислен;

FAh - непосредственный адрес вычислить невозмож- но, так как к указанной станции нет ни одного пути доступа по сети.

CX = Время пересылки пакета до станции назначения (только если AL равен нулю) в тиках системного таймера. Тики таймера следуют с периодом примерно 1/18 секунды.

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

Так как станция-получатель может находиться в другой сети, прежде чем достигнуть цели, пакет может пройти один или несколько мостов. Поле непосредственного адреса ImmAddress блока ECB должно содержать либо адрес станции назначения (если передача происходит в пределах одной сети), либо адрес моста (если пакет предназначен для рабочей станции, расположенной в другой сети). Используя указанный в буфере размером 12 байт полный сетевой адрес, состоящий из номера сети, адреса станции в сети и сокета приложения, функция IPXGetLocalTaget вычисляет непосредственный адрес, т. е. адрес той станции в данной сети, которая получит передаваемый пакет.

Формат полного адреса представлен на рис. 4.

Рис. 4. Формат полного адреса

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

struct NET_ADDRESS { unsigned char Network[4]; unsigned char Node[6]; unsigned char Socket[2]; };

В поле Network указывается номер сети, в которой расположена станция, принимающая пакет.

Поле Node должно содержать адрес станции в сети с номером, заданным содержимым поля Network. Если пакет должны принять все станции, находящиеся в сети Network, в поле Node необходимо записать адрес FFFFFFFFFFFFh.

Поле Socket адресует конкретную программу, работающую на станции с заданным адресом.

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



IPXListenForPacket


На входе: BX = 04h.
ES:DI = Указатель на заполненный блок ECB. Необходимо заполнить поля:

ESRAddress;

Socket;

FragmentCnt;

указатели на буферы фрагментов Address;

размеры фрагментов Size.

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

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

Данная функция сразу возвращает управление вызвавшей ее программе, не дожидаясь прихода пакета. Определить момент приема пакета программа может либо анализируя поле InUse блока ECB, либо указав перед вызовом функции адрес программы ESR (в блоке ECB), которая получит управление сразу после прихода пакета. Если программа ESR не используется, в поле ESRAddress должно быть нулевое значение.

Сразу после вызова функции IPXListenForPackets в поле InUse блока ECB устанавливается значение FEh, которое означает, что для данного блока ECB ожидается прием пакета. Как мы уже говорили, программа может ожидать одновременно много пакетов.

Если программа подготовила для приема пакетов несколько блоков ECB, то для приема пришедшего пакета будет использован один из подготовленных ECB. Однако не гарантируется, что блоки ECB будут использоваться в том порядке, в котором они ставятся на ожидание приема функцией IPXListenForPackets. Если свободных, ожидающих приема пакета, блоков ECB нет, то приходящий пакет будет проигнорирован. Аналогично, если ожидается пакет по данному сокету, а сокет не открыт, пришедший пакет также будет проигнорирован.

После прихода пакета в поле CCode использованного блока ECB драйвер IPX записывает код результата приема пакета, а в поле ImmAddress - непосредственный адрес станции, из которой пришел пакет. Если пакет пришел из другой сети, в этом поле будет стоять адрес моста (адрес моста в той сети, где находится принимающая станция).


Затем в поле InUse блока ECB проставляется нулевое значение и вызыва-

ется программа ESR, если ее адрес был задан перед вызовом функции IPXListenForPackets.

После приема пакета в поле CCode могут находиться следующие значения:

00 пакет был принят без ошибок;
FFh указанный в ECB сокет не был предварительно открыт программой;
FDh переполнение пакета: либо поле количества фрагментов в пакете FragmentCnt равно нулю, либо буферы, описанные дескрипторами фрагментов, имеют недостаточный размер для записи принятого пакета;
FCh запрос на прием данного пакета был отменен специальной функцией драйвера IPX.
Функция IPXListenForPackets может использоваться для приема только таких пакетов, в адресе назначения которых указан сокет, совпадающий с номером сокета, подготовленного в блоке ECB. Перед тем, как использовать сокет для приема пакетов, его необходимо открыть функцией IPXOpenSocket, описанной выше.

Если запрос на прием пакета был отменен специальной функцией или в результате выполнения функции IPXCloseSoket, поле InUse блока ECB устанавливается в нулевое значение, однако программа ESR, даже если ее адрес был задан, не вызывается. В поле CCode проставляется значение FCh.


IPXOpenSocket


На входе: BX = 00h.
AL = Тип сокета:

00h - короткоживущий;

FFh - долгоживущий.

DX = Запрашиваемый номер сокета или 0000h, если требуется получить динамический номер сокета.

Примечание. Байты номера сокета находятся в перевернутом виде.

На выходе: AL = Код завершения:

00h - сокет открыт;

FFh - этот сокет уже был открыт раньше;

FEh - переполнилась таблица сокетов.

DX = Присвоенный номер сокета.

Перед началом передачи пакетов программа должна получить свой идентификатор - сокет. Функция IPXOpenSocket как раз и предназначена для получения сокета.

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

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

Если вы не используете динамическое распределение сокетов и задаете свой номер сокета, используйте значения в диапазоне от 4000h до 8000h или получите персональный зарегистрированный сокет у фирмы Novell.

По умолчанию при загрузке оболочки рабочей станции вам доступно максимально 20 сокетов. При соответствующей настройке сетевой оболочки вы можете увеличить это значение до 150.



IPXRelinquishControl


На входе: BX = 0Ah.
На выходе: Регистры не используются.

Если ваша программа не использует ESR, она, очевидно, должна в цикле опрашивать поле InUse блока ECB, для которого выполняется ожидание завершения процесса приема или передачи пакета. Однако для правильной работы драйвера IPX в цикл ожидания необходимо вставлять вызов функции IPXRelinquishControl. Эта функция выделяет драйверу IPX процессорное время, необходимое для его правильной работы.


На входе: BX = 0Ah.
На выходе: Регистры не используются.

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



IPXScheduleIPXEvent


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

Функция IPXScheduleIPXEvent немедленно возвращает управление вызвавшей ее программе. После истечения временного интервала, заданного в регистре AX, поле InUse блока ECB, адрес которого задавался при вызове этой функции, сбрасывается в ноль. После этого вызывается программа ESR, если она была задана для данного ECB.

Обычно функция IPXScheduleIPXEvent используется внутри ESR, для того чтобы отложить на некоторое время обработку принятого пакета.



IPXSendPacket


На входе: BX = 03h.
ES:DI = Указатель на заполненный блок ECB. Необходимо заполнить поля:

ESRAddress;

Socket;

ImmAddress;

FragmentCnt;

указатели на буферы фрагментов Address;

размеры фрагментов Size.В заголовке пакета IPX необходимо заполнить поля:

PacketType;

DestNetwork;

DestNode;

DestSocket.

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

Эта функция подготавливает блок ECB и связанный с ним заголовок пакета для передачи пакета по сети. Она сразу возвращает управление вызвавшей ее программе, не дожидаясь завершения процесса передачи пакета. Определить момент завершения передачи пакета программа может либо анализируя поле InUse блока ECB, либо указав перед вызовом функции адрес программы ESR (в блоке ECB), которая получит управление сразу после завершения процесса передачи пакета. Если программа ESR не используется, в поле ESRAddress должно быть нулевое значение.

Перед вызовом этой функции вам необходимо заполнить указанные выше поля в блоке ECB, подготовить заголовок пакета и, разумеется, сам передаваемый пакет. Затем вы вызываете функцию IPXSendPacket, которая ставит блок ECB в очередь на передачу. Сама передача пакета происходит асинхронно по отношению к вызывавшей ее программе.

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

Сразу после вызова функции IPXSendPacket в поле InUse блока ECB устанавливается значение FFh. После завершения процесса передачи пакета поле InUse принимает значение 00h. Результат выполнения передачи пакета можно узнать, если проанализировать поле CCode блока ECB:

00 пакет был передан без ошибок (что, кстати, не означает, что пакет был доставлен по назначению и успешно принят станцией-адресатом, так как протокол IPX не обеспечивает гарантированной доставки пакетов);
FFh пакет невозможно передать физически из-за неисправности в сетевом адаптере или в сети;
FEh пакет невозможно доставить по назначению, так как станция с указанным адресом не существует или неисправна;
FDh сбойный пакет: либо имеет длину меньше 30 байт, либо первый фрагмент пакета по размеру меньше размера стандартного заголовка пакета IPX, либо поле количества фрагментов в пакете FragmentCnt равно нулю;
FCh запрос на передачу данного пакета был отменен специальной функцией драйвера IPX.
<
Обратим еще раз ваше внимание на то, что, даже если код завершения в поле CCode равен нулю, это не гарантирует успешной доставки пакета адресату.

Из-за чего пакет может не дойти до адресата? Во-первых, пакет может быть потерян в процессе передачи по кабелю. Во-вторых, станция, адрес которой указан в заголовке пакета, может не работать или такой станции может вообще не быть в указанной сети. В-третьих, станция-адресат может не ожидать пакет на указанном сокете.

Если в поле CCode оказалось значение FEh, это также может произойти по трем причинам. Во-первых, если пакет предназначен для другой сети, может оказаться так, что невозможно найти мост, который соединял бы эти сети. Во-вторых, если пакет предназначен для станции в той же сети, может произойти сбой в сетевом адаптере или другом сетевом оборудовании. В-третьих, пакет может быть передан станции, на которой не открыт соответствующий сокет или нет запросов на прием пакета по данному сокету.

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


Использование API драйвера IPX


Теперь, после того как мы научились проверять наличие драйвера IPX и определять точку входа для вызова его API, нам предстоит научиться пользоваться этим API. Без преувеличения можно сказать, что от того, насколько хорошо вы освоите API драйвера IPX, зависят ваши успехи в создании программного обеспечения для сетей Novell NetWare.

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



Настройка параметров IPX


Мы уже говорили, что драйвер протоколов IPX/SPX для MS-DOS реализован в виде резидентной программы. В версии 3.11 операционной системы Novell NetWare на рабочих станциях MS-DOS используется программа ipxodi.com.

При запуске этой программы вы можете указать параметры "d" и "a". Если указывается параметр "d", на рабочей станции не загружается диагностический сервис, что экономит примерно 4 Кбайт памяти. Если же указывается параметр "a", в память загружается только драйвер протокола IPX, а драйвер протокола SPX и диагностический сервис не загружаются. При этом освобождается 8 Кбайт основной памяти.

Однако учтите, что такие сетевые утилиты, как RCONSOLE и NVER, требуют присутствия драйвера протокола SPX и диагностического сервиса.

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

IPX RETRY COUNT Параметр определяет, сколько раз будет выполнена повторная передача пакета, прежде чем будет сделан вывод о невозможности его передачи. Сам протокол IPX не выполняет повторные передачи (так как этот протокол не гарантирует доставку передаваемых пакетов), но это значение используется протоколами более высокого уровня, реализованными на базе IPX, в частности протоколом SPX.

По умолчанию пакет передается 20 раз.

IPX SOCKETS Параметр определяет максимальное количество сокетов, которые программа может открыть на рабочей станции.

По умолчанию можно открыть 20 сокетов.

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

Составляя документацию по установке разрабатываемого вами сетевого программного обеспечения, не забудьте упомянуть о том, как надо настраивать параметры протокола IPX (если такая настройка необходима для нормальной работы вашей программы).



Определение топологии сети


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

В качестве номера сети можно указать нуль, при этом вам не надо будет даже знать номер сети, в которой расположена принимающая станция. Мы уже приводили примеры программ, передающих данные в пределах одной сети.

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

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

Программа-клиент знала номер сокета, который используется программой-сервером. Клиент посылал пакет на этот сокет, при этом в качестве номера сети использовалось нулевое значение (пакет предназначен для передачи в пределах той сети, в которой находится передающая станция), а в качестве сетевого адреса станции - значение FFFFFFFFFFFFh (пакет предназначен для всех станций в сети). Сервер, расположенный в той же сети, что и клиент, принимал такой пакет. Анализируя поле "обратного адреса" пакета, сервер мог определить точный сетевой адрес клиента. Более того, в поле ImmAddress блока ECB, использовавшегося для приема пакета, стоял непосредственный адрес станции, от которой пришел пакет (пакет мог прийти и из другой сети через мост, в этом случае в поле ImmAddress стоял бы адрес моста).

Узнав сетевой адрес клиента, сервер отправлял ему пакет. Приняв пакет от сервера, клиент мог определить сетевой адрес сервера из заголовка пришедшего к нему пакета.
Поле ImmAddress блока ECB, использовавшегося при приеме пакета от сервера, содержало непосредственный адрес станции, от которой пришел пакет.

Если сервер и клиент расположены в разных сетях, ситуация сильно усложняется. Если клиент будет посылать пакет по адресу FFFFFFFFFFFFh, указав нулевой номер сети, пакет будет принят только теми станциями, которые

расположены в той же сети, что и передающая станция. Через мост такой пакет не пройдет, поэтому если программа-сервер работает на станции, которая находится в другой сети, она не получит пакет от клиента.

Для того, чтобы пакет был принят всеми станциями сети, подключенной через мост, вам необходимо послать этот пакет в мост, указав в заголовке пакета номер сети, в которую передается пакет, а также адрес станции, равный FFFFFFFFFFFFh. Для того, чтобы послать пакет в мост, в поле ImmAddress соответствующего блока ECB надо указать адрес моста.

Следовательно, для того чтобы установить связь с сервером, программа-клиент должна узнать номер сети, в которой расположен сервер, и сетевой адрес моста, через который можно послать пакет в эту сеть. К сожалению, ни одна из функций драйвера IPX или SPX не возвращает информации о конфигурации сети, поэтому ваша программа должна уметь получать такую информацию самостоятельно.

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

Сетевая оболочка, запущенная на рабочих станциях в сети Novell NetWare, может принимать пакеты на специальном диагностическом сокете с номером 0456h. В ответ на принятый пакет диагностический сервис возвращает станции, пославшей такой пакет, информацию о конфигурации сетевого программного и аппаратного обеспечения станции.

Основная идея определения конфигурации сети заключается в том, что программа-клиент посылает запрос о конфигурации одновременно всем станциям данной сети на сокете 0456h, указав в качестве номера сети нуль, а в качестве адреса станции значение FFFFFFFFFFFFh.


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

Зная сетевой адрес мостов и номера подключенных к ним сетей, программа-клиент сможет посылать запросы для поиска программы-сервера во все подключенные к мостам сети.

Очевидно, можно посылать диагностические запросы на сокете 0456h и в другие сети с целью поиска имеющихся там мостов. Таким образом можно выяснить конфигурацию всей сети и установить связь с программой-сервером, где бы она ни находилась.

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


Основные функции API драйвера IPX


API драйвера протокола IPX состоит из примерно дюжины функций, предназначенных для выполнения операций с сокетами, сетевыми адресами, для приема и передачи пакетов и некоторых других операций. В этом разделе мы кратко рассмотрим состав и назначение основных функций IPX.



Прием и передача пакетов данных


Рассмотрим теперь процедуру приема пакетов данных средствами IPX.

Прием и передачу пакетов выполняет сетевой адаптер, работающий с использованием прерываний. Некоторые сетевые адаптеры работают с памятью через канал прямого доступа DMA. Прерывание от сетевого адаптера обрабатывает драйвер сетевого адаптера. Например, в операционной системе MS-DOS для адаптеров, совместимых с адаптером Novell NE2000 в составе Novell NetWare поставляется драйвер ne2000.com, реализованный в виде резидентной программы.

Прикладные программы не работают напрямую с драйвером сетевого адаптера. Все свои запросы на прием и передачу пакетов они направляют драйверу IPX (программа ipx.exe или ipxodi.exe), который, в свою очередь, обращается к драйверу сетевого адаптера.

Для приема или передачи пакета прикладная программа должна подготовить пакет данных, сформировав его заголовок, и построить так называемый блок управления событием ECB (Event Control Block). В блоке ECB задается адресная информация для передачи пакета, адрес самого передаваемого пакета в оперативной памяти и некоторая другая информация.

Подготовив блок ECB, прикладная программа передает его адрес соответствующей функции IPX для выполнения операции приема или передачи пакета.

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

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



Пример c использованием ESR


В предыдущем примере при ожидании пакета мы опрашивали в цикле поле InUse блока ECB. Однако более эффективным является использование программы ESR. Эта программа получает управление тогда, когда пакет принят и поле InUse установлено в ноль.

Когда ESR получает управление, регистры процессора содержат следующие значения:

AL идентификатор вызывающего процесса:

FFh - программа ESR вызвана драйвером IPX;

00h - программа ESR вызвана планировщиком асинхронных событий AES (будет описан позже);

ES:SI адрес блока ECB, связанного с данной ESR.

Содержимое всех регистров, кроме SS и SP, а также флаги процессора записаны в стек программы.

Если ESR будет обращаться к глобальным переменным программы, необходимо правильно загрузить регистр DS. Непосредственно перед вызовом ESR прерывания запрещаются. Функция ESR не возвращает никакого значения и должна завершать свою работу командой дальнего возврата RETF. Перед возвратом управления прерывания должны быть запрещены.

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

Наша программа-ESR (листинг 8) выполняет простую задачу - записывает адрес связанного с ней блока ECB в глобальную переменную completed_ecb_ptr, которая сбрасывается в главной программе перед ожиданием приема пакета. Программа-клиент (листинг 7), ожидая прихода пакета, выполняет какие-либо действия (в нашем случае она просто вводит символы с клавиатуры и выводит их на экран) и периодически опрашивает глобальную переменную. Как только пакет будет принят, в эту переменную будет записан отличный от нуля адрес блока ECB.

// =================================================== // Листинг 7. Клиент IPX // Файл ipxclien.c // // (C) A. Frolov, 1993 // ===================================================

#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include <dos.h> #include "ipx.h"


// Максимальный размер буфера данных
#define BUFFER_SIZE 512
extern struct ECB far * completed_ecb_ptr; extern void far ipxspx_esr(void);
void main(void) {
// Будем работать с сокетом 0x4567
static unsigned Socket = 0x4567;
// ECB для приема и передачи пакетов
struct ECB RxECB, TxECB;
// Заголовки принимаемых и передаваемых пакетов
struct IPX_HEADER RxHeader, TxHeader;
// Буферы для принимаемых и передаваемых данных
unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE];
printf("\n*Клиент IPX*, (C) Фролов А., 1993\n\n");
// Проверяем наличие драйвера IPX и определяем // адрес точки входа его API
if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); }
// Открываем сокет, на котором будем принимать и передавать пакеты
if(IPXOpenSocket(SHORT_LIVED, &Socket)) { printf("Ошибка при открытии сокета\n"); exit(-1); };
// Подготавливаем ECB для передачи пакета
memset(&TxECB, 0, sizeof(TxECB));
TxECB.Socket = IntSwap(Socket); TxECB.FragmentCnt = 2; TxECB.Packet[0].Address = &TxHeader; TxECB.Packet[0].Size = sizeof(TxHeader); TxECB.Packet[1].Address = TxBuffer; TxECB.Packet[1].Size = BUFFER_SIZE;
// Пакет предназначен всем станциям данной сети
memset(TxECB.ImmAddress, 0xff, 6);
// Подготавливаем заголовок пакета
TxHeader.PacketType = 4; memset(TxHeader.DestNetwork, 0, 4); memset(TxHeader.DestNode, 0xff, 6); TxHeader.DestSocket = IntSwap(Socket);
// Записываем передаваемые данные
strcpy(TxBuffer, "ESR/CLIENT *DEMO*");
// Передаем пакет всем станциям в данной сети
IPXSendPacket(&TxECB);
completed_ecb_ptr = (unsigned long)0;
// Подготавливаем ECB для приема пакета от сервера
memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &RxHeader; RxECB.Packet[0].Size = sizeof(RxHeader); RxECB.Packet[1].Address = RxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE; RxECB.ESRAddress = ipxspx_esr;
IPXListenForPacket(&RxECB);


printf("Ожидание ответа от сервера\n"); printf("Нажимайте любые клавиши\n"); printf("Для отмены нажмите клавишу <ESC>\n");
// Ожидаем прихода ответа от сервера
while(completed_ecb_ptr == NULL) { if( getche() == 27) { IPXCloseSocket(&Socket); exit(0); } } if(RxECB.CCode == 0) { printf("\nПринят ответ от сервера '%s'\n", RxBuffer); }
// Закрываем сокет
IPXCloseSocket(&Socket); exit(0); }
В листинге 8 приведен текст программы ESR, составленный на языке ассемблера. Программа загружает регистр DS адресом сегмента данных программы, затем записывает в глобальную переменную completed_ecb_ptr содержимое регистров ES:SI.
; =================================================== ; Листинг 8. Программа ESR ; Файл esr.asm ; ; (C) A. Frolov, 1992 ; ===================================================
.286 .MODEL SMALL
.DATA
_completed_ecb_ptr dd 0
.CODE
PUBLIC _ipxspx_esr PUBLIC _completed_ecb_ptr
_ipxspx_esr PROC FAR
mov ax, DGROUP mov ds, ax
mov word ptr _completed_ecb_ptr+2, es mov word ptr _completed_ecb_ptr, si
retf _ipxspx_esr ENDP
end

Пример программы


Приведем пример программы, которая демонстрирует способ определения конфигурации текущей сети, т. е. той сети, в которой работает данная программа. Программа создает 20 блоков ECB для приема пакетов конфигурации от рабочих станций, ставит их в очередь на прием пакетов и посылает диагностический пакет в текущую сеть по адресу FFFFFFFFFFFFh. Затем после небольшой задержки программа анализирует блоки ECB, поставленные в очередь на прием. Если был принят пакет конфигурации, он расшифровывается и в текстовом виде выводится в стандартный выходной поток.

Приводимая ниже программа - первая программа в серии "Библиотека системного программиста", составленная на языке С++. В ней мы использовали только некоторые возможности языка С++. Для более глубокого понимания объектно-ориентированного подхода в программировании вам необходимо ознакомиться с литературой, список которой приведен в конце книги.

В функции main() создается объект класса IPX_CLIENT, который по своим функциям является клиентом. В нашем случае задача клиента - послать диагностический запрос всем станциям сети и получить от них пакет конфигурации. Можно считать, что программа диагностики, работающая на каждой станции, является сервером. Принимая запросы от клиентов на диагностическом сокете, она посылает им в ответ пакеты конфигурации.

При создании объекта класса IPX_CLIENT вызывается конструктор, выполняющий все необходимые инициализирующие действия. Конструктор инициализирует драйвер IPX, получает его точку входа и открывает динамический сокет. Соответствующий деструктор автоматически закрывает полученный сокет при завершении работы программы.

Описание класса IPX_CLIENT находится в файле ipx.hpp (см. ниже).

После того как отработает конструктор объекта IPX_CLIENT, для созданного объекта вызывается функция go(), которая и выполняет все необходимые действия. В теле функции определен массив ECB*RxECB[20] из 20 указателей на объекты класса ECB. Эти объекты используются для приема пакетов конфигурации.
Кроме того, определен один объект ECB TxECB для посылки пакета с диагностическим запросом.
Программа в цикле создает 20 объектов класса ECB и с помощью функции ListenForPacket() ставит их в очередь на прием пакетов. Затем программа посылает в сеть пакет с диагностическим запросом и ждет одну секунду. За это время станции сети присылают пакеты конфигурации.
Полученные пакеты конфигурации выводятся в стандартный поток функцией PrintDiagnostics(), определенной в классе ECB. Эта функция выводит для каждой ответившей станции версию используемой диагностики, номер сокета для работы с SPX-диагностикой (не описана в нашей книге), количество программных компонентов, работающих на станции, номер сети и сетевой адрес станции (узла). Для файл-серверов и мостов дополнительно выводится количество подключенных к ним сетей. Для каждой сети выводится ее номер и сетевой адрес соответствующего адаптера.
Для упрощения программы мы ограничились диагностикой только одной сети. Кроме того, мы послали только один диагностический запрос, в котором не исключали ни одной станции. Если вам нужно определить конфигурацию всей сети, вам надо сделать следующее.
Во-первых, после приема пакетов конфигурации запишите адреса ответивших станций в список станций, исключаемых из диагностического запроса. Не забудьте проставить количество исключаемых станций. Затем выполните повторную передачу диагностического запроса. Выполняйте описанную процедуру до тех пор, пока в ответ на диагностический запрос не будет передан ни один пакет конфигурации. В этом случае адреса всех станций, имеющихся в текущей сети, будут записаны в список станций, исключаемых из диагностического запроса.
Во-вторых, выполните анализ пришедших пакетов конфигурации. Найдите пакеты, которые пришли от файл-серверов и мостов. Определите номера сетей, подключенных к ним. Затем выполните предыдущую процедуру многократной посылки диагностических пакетов в остальные сети. Для этого при передаче диагностического пакета в заголовке укажите номер сети, которую вы желаете проверить.


В качестве сетевого адреса станции используйте значение FFFFFFFFFFFFh. В блоке ECB в поле непосредственного адреса укажите сетевой адрес моста, через который можно получить доступ в исследуемую сеть.
А теперь приведем текст основной программы (листинг 9):
// =================================================== // Листинг 9. Вызов диагностики и определение // конфигурации текущей сети // // Файл ipxdiagn.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.hpp"
// Вход в программу. // Создаем объект - программу-клиент. Затем запускаем ее.
void main(void) { IPX_CLIENT NetView; NetView.Go(); }
// Функция определяет и распечатывает конфигурацию текущей сети.
void IPX_CLIENT::Go(void) {
// Создаем 20 ECB для приема ответов от станций
ECB *RxECB[20];
// Создаем ECB для передачи диагностического запроса.
ECB TxECB(this->Socket, 0x456);
// Ставим заказанные ECB в очередь на прием пакетов.
for(int i=0; i<20; i++) { RxECB[i] = new ECB(this->Socket); RxECB[i]->ListenForPacket(); }
// Посылаем диагностический пакет всем станциям текущей сети.
TxECB.SendPacket();
printf("*NetView* v1.0, (C) Фролов А.В., 1993\n" "Подождите немного...\n\n");
// Ждем примерно одну секунду
sleep(1);
// Распечатываем конфигурацию сети
printf("Конфигурация сети:\n\n"); printf("Версия\tСокет\tКомпоненты\tСеть\t\tУзел\n"); printf("------\t-----\t----------\t----\t\t----\n");
for(i=0; i<20; i++) { RxECB[i]->PrintDiagnostics(); } }
Файл ipx.hpp содержит определения классов для приведенной выше программы (листинг 10):
// =================================================== // Листинг 10. Include-файл для работы с IPX // Файл ipx.hpp // // (C) A. Frolov, 1993 // ===================================================
#include <mem.h> #include <dos.h>


// ----------------------- // Команды интерфейса IPX // -----------------------
#define IPX_CMD_OPEN_SOCKET 0x00 #define IPX_CMD_CLOSE_SOCKET 0x01 #define IPX_CMD_GET_LOCAL_TARGET 0x02 #define IPX_CMD_SEND_PACKET 0x03 #define IPX_CMD_LISTEN_FOR_PACKET 0x04 #define IPX_CMD_SCHEDULE_IPX_EVENT 0x05 #define IPX_CMD_CANCEL_EVENT 0x06 #define IPX_CMD_GET_INTERVAL_MARKER 0x08 #define IPX_CMD_GET_INTERNETWORK_ADDRESS 0x09 #define IPX_CMD_RELINQUISH_CONTROL 0x0a #define IPX_CMD_DISCONNECT_FROM_TARGET 0x0b
// ----------------------- // Коды ошибок // -----------------------
#define NO_ERRORS 0 #define ERR_NO_IPX 1 #define ERR_NO_SPX 2 #define NO_LOGGED_ON 3 #define UNKNOWN_ERROR 0xff
// ----------------------- // Константы // -----------------------
#define SHORT_LIVED 0 #define LONG_LIVED 0xff #define IPX_DATA_PACKET_MAXSIZE 546
// Максимальный размер буфера данных
#define BUFFER_SIZE 512
// Внешние процедуры для инициализации и вызова драйвера IPX/SPX
extern "C" void far ipxspx_entry(void far *ptr); extern "C" int ipx_init(void); extern unsigned IntSwap(unsigned i);
void IPXRelinquishControl(void);
// Структура для вызова драйвера IPX/SPX
struct IPXSPX_REGS { unsigned int ax; unsigned int bx; unsigned int cx; unsigned int dx; unsigned int si; unsigned int di; unsigned int es; };
// Класс динамических сокетов
class DYNAMIX_SOCKET { public: unsigned errno; unsigned Socket; struct IPXSPX_REGS iregs;
// Конструктор динамического сокета. // Открывает сокет и запоминает его номер.
DYNAMIX_SOCKET() { iregs.bx = IPX_CMD_OPEN_SOCKET; iregs.dx = 0; iregs.ax = 0; ipxspx_entry( (void far *)&iregs ); Socket = iregs.dx; errno = iregs.ax; };
// Деструктор. Закрывает ранее открытый сокет.
~DYNAMIX_SOCKET() { iregs.bx = IPX_CMD_CLOSE_SOCKET; iregs.dx = Socket; ipxspx_entry( (void far *)&iregs ); };
};
// Класс программ-клиентов IPX
class IPX_CLIENT { public:
unsigned errno;
// Сокет, с которым работает программа-клиент
DYNAMIX_SOCKET *Socket;


// Конструктор. Выполняет инициализацию клиента: // инициализирует драйвер IPX и открывает динамический сокет. IPX_CLIENT() { if(ipx_init() != 0xff) { errno = 0xff; return; } Socket = new DYNAMIX_SOCKET; } // Деструктор. Автоматически закрывает // сокет при завершении работы программы.
~IPX_CLIENT() { delete Socket; }
// Функция, определяющая конфигурацию сети
void Go(void); };
// Класс заголовков IPX-пакетов.
struct IPX_HEADER {
// Структура, описывающая заголовок
struct _IPX_HEADER { unsigned int Checksum; unsigned int Length; unsigned char TransportControl; unsigned char PacketType; unsigned char DestNetwork[4]; unsigned char DestNode[6]; unsigned int DestSocket; unsigned char SourceNetwork[4]; unsigned char SourceNode[6]; unsigned int SourceSocket; } _ipx_header;
// Конструктор. Записывает в заголовок тип пакета, // нулевой номер сети, в которую будет отправлен пакет, // адрес 0xFFFFFFFFFFFF в качестве адреса назначения, // номера сокетов адресата и отправителя пакета,
IPX_HEADER(unsigned Socket, unsigned SrcSocket) { _ipx_header.PacketType = 4; memset(_ipx_header.DestNetwork, 0, 4); memset(_ipx_header.DestNode, 0xff, 6); _ipx_header.DestSocket = Socket; _ipx_header.SourceSocket = SrcSocket; }
// Конструктор. Записывает в заголовок тип пакета, // нулевой номер сети, в которую будет отправлен пакет, // адрес 0xFFFFFFFFFFFF в качестве адреса назначения.
IPX_HEADER() { _ipx_header.PacketType = 4; memset(_ipx_header.DestNetwork, 0, 4); memset(_ipx_header.DestNode, 0xff, 6); } };
// Класс блоков ECB.
struct ECB {
// Сам блок ECB в стандарте IPX/SPX.
struct _ECB { void far *Link; void far (*ESRAddress)(void); unsigned char InUse; unsigned char CCode; unsigned int Socket; unsigned int ConnectionId; unsigned int RrestOfWorkspace; unsigned char DriverWorkspace[12]; unsigned char ImmAddress[6]; unsigned int FragmentCnt; struct { void far *Address; unsigned int Size; } Packet[2]; } _ecb;
// Указатель на заголовок пакета, связанного с данным ECB.
struct IPX_HEADER *IPXHeader;


// Структура для приема ответа от станции // после посылки диагностического пакета.
struct Reply { unsigned char MajVer; unsigned char MinVer; unsigned Socket; unsigned char NumberOfComponents; unsigned char Buffer[512]; } Rep;
// Структура для хранения диагностического пакета.
struct DiagnRequest { unsigned char Exclusions; unsigned char List[80][6]; } DReq;
struct IPXSPX_REGS iregs;
// Конструктор. Создается заголовок пакета, // в блок ECB записывается номер сокета, используемого клиентом, // инициализируются счетчик фрагментов и дескрипторы фрагментов. // В качестве непосредственного адреса указывается // адрес 0xFFFFFFFFFFFF.
ECB(DYNAMIX_SOCKET *Socket) { IPXHeader = new IPX_HEADER; memset(&_ecb, 0, sizeof(_ecb)); _ecb.Socket = Socket->Socket; _ecb.FragmentCnt = 2; _ecb.Packet[0].Address = &(IPXHeader->_ipx_header); _ecb.Packet[0].Size = 30; _ecb.Packet[1].Address = &Rep; _ecb.Packet[1].Size = sizeof(Rep); memset(_ecb.ImmAddress, 0xff, 6); }
// Конструктор. Создается заголовок пакета, в блок ECB записывается // номер сокета, используемого клиентом, а также номер сокета // адресата, инициализируются счетчик фрагментов и дескрипторы // фрагментов. В качестве непосредственного адреса указывается // адрес 0xFFFFFFFFFFFF.
ECB(DYNAMIX_SOCKET *Socket, unsigned DstSocket) {
IPXHeader = new IPX_HEADER(IntSwap(DstSocket), Socket->Socket);
// Запрос адресуется всем станциям без исключения.
DReq.Exclusions = 0;
memset(&_ecb, 0, sizeof(_ecb)); _ecb.Socket = Socket->Socket; _ecb.FragmentCnt = 2; _ecb.Packet[0].Address = &(IPXHeader->_ipx_header); _ecb.Packet[0].Size = 30; _ecb.Packet[1].Address = &DReq; _ecb.Packet[1].Size = sizeof(DReq); memset(_ecb.ImmAddress, 0xff, 6); }
// Прием IPX-пакета.
void ListenForPacket(void) { iregs.es = FP_SEG((void far*)&_ecb); iregs.si = FP_OFF((void far*)&_ecb); iregs.bx = IPX_CMD_LISTEN_FOR_PACKET; ipxspx_entry( (void far *)&iregs ); }
// Передача IPX-пакета.
void SendPacket(void) { iregs.es = FP_SEG((void far*)&_ecb); iregs.si = FP_OFF((void far*)&_ecb); iregs.bx = IPX_CMD_SEND_PACKET; ipxspx_entry( (void far *)&iregs ); }


// Распечатать принятый пакет конфигурации
void PrintDiagnostics(void); };
В файл ipx.cpp ( листинг 11) мы вынесли остальные используемые программой функции, в частности функцию PrintDiagnostics(). Кроме того, программа вызывает функции, определенные в файле ipxdrv.asm, содержимое которого уже было приведено нами раньше.
// =================================================== // Листинг 11. Функции IPX. // // Файл ipx.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include "ipx.hpp"
/** * .Name IntSwap * * .Title Обмен байтов в слове * * .Descr Функция меняет местами байты в слове, * которое передается ей в качестве параметра * * .Params unsigned i - преобразуемое слово * * .Return Преобразованное слово **/
unsigned IntSwap(unsigned i) { return((i>>8) | (i & 0xff)<<8); }
/** * .Name IPXRelinquishControl * * .Title Передать управление IPX при ожидании * * .Descr Функция используется при ожидании * завершения приема через опрос поля InUse * блока ECB. * * .Params Не используются * * .Return Ничего **/
void IPXRelinquishControl(void) {
struct IPXSPX_REGS iregs;
iregs.bx = IPX_CMD_RELINQUISH_CONTROL; ipxspx_entry( (void far *)&iregs ); }
// Функция для печати содержимого принятого пакета конфигурации.
void ECB::PrintDiagnostics(void) { int i, j, k, networks, component;
// Печатаем конфигурацию только для тех ECB,в поле InUse которых // стоит нулевое значение, т.е. если был принят пакет.
if(!_ecb.InUse) { // Распечатываем версию диагностической поддержки, номер сокета для // SPX-диагностики и количество компонентов программного //обеспечения, работающего на станции.
printf("\n%d.%d\t%d\t%d\t\t", Rep.MajVer, Rep.MinVer, Rep.Socket, Rep.NumberOfComponents);
// Распечатываем номер сети, из которой пришел пакет конфигурации.
for(i=0;i<4;i++) { printf("%02.2X",(unsigned char) IPXHeader->_ipx_header.SourceNetwork[i]); } printf("\t"); // Распечатываем сетевой адрес станции, из // которой пришел пакет конфигурации.


for(i=0;i<6;i++) { printf("%02.2X",(unsigned char) IPXHeader->_ipx_header.SourceNode[i]); } printf("\n\n");
// Для каждого программного компонента распечатываем его название.
for(i=0;i<Rep.NumberOfComponents;) { switch(component=Rep.Buffer[i]) { case 0: printf("\tДрайвер IPX/SPX\n"); i++; break; case 1: printf("\tДрайвер моста\n"); i++; break; case 2: printf("\tДрайвер сетевой оболочки\n"); i++; break; case 3: printf("\tСетевая оболочка\n"); i++; break; case 4: printf("\tОболочка VAP\n"); i++; break;
// Для мостов и серверов дополнительно выводим количество // подключенных к ним сетей,тип каждой сети, номера подключенных // сетей и сетевые адреса адаптеров.
case 5: case 6: case 7: switch(component) { case 5: printf("\tВыделенный мост\n"); break; case 6: printf("\tФайл-сервер/внутренний мост\n"); break; case 7: printf("\tНевыделенный сервер\n"); break; } i++; // Количество подключенных сетей
printf("\t\tПодключено сетей: %d", (unsigned char)Rep.Buffer[i]); networks = Rep.Buffer[i]; i++;
// Для каждой сети печатаем ее тип, // номер сети и сетевой адрес адаптера.
for(j=0;j<networks;j++) {
// Тип сети
printf("\n\t\t\tТип сети: %d\t", (unsigned char) Rep.Buffer[i++]);
// Номер сети
for(k=0;k<4;k++,i++) { printf("%02.2X",(unsigned char) Rep.Buffer[i]); } printf("\t"); // Сетевой адрес адаптера
for(k=0;k<6;k++,i++) { printf("%02.2X",(unsigned char) Rep.Buffer[i]); } } printf("\n"); break; } }
} }
Приведем образец листинга, выдаваемого программой в стандартный поток вывода:
*NetView* v1.0, (C) Фролов А.В., 1993 Подождите немного...
Конфигурация сети:
Версия Сокет Компоненты Сеть Узел ------ ----- ---------- ---- ----
1.0 576 3 00000010 000000000001
Драйвер IPX/SPX Драйвер моста Файл-сервер/внутренний мост Подключено сетей: 3 Тип сети: 1 00000010 000000000001 Тип сети: 0 00000013 48450000456C Тип сети: 0 00000012 4845000047C7


1.0 576 3 0000000E 000000000001
Драйвер IPX/SPX Драйвер моста Файл-сервер/внутренний мост Подключено сетей: 2 Тип сети: 1 0000000E 000000000001 Тип сети: 0 00000012 008428801E9D
1.1 320 3 00000012 008058801C82
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 484500004666
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.0 320 3 00000012 484500004889
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 008058801F0F
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 000561E5D284
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 008058801E1D
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 484506004726
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.1 320 3 00000012 008058801EB5
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
1.0 320 3 00000012 484556004705
Драйвер IPX/SPX Драйвер сетевой оболочки Сетевая оболочка
Из приведенного листинга видно, что в сети имеются два файл-сервера и 9 рабочих станций. Все рабочие станции находятся в сети 00000012. К этой же сети подключены оба файл-сервера. Первый в списке файл-сервер подключен к сетям 00000012 и 00000013, следовательно, этот файл-сервер является внутренним мостом между сетью 00000012 и 00000013.
Обратите внимание, что для двух файл-серверов, имеющихся в сети, указан тип сети 1 и номера сетей 10h и 0Eh. Это так называемые внутренние номера сетей, которые задавались при генерации Novell NetWare версии 3.11. Физические сети имеют в нашем случае номера 12 и 13.

Простая система "клиент-сервер"


В качестве примера рассмотрим две программы. Первая из них является сервером, вторая - клиентом.

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

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

Клиент, в свою очередь, получив пакет от сервера, сможет узнать его сетевой адрес (по содержимому поля ImmAddress блока ECB). Следующий пакет клиент отправит серверу используя полученный непосредственный адрес сервера.

Начиная с этого момента сервер знает адрес клиента, а клиент знает адрес сервера. Они могут обмениваться пакетами друг с другом не прибегая к посылке пакетов по адресу FFFFFFFFFFFFh.

Исходный текст программы-сервера приведен в листинге 4.

Вначале с помощью функции ipx_init() сервер проверяет наличие драйвера IPX и получает адрес его API. Затем с помощью функции IPXOpenSocket() программа открывает короткоживущий сокет с номером 0x4567. Этот номер мы выбрали произвольно из диапазона сокетов, распределяемых динамически.

Далее программа-сервер подготавливает блок ECB для приема пакета от клиента (RxECB). Сперва весь блок расписывается нулями. Затем заполняются поля номера сокета, счетчик фрагментов (всего используются два фрагмента) и дескрипторы фрагментов. Первый фрагмент предназначен для заголовка пакета, второй - для принятых данных.

Подготовленный блок ECB ставится в очередь на прием пакета при помощи функции IPXListenForPacket().

Затем программа в цикле опрашивает содержимое поля InUse блока ECB, дожидаясь прихода пакета. В цикл ожидания вставляется вызов функции IPXRelinquishControl() и функция опроса клавиатуры getch(). С помощью последней вы можете прервать ожидание, если нажмете на любую клавишу.

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


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

В качестве передаваемых данных сервер использует буфер TxBuffer с записанной в него текстовой строкой "SERVER *DEMO*". Эта строка будет выведена клиентом на консоль после приема от сервера ответного пакета.

Подготовив блок ECB для передачи, программа ставит его в очередь на передачу при помощи функции IPXSendPacket(), после чего закрывает сокет и завершает свою работу.

// =================================================== // Листинг 4. Сервер IPX // // Файл ipxserv.c // // (C) A. Frolov, 1993 // ===================================================

#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.h"

#define BUFFER_SIZE 512

void main(void) {

// Используем сокет 0x4567

static unsigned Socket = 0x4567;

// Этот ECB мы будем использовать и для приема // пакетов, и для их передачи.

struct ECB RxECB;

// Заголовки принимаемых и передаваемых пакетов

struct IPX_HEADER RxHeader, TxHeader;

// Буферы для принимаемых и передаваемых пакетов

unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE];

printf("\n*Сервер IPX*, (C) Фролов А., 1993\n\n");

// Проверяем наличие драйвера IPX и определяем // адрес точки входа его API

if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); }

// Открываем сокет, на котором мы будем принимать пакеты

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



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

memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &RxHeader; RxECB.Packet[0].Size = sizeof(RxHeader); RxECB.Packet[1].Address = RxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE;

IPXListenForPacket(&RxECB);

printf("Ожидание запроса от клиента\n"); printf("Для отмены нажмите любую клавишу\n");

while(RxECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); RxECB.CCode = 0xfe; break; } } if(RxECB.CCode == 0) { printf("Принят запрос от клиента '%s'\n", RxBuffer); printf("Для продолжения нажмите любую клавишу\n"); getch();

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

RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &TxHeader; RxECB.Packet[0].Size = sizeof(TxHeader); RxECB.Packet[1].Address = TxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE;

// Подготавливаем заголовок пакета

TxHeader.PacketType = 4; memset(TxHeader.DestNetwork, 0, 4); memcpy(TxHeader.DestNode, RxECB.ImmAddress, 6); TxHeader.DestSocket = IntSwap(Socket);

// Подготавливаем передаваемые данные

strcpy(TxBuffer, "SERVER *DEMO*");

// Передаем пакет обратно клиенту

IPXSendPacket(&RxECB); }

// Закрываем сокет

IPXCloseSocket(&Socket); exit(0); }

Программа-клиент (листинг 5) после проверки наличия драйвера IPX/SPX и получения адреса его API подготавливает блок ECB и передает первый пакет по адресу FFFFFFFFFFFFh. Его принимают все станции в текущей сети, но откликается на него только та станция, на которой запущена программа-сервер.

Послав первый пакет, клиент подготавливает ECB для приема пакета и ожидает ответ от сервера, вызывая в цикле функции IPXRelinquishControl и getch().


После прихода ответного пакета клиент закрывает сокет и завершает свою работу.

// =================================================== // Листинг 5. Клиент IPX // // Файл ipxclien.c // // (C) A. Frolov, 1993 // ===================================================

#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <mem.h> #include <string.h> #include "ipx.h"

// Максимальный размер буфера данных

#define BUFFER_SIZE 512

void main(void) {

// Будем работать с сокетом 0x4567

static unsigned Socket = 0x4567;

// ECB для приема и передачи пакетов

struct ECB RxECB, TxECB;

// Заголовки принимаемых и передаваемых пакетов

struct IPX_HEADER RxHeader, TxHeader;

// Буфера для принимаемых и передаваемых данных

unsigned char RxBuffer[BUFFER_SIZE]; unsigned char TxBuffer[BUFFER_SIZE];

printf("\n*Клиент IPX*, (C) Фролов А., 1993\n\n");

// Проверяем наличие драйвера IPX и определяем // адрес точки входа его API

if(ipx_init() != 0xff) { printf("IPX не загружен!\n"); exit(-1); }

// Открываем сокет, на котором будем принимать и передавать пакеты

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

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

memset(&TxECB, 0, sizeof(TxECB));

TxECB.Socket = IntSwap(Socket); TxECB.FragmentCnt = 2; TxECB.Packet[0].Address = &TxHeader; TxECB.Packet[0].Size = sizeof(TxHeader); TxECB.Packet[1].Address = TxBuffer; TxECB.Packet[1].Size = BUFFER_SIZE;

// Пакет предназначен всем станциям данной сети

memset(TxECB.ImmAddress, 0xff, 6);

// Подготавливаем заголовок пакета

TxHeader.PacketType = 4; memset(TxHeader.DestNetwork, 0, 4); memset(TxHeader.DestNode, 0xff, 6); TxHeader.DestSocket = IntSwap(Socket);

// Записываем передаваемые данные

strcpy(TxBuffer, "CLIENT *DEMO*");

// Передаем пакет всем станциям в данной сети

IPXSendPacket(&TxECB);

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

memset(&RxECB, 0, sizeof(RxECB)); RxECB.Socket = IntSwap(Socket); RxECB.FragmentCnt = 2; RxECB.Packet[0].Address = &RxHeader; RxECB.Packet[0].Size = sizeof(RxHeader); RxECB.Packet[1].Address = RxBuffer; RxECB.Packet[1].Size = BUFFER_SIZE;



IPXListenForPacket(&RxECB);

printf("Ожидание ответа от сервера\n"); printf("Для отмены нажмите любую клавишу\n");

// Ожидаем прихода ответа от сервера

while(RxECB.InUse) { IPXRelinquishControl(); if(kbhit()) { getch(); RxECB.CCode = 0xfe; break; } } if(RxECB.CCode == 0) { printf("Принят ответ от сервера '%s'\n", RxBuffer); }

// Закрываем сокет

IPXCloseSocket(&Socket); exit(0); }

Исходные тексты функций для обращения к API драйвера IPX приведены в листинге 5.1. Здесь же определена функция IntSwap(), переставляющая местами байты в слове.

// =================================================== // Листинг 5.1. Функции IPX. // // Файл ipx.c // // (C) A. Frolov, 1993 // ===================================================

#include <stdio.h> #include <stdlib.h> #include <dos.h> #include "ipx.h"

/** * .Name IntSwap * * .Title Обмен байтов в слове * * .Descr Функция меняет местами байты в слове, * которое передается ей в качестве параметра * * .Params unsigned i - преобразуемое слово * * .Return Преобразованное слово **/

unsigned IntSwap(unsigned i) { return((i>>8) | (i & 0xff)<<8); }

/** * .Name IPXOpenSocket * * .Title Открыть сокет * * .Descr Функция открывает сокет, тип которого * передается ей через параметр SocketType. * Перед вызовом необходимо подготовить в памяти * слово и записать в него значение открываемого * сокета (или нуль, если нужен динамический сокет). * Адрес слова передается через параметр Socket. * Если открывается динамический сокет, его * значение будет записано по адресу Socket. * * .Params int SocketType - тип сокета: * 0x00 - короткоживущий; * 0xFF - долгоживущий. * * unsigned *Socket - указатель на слово, * в котором находится номер * открываемого сокета или нуль, * если нужен динамический сокет. * * .Return 0 - сокет открыт успешно; * 0xFE - переполнилась таблица сокетов; * 0xFF - такой сокет уже открыт. **/

int IPXOpenSocket(int SocketType, unsigned *Socket) {



struct IPXSPX_REGS iregs;

iregs.bx = IPX_CMD_OPEN_SOCKET; iregs.dx = IntSwap(*Socket); iregs.ax = SocketType; ipxspx_entry( (void far *)&iregs );

*Socket = IntSwap(iregs.dx); return(iregs.ax); }

/** * .Name IPXCloseSocket * * .Title Закрыть сокет * * .Descr Функция закрывает сокет. * Перед вызовом необходимо подготовить в памяти * слово и записать в него значение закрываемого * сокета. Адрес слова передается через параметр Socket. * * .Params unsigned *Socket - указатель на слово, в котором * находится номер закрываемого сокета. * * .Return Ничего **/

void IPXCloseSocket(unsigned *Socket) {

struct IPXSPX_REGS iregs;

iregs.bx = IPX_CMD_CLOSE_SOCKET; iregs.dx = IntSwap(*Socket); ipxspx_entry( (void far *)&iregs ); }

/** * .Name IPXListenForPacket * * .Title Принять пакет * * .Descr Функция подготавливает ECB для приема * пакета из сети. Указатель на ECB передается * через параметр RxECB. * * .Params struct ECB *RxECB - указатель на ECB, * заполненное для приема пакета. * * .Return Ничего **/ void IPXListenForPacket(struct ECB *RxECB) {

struct IPXSPX_REGS iregs;

iregs.es = FP_SEG((void far*)RxECB); iregs.si = FP_OFF((void far*)RxECB); iregs.bx = IPX_CMD_LISTEN_FOR_PACKET; ipxspx_entry( (void far *)&iregs ); }

/** * .Name IPXSendPacket * * .Title Передать пакет * .Descr Функция подготавливает ECB для передачи * пакета. Указатель на ECB передается через * параметр TxECB. * * .Params struct ECB *TxECB - указатель на ECB, * заполненное для передачи пакета. * * .Return Ничего **/

void IPXSendPacket(struct ECB *TxECB) {

struct IPXSPX_REGS iregs;

iregs.es = FP_SEG((void far*)TxECB); iregs.si = FP_OFF((void far*)TxECB); iregs.bx = IPX_CMD_SEND_PACKET; ipxspx_entry( (void far *)&iregs ); }

/** * .Name IPXRelinquishControl * * .Title Передать управление IPX при ожидании * * .Descr Функция используется при ожидании * завершения приема через опрос поля InUse блока ECB. * * .Params Не используются * * .Return Ничего **/

void IPXRelinquishControl(void) {



struct IPXSPX_REGS iregs;

iregs.bx = IPX_CMD_RELINQUISH_CONTROL; ipxspx_entry( (void far *)&iregs ); }

Листинг 6 содержит include-файл, в котором определены необходимые константы, структуры данных и прототипы функций.

// =================================================== // Листинг 6. Include-файл для работы с IPX // Файл ipx.h // // (C) A. Frolov, 1993 // ===================================================

// ----------------------- // Команды интерфейса IPX // -----------------------

#define IPX_CMD_OPEN_SOCKET 0x00 #define IPX_CMD_CLOSE_SOCKET 0x01 #define IPX_CMD_GET_LOCAL_TARGET 0x02 #define IPX_CMD_SEND_PACKET 0x03 #define IPX_CMD_LISTEN_FOR_PACKET 0x04 #define IPX_CMD_SCHEDULE_IPX_EVENT 0x05 #define IPX_CMD_CANCEL_EVENT 0x06 #define IPX_CMD_GET_INTERVAL_MARKER 0x08 #define IPX_CMD_GET_INTERNETWORK_ADDRESS 0x09 #define IPX_CMD_RELINQUISH_CONTROL 0x0a #define IPX_CMD_DISCONNECT_FROM_TARGET 0x0b

// ----------------------- // Команды интерфейса SPX // -----------------------

#define SPX_CMD_INSTALL_CHECK 0x10

// ----------------------- // Коды ошибок // -----------------------

#define NO_ERRORS 0 #define ERR_NO_IPX 1 #define ERR_NO_SPX 2 #define NO_LOGGED_ON 3 #define UNKNOWN_ERROR 0xff

// ----------------------- // Константы // -----------------------

#define SHORT_LIVED 0 #define LONG_LIVED 0xff #define IPX_DATA_PACKET_MAXSIZE 546

// Внешние процедуры для инициализации и вызова драйвера IPX/SPX

void far ipxspx_entry(void far *ptr); int ipx_init(void);

// Структура для вызова драйвера IPX/SPX

struct IPXSPX_REGS { unsigned int ax; unsigned int bx; unsigned int cx; unsigned int dx; unsigned int si; unsigned int di; unsigned int es; };

// =========================================================== // Заголовок пакета IPX // ===========================================================

struct IPX_HEADER { unsigned int Checksum; unsigned int Length; unsigned char TransportControl; unsigned char PacketType; unsigned char DestNetwork[4]; unsigned char DestNode[6]; unsigned int DestSocket; unsigned char SourceNetwork[4]; unsigned char SourceNode[6]; unsigned int SourceSocket; };



// ============================================================ // ECB // ============================================================

struct ECB { void far *Link; void far (*ESRAddress)(void); unsigned char InUse; unsigned char CCode; unsigned int Socket; unsigned int ConnectionId; unsigned int RrestOfWorkspace; unsigned char DriverWorkspace[12]; unsigned char ImmAddress[6]; unsigned int FragmentCnt; struct { void far *Address; unsigned int Size; } Packet[2]; };

unsigned IntSwap(unsigned i); int IPXOpenSocket(int SocketType, unsigned *Socket); void IPXCloseSocket(unsigned *Socket); void IPXListenForPacket(struct ECB *RxECB); void IPXRelinquishControl(void); void IPXSendPacket(struct ECB *TxECB);


ПРОТОКОЛ IPX


2.1.

2.2.

2.3.

2.4.

2.5.

2.6.

2.7.

2.8.

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

В сети Novell NetWare наиболее быстрая передача данных при наиболее экономном использовании памяти реализуется именно протоколом IPX. Протоколы SPX и NETBIOS сделаны на базе IPX и поэтому требуют дополнительных ресурсов. Поэтому мы начнем изучение программирования для локальных сетей именно с протокола IPX.

Если на рабочей станции используется операционная система MS-DOS, функции, необходимые для реализации протокола IPX, реализуются резидентными программами ipx.com или ipxodi.com, входящими в состав сетевой оболочки рабочей станции сети NetWare.

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



Работа с драйвером IPX/SPX


Первое, что должна сделать программа, желающая работать в сети с протоколом IPX или SPX, - проверить, установлен ли драйвер соответствующего протокола. Затем необходимо получить адрес вызова этого драйвера - точку входа API (Application Programm Interface - интерфейс для приложений). Вдальнейшем программа вызывает драйвер при помощи команды межсегментного вызова процедуры по адресу точки входа API драйвера IPX/SPX.



Схема "клиент-сервер"


Обычно в сети одна из рабочих станций принимает запросы на выполнение каких-либо действий от других рабочих станций. Так как станция обслуживает запросы, она называется сервером (serve - обслуживать, server - обслуживающее устройство). Выполнив запрос, сервер посылает ответ в запросившую станцию, которая называется клиентом.

В сети может быть много серверов и много клиентов. Одни и те же клиенты могут посылать запросы разным серверам.

Говоря более строго, сервером или клиентом является не рабочая станция, а запущенная на ней программа. В мультизадачной среде разные программы, запущенные одновременно на одной и той же рабочей станции могут являться и клиентами, и серверами.

Программа-сервер, выполнив очередной запрос, переходит в состояние ожидания. Она ждет прихода пакета данных от программы-клиента. В зависимости от содержимого этого пакета программа-сервер может выполнять различные действия, в соответствии с логикой работы программы. Например, она может принять от программы-клиента дополнительные пакеты данных или передать свои пакеты.

Сервер и клиент при необходимости на какое-то время или навсегда могут поменяться местами, изменив свое назначение на противоположное.

Для того, чтобы создавать программы-серверы и программы-клиенты, нам необходимо научиться выполнять две задачи:

инициализацию сервера и клиента;

прием и передачу пакетов данных.



Точка входа API драйвера IPX/SPX


Для того чтобы проверить, загружен ли драйвер IPX, необходимо загрузить в регистр AX значение 7A00h и вызвать мультиплексное прерывание INT 2Fh.

Если после возврата из прерывания в регистре AL будет значение FFh, драйвер IPX загружен. Адрес точки входа для вызова API драйвера при этом будет находиться в регистровой паре ES:DI.

Если же содержимое регистра AL после возврата из прерывания INT 2Fh будет отлично от FFh, драйвер IPX/SPX не загружен. Это означает, что на данной рабочей станции не загружены резидентные программы ipx.exe или ipxodi.exe, обеспечивающие API для работы с протоколами IPX и SPX.

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

Например, функция с кодом 10h используется для проверки присутствия в системе протокола SPX (может быть такая ситуация, когда протокол IPX присутствует, а SPX - нет). Для того, чтобы определить наличие SPX, необходимо загрузить в BX значение 10h, в AX значение 00h и вызвать API драйвера IPX. Если после возврата регистр AX будет содержать значение FFh, протокол SPX присутствует и может быть использован. В регистрах CX и DX передаются параметры SPX - максимальное число каналов связи, которое данная станция может установить с программами, работающими на других станциях, и количество каналов, доступное в настоящее время. О назначении этих параметров мы будем говорить в главе, посвященной протоколу SPX.

Приведем текст программы, определяющей наличие драйвера протоколов IPX и SPX (листинг 1). Программа вызывает функции ipx_init() и ipxspx_entry(), тексты которых находятся в листинге 2. Текст сокращенного варианта include-файла ipx.h представлен в листинге 3.

Вы можете попробовать запустить эту программу на рабочей станции сети Novell NetWare под управлением MS-DOS или на виртуальной машине MS Windows, работающей в расширенном (Enchanced) режиме.

// =================================================== // Листинг 1. Программа для обнаружения драйвера // протокола IPX/SPX и определения его версии // // Файл ipxver.c // // (C) A.
Frolov, 1993 // ===================================================

#include <stdio.h> #include <stdlib.h> #include "ipx.h"

void main(void) {

// Точка входа в IPX/SPX API, переменная находится // в файле ipxdrv.asm и инициализируется функцией ipx_init().

extern far char *ipxspx_drv_entry;

// Структура для вызова API IPX

struct IPXSPX_REGS iregs;

unsigned error; unsigned spx_ver; unsigned spx_max_connections, spx_avail_connections;

printf("\n*Детектор IPX/SPX*, (C) Фролов А., 1993\n\n");

// Проверяем наличие драйвера IPX и определяем // адрес точки входа его API

if(ipx_init() == 0xff) printf("IPX загружен! "); else { printf("IPX не загружен!\n"); exit(-1); } printf("Точка входа в IPX API - %Fp\n",ipxspx_drv_entry);

// Проверяем доступность протокола SPX

error = NO_ERRORS;

// Вызываем функцию проверки доступности SPX // Здесь мы вызываем API драйвера IPX/SPX

iregs.bx = SPX_CMD_INSTALL_CHECK; iregs.ax = 0; ipxspx_entry( (void far *)&iregs );

if(iregs.ax == 0x00) error = ERR_NO_SPX; if(iregs.ax != 0xff) error = UNKNOWN_ERROR;

if(error != NO_ERRORS) { printf("SPX не загружен!\n"); exit(-1); }

// Запоминаем параметры IPX/SPX

spx_ver = iregs.bx; spx_max_connections = iregs.cx; spx_avail_connections = iregs.dx;

printf("SPX загружен! "); printf("Версия SPX: %d.%d\n", (spx_ver>>8) & 0xff, spx_ver & 0xff); printf("Всего соединений: %d, ", spx_max_connections); printf("из них доступно: %d\n", spx_avail_connections);

exit(0); }

Далее расположен исходный текст модуля инициализации IPX (листинг 2).

В этом модуле находится функция ipxspx_entry(), необходимая для вызова драйвера IPX/SPX. Ее имя начинается с символа "_", что необходимо для выполнения соглашения об именах в языке Си.

Здесь же имеется функция ipx_init(), которая проверяет наличие драйвера в системе, получает адрес API драйвера и сохраняет его в области памяти _ipxspx_drv_entry.



; =================================================== ; Листинг 2. Инициализация и вызов драйвера IPX/SPX ; Файл ipxdrv.asm ; ; (C) A. Frolov, 1993 ; ===================================================

.286 .MODEL SMALL ; --------------------------------------- ; Структура для вызова драйвера IPX/SPX ; ---------------------------------------

IPXSPX_REGS struc rax dw ? rbx dw ? rcx dw ? rdx dw ? rsi dw ? rdi dw ? res dw ? IPXSPX_REGS ends

.DATA

; Точка входа в драйвер IPX/SPX

_ipxspx_drv_entry dd ?

.CODE

PUBLIC _ipxspx_entry, _ipx_init PUBLIC _ipxspx_drv_entry

; --------------------------------------- ; Процедура, вызывающая драйвер IPX/SPX ; ---------------------------------------

_ipxspx_entry PROC FAR

; Готовим BP для адресации параметра функции

push bp mov bp,sp

; Сохраняем регистры, так как драйвер IPX/SPX ; изменяет содержимое практически всех регистров

push es push di push si push dx push cx push bx push ax

; Загружаем регистры из структуры, ; адрес которой передается как параметр

push ds mov bx, [bp+6] ; смещение mov ds, [bp+8] ; сегмент mov es, ds:[bx].res mov di, ds:[bx].rdi mov si, ds:[bx].rsi mov dx, ds:[bx].rdx mov cx, ds:[bx].rcx mov ax, ds:[bx].rax mov bx, ds:[bx].rbx pop ds

; Вызываем драйвер IPX/SPX

call [dword ptr _ipxspx_drv_entry]

; Сохраняем регистры

push ds push dx mov dx, bx

; Записываем в структуру содержимое регистров после вызова драйвера

mov bx, [bp+6] ; смещение mov ds, [bp+8] ; сегмент mov ds:[bx].rax, ax mov ds:[bx].rcx, cx mov ds:[bx].rbx, dx pop dx mov ds:[bx].rdx, dx pop ds

; Восстанавливаем регистры

pop ax pop bx pop cx pop dx pop si pop di pop es

pop bp retf _ipxspx_entry ENDP

; --------------------------------------------- ; Процедура инициализации драйвера IPX/SPX ; ---------------------------------------------

_ipx_init PROC NEAR push bp mov bp,sp

; Определяем наличие драйвера в системе и его точку входа

mov ax, 7a00h int 2fh

; Если драйвера нет, завершаем процедуру

cmp al, 0ffh jne _ipx_init_exit

; Сохраняем адрес точки входа



mov word ptr _ipxspx_drv_entry+2, es mov word ptr _ipxspx_drv_entry, di

_ipx_init_exit:

; В регистре AX - код завершения процедуры

mov ah, 0 pop bp ret _ipx_init ENDP end

Описания типов и констант, а также прототипы функций для программы ipxver.c находятся в файле ipx.h (листинг 3).

// =================================================== // Листинг 3. Include-файл для работы с IPX // Сокращенный вариант для программы ipxver.c // Файл ipx.h // // (C) A. Frolov, 1993 // ===================================================

// ----------------------- // Команды интерфейса IPX // -----------------------

#define IPX_CMD_OPEN_SOCKET 0x00 #define IPX_CMD_CLOSE_SOCKET 0x01 #define IPX_CMD_GET_LOCAL_TARGET 0x02 #define IPX_CMD_SEND_PACKET 0x03 #define IPX_CMD_LISTEN_FOR_PACKET 0x04 #define IPX_CMD_SCHEDULE_IPX_EVENT 0x05 #define IPX_CMD_CANCEL_EVENT 0x06 #define IPX_CMD_GET_INTERVAL_MARKER 0x08 #define IPX_CMD_GET_INTERNETWORK_ADDRESS 0x09 #define IPX_CMD_RELINQUISH_CONTROL 0x0a #define IPX_CMD_DISCONNECT_FROM_TARGET 0x0b

// ----------------------- // Команды интерфейса SPX // -----------------------

#define SPX_CMD_INSTALL_CHECK 0x10

// ----------------------- // Коды ошибок // -----------------------

#define NO_ERRORS 0 #define ERR_NO_IPX 1 #define ERR_NO_SPX 2 #define NO_LOGGED_ON 3 #define UNKNOWN_ERROR 0xff

// ----------------------- // Константы // -----------------------

#define SHORT_LIVED 0 #define LONG_LIVED 0xff #define IPX_DATA_PACKET_MAXSIZE 546

// Внешние процедуры для инициализации и вызова драйвера IPX/SPX

void far ipxspx_entry(void far *ptr); int ipx_init(void);

// Структура для вызова драйвера IPX/SPX

struct IPXSPX_REGS { unsigned int ax; unsigned int bx; unsigned int cx; unsigned int dx; unsigned int si; unsigned int di; unsigned int es; };


Блок ECB


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



Формат пакета 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


В разделе, посвященном настройке параметров драйвера 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



Простая система "клиент-сервер" на базе 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);


ПРОТОКОЛ SPX


3.1.

3.2.

3.3.

3.4.

3.5.

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

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



SPXAbortConnection


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

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

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



SPXCheckInstallation


На входе: BX = 10h.
AL = 00h.
На выходе: AL = Код завершения:

00h - SPX не установлен;

FFh - SPX установлен.

BH = Верхний (major) номер версии SPX.
BL = Нижний (minor) номер версии SPX.
CX = Максимальное количество каналов SPX, поддерживаемых драйвером SPX.
DX = Количество доступных каналов SPX.

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



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.


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 для их приема.


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.



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.



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.