Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS© Александр Фролов, Григорий ФроловТом 4, М.: Диалог-МИФИ, 1993, 160 стр. 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); } |