Библиотека Системного Программиста. Том 8
Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS 4. ПРОТОКОЛ NETBIOS4.1. Адресация станций и программ 4.2. Работа с протоколом NETBIOS 4.3. Команды NETBIOS 4.4. Коды ошибок 4.5. Система "клиент-сервер" на базе датаграмм 4.6. Система "клиент-сервер" на базе каналов Последний протокол, который мы рассмотрим в нашей книге, - протокол NETBIOS (Network Basic Input/Output System - базовая сетевая система ввода/вывода), разработанный IBM. Этот протокол работает на трех уровнях семиуровневой модели OSI: сетевом уровне, транспортном уровне и на уровне каналов связи. Уровень каналов связи обеспечивает механизм обмена сообщениями между программами, работающими на станциях в рамках канала связи или сессии. NETBIOS может обеспечить интерфейс более высокого уровня, чем протоколы IPX и SPX. Протокол NETBIOS поддерживается в сетях IBM (IBM PC LAN), Novell NetWare, Microsoft Windows for Workgroups и в других сетях. К сожалению, нет единого стандарта на протокол NETBIOS, поэтому в сетевом программном обеспечении разных фирм используются разные интерфейсы для вызова команд NETBIOS. С нашей точки зрения, наибольший интерес представляет применение NETBIOS в сетях Novell NetWare и Microsoft Windows for Workgroups. Мы рассмотрим основные возможности NETBIOS, связанные с передачей данных между рабочими станциями в пределах одного логического сегмента сети. Для работы с протоколом NETBIOS в сетях Novell NetWare необходимо запустить специальный эмулятор NETBIOS - программу netbios.exe, входящую в комплект поставки Novell NetWare. Эта программа эмулирует протокол NETBIOS с использованием уже знакомых нам протоколов IPX/SPX. Использовать NETBIOS проще, чем IPX или SPX. Однако, так как в среде Novell NetWare нужен специальный эмулятор NETBIOS, эффективность работы программы может снизиться. Кроме того, для эмулятора нужна дополнительная память, так как он реализован в виде резидентной программы. 4.1. Адресация станций и программКак вы помните, для идентификации рабочей станции протоколы IPX и SPX используют номер сети, адрес станции в сети и сокет. Адрес станции определяется на аппаратном уровне и представляет собой число длиной 6 байт. Номер сети занимает 4 байта. Сокеты выделяются динамически драйвером протокола IPX или могут быть получены в Novell. Протокол NETBIOS использует другой механизм адресации станций и программ. Для адресации станции используются имена размером 16 байт. Каждая станция имеет одно постоянное имя (permanent name), которое образуется из аппаратного адреса добавлением к нему слева десяти нулевых байт. Кроме постоянного имени протокол NETBIOS позволяет добавлять (и удалять) обычные имена и групповые имена. Обычные имена служат для идентификации рабочей станции, групповые могут служить для посылки пакетов одновременно нескольким станциям в сети. Постоянное имя удалить нельзя, так как оно полностью определяется аппаратным обеспечением станции.
При добавлении обычного имени протокол NETBIOS опрашивает всю
сеть для проверки уникальности имени. Групповое имя может быть
одинаковое на нескольких станциях, поэтому при добавлении группового
имени опрос сети После добавления нового имени этому имени присваивается так называемый номер имени (name number), который используется для передачи данных по сети. Сравнивая методы адресации, используемые протоколами IPX/SPX и NETBIOS, нетрудно заметить, что метод адресации протокола NETBIOS более удобен. Вы можете адресовать данные не только одной станции (как в IPX и SPX) или всем станциям сразу (как в IPX), но и группам станций, имеющим одинаковое групповое имя. Это может быть удобно, если в сети работают несколько групп пользователей, которые интенсивно обмениваются данными между собой. Другим преимуществом схемы адресации протокола NETBIOS перед схемой адресации протоколов IPX/SPX можно считать отсутствие необходимости получать в фирме Novell свой собственный номер сокета для идентификации вашего программного обеспечения. Вы можете придумать свое собственное уникальное групповое имя, включающее, например, название программы и вашей фирмы, и использовать его для работы по схеме клиент-сервер. 4.2. Работа с протоколом NETBIOSПротокол NETBIOS предоставляет программам интерфейс для передачи данных на уровне датаграмм и на уровне каналов связи. Для вызова NETBIOS программа должна создать в памяти управляющий блок, который называется NCB (Network Control Block - сетевой управляющий блок). Адрес заполненного блока NCB передается прерыванию INT 5Ch, в рамках которого и реализован интерфейс протокола NETBIOS. Есть также альтернативный интерфейс, реализованный в рамках прерывания INT 2Ah, который поддерживается эмулятором NETBIOS, разработанным фирмой Novell, а также операционной системой Windows for Workgroups версии 3.1. 4.2.1. Проверка присутствия NETBIOSПервое, что должна сделать программа, желающая воспользоваться протоколом NETBIOS, - проверить наличие в системе интерфейса NETBIOS. Ниже приведена программа, которая определяет, установлен ли драйвер NETBIOS. С помощью функции getvect() программа получает указатель на обработчик прерывания INT 5Ch. Это прерывание используется для вызова NETBIOS. Если сегментная компонента адреса равна нулю или F000h, обработчик прерывания не установлен или установлена заглушка, расположенная в BIOS. В этом случае программа считает, что NETBIOS отсутствует. // =================================================== // Листинг 16. Проверка присутствия NETBIOS // // Файл nbver.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> void main(void) { void interrupt ( *int5C)(...); printf("Check if NETBIOS is installed\n"); int5C = getvect(0x5c); if(FP_SEG(int5C) == 0x0000 || FP_SEG(int5C) == 0xF000) { printf("NETBIOS NOT installed.\n"); } else printf("NETBIOS is installed!\n"); }
Другой способ проверки наличия интерфейса NETBIOS заключается
в вызове прерывания INT 2Ah. Загрузите в регистр AH нулевое
значение и вызовите прерывание INT 2Ah. Если после возврата
из прерывания в регистре AH Данный способ проверки будет работать на виртуальной машине DOS, запущенной в среде Windows for Workgroups версии 3.1 (если Windows работает в расширенном режиме). Приведенная ниже программа определяет присутствие NETBIOS с помощью вызова прерывания INT 2Ah (листинг 17): // =================================================== // Листинг 17. Проверка присутствия NETBIOS // с использованием интерфейса INT 2Ah // // Файл 2atest.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> void main(void) { union REGS regs; printf("Check if NETBIOS is installed\n"); regs.h.ah = 0; int86(0x2a, ®s, ®s); if(regs.h.ah == 0) { printf("NETBIOS NOT installed.\n"); } else printf("NETBIOS is installed!\n"); } Прерывание INT 2Ah используется в сетях фирм Microsoft и Lantastic. Эмулятор протокола NETBIOS, поставляющийся фирмой Novell вместе с операционной системой Novell NetWare, поддерживает как интерфейс прерывания INT 5Ch, так и интерфейс INT 2Ah. 4.2.2. Вызов команд протокола NETBIOSИнтерфейс протокола NETBIOS реализован в рамках прерывания INT 5Ch или INT 2Ah и очень прост. Для вызова команд протокола NETBIOS вам достаточно подготовить блок NCB, загрузить его дальний адрес в регистры ES:BX и вызвать прерывание INT 5Ch. Приведем формат вызова функции прерывания INT 2Ah, предназначенной для выполнения команд NETBIOS:
Вы можете использовать описанный выше вызов прерывания INT 2A для вызова NETBIOS из программы, работающей на виртуальной машине Windows for Workgroups версии 3.1. Для вызова команд протокола NETBIOS из пограммы, составленной на языке Си, вы можете воспользоваться обычными средствами вызова программных прерываний, такими, как функция int86x(). 4.2.3. Формат блока NCBПриведем формат блока NCB: struct _NCB { unsigned char Cmd; unsigned char CCode; unsigned char LocalSessionNumber; unsigned char NetworkNameNumber; void far *Buffer; unsigned Size; char CallName[16]; char OurName[16]; unsigned char ReceiveTimeout; unsigned char SendTimeout; void interrupt (*PostRoutine)(void); unsigned char AdapterNumber; unsigned char FinalCCode; unsigned char Reserved[14]; };
Поле Cmd содержит код команды, которую необходимо выполнить.
Существуют команды для работы с именами станций, для передачи
и приема дата- Поле CCode содержит код ошибки, возвращаемый NETBIOS до выполнения команды. Если, например, программа затребовала выполнение неправильной команды или задала для команды неправильные параметры, NETBIOS не будет выполнять такую команду и установит в поле CCode соответствующий код ошибки. Если же в этом поле после вызова NETBIOS находится нулевое значение, это еще не означает, что команда выполнилась правильно, однако она начала выполняться. Поле LocalSessionNumber содержит номер канала, установленного с другой программой. Это поле необходимо заполнять при выдаче команд передачи данных через каналы. Поле NetworkNameNumber содержит номер имени, который присваивается при добавлении обычного или группового имени. Это поле должно быть заполнено при приеме датаграмм. Поле Buffer представляет собой дальний указатель в формате [сегмент:смещение] на буфер, который должен содержать данные перед выполнением передачи или на буфер, который будет использован для приема данных. Размер буфера, используемого для приема или передачи данных, определяется содержимым поля Size. Поле CallName заполняется именем станции, с которой ваша станция желает установить канал для передачи данных. Поле OurName должно содержать имя вашей программы, под которым она будет принимать данные. В качестве этого имени может выступать обычное, групповое или постоянное имя. Поля ReceiveTimeout и SendTimeout содержат интервал времени (измеряемый в 1/2 с), в течение которого ожидается завершение соответственно команд приема и передачи. Поле PostRoutine - указатель на программу, которая получает управление после завершения команды. Эта программа (POST-программа) аналогична программе ESR протоколов IPX/SPX и вызывается только в том случае, если в поле PostRoutine был указан адрес программы. Если же это поле заполнить нулями, никакая программа вызываться не будет. Поле AdapterNumber используется, если в станции установлено несколько сетевых адаптеров (в сетях Ethernet этого обычно не бывает). В этом поле указывается номер адаптера, для которого предназначена команда. Первый адаптер имеет номер 0, второй - 1. Поле FinalCCode содержит во время выполнения команды значение 0xFF. После завершения выполнения команды в это поле записывается код ошибки, который относится к выполнению команды в целом (в отличие от кода в поле CCode). Если ваша программа не задала адрес для программы в поле PostRoutine, она должна опрашивать в цикле содержимое этого поля, ожидая, пока в нем не появится значение, отличное от 0xFF. Поле Reserved зарезервировано для использования протоколом NETBIOS, ваша программа не должна изменять его содержимое. 4.2.4. POST-программа
POST-программа является программой обработки прерывания. Она получает
управление в состоянии с запрещенными прерываниями. Регистры ES:BX
содержат адрес блока NCB, который использовался при выполнении
команды. Учтите, что, как и из любой другой программы обработки прерывания, из POST-программы не следует вызывать функции MS-DOS. Требования к POST-программе во многом такие же, как и к ESR-программе, используемой протоколами IPX и SPX. Она должна позаботиться о сохранении изменяемых регистров, установить регистр DS на сегмент данных программы (для обеспечения доступа к переменным). POST-программа должна работать как можно быстрее. Лучше всего если она будет использоваться только для установки флага, сигнализирующего основной программе о завершении выполнения команды. Перед завершением своей работы POST-программа должна восстановить содержимое всех регистров и выполнить команду возврата из прерывания IRET. Если вы составляете POST-программу на языке программирования Си, вы можете воспользоваться ключевым словом interrupt: void interrupt NETBIOS_Post_Routine(void); Лучше всего составить POST-программу на языке ассемблера, например, так: .286 .MODEL SMALL .DATA _completed_ncb_ptr dd 0 .CODE PUBLIC _netbios_post PUBLIC _completed_ncb_ptr _netbios_post PROC FAR push ax push ds push es push si mov ax, DGROUP mov ds, ax mov word ptr _completed_ncb_ptr+2, es mov word ptr _completed_ncb_ptr, si pop si pop es pop ds pop ax iret _netbios_post ENDP end 4.3. Команды NETBIOSПеред выполнением команды ее код должен быть записан в поле Cmd блока NCB. Каждая команда NETBIOS реализована в двух вариантах - с ожиданием и без ожидания.
Если вашей программе нечего делать до тех пор, пока выполнение
команды NETBIOS не будет полностью завершено, вы можете выбрать
вариант с ожиданием. В этом случае после вызова NETBIOS программа
вновь получит управление только после завершения выполнения команды.
При использовании протоколов IPX/SPX ваша программа должна была
сама дожидаться выполнения Вариант без ожидания похож на вариант использования функций IPX/SPX с программой ESR, вызываемой после завершения операции. Программа, вызвавшая команду NETBIOS без ожидания, получает управление немедленно. Команда будет выполняться в фоновом режиме параллельно с работой вызвавшей ее программы. После того как выполнение команды будет завершено, управление будет передано функции, адрес которой необходимо указать в поле PostRoutine блока NCB. Можно также дожидаться окончания выполнения команды, опрашивая в цикле поле FinalCCode, которое будет содержать значение 0xFF до тех пор, пока команда не будет выполнена. Все команды NETBIOS можно разделить на несколько групп:
для работы с именами;
Так как большинство команд NETBIOS реализованы в двух вариантах
У команд без ожидания старший бит кода команды установлен в единицу. 4.3.1. Работа с именамиВ этой группе есть команды, позволяющие добавлять обычное или групповое имя, удалять имя. Все эти команды могут работать в двух вариантах - с ожиданием и без ожидания. NB_WAddName (0x30)Команда добавляет указанное в поле OurName имя в таблицу имен, расположенную на рабочей станции. Имя должно быть уникальным в сети. Оно не может использоваться на других станциях ни как обычное, ни как групповое. Если длина имени меньше 16 байт, оно должно быть дополнено справа символами пробела. Можно закрыть имя двоичным нулем для совместимости со строками языка Си. Нуль должен находиться в последней позиции имени. После успешного выполнения команды NETBIOS присваивает имени номер и возвращает его в поле LocalSessionNumber блока NCB. Номер имени может потребоваться вам для работы с датаграммами.
В процессе добавления имени NETBIOS посылает по сети запрос. Если
такое имя уже используется на какой-либо станции, эта станция
пришлет ответ. Процедура добавления имени занимает достаточно много времени. Это связано с необходимостью выполнить опрос всех станций сети. Поэтому вы должны добавлять имена один раз в самом начале работы программы.
NB_AddName (0xB0)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xB0. NB_WAddGroupName (0x36)Команда добавляет указанное в поле OurName групповое имя в таблицу имен, расположенную на рабочей станции. Имя не должно использоваться другими станциями в сети как обычное. Однако несколько станций могут использовать одно и тоже имя как групповое. Если длина имени меньше 16 байт, оно должно быть дополнено справа символами пробела. Можно закрыть имя двоичным нулем для совместимости со строками языка Си. Нуль должен находиться в последней позиции имени. После успешного выполнения команды NETBIOS присваивает имени номер и возвращает его в поле LocalSessionNumber блока NCB. Номер имени нужен для работы с датаграммами.
NB_AddGroupName (0xB6)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xB6. NB_WDeleteName (0x31)Команда удаляет имя из таблицы имен рабочей станции, если оно не используется каким-либо каналом. Если же имя используется каналом, то оно помечается как назначенное для удаления и удаляется после закрытия канала. Если вы попытаетесь удалить имя, которое используется каналом, команда завершится с кодом ошибки 0xF. В этом случае перед удалением имени необходимо закрыть канал (см. дальше описание команд для работы с каналами). Как только канал будет закрыт, связанное с ним имя будет автоматически удалено, если перед закрытием канала выполнялась попытка удалить имя. Если имя используется несколькими каналами, его можно удалить только после закрытия всех связанных с ним каналов.
NB_DeleteName (0xB1)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xB1. 4.3.2. Прием и передача датаграммС помощью команд приема и передачи датаграмм вы можете передавать и принимать пакеты без подтверждения, аналогично тому, как это выполняет протокол IPX. Есть команды для передачи и приема датаграмм по обычному или групповому имени, а также для передачи и приема датаграмм, адресованных одновременно всем станциям в сети. В отличие от протокола IPX протокол NETBIOS использует разные команды для приема обычных датаграмм и датаграмм, адресованных всем станциям в сети. Протокол IPX не позволяет вам передавать пакет группе станций в сети. Вы можете передать пакет либо какой-либо одной станции, либо всем станциям сразу. С помощью NETBIOS вы можете организовать передачу данных так, что пакеты будут приниматься только одной группой станций в сети по ее групповому имени. Длина сообщений, передаваемых при помощи команд данной группы, ограничена 512 байтами. Через каналы вы можете передавать блоки данных существенно большего размера. NB_WSendDatagram (0x20)Команда предназначена для передачи блока данных размером от 1 до 512 байт в виде датаграммы (без подтверждения приема). Датаграмма может быть послана на обычное или групповое имя. Для передачи датаграммы вам не надо создавать канал с принимающей станцией.
NB_SendDatagram (0xA0)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xA0. NB_WSendBroadcastDatagram (0x22)Команда посылает датаграмму, которую примут все станции, выдавшие команду NB_ReceiveBroadcastDatagram (в том числе и передающая станция, если она тоже выдала команду NB_ReceiveBroadcastDatagram). Если на одной станции команда NB_ReceiveBroadcastDatagram выдана несколько раз, все буферы после приема данных будут содержать одну и ту же информацию. Заметим, что датаграммы, посылаемые этой командой одновременно всем станциям, могут быть приняты только теми станциями, которые выдали команду NB_ReceiveBroadcastDatagram. Поэтому если станция желает принимать датаграммы, передаваемые в "широковещательном" режиме, она должна специально к этому подготовиться. В протоколе IPX (в отличие от протокола NETBIOS) существует одна универсальная функция, которая может принимать и обычные, и "широковещательные" датаграммы.
NB_SendBroadcastDatagram (0xA2)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xA2. NB_WReceiveDatagram (0x21)Команда предназначена для приема датаграмм, переданных командой NB_SendDatagram. Она не может принимать датаграммы, переданные в "широковещательном" режиме командой NB_SendBroadcastDatagram. Однако эта команда может принимать датаграммы, посланные на групповое имя. Если перед вызовом команды в поле NetworkNameNumber блока NCB записать значение 0xFF, команда сможет принимать датаграммы от любой станции для любого имени. Если длина принятой датаграммы превышает значение, указанное в поле Size, принятый блок данных будет обрезан.
NB_ReceiveDatagram (0xA1)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xA1. NB_WReceiveBroadcastDatagram (0x23)Команда предназначена для приема датаграмм, переданных командой NB_SendBroadcastDatagram. Она не может принимать датаграммы, переданные командой NB_SendDatagram. Если перед вызовом команды в поле NetworkNameNumber блока NCB записать значение 0xFF, команда сможет принимать датаграммы от любой станции для любого имени. Если длина принятой датаграммы превышает значение, указанное в поле Size, принятый блок данных будет обрезан.
NB_ReceiveBroadcastDatagram (0xA3)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xA3. 4.3.3. Работа с каналамиВ группе команд, предназначенных для работы с каналами, есть команды для создания канала, команды для удаления канала, а также команда для определения состояния канала. Канал создается одновременно двумя станциями, одна из которых при этом находится в режиме приема запроса на создание канала, а другая передает такой запрос. Вы можете создать канал между любыми двумя именами в сети. При работе с каналами имена используются только для создания каналов. Впоследствии, когда каналы уже будут созданы, для передачи данных используются номера каналов, а не имена или номера имен. Можно создать канал с самим собой, если при создании канала указать в качестве имени партнера свое имя. NB_WCall (0x10)Команда устанавливает канал между двумя именами, заданными в блоке NCB. Эти имена могут относиться к программам, работающим на разных станциях или на одной станции. В поле OurName указывается имя станции, которая устанавливает канал, в поле CallName - имя станции, с которой устанавливается канал. Для успешного создания канала принимающая сторона должна выдать команду NB_Listen, которая будет описана ниже. Можно установить канал не только с обычным, но и с групповым именем. Для этого придется выдать команду NB_WCall несколько раз, так как за один вызов создается только один канал.
Команда NB_WCall делает несколько попыток создать канал и в случае
При создании канала указывается время тайм-аута для операций приема и передачи данных. Если команды приема или передачи данных через каналы не будут выполнены в течение времени тайм-аута, они (команды) будут прерваны. При этом считается, что канал неработоспособен. После создания канала поле LocalSessionNumber будет содержать присвоенный номер канала. Сохраните его для использования в процессе приема и передачи данных по каналу.
NB_Call (0x90)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x90. NB_WListen (0x11)Команда работает в паре с предыдущей командой и предназначена для организации канала с вызываемой стороны. В поле CallName блока NCB необходимо указать имя, с которым устанавливается канал. Если в первый байт имени записать символ "*", канал будет установлен с любой вызывающей станцией.
Программа может выдать несколько команд NB_Listen для создания
одно- В случае успешного завершения команда запишет в поле LocalSessionNumber номер созданного канала. При создании канала необходимо указать время тайм-аута для операций приема и передачи данных через канал. Сама команда NB_WListen не использует тайм-аут. Программа, выдавшая эту команду, будет находиться в состоянии ожидания до тех пор, пока какая-либо станция не пожелает создать с ней канал. Для исключения состояния "зависания" программы лучше использовать вариант NB_Listen этой команды (без ожидания).
NB_Listen (0x91)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x91. NB_WHangUp (0x12)Команда предназначена для закрытия канала, номер которого указан в поле LocalSessionNumber блока NCB. Она должна быть выполнена с обеих сторон канала после завершения работы. Если для закрываемого с помощью этой команды канала на других станциях выдана команда NB_WReceive, она завершается с кодом ощибки 0x0A. Выданная для закрываемого канала команда NB_WSend также завершается с кодом 0x0A, но через 20 секунд, которые отводятся ей для завершения своей работы. Если команда NB_WSend не успела завершить передачу за 20 секунд, команда NB_WHangUp завершается с кодом 0x05. Аналогично с кодом 0x0A завершает свою работу и команда NB_WReceiveAny, выданная для закрываемого канала. Другие команды, выданные для закрываемого канала, завершаются с кодом 0x18. Если программа пытается закрыть канал, который был уже закрыт или не существовал, такая ситуация не считается ошибочной и в поле кода ошибки проставляется нулевое значение.
NB_HangUp (0x92)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x92. NB_WSessionStatus (0x34)Команда возвращает программе состояние канала, имя которого указано в поле OurName блока NCB. В качестве имени можно указать символ "*", в этом случае программа получит информацию о каналах, относящихся ко всем именам станций, имеющихся в локальной таблице имен на вызывающей команду станции. Информация о состоянии каналов возвращается в буфер, адрес которого программа должна записать в поле Buffer блока NCB. Размер буфера должен быть указан в поле Size блока NCB. Формат буфера можно описать следующей структурой: struct _SESSION_STATUS { unsigned char NameNumber; unsigned char SessionCount; unsigned char DatagramsOutstanding; unsigned char ReceiveAnyoutstanding; struct _SESSION { unsigned char LocalSessionNumber; unsigned char State; char LocalName[16]; char RemoteName[16]; unsigned char ReceiveCount; unsigned char SendCount; } Session[40]; }; Приведем список полей буфера:
NB_SessionStatus (0xB4)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xB4. 4.3.4. Прием и передача данных через каналыNB_WSend (0x14)С помощью этой команды программа может передать блок данных размером от 1 до 65535 байт по созданному ранее каналу. Перед вызовом команды программа должна записать номер канала, по которому будет выполняться передача, в поле LocalSessionNumber блока NCB. Адрес передаваемого блока данных и его длина должны быть записаны в поля Buffer и Size. Для приема данных, передаваемых командой NB_WSend, необходимо использовать команду NB_WReceive (или NB_Receive). Механизм передачи данных с использованием каналов гарантирует не только доставку блоков данных, но и правильную последовательность, в которой эти блоки будут приняты. Если истекло время тайм-аута, заданного при создании канала, команда завершается с ошибкой.
NB_Send (0x94)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x94. NB_WSendNoAck (0x71)По своему назначению команда полностью аналогична предыдущей, однако в отличие от нее не выполняет проверку доставки блока данных принимающей стороне. За счет этого она работает немного быстрее.
NB_SendNoAck (0xF1)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xF1. NB_WChainSend (0x17)Команда работает аналогично команде NB_WSend, однако с ее помощью можно передать сразу два блока данных. Данные передаются как один блок. Общий размер передаваемых с помощью этой команды данных может достигать 131070 байт. Первый буфер задается, как и для команды NB_WSend, через поля Buffer и Size. Размер второго буфера должен быть записан в первые два байта поля CallName блока ECB, а его адрес занимает следующие четыре байта этого поля.
NB_ChainSend (0x97)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x97. NB_WChainSendNoAck (0x72)Команда аналогична команде NB_WCainSend, однако в отличие от нее не выполняет проверку доставки блока данных принимающей стороне. За счет этого она работает немного быстрее.
NB_ChainSendNoAck (0xF2)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0xF2. NB_WReceive (0x15)Команда принимает данные, посланные командами NB_WSend или NB_WChainSend.
Если размер буфера недостаточен для записи принятых данных, команда
возвращает код ошибки 0x06; в этом случае вы можете вызвать команду
еще раз для того, чтобы прочесть данные, не поместившиеся в буфере
при предыдущем вызове команды.
NB_Receive (0x95)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x95. NB_WReceiveAny (0x16)Команда принимает данные от всех партнеров. Для этой команды вместо номера канала необходимо указать номер имени, полученный вашей программой при добавлении имени. Если в поле NetworkNameNumber проставить значение 0xFF, эта команда будет принимать данные от любых партнеров для любых каналов, созданных на вашей станции. Если размер буфера недостаточен для записи принятых данных, команда возвращает код ошибки 0x06; в этом случае вы можете вызвать команду еще раз для того, чтобы прочесть данные, не поместившиеся в буфере при предыдущем вызове команды.
NB_ReceiveAny (0x96)Команда аналогична предыдущей, за исключением того, что она выполняется без ожидания и в поле Cmd необходимо записать значение 0x96. 4.3.5. Другие командыВ этом разделе мы опишем команды NETBIOS, позволяющие сбросить драйвер NETBIOS в исходное состояние, отменить выданную ранее команду. NB_WResetAdapter (0x32)Команда сбрасывает NETBIOS в исходное состояние, удаляет все имеющиеся каналы и имена (кроме постоянного имени, которое нельзя удалить, не вытащив сетевой адаптер из компьютера). С помощью этой команды можно также изменить максимальное количество доступных программе каналов и используемых одновременно блоков NCB. По умолчанию доступны шесть каналов и 12 блоков NCB.
NB_WCancel (0x35)Команда используется для отмены других запущенных команд. Адрес NCB для отменяемой команды должен быть записан в поле Buffer. С помощью этой команды нельзя отменить следующие команды: NB_AddName, NB_AddGroupName, NB_DeleteName, NB_SendDatagramm, NB_SendBroadcastDatagramm, NB_ResetAdapter, NB_SessionStatus, NB_Cancel, NB_Unlink.
4.4. Коды ошибокПриведем список кодов ошибок, возвращаемых NETBIOS:
4.5. Система "клиент-сервер" на базе датаграммПриведем пример простейшей системы "клиент-сервер", в которую входят две программы, обменивающиеся датаграммами (листинг 18). Аналогичная система, сделанная на базе протокола IPX, требовала использования процедуры определения сетевых адресов сервера и клиента. Так как сервер может быть запущен на любой станции в сети, программа-клиент сразу после своего запуска не может знать сетевой адрес сервера. Если вы помните, для определения адреса программы-сервера программа-клиент посылала пакет в "широковещательном" режиме сразу всем станциям в сети. Сервер, приняв этот пакет, отвечал клиенту, и таким образом программы узнавали адрес друг друга. Единственное, что должно быть известно программе-клиенту, - это номер сокета, на котором программа-сервер ожидает прихода пакетов. При использовании протокола NETBIOS ситуация сильно упрощается. Вполне достаточно, если программа-клиент будет знать имя, которое добавляется сервером и используется для приема пакетов от клиентов. Программа-сервер начинает свою работу с создания объекта класса NETBIOS_DATAGRAM_SERVER. Конструктор в качестве параметра получает имя, добавляемое сервером и используемое для приема пакетов. В нашем случае это имя "NETBIOS Server". В процессе своей работы конструктор класса NETBIOS_DATAGRAM_SERVER проверяет наличие интерфейса NETBIOS, добавляет имя, полученное им в качестве параметра, и сохраняет полученный номер имени для дальнейшего использования. После завершения работы программы деструктор класса NETBIOS_DATAGRAM_SERVER удалит добавленное имя. После проверки возможных ошибок программа-сервер вызывает функцию Receive(), которая ожидает прихода пакета от программы-клиента. Когда пакет будет получен, сервер выводит его содержимое как текстовую строку в стандартный поток вывода и завершает свою работу. // =================================================== // Листинг 18. Сервер NETBIOS, вариант с // использованием датаграмм // // Файл nbserver.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> #include "netbios.hpp" // Класс серверов NETBIOS class NETBIOS_DATAGRAM_SERVER { unsigned errno; void interrupt ( *int5C)(...); public: // Здесь хранится имя сервера и номер этого имени char OurName[16]; unsigned NetworkNameNumber; // Конструктор, проверяет наличие NETBIOS и добавляет имя NETBIOS_DATAGRAM_SERVER(char *Name) { // Блок NCB, который будет использован при добавлении имени // NCB AddNameNCB; // Проверяем длину имени if(strlen(Name) > 15) { errno = 0xff; return; } strcpy(OurName, Name); // Проверяем наличие интерфейса NETBIOS int5C = getvect(0x5c); errno = 0; if(FP_SEG(int5C) == 0x0000 || FP_SEG(int5C) == 0xF000) { errno=0xff; exit(-1); } // Добавляем имя AddNameNCB.WAddName(OurName); // Запоминаем полученный номер имени NetworkNameNumber = AddNameNCB.GetNetworkNameNumber(); errno = AddNameNCB.Error(); } // Деструктор, удаляет имя. ~NETBIOS_DATAGRAM_SERVER() { NCB AddNameNCB; AddNameNCB.WDeleteName(OurName); errno = AddNameNCB.Error(); } // Функция для проверки кода ошибки int Error(void) {return errno;} // Функция для приема датаграммы void Receive(char *ReceiveBuffer, unsigned BufferSize) { NCB ReceiveNCB; // Записываем в NCB адрес и длину буфера ReceiveNCB.SetBuffer(ReceiveBuffer, BufferSize); // Выполняем прием датаграммы с ожиданием ReceiveNCB.WReceiveDatagram(NetworkNameNumber); } }; void main(void) { // Наш сервер с именем "NETBIOS Server" NETBIOS_DATAGRAM_SERVER Server("NETBIOS Server"); char ReceiveBuffer[512]; // Проверяем, были ли ошибки на этапе инициализации сервера. if(Server.Error()) { printf("Ошибка %02.2X\n", Server.Error()); return; } printf("Инициализация завершена.\n"); printf("Ожидаем сообщение от клиента.\n"); // Принимаем сообщение от клиента Server.Receive(ReceiveBuffer, 512); printf("Принято: >%s<\n",ReceiveBuffer); } Программа-сервер работает в паре с программой-клиентом (листинг 19). После запуска программа-клиент создает объект класса NETBIOS_DATAGRAM_CLIENT. Конструктор и деструктор этого класса выполняют действия, аналогичные конструктору и деструктору класса NETBIOS_DATAGRAM_SERVER. После инициализации и проверки ошибок программа-клиент посылает сообщение "Привет от клиента NETBIOS!" серверу с именем "NETBIOS Server" при помощи функции Send(). Затем программа-клиент завершает свою работу. // =================================================== // Листинг 19. Клиент NETBIOS, вариант с // использованием датаграмм // // Файл nbclient.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> #include "netbios.hpp" // Класс клиентов NETBIOS class NETBIOS_DATAGRAM_CLIENT { unsigned errno; public: // Здесь хранится имя клиента и номер этого имени char OurName[16]; unsigned NetworkNameNumber; union REGS regs; // Конструктор, проверяет наличие NETBIOS и добавляет имя NETBIOS_DATAGRAM_CLIENT(char *Name) { // Блок NCB, который будет использован при добавлении имени NCB AddNameNCB; // Проверяем длину имени имя if(strlen(Name) > 15) { errno = 0xff; return; } strcpy(OurName, Name); // Проверяем наличие интерфейса NETBIOS int5C = getvect(0x5c); errno = 0; if(FP_SEG(int5C) == 0x0000 || FP_SEG(int5C) == 0xF000) { errno=0xff; exit(-1); } // Добавляем имя AddNameNCB.WAddName(OurName); // Запоминаем полученный номер имени NetworkNameNumber = AddNameNCB.GetNetworkNameNumber(); errno = AddNameNCB.Error(); } // Деструктор, удаляет имя. ~NETBIOS_DATAGRAM_CLIENT() { NCB AddNameNCB; AddNameNCB.WDeleteName(OurName); errno = AddNameNCB.Error(); } // Функция для проверки кода ошибки int Error(void) {return errno;} // Функция для приема датаграммы void Receive(char *ReceiveBuffer, unsigned BufferSize) { NCB ReceiveNCB; // Записываем в NCB адрес и длину буфера ReceiveNCB.SetBuffer(ReceiveBuffer, BufferSize); // Выполняем прием датаграммы с ожиданием ReceiveNCB.WReceiveDatagram(NetworkNameNumber); } // Функция для передачи датаграммы void Send(char *ReceiveBuffer, unsigned BufferSize, char *CallName) { NCB SendNCB; // Устанавливаем адрес и длину буфера SendNCB.SetBuffer(ReceiveBuffer, BufferSize); // Устанавливаем имя партнера, которому будет передана // наша датаграмма SendNCB.SetCallName(CallName); // Передаем датаграмму с ожиданием SendNCB.WSendDatagram(NetworkNameNumber); } }; void main(void) { // Наш клиент с именем "NETBIOS Client" NETBIOS_DATAGRAM_CLIENT Client("NETBIOS Client"); // Проверяем, были ли ошибки на этапе инициализации клиента. if(Client.Error()) { printf("Ошибка %02.2X\n", Client.Error()); return; } printf("Инициализация завершена.\n"); // Передаем сообщение серверу с именем "NETBIOS Server" Client.Send("Привет от клиента NETBIOS!", 512, "NETBIOS Server"); } В include-файле netbios.hpp (листинг 20) приведены определения констант и классов для работы с протоколом NETBIOS через прерывание INT 5Ch. В классе NCB, кроме структуры данных _NCB, определены конструктор NCB() и несколько других функций для работы с этим классом. Конструктор расписывает структуру ncb нулями и сбрасывает код ошибки в переменной errno. Функция NetBios() вызывает прерывание NETBIOS. Функции WAddName() и WDeleteName() определены в файле nbfunc.cpp (листинг 21). Они предназначены для добавления и удаления имени. Назначение остальных функций вы можете узнать, прочитав комментарии к программе в листинге 20. // =================================================== // Листинг 20. Классы для работы с NETBIOS // // Файл netbios.hpp // // (C) A. Frolov, 1993 // =================================================== // Команды NETBIOS // Команды для работы с именами #define NB_WAddName 0x30 #define NB_AddName 0xb0 #define NB_WAddGroupName 0x36 #define NB_AddGroupName 0xb6 #define NB_WDeleteName 0x31 #define NB_DeleteName 0xb1 // Команды для передачи датаграмм #define NB_WSendDatagram 0x20 #define NB_SendDatagram 0xa0 #define NB_WSendBroadcastDatagram 0x22 #define NB_SendBroadcastDatagram 0xa2 // Команды для приема датаграмм #define NB_WReceiveDatagram 0x21 #define NB_ReceiveDatagram 0xa1 #define NB_WReceiveBroadcastDatagram 0x23 #define NB_ReceiveBroadcastDatagram 0xa3 // Команды для работы с каналами #define NB_WCall 0x10 #define NB_Call 0x90 #define NB_WListen 0x11 #define NB_Listen 0x91 #define NB_WHangUp 0x12 #define NB_HangUp 0x92 // Команды для передачи данных по каналу #define NB_WSend 0x14 #define NB_Send 0x94 #define NB_WSendNoAck 0x71 #define NB_SendNoAck 0xf1 #define NB_WChainSend 0x17 #define NB_ChainSend 0x97 #define NB_WChainSendNoAck 0x72 #define NB_ChainSendNoAck 0xf2 // Команды для приема данных по каналу #define NB_WReceive 0x15 #define NB_Receive 0x95 #define NB_WReceiveAny 0x16 #define NB_ReceiveAny 0x96 // Прочие команды #define NB_WResetAdapter 0x32 #define NB_WCancel 0x35 #define NB_WSessionStatus 0x34 #define NB_SessionStatus 0xb4 // Класс NCB для работы с командами NETBIOS class NCB { // Стандартный блок NCB в формате NETBIOS struct _NCB { unsigned char Cmd; unsigned char CCode; unsigned char LocalSessionNumber; unsigned char NetworkNameNumber; void far *Buffer; unsigned Size; char CallName[16]; char OurName[16]; unsigned char ReceiveTimeout; unsigned char SendTimeout; void interrupt (*PostRoutine)(void); unsigned char AdapterNumber; unsigned char FinalCCode; unsigned char Reserved[14]; } ncb; struct SREGS sregs; union REGS regs; unsigned errno; // Функция для вызова NETBIOS void NetBios(void) { sregs.es = FP_SEG(&ncb); regs.x.bx = FP_OFF(&ncb); int86x(0x5c, ®s, ®s, &sregs); } public: // Конструктор, расписывает ncb нулями NCB() { memset(&ncb, 0, sizeof(ncb)); errno = 0; } // Функция возвращает код ошибки int Error(void) {return errno;} // Функция для добавления имени void WAddName(char *name); // Функция для удаления имени void WDeleteName(char *name); // Функция для определения номера имени unsigned GetNetworkNameNumber(void) { return(ncb.NetworkNameNumber); } // Функция для установки адреса и размера буфера void SetBuffer(char far *Buf, unsigned BufSize) { ncb.Buffer = Buf; ncb.Size = BufSize; } // Установка в ncb имени вызываемого партнера void SetCallName(char *name); // Прием датаграмм с ожиданием void WReceiveDatagram(int NetwrkNameNumber) { // Заполняем поле номера своего имени ncb.NetworkNameNumber = NetwrkNameNumber; // Вызываем NETBIOS ncb.Cmd = NB_WReceiveDatagram; NetBios(); } // Передача датаграмм с ожиданием void WSendDatagram(int NetwrkNameNumber) { // Заполняем поле номера своего имени ncb.NetworkNameNumber = NetwrkNameNumber; ncb.Cmd = NB_WSendDatagram; // Вызываем NETBIOS NetBios(); } }; В файле nbfunc.cpp приведены определения некоторых функций из класса NCB (листинг 21): // =================================================== // Листинг 21. Функции для NETBIOS // // Файл nbfunc.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> #include "netbios.hpp" // Добавляем имя void NCB::WAddName(char *name) { char buf[16]; // Проверяем длину имени if(strlen(name) > 15) { errno = 0xff; return; } strcpy(buf, name); // При необходимости дополняем имя пробелами while (strlen(buf) < 15) strcat(buf, " "); // Вызываем NETBIOS ncb.Cmd = NB_WAddName; strcpy(ncb.OurName, buf); NetBios(); errno = ncb.FinalCCode; } // Удаление имени void NCB::WDeleteName(char *name) { char buf[16]; // Проверяем длину имени if(strlen(name) > 15) { errno = 0xff; return; } strcpy(buf, name); // При необходимости дополняем имя пробелами while (strlen(buf) < 15) strcat(buf, " "); strcpy(ncb.OurName, buf); // Вызываем NETBIOS ncb.Cmd = NB_WDeleteName; NetBios(); errno = ncb.FinalCCode; } // Установка имени вызываемого партнера void NCB::SetCallName(char *name) { char buf[16]; if(strlen(name) > 15) { errno = 0xff; return; } strcpy(buf, name); while (strlen(buf) < 15) strcat(buf, " "); strcpy(ncb.CallName, buf); } 4.6. Система "клиент-сервер" на базе каналовПриведем пример системы "клиент-сервер", реализованной с использованием каналов протокола NETBIOS (листинг 22). После запуска программа-сервер создает объект класса NETBIOS_SESSION_SERVER. Конструктор этого объекта проверяет присутствие интерфейса NETBIOS, добавляет имя, переданное ему в качестве параметра, затем создает канал при помощи функции WListen(). Деструктор класса NETBIOS_SESSION_SERVER перед удалением имени удаляет канал, так как имя нельзя удалить, если оно используется каким-либо каналом. После того, как отработал конструктор, программа-сервер проверяет ошибки и вызывает функцию Receive(), которая ожидает приема данных по созданному каналу. После приема сервер отображает принятые данные как текстовую строку и завершает свою работу. // =================================================== // Листинг 22. Сервер NETBIOS, вариант с // использованием каналов // // Файл nbserver.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> #include "netbios.hpp" // Класс серверов NETBIOS class NETBIOS_SESSION_SERVER { unsigned errno; void interrupt ( *int5C)(...); public: // Здесь хранится имя сервера и номер этого имени char OurName[16]; unsigned NetworkNameNumber; // Блок NCB, который будет использован при добавлении имени NCB AddNameNCB; // Конструктор, проверяет наличие NETBIOS и добавляет имя NETBIOS_SESSION_SERVER(char *Name) { // Проверяем длину имени имя if(strlen(Name) > 15) { errno = 0xff; return; } strcpy(OurName, Name); // Проверяем наличие интерфейса NETBIOS int5C = getvect(0x5c); errno = 0; if(FP_SEG(int5C) == 0x0000 || FP_SEG(int5C) == 0xF000) { errno=0xff; exit(-1); } // Добавляем имя AddNameNCB.WAddName(OurName); // Запоминаем полученный номер имени NetworkNameNumber = AddNameNCB.GetNetworkNameNumber(); errno = AddNameNCB.Error(); if(errno) return; // Устанавливаем "*" в поле CallName, это означает, // что сервер будет обрабатывать запросы на создание // канала от любого имени AddNameNCB.SetCallName("*"); // Устанавливаем время тайм-аута для команд // приема и передачи данных по каналу AddNameNCB.SetRtoSto(20,20); // Создаем канал с принимающей стороны AddNameNCB.WListen(); } // Деструктор, удаляет канал и имя. ~NETBIOS_SESSION_SERVER() { // Удаление канала AddNameNCB.WHangUp(); // Удаление имени AddNameNCB.WDeleteName(OurName); errno = AddNameNCB.Error(); } // Функция для проверки кода ошибки int Error(void) {return errno;} // Функция для приема данных по каналу void Receive(char *ReceiveBuffer, unsigned BufferSize) { // Записываем в NCB адрес и длину буфера AddNameNCB.SetBuffer(ReceiveBuffer, BufferSize); // Выполняем прием датаграммы с ожиданием AddNameNCB.WReceive(); } }; void main(void) { // Наш сервер с именем "NETBIOS Server" NETBIOS_SESSION_SERVER Server("NETBIOS Server"); char ReceiveBuffer[512]; // Проверяем, были ли ошибки на этапе инициализации сервера. if(Server.Error()) { printf("Ошибка %02.2X\n", Server.Error()); return; } printf("Инициализация завершена.\n"); printf("Ожидаем сообщение от клиента.\n"); // Принимаем сообщение от клиента по каналу, который был создан // конструктором класса NETBIOS_SESSION_SERVER Server.Receive(ReceiveBuffer, 512); printf("Принято: >%s<\n",ReceiveBuffer); } В файле nbclient.cpp находится программа-клиент (листинг 23), работающая в паре с только что приведенной программой-сервером. Программа-клиент создает объект NETBIOS_SESSION_CLIENT, конструктор которого выполняет действия, аналогичные конструктору класса NETBIOS_SESSION_SERVER. Есть одно отличие: для создания канала в конструкторе класса NETBIOS_SESSION_CLIENT используется функция WCall(), а не WListen(). Конструктор создает канал с программой-сервером, указывая имя "NETBIOS Server", которое используется сервером для работы с клиентом. После проверки ошибок программа-клиент с помощью функции Send() передает по созданному каналу программе-серверу сообщение "Привет от клиента NETBIOS!" и завершает свою работу. // =================================================== // Листинг 23. Клиент NETBIOS, вариант с // использованием каналов // // Файл nbclient.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <mem.h> #include <string.h> #include "netbios.hpp" // Класс клиентов NETBIOS class NETBIOS_SESSION_CLIENT { unsigned errno; void interrupt ( *int5C)(...); // Блок NCB, который будет использован при добавлении имени NCB AddNameNCB; public: // Здесь хранится имя клиента и номер этого имени char OurName[16]; unsigned NetworkNameNumber; // Конструктор, проверяет наличие NETBIOS и добавляет имя NETBIOS_SESSION_CLIENT(char *Name) { // Проверяем длину имени имя if(strlen(Name) > 15) { errno = 0xff; return; } strcpy(OurName, Name); // Проверяем наличие интерфейса NETBIOS int5C = getvect(0x5c); errno = 0; if(FP_SEG(int5C) == 0x0000 || FP_SEG(int5C) == 0xF000) { errno=0xff; exit(-1); } // Добавляем имя AddNameNCB.WAddName(OurName); // Запоминаем полученный номер имени NetworkNameNumber = AddNameNCB.GetNetworkNameNumber(); // Если при добавлении имени были ошибки, // завершаем работу программы errno = AddNameNCB.Error(); if(errno) return; // Устанавливаем имя сервера, с которым будем создавать канал AddNameNCB.SetCallName("NETBIOS Server"); // Устанавливаем время тайм-аута // для передачи и приема данных по каналу AddNameNCB.SetRtoSto(20,20); // Устанавливаем канал с передающей стороны AddNameNCB.WCall(); } // Деструктор, удаляет канал и имя. ~NETBIOS_SESSION_CLIENT() { // Удаление канала AddNameNCB.WHangUp(); // Удаление имени AddNameNCB.WDeleteName(OurName); errno = AddNameNCB.Error(); } // Функция для проверки кода ошибки int Error(void) {return errno;} // Функция для передачи по каналу void Send(char *ReceiveBuffer, unsigned BufferSize) { // Устанавливаем адрес и длину буфера AddNameNCB.SetBuffer(ReceiveBuffer, BufferSize); // Передаем данные по каналу с ожиданием AddNameNCB.WSend(); } }; void main(void) { // Наш клиент с именем "NETBIOS Client" NETBIOS_SESSION_CLIENT Client("NETBIOS Client"); // Проверяем, были ли ошибки на этапе инициализации клиента. if(Client.Error()) { printf("Ошибка %02.2X\n", Client.Error()); return; } printf("Инициализация завершена.\n"); // Передаем сообщение серверу по созданному каналу. Канал был // создан при работе конструктора класса NETBIOS_SESSION_CLIENT. Client.Send("Привет от клиента NETBIOS!", 512); } Файл netbios.hpp (листинг 24) содержит все необходимые определения для программ, работающих с каналами NETBIOS: // =================================================== // Листинг 24. Классы для работы с NETBIOS // // Файл netbios.hpp // // (C) A. Frolov, 1993 // =================================================== // Команды NETBIOS // Команды для работы с именами #define NB_WAddName 0x30 #define NB_AddName 0xb0 #define NB_WAddGroupName 0x36 #define NB_AddGroupName 0xb6 #define NB_WDeleteName 0x31 #define NB_DeleteName 0xb1 // Команды для передачи датаграмм #define NB_WSendDatagram 0x20 #define NB_SendDatagram 0xa0 #define NB_WSendBroadcastDatagram 0x22 #define NB_SendBroadcastDatagram 0xa2 // Команды для приема датаграмм #define NB_WReceiveDatagram 0x21 #define NB_ReceiveDatagram 0xa1 #define NB_WReceiveBroadcastDatagram 0x23 #define NB_ReceiveBroadcastDatagram 0xa3 // Команды для работы с каналами #define NB_WCall 0x10 #define NB_Call 0x90 #define NB_WListen 0x11 #define NB_Listen 0x91 #define NB_WHangUp 0x12 #define NB_HangUp 0x92 // Команды для передачи данных по каналу #define NB_WSend 0x14 #define NB_Send 0x94 #define NB_WSendNoAck 0x71 #define NB_SendNoAck 0xf1 #define NB_WChainSend 0x17 #define NB_ChainSend 0x97 #define NB_WChainSendNoAck 0x72 #define NB_ChainSendNoAck 0xf2 // Команды для приема данных по каналу #define NB_WReceive 0x15 #define NB_Receive 0x95 #define NB_WReceiveAny 0x16 #define NB_ReceiveAny 0x96 // Прочие команды #define NB_WResetAdapter 0x32 #define NB_WCancel 0x35 #define NB_WSessionStatus 0x34 #define NB_SessionStatus 0xb4 // Класс NCB для работы с командами NETBIOS class NCB { // Стандартный блок NCB в формате NETBIOS struct _NCB { unsigned char Cmd; unsigned char CCode; unsigned char LocalSessionNumber; unsigned char NetworkNameNumber; void far *Buffer; unsigned Size; char CallName[16]; char OurName[16]; unsigned char ReceiveTimeout; unsigned char SendTimeout; void interrupt (*PostRoutine)(void); unsigned char AdapterNumber; unsigned char FinalCCode; unsigned char Reserved[14]; } ncb; struct SREGS sregs; union REGS regs; unsigned errno; // Функция для вызова NETBIOS void NetBios(void) { sregs.es = FP_SEG(&ncb); regs.x.bx = FP_OFF(&ncb); int86x(0x5c, ®s, ®s, &sregs); } public: // Конструктор, расписывает ncb нулями NCB() { memset(&ncb, 0, sizeof(ncb)); errno = 0; } // Функция возвращает код ошибки int Error(void) {return errno;} // Функция для добавления имени void WAddName(char *name); // Функция для удаления имени void WDeleteName(char *name); // Функция для определения номера имени unsigned GetNetworkNameNumber(void) { return(ncb.NetworkNameNumber); } // Функция для установки адреса и размера буфера void SetBuffer(char far *Buf, unsigned BufSize) { ncb.Buffer = Buf; ncb.Size = BufSize; } // Установка в NCB тайм-аута void SetRtoSto(int rto, int sto) { ncb.ReceiveTimeout = rto; ncb.SendTimeout = sto; } // Установка в ncb имени вызываемого партнера void SetCallName(char *name); // Установка в ncb имени нашей станции void SetOurName(char *name); // Прием датаграмм с ожиданием void WReceiveDatagram(int NetwrkNameNumber) { // Заполняем поле номера своего имени ncb.NetworkNameNumber = NetwrkNameNumber; // Вызываем NETBIOS ncb.Cmd = NB_WReceiveDatagram; NetBios(); } // Передача датаграмм с ожиданием void WSendDatagram(int NetwrkNameNumber) { // Заполняем поле номера своего имени ncb.NetworkNameNumber = NetwrkNameNumber; ncb.Cmd = NB_WSendDatagram; // Вызываем NETBIOS NetBios(); } // Создание канала с принимающей стороны void WListen(void) { ncb.Cmd = NB_WListen; NetBios(); } // Создание канала с передающей стороны void WCall(void) { ncb.Cmd = NB_WCall; NetBios(); } // Удаление канала void WHangUp(void) { ncb.Cmd = NB_WHangUp; NetBios(); } // Прием из канала с ожиданием void WReceive(void) { ncb.Cmd = NB_WReceive; NetBios(); } // Передача в канал с ожиданием void WSend(void) { ncb.Cmd = NB_WSend; NetBios(); } }; |
|
|
|