Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

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

© Александр Фролов, Григорий Фролов
Том 4, М.: Диалог-МИФИ, 1993, 160 стр.

[Назад] [Содеожание] [Дальше]

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

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

2.2.1. Точка входа 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;
};



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

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

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

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

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

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

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

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

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

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

  • инициализацию сервера и клиента;
    • прием и передачу пакетов данных.

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

Для инициализации программ сервера и клиента, работающих на базе 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, будет принят всеми станциями сети даже в том случае, если файл-сервер выключен или его вовсе нет. Поэтому способ определения сетевого адреса через запрос по всей сети более универсален.

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

Рассмотрим теперь процедуру приема пакетов данных средствами 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.

Формат блока 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.

[Назад] [Содеожание] [Дальше]