Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование в сетях Windows

.pdf
Скачиваний:
538
Добавлен:
11.03.2015
Размер:
3.02 Mб
Скачать

Листинг 1 -3.

(продолжение)

 

HANDLE

hArray[64],

 

 

hThread;

^y

DWORD

dwHandleCount=0,

 

 

dwRet,

 

 

dwThreadld;

 

int

i,

 

 

num;

 

LANA_ENUM

lenum;

 

// Перечисление и сброс всех номеров LANA

 

//

 

 

if (LanaEnum(&lenum) != NRC_GOODRET)

 

return

1;

 

if (ResetAlK&lenum, (UCHAR)MAX_SESSIONS, (UCHAR)MAX_NAMES,

 

FALSE) != NRC_GOODRET)

 

return

1;

 

//

// Выделение массива структур NCB (по одной для каждого номера LANA)

//

g_Clients = (PNCB)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(NCB) * lenum.length);

//

// Создание событий, добавление имени сервера каждому номеру LANA и начало // асинхронного прослушивания на каждом LANA.

//

for(i = 0; i < lenum.length; i++)

{

 

hArray[i]

=

g_Clients[i].ncb_event = CreateEvent(NULL,

TRUE,

щ

 

 

FALSE,

NULL);

 

 

 

 

 

 

 

 

 

 

 

 

4

 

AddName(lenum.lana[i], SERVER_NAME, &num);

 

 

\

 

Listen(&g_Clients[i], lenum.lana[i], SERVER.NAME);

 

 

 

}

 

 

 

 

 

 

 

 

while

(1)

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

//

Ожидание

подключения

клиента

 

 

 

 

//

 

 

 

 

 

 

 

 

dwRet = WaitForMultipleObjectsdenum.length,

hArray,

FALSE,

\V

 

 

INFINITE);

 

 

 

 

 

if (dwRet == WAIT_FAILED)

 

 

 

 

{

 

 

 

 

 

•*>

\

 

 

printf("ERROR: WaitForMultipleObjects: Xd\n",

 

 

 

 

GetLastErrorO);

 

 

 

 

 

break;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

//

Проверка

всех структур NCB для определения,

достигла ли успеха

более г

 

//

чем одна

структура.

Если поле ncb_cmd_plt

не содержит значение

}

 

//

NRC_PENDING, значит существует клиент, необходимо создать поток

и

(

//

выделить ему новую

структуру NCB.

 

 

 

Листинг 1 -3. (продолжение)

II Нам нужно многократно использовать исходную структуру // NCB для других клиентских соединений.

//

for(i = 0; i < lenum.length; i++)

<

if (g_Clients[i].ncb_cmd_cplt != NRC_PENDING)

{

pncb = (PNCB)GlobalAlloc(GMEM_FIXED, sizeof(NCB)); memcpy(pncb, &g_Clients[i], sizeof(NCB)); pncb->ncb_event = 0;

hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)pncb, 0, &dwThreadId);

CloseHandle(hThread);

//

//Описатель сбрасывается, асинхронно начинается еще одно

//прослушивание ResetEvent(hArray[i]);

//

Listen(&g Clients[i], lenum.lana[i], SERVER_NAME);

// Очистка

о//

?<for(i = 0; i < lenum.length; i

~*

DelName(lenum.lana[i], SERVER_NAHE);

nil

CloseHandle(hArray[i]);

к.

}

is

GlobalFree(g_Clients);

-return 0;

Первый цикл функции main получает доступные номера LANA и при этом добавляет имя сервера и отдает команду NCBLJSTEN для каждого LANA, а также формирует массив описателей событий. Затем вызывается функция WaitForMultipleObjects, которая блокируется, пока по меньшей мере один из описателей не перейдет в занятое состояние. Тогда WaitForMultipleObjects завершается, и код порождает поток для чтения входящих сообщений и отправки их обратно клиенту. Код копирует занятую структуру NCB, чтобы передать ее в клиентский поток. Это позволяет многократно использовать исходную структуру NCB для асинхронного вызова других команд NCBLISTEN, что можно сделать путем сброса события и повторного вызова функции Listen для этой структуры. Не обязательно копировать всю структуру — на самом деле вам нужны только локальный номер сеанса {ncbjsri) и номер LANA (ncbjanajnctri). Впрочем, структура NCB — удобный контейнер для хранения обоих значений, чтобы потом передать их в один параметр потока. Кли-

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

Асинхронные стратегии сервера

При использовании обоих видов серверов существует вероятность что клиенту будет отказано в обслуживании По завершении команды NCBLJSTEN происходит небольшая задержка, которая длится вплоть до вызова функции обратного вызова или пока событие не будет занято Рассмотренные нами серверы не отдают асинхронно еще одну команду NCBLJSTEN ранее, чем через несколько операторов Например, если сервер обслуживал клиента на номере LANA 2 и еще один клиент попытается подключиться прежде, чем сервер отдаст следующую команду NCBLJSTEN на том же номере LANA, клиент получит ошибку NRC_NOCALL (0x14) Это означает, что для данного имени еще не была асинхронно вызвана команда NCBLJSTEN Чтобы избежать такой ситуации, сервер может асинхронно отдавать несколько командNCBLJSTEN на каждом LANA

Как видите, использовать асинхронные команды достаточно просто Флаг ASYNCH может применяться почти к любой команде NetBIOS Помните только, что структура NCB, которую вы передаете Netbios, должна быть видима глобально

Клиент сеанса NetBIOS [

Клиент NetBIOS напоминает асинхронный сервер событий Листинг 1 -4 содержит пример кода для клиента Клиент выполняет уже знакомые нам стандартные шаги инициализации имени добавляет свое имя к таблице имен каждого номера LANA и затем дает асинхронную команду соединения Цикл main ждет, пока одно из событий не будет занято В это время в цикле проверяется поле ncb_cmdj3ptt каждой структуры NCB, соответствующей отданным командам подключения, по одной для каждого номера LANA Если поле ncb_cmd_cplt равно NRC_PENDLNG — код отменяет асинхронную команду, если команда завершается (соединение установлено) и данная NCB не соответствует оповещенной (определяется по значению, возвращаемому WaitForMultipleObjects) — соединение разрывается Когда сервер слушает на каждом номере LANA на своей стороне и клиент пытается соединиться на каждом из его номеров LANA, может быть установлено несколько соединений В этом случае код просто закрывает лишние соединения командой NCBHANGUP — нужна связь только по одному каналу Попытки соединения с каждым номером LANA с обеих

сторон практически гарантированно успешны

,

Листинг 1-4. Клиент на основе асинхронных событий (Nbclient.c)

 

// Nbclient с

 

((include <windows h> «include <stdio h> ((include <stdlib h>

Г Л А ВА 1 Интерфейс NetBIOS

31

Листинг1-4. (продолжение)

«include

\Common\nbcommon h

«define

MAX_SESSIONS

254

«define

MAX_NAMES

254

«define

MAX_BUFFER

1024

char szServerName[NCBNAMSZ];

//

//функция Connect

//Описание

//Установка асинхронного соединения с сервером на данном LANA

// В переданной структуре NCB полю ncb_event

уже присвоено значение

// действительного описателя события Windows

Просто

// заполните пробелы и сделайте вызов

 

//

 

 

 

 

 

int Connect(PNCB

pncb,

mt

lana,

char *server, char «client)

{

 

 

 

 

 

pncb->ncb_command = NCBCALL | ASYNCH,

 

pncb->ncb_lana_num = lana,

 

 

memset(pncb->ncb_name,

,

NCBNAMSZ),

 

strncpy(pncb->ncb_name, client, strlen(client));

memset(pncb->ncb_callname,

, NCBNAMSZ),

strncpy(pncb->ncb_callname, server, strlen(server));

if (Netbios(pncb) != NRC_GOODRET)

 

{

 

 

 

 

 

pnntf(

ERROR

Netbios

NCBCONNECT

Xd\n",

pncb->ncb_retcode), return pncb->ncb_retcode,

}

return NRCJ3OODRET,

//Функция main

//Описание

//Инициализирует интерфейс NetBIOS, распределяет некоторые ресурсы

//

(описатели событий,

буфер отправки

и т п )

и дает команду

//

NCBCALL для каждого

номера LANA на

указанном

сервере Когда соединение

//создано, отменяет или разрывает любые неудавшиеся

//соединения Затем посылает/получает данные Наконец, выполняет очистку

ян

>uki

&*

смслед.стр.

 

32

ЧАСТЬ I

Устаревшие сетевые API

 

Листинг 1 -4.

{продолжение)

 

int. main(int

argc,

char **argv)

 

HANDLE

 

 

•hArray;

 

NCB

 

 

*pncb;

 

char

 

 

szSendBuff[MAX_BUFFER];

 

DWORD

 

 

dwBufferLen,

 

 

 

 

 

dwRet,

 

 

 

 

 

dwlndex,

 

 

 

 

 

dwNum;

 

LANA.ENUM

 

lenum;

 

int

 

 

1;

 

if

(argc

!=

3)

 

 

printf("usage: nbclient CLIENT-NAME SERVER-NAHE\n");

 

 

return 1;

 

 

}

 

 

 

 

 

 

// Перечисление и сброс всех номеров LANA

 

//

 

 

 

 

.

.

if (LanaEnum(&lenum) != NRC_GOODRET)

 

 

return 1;

 

 

if (ResetAll(&lenum, (UCHAR)MAX_SESSIONS, (UCHAR)MAX_NAMES,

iq

 

 

FALSE) != NRC_GOODRET)

 

 

return 1;

 

 

strcpy(szServerName, argv[2]);

 

//

 

 

 

 

 

 

//

Выделение

массива описателей для использования асинхронных событий.

l

// Выделение

массива структур NCB. Нам нужен один описатель

!

// и одна структура NCB для каждого номера LANA.

 

//

 

 

 

 

 

f

hArray

=

(HANDLE *)GlobalAlloc(GMEM_FIXED,

 

 

sizeof(HANDLE) * lenum.length);

 

pncb

=

(NCB

*)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,

 

 

sizeof(NCB) * lenum.length);

 

//

//Создание события и присвоение его соответствующей структуре NCB,

//начало асинхронного соединения (NCBCALL).

//Не забудьте добавить имя клиента к каждому номеру

//LANA, no которому он хочет соединиться.

for(i = 0; i < lenum.length; i++)

a

{

<

К

hArray[i] = CreateEvent(NULL, TRUE, FALSE, NULL);

д

pncb[i].ncb_event = hArray[i];

 

AddName(lenum.lana[i], argv[1], &dwNum);

 

Connect(&pncb[i], lenum.lana[i], szServerName,argv[1]);

^

}

// Ожидание успеха по меньшей мере одного соединения

Г Л А В А 1 Интерфейс NetBIOS

33

Листинг 1-4. (продолжение)

II

dwlndex = WaitForMultipleObjectsQenum.length, hArray, FALSE, INFINITE);

if (dwlndex == WAIT_FAILED)

{

printfC'ERROR: WaitForMultipleObjects: Xd\n", GetLastErrorO);

else

//Если успешно более чем одно соединение, лишние

//соединения разрываются. Мы будем использовать соединение, возвращенное

//WaitForMultipleObjects, иначе если оно еще не установлено,

//отменим его.

//

for(i = 0; 1 < lenum.length; i++)

{

if (i != dwlndex)

{

if (pncb[i].ncb_cmd_cplt == NRC.PENDING) Cancel(&pncb[i]);

else

Hangup(pncb[i].ncb_lana_num, pncb[i].ncb_lsn);

printf("Connected on LANA; Xd\n", pncb[dwlndex].ncb_lana_num);

rti- //

i // Отправка и прием сообщений

к

for(i = 0; i <

20;

i

 

i'

wsprintf(szSendBuff,

"Test message X03d", i);

 

dwRet = Send(pncb[dwlndex].ncb_lana_num,

"*

pncb[dwlndex].ncb_lsn, szSendBuff,

'*«'

strlen(szSendBuff));

•A'i

if (dwRet

1=

NRCJ300DRET)

 

break;

 

 

 

 

dwBufferLen = MAX_BUFFER;

 

dwRet = Recv(pncb[dwlndex].ncb_lana_num,

 

pncb[dwlndex].ncb_lsn, szSendBuff, &dwBufferLen);

 

if (dwRet != NRC_GOODRET)

 

break;

 

 

 

 

szSendBuff[dwBufferLen] = 0;

 

printf("Read:

•Xs'W,

szSendBuff);

}

Hangup(pncb[dwlndex].ncb_lana_num, pncb[dwlndex].ncb_lsn);

}

// Очистка

см.след.стр.

34

ЧАСТЬ I Устаревшие сетевые API

Листинг 1-4. (продолжение)

//

for(i = 0, 1 < lenum length,

DelName(lenum lana[i], argv[1]), CloseHandle(hArray[i]),

}

GlobalFree(hArray),

GlobalFree(pncb),

return 0,

Дейтаграммные операции

Дейтаграмма — это способ связи без установления логического соединения Отправитель просто направляет каждый пакет по указанному имени NetBIOS Целостность данных и порядок доставки пакетов не гарантируются

Есть три способа отправить дейтаграмму Первый — направить дейтаграмму на определенное (уникальное или групповое) имя Это означает, что получить эту дейтаграмму может только процесс, зарегистрировавший имя приемника Второй способ — отправить дейтаграмму на групповое имя тогда получить сообщение смогут только процессы, зарегистрировавшие данное имя Наконец, третий способ — широковещательно разослать дейтаграмму по всей сети Такую дейтаграмму сможет получить любой процесс на любой рабочей станции в локальной сети Для отправки дейтаграммы на уникальное или групповое имя применяется команда NCBDGSEND, а для широковещания—NCBDGSENDBC

Отправить дейтаграмму с помощью любой из этих команд элементарно Определите для поля ncb_num значение номера имени, возвращенного командойNCBADDNAMEилиNCBADDGRNAMEЭтотномеридентифицируетотправителя сообщения Присвойте полю ncbjmffer значение адреса буфера, содержащего отправляемые данные, а полю ncbjength — количество отправляемых байтов Затем задайте для поля ncbjanajnum значение номера LANA, по которому хотите передать дейтаграмму Наконец, присвойте полю ncbjcalIname значение NetBIOS-имени приемника Это может быть уникальное или групповое имя Чтобы широковещательно послать дейтаграмму, выполните все описанные шаги, кроме последнего так как сообщение получат все рабочие станции, присвоение значения полю neb_callname не требуется

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

Г Л А ВА 1 Интерфейс NetBIOS

35

Для получения дейтаграмм также предусмотрено три способа Первые два используют команду NCBDGRECV Прежде всего, вы можете отдать команду приема дейтаграммы для сообщений, направленных на конкретное имя — уникальное или групповое Во-вторых, вы вправе отдать такую команду для любой дейтаграммы, предназначенной для любого имени в таблице имен процесса NetBIOS И наконец, отдайте такую команду для широковещательной дейтаграммы с помощью команды NCBDGRECVBC

ПРИМЕЧАНИЕ Невозможно дать команду асинхронного приема дейтаграмм, предназначенных для имени, которое зарегистрировано иным процессом, если только оба процесса не зарегистрировали групповое имя (тогда они могут получить одно и то же сообщение)

Чтобы отдать команду приема, присвойте полю ncbjium значение номера имени, возвращенного успешным вызовом NCBADDNAME или NCBADDGRNAME Этот номер определяет, для какого имени вы слушаете входящие дейтаграммы Если вы присваиваете этому полю значение OxFF, то будете получать дейтаграммы, предназначенные для любого имени в таблице имен NetBIOS этого процесса Создайте буфер для получения данных и присвойте полю ncb_buffer значение адреса буфера Присвойте полю ncbjength значение размера буфера Наконец, задайте для поля ncb_lana_num значение номера LANA, на котором надо ждать дейтаграммы После возвращения успешного вызова Netbios с командой NCBDGRECV или NCBDGRECVBC поле ncbjength

будет содержать фактическое количество полученных байтов, а поле ncb_calIname — NetBIOS-имя процесса отправки

Код в листинге 1-5 содержит основные дейтаграммные функции Вся отправка реализуется блокирующими вызовами как только команда выдана и данные отправлены, функция завершается и вам не грозит блокировка изза переполнения буфера данными Вызовы приема — асинхронные события, так как неизвестно на каком из номеров LANA будут получены данные Код похож на код сервера сеансов, использующего события Для каждого номера LANA код отдает асинхронную команду NCBDGRECV или NCBDGRECVBC и ждет, пока она не достигнет успеха Затем проверяются все асинхронные команды, печатаются сообщения о тех, которые были успешны, и отменяются еще ожидающие команды В примере есть функции, как для направленного, так и для широковещательного отправления и приема Программа может быть скомпилирована в пример приложения, конфигурируемого для отправки или получения дейтаграмм Несколько параметров командной строки позволяют пользователю указать количество отправляемых или принимаемых дейтаграмм, задержку между отправками, использование широковещательных, а не направленных дейтаграмм, получение дейтаграмм для любого имени и т п

Листинг 1-5. Пример работы NetBIOS с дейтаграммами (Nbdgram.c)

// Nbdgram с

 

 

W

 

§

«include <windows h>

*

)in)t

i

см cjieo.cmp.

36

ЧАСТЬ I Устаревшие сетевые API

Листинг 1-5. (продолжение)

«include <stdio.h> «include <stdlib.h>

«include "..\Common\nbcommon.h"

«define

MAX.SESSIONS

254

 

 

 

«define

MAX_NAMES

 

254

 

 

 

«define MAX_DATAGRAM_SIZE

512

 

 

 

BOOL

bSender

=

FALSE,

// Отправка или прием дейтаграмм

 

bRecvAny

=

FALSE,

// Прием для любого имени

 

 

bUniqueName

= TRUE,

// Зарегистрировать мое имя как уникальное?

 

bBroadcast

= FALSE,

//

Использовать

широковещательные

дейтаграммы?

 

 

 

 

 

 

 

bOneLana

=

FALSE;

//

Использовать все LANA или только один?

char

szLocalName[NCBNAMSZ + 1],

// Локальное NetBIOS-имя

 

 

szRecipientName[NCBNAMSZ + 1 ] ; //

NetBIOS-имя

приема

 

DWORD

dwNumDatagrams = 25,

//

Количество отправляемых

дейтаграмм

 

dwOneLana,

 

//

Если использовать один

LANA, то какой?

 

dwDelay = 0;

//

Задержка между отправками дейтаграмм

//Функция: ValidateArgs

//Описание:

//Эта функция анализирует аргументы командной строки

//и устанавливает различные глобальные флаги, соответствующие выбору void ValidateArgs(int argc, char **argv)

int i;

for(i = 1; i < argc;

{ if (strlen(argv[i]> < 2) continue;

if ((argv[i][0] == •-') || (argv[i][0] V))

{

switch (tolower(argv[i][1]))

case

'n':

// Используется

уникальное

имя

 

bUniqueName

= TRUE;

 

 

 

 

if (strlen(argv[i])

> 2)

 

 

 

strcpy(szLocalName, &argv[i][3]);

 

 

break;

 

 

 

 

case

'g1:

//

Используется

групповое

имя

 

bUniqueName

= FALSE;

 

M

 

if (strlen(argv[i])

> 2)

 

 

Листинг 1-5.

if-

t/

2»'

i f дв*|«грамм

tf

printr return

>

return;

Г Л А В А 1 Интерфейс NetBIOS

37

 

strcpy(szLocalName, &argv[i][3]);

 

break;

 

case

's':

// Отправка дейтаграмм

 

bSender = TRUE;

 

break;

 

case

'с':

// Количество дейтаграмм для отправки или приема

 

if (strlen(argv[i]) > 2)

 

dwNumDatagrams = atoi(&argv[i][3]);

 

break;

 

case

'r':

// Имя получателя дейтаграмм

 

if (strlen(argv[i]) > 2)

 

strcpy(szRecipientName, &argv[i][3]);

 

break;

 

case

'b':

// Используется широковещательная рассылка

 

bBroadcast

= TRUE;

 

break;

 

case

'a':

// Прием дейтаграмм для любого имени

 

bRecvAny = TRUE;

 

break;

 

case

'1';

// Работа только на этом номере LANA

 

bOneLana = TRUE;

 

if (strlen(argv[i]) > 2)

 

dwOneLana = atoi(&argv[i][3]);

 

break;

 

case

'd':

// Задержка (в миллисекундах) между отправками

if (strlen(argv[i]) > 2) dwDelay = atoi(&argv[i][3]);

break;

default:

printf("usage: nbdgram ?\n"); break;

:((«•

//Функция: DatagramSend

//Описание:

//Отправляет направленные дейтаграммы к указанному приемнику на

//

заданном номере LANA от данного

номера имени к соответствующему приемнику.

//

Также указывается буфер данных и

количество отправляемых байтов.

int DatagramSend(int lana, int num, char «recipient, char «buffer, int buflen)

см.след.стр-