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

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

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

лом /О в конце. После успешного добавления имени Netbios возвращает в поле ncbjium номер добавленного имени NetBIOS. Это значение используется для дейтаграмм (подробно мы обсудим их далее), чтобы идентифицировать исходный процесс NetBIOS. Наиболее типичная ошибка, с которой сталкиваются при добавлении уникального имени — NRCJDUPNAME, происходит, когда имя уже используется другим процессом в сети.

AddGroupName работает так же, как AddName, за одним исключением: AddGroupName запускает команду NCBADDGRNAME и никогда не вызывает ошибку NRCJDUPNAME.

Функция DelName удаляет имя NetBIOS из таблицы имен. Ей требуются только номер LANA, для которого вы хотите удалить имя, и само имя.

Следующие две функции в листинге 1-1 — Send и Recv, используются для отправки и получения данных в сеансе связи. Эти функции почти идентичны за исключением значения поля neb_command. ему присваивается NCBSEND, либо NCBRECV. Обязательные параметры команды: номер LANA, по которому будут отправлены данные, и номер сеанса. Успешная команда NCBCALL или NCBLISTEN возвращает номер сеанса. Клиенты используют команду NCBCALL для соединения с известной службой, а серверы — команду NCBLISTEN, чтобы ждать входящих клиентских соединений. Когда какая-либо из этих команд успешно выполняется, интерфейс NetBIOS устанавливает сеанс с уникальным целым идентификатором.

Send и Recv также требуют параметров, которые проецируются в поля ncbjouffer (буфера) и ncbjength (длины). При отправке данных поле ncb_buffer указывает на содержащий эти данные буфер. Поле длины задает количество отправляемых символов в буфере. При получении данных поле буфера указывает на блок памяти, в который копируются входящие данные. Поле длины содержит размер блока памяти. Когда функция Netbios завершает работу, она записывает в поле длины количество успешно полученных байтов.

Важный аспект отправки данных через сеанс: вызов функции Send не осуществим, пока получатель не вызовет функцию Recv. To есть если отправитель выдает большое количество данных, а получатель не читает их, используются значительные ресурсы для локальной буферизации данных. Поэтому лучше вызывать немного команд NCBSEND или NCBCHAINSEND одновременно. Для решения этой проблемы используйте Netbios-команды NCBSENDNA и NCBCHAINSENDNA. В этом случае отправлять данные можно без подтверждения получателя.

Последние две функции в конце листинга 1-1 — Hangup и Cancel, предназначены для закрытия установленных сеансов или отмены невыполненной команды. Вызов команды NetBIOS NCBHANGUP позволит корректно завершить сеанс. При этом все невыполненные запросы на получение данных завершаются и возвращают ошибку закрытого сеанса — NRC_SCLOSED (0x0А). Если какие-либо команды отправки данных не выполнены, команда завершения связи блокируется вплоть до их выполнения. Эта задержка происходит независимо от того, передает ли команда данные или ожидает, когда удаленная сторона запросит прием данных.

интерфейс iNeitsiUb

Сервер сеансов: модель асинхронного обратного вызова

Займемся сервером, который будет слушать входящие клиентские соединения. Это простой эхо-сервер, отправляющий обратно любые данные, полученные от клиента. Листинг 1-2 содержит код сервера, использующего асинхронные функции обратного вызова. Код взят из файла Cbnbsvr.c, на прилагаемом компакт-диске он находится в папке /Examples/ChapterOl /Server. В функции main сначала перечисляются доступные номера LANA с помощью LanaEnum, а затем — сбрасывается каждый номер LANA с помощью ResetAll. Помните, что эти два шага обычно требуются от всех приложений NetBIOS.

Листинг 1-2. Сервер асинхронного обратного вызова (Cbnbsvr.c) // Cbnbsvr.c

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

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

«define

MAX.BUFFER

2048

«define

SERVER_NAME

"TEST-SERVER-1"

DWORD WINAPI ClientThread(PVOID lpParam);

//Функция: ListenCallback

//Описание:

//

Эта

функция

вызывается,

когда

завершается

асинхронное

прослушивание.

//

Если

не происходит никакой ошибки, создает

поток, чтобы работать клиентом.

// Также асинхронно дается команда слушать другие клиентские соединения.

//

 

 

 

 

 

 

 

 

void CALLBACK ListenCallback(PNCB pncb)

 

 

 

{

 

 

 

 

 

 

 

 

 

HANDLE

hThread;

 

 

 

 

 

 

DWORD

dwThreadld;

 

 

 

 

 

if

(pncb->ncb_retcode

!= NRC_GOODRET)

 

 

 

 

{

 

 

 

 

 

 

 

 

 

printf("ERROR: ListenCallback: Xd\n", pncb->ncb_retcode);

 

 

return;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

Listen(pncb->ncb_lana_num, SERVER_NAME);

 

 

 

 

hThread = CreateThread(NULL,

0, ClientThread,

(PVOID)pncb, 0,

 

 

&dwThreadId);

 

 

 

 

 

 

if

(hThread

== NULL)

 

 

 

 

 

 

 

 

 

 

 

ы

AMAJ

no

,(«ш<

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

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

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

{

printf("ERROR: CreateThread: Xd\n", GetLastErrorO); return;

}

CloseHandle(hThread);

return;

//Функция: ClientThread

//Описание:

//Клиентский поток блокирует получение данных от клиентов и

//просто посылает их назад. Это непрерывный цикл, выполняющийся

//пока не будет закрыт сеанс или не произойдет ошибка. Если

//чтение или запись дают ошибку NRC_SCLOSED, сеанс

//корректно завершается и происходит выход из цикла.

//

DWORD WINAPI ClientThread(PVOID lpParam)

PNCB

pncb = (PNCB)lpParara;

NCB

neb;

char

szRecvBuff[MAX_BUFFER];

DWORD

dwBufferLen = MAX.BUFFER,

 

dwRetVal = NRC_GOODRET;

char

szClientName[NCBNAMSZ+1];

FormatNetbiosName(pncb->ncb_callname, szClientName);

while (1)

{

dwBufferLen = MAX_BUFFER;

dwRetVal = Recv(pncb->ncb_lana_num, pncb->ncb_lsn, szRecvBuff, AdwBufferLen);

if (dwRetVal != NRC_GOODRET) break;

szRecvBuff[dwBufferLen] = 0;

printf("READ [LANA=Xd]: 'XsAn", pncb->ncb_lana_num, szRecvBuff);

dwRetVal = Send(pncb->ncb_lana_num, pncb->ncb_lsn, szRecvBuff, dwBufferLen);

if (dwRetVal != NRC.GOODRET) break;

}

printf("Client 'Xs1 on LANA Xd disconnected\n", szClientName, pncb->ncb_lana_num);

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

21

Листинг 1-2. (продолжение) if (dwRetVal != NRC_SCLOSED)

{

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

//

ZeroMemory(&ncb, sizeof(NCB)); ncb.ncb_command = NCBHANGUP; ncb.ncb_lsn = pncb->ncb_lsn; ncb.ncb_lana_num = pncb->ncb_lana_num;

if (Netbios(&ncb) != NRC_GOODRET)

<

printf("ERROR: Netbios: NCBHANGUP: Xd\n", ncb.ncb.retcode); dwRetVal = neb.ncb_retcode;

>

GlobalFree(pncb); return dwRetVal;

}

GlobalFree(pncb); return NRC_GO0DRET;

//Функция: Listen

//Описание:

//Инициируется асинхронное прослушивание с помощью функции обратного вызова.

//Создается структура NCB для использования обратным вызовом (поскольку она

//должна быть видима глобально).

//

int Listen(int lana, char *name)

{

PNCB

pneb = NULL;

pneb = (PNCB)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(NCB)); pncb->ncb_command = NCBLISTEN | ASYNCH;

pncb->ncb_lana_num = lana; pncb->ncb_post = ListenCallback;

//

// Это ими, с которым клиенты будут соединяться

//

memset(pncb->ncb_name, ' ', NCBNAMSZ); strncpy(pncb->ncb name, name, strlen(name));

//

// '*' означает, что мы примем клиентское соединение от любого клиента.

//Задавая здесь конкретное имя, мы разрешаем соединение только с

//указанным клиентом.

//

memset(pncb->ncb_callname, ' ', NCBNAMSZ);

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

Листинг 1 -2.

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

pncb->ncb_callname[O]

= '•';

 

 

_/

if (Netbios(pncb) !=

NRC.GOODRET)

{

 

 

printfC'ERROR: Netbios: NCBLISTEN: Xd\n", pncb->ncb_retcode); return pncb->ncb_retcode;

}

return NRC_GOODPET;

}

<n. m

и

 

II Функция: main

,"n/bl :ЧШ>'

//

 

//Описание:

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

//имя сервера к каждому номеру LANA и дает асинхронную команду NCBLISTEN на

//каждый номер LANA с соответствующим обратным вызовом. Затем ждет входящих

//клиентских соединений, порождая рабочий поток, чтобы

//работать с ними. Главный поток просто ждет, пока серверные

// потоки работают с клиентскими запросами.

Вы навряд ли

будете делать это в

{

II настоящем

приложении:

этот пример приведен только для

иллюстрации.

 

//

 

 

 

 

\\

i n t main(int

argc, char

**argv)

,

 

 

<

 

 

 

 

w

LANA.ENUM

lenum;

 

 

 

 

int

i,

 

 

 

,\

 

num;

 

 

 

д

// Перечисляются и инициализируются все номера LANA

 

A

 

 

//

 

 

 

I1

*ni

if (LanaEnum(&lenum) != NRC_GOODRET) return 1;

if (ResetAlK&lenum, 254, 254, FALSE) != NRC.GOODRET) return 1;

//

// Каждому номеру LANA добавляется имя сервера и дается команда слушать

//

for(i = 0; i < lenum.length; AddName(lenum.lana[i],SERVER_NAME, inura);

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

>

,i

 

\

while (1)

\

<

\

Sleep(5000);

\

>

S

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

23

Далее функция main добавляет имя вашего процесса к каждому номеру LANA, на котором вы хотите принимать соединения. Сервер добавляет имя своего процесса — TEST-SERVER-1, к каждому номеру LANA. Это имя (дополненное пробелами, конечно) клиенты будут использовать для соединения с сервером. При попытке установить или принять соединение важен каждый символ в имени NetBIOS. Большинство проблем, с которыми сталкиваются при программировании клиентов и серверов NetBIOS, связаны с несоответствием имен. Будьте последовательны, дополняя имена пробелами или ка- ким-либо другим символом. Пробелы — наиболее популярные символы-за- полнители, так как их удобно перечислять и распечатывать.

Последний и наиболее важный шаг для сервера — асинхронно выдать ряд команд NCBLISTEN. Функция Listen сначала выделяет структуру NCB. При использовании асинхронных вызовов NetBIOS, переданная вами структура NCB должна сохраняться с момента вызова до его завершения. Для этого требуется динамически выделять каждую структуру NCB перед выдачей команды или поддерживать глобальный пул структур NCB, чтобы использовать его в асинхронных запросах. Для команды NCBLISTEN задайте номер LANA, к которому должен обращаться вызов. Заметьте, что код в листинге 1-1 выполняет логическую операцию OR над командами NCBLISTEN и ASYNCH. При указании команды ASYNCH поле ncb_post или ncb_event не должно равняться 0, иначе вызов Netbios даст ошибку NRCJLLCMD.

В листинге 1-2 функция Listen присваивает полю ncb_post функцию обратного вызова ListenCallback. Затем функция Listen присваивает полю ncbname имя процесса сервера. С этим именем будут соединяться клиенты. Функция также записывает в первый символ поля ncb_callname астериск (*). Это означает, что сервер примет соединение от любого клиента. С другой стороны, вы можете поместить в поле ncbcallname конкретное имя — тогда только зарегистрированный под этим именем клиент сможет соединиться с сервером. Наконец, функция Listen обращается к Netbios. Запрос завершается немедленно, и функция Netbios до завершения команды присваивает полю ncbjzmdjzplt переданной структуры NCB значение NRC PENDING (OxFF).

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

ПРИМЕЧАНИЕ Так как этот сервер — только пример, схема его работы очень проста. При разработке собственных серверов NetBIOS вы можете написать другую обработку в главном цикле или выдать в цикле main синхронную команду NCBLISTEN одному из LANA.

Функция обратного вызова выполняется, только когда входящее соединение принято на номере LANA. Когда команда NCBLISTEN принимает соединение, она вызывает эту функцию в поле ncb_post с исходной структурой NCB в качестве параметра. Полю ncb_retcode присваивается значение кода возврата. Всегда проверяйте это значение, чтобы увидеть, последовало ли клиентское соединение. В случае успешного соединения поле ncb_retcode будет равно NRCjSOODRET (0x00).

Еслисоединениеуспешно,асинхронновыдайтедругуюкомандуNCBLISTEN тому же самому номеру LANA. Это необходимо потому, что как только исходное прослушивание завершится успехом, сервер прекратит слушать клиентские соединения на этом LANA, пока не будет дана другая команда NCBLISTEN. Таким образом, чтобы упростить доступ к вашим серверам, вызовите несколько команд NCBLISTENдля одного LANA — тогда будет можно одновременно принимать соединения от множества клиентов. Наконец, функция обратного вызова создает поток, который будет обслуживать клиента. В нашем примере данный поток просто работает в цикле и вызывает команду блокирующего чтения (NCBRECV), а затем — блокирующей передачи (NCBSEND). В примере реализован эхо-сервер, читающий сообщения клиентов и отправляющий их обратно. Клиентский поток входит в цикл, пока клиент не прервет соединение, после чего клиентский поток дает команду NCBHANGUP, чтобы закрыть соединение со своей стороны. С этого момента клиентский поток освобождает структуру NCB и завершается.

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

Для решения этой проблемы можно задействовать команду ASYNCH при передаче и приеме. Буфер, предоставленный для асинхронной передачи и приема, должен быть виден за пределами вызывающей процедуры. Еще один способ обойти блокирование заключается в использовании полей ncb_sto и ncbjrto. Поле ncb_sto задает тайм-аут отправления. Указывая ненулевое значение в 500-миллисекундных единицах, вы можете задать максимальную продолжительность блокирования отправки перед возвратом. Если время команды истекает, данные не посылаются. То же верно для времени ожидания приема: если данные не получены за указанное время, вызов завершается без передачи данных в буфер.

Серверсеансов: модельасинхронныхсобытий

В листинге 1-3 приведен код эхо-сервера, отличный от кода в листинге 1-2. Здесь в качестве механизма оповещения о завершении используются события Win32. Модель событий похожа на модель обратного вызова, единственное отличие: в модели обратного вызова система выполняет ваш код, когда асинхронная операция завершается, а в модели событий — приложение должно убедиться в завершении операции, проверяя состояние события. Поскольку речь идет о стандартных событиях Win32, вы можете использовать любую из доступных процедур синхронизации, например WaitForSingleEvent или WaitForMultipleEvents. Модель событий более эффективна, так как застав-

1 интерфейс NetBIOS

25

ляет программиста структурировать программу, чтобы сознательно проверять завершение.

Наш сервер модели событий начинает работу с того же, что и сервер обратного вызова.

1.Перечисляет номера LANA.

2.Сбрасывает все номера LANA.

3.Добавляет имя сервера к каждому номеру LANA.

4.Инициирует прослушивание на каждом LANA.

Единственное различие — нужно отслеживать все невыполненные команды прослушивания, чтобы непременно сопоставить завершение события с соответствующими блоками NCB, инициализирующими конкретную команду. Код в листинге 1-3 выделяет массив структур NCB, размер которого соответствует количеству номеров LANA (поскольку требуется выполнять команду NCBLISTEN для каждого номера). Дополнительно код создает событие для каждой структуры NCB, чтобы оповещать о завершении команды. Функция Listen использует одну из структур NCB из массива в качестве параметра.

Листинг 1-3. Сервер на основе асинхронных событий (Evnbsvr.c) // Evnbsvr.c

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

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

«define

MAX_SESSIONS

254

«define

MAX_NAMES

254

«define

MAX_BUFFER

2048

«define

SERVER_NAME

"TEST-SERVER-1"

NCB

*g_Clients=NULL;

// Global NCB structure for clients

//Функция: ClientThread

//Описание:

//Этот поток берет структуру NCB из сеанса соединения

//и ждет входящих данных, которые затем посылает обратно

//клиентам, пока сеанс не будет закрыт.

//

DWORD W1NAPI ClientThread(PVOID lpParam)

{

PNCB

pncb = (PNCB)lpParam;

NCB

neb;

char

szRecvBuff[MAX_BUFFER],

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

Листинг 1-3.

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

 

 

szClientName[NCBNAHSZ + 1];

 

DWORD

dwBufferLen = MAX_BUFFER,

/

 

dwRetVal = NRC_GOODRET;

 

// Отправка

и прием сообщений, пока сеанс

не будет закрыт

//

FormatNetbiosName(pncb->ncb_callname, szClientName); while (1)

{

dwBufferLen = MAX_BUFFER;

dwRetVal = Recv(pncb->ncb_lana_num, pncb->ncb_lsn, szRecvBuff, idwBufferLen);

if (dwRetVal != NRC_GOODRET) break;

szRecvBuff[dwBufferLen] = 0;

printf("READ [LANA=Xd]: -Xs'\n", pncb->ncb_lana_num, szRecvBuff);

dwRetVal = Send(pncb->ncb_lana_num, pncb->ncb_lsn, szRecvBuff, dwBufferLen);

if (dwRetVal != NRC.GOODRET) break;

}

prmtf("Client 'Xs' on LANA Xd disconnected\n", szClientName, pncb->ncb_lana_num);

//

 

 

 

// Если в ходе чтения

или записи выдается ошибка NRC.SCLOSED,

^

// то все в порядке;

иначе

возникла некоторая другая ошибка, так что

щ

II соединение разрывается с

этой стороны.

 

'*ft

x%

//

 

л

if (dwRetVal !=

NRC SCLOSED)

 

{

'

 

ZeroMemory(&ncb, sizeof(NCB));

 

ncb.ncb_command = NCBHANGUP;

 

ncb.ncb_lsn

= pncb->ncb_lsn;

 

ncb.ncb_lana_num = pncb->ncb_lana_num;

3

if (Netbios(&ncb) != NRC_GOODRET)

\\

 

{

 

\

printf("ERROR: Netbios: NCBHANGUP: Xd\n",

 

neb.ncb_retcode);

 

GlobalFree(pncb);

 

dwRetVal

= neb.ncb_retcode;

 

//

Передаваемая структура NCB выделяется динамически, поэтому

ь

//

ее следует вначале удалить

 

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

GlobalFree(pncb); return NRC_G00DRET;

//Функция: Listen

//Описание:

//Дается команда асинхронно слушать на указанном LANA.

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

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

int Listen(PNCB pncb, int lana, char «name)

{

pncb->ncb_command = NCBLISTEN | ASYNCH; pncb->ncb_lana_num = lana;

//

// Это имя, с которым будут соединяться клиенты

//

memset(pncb->ncb_name, ' ', NCBNAMSZ); strncpy(pncb->ncb_name, name, strlen(name));

//

// '*' означает, что мы примем клиентское соединение от любого клиента.

//Задавая здесь конкретное имя, мы разрешаем соединение только с

//указанным клиентом.

//

memset(pncb->ncb_callname, ' ', NCBNAMSZ); pncb->ncb_callname[O] = '*';

if (Netbios(pncb) != NRC_GOODRET)

{

printf("ERROR: Netbios: NCBLISTEN: Xd\n", pncb->ncb_retcode); return pncb->ncb_retcode;

}

return NRC GOODRET;

//Функция: main

//Описание:

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

//дает асинхронную команду слушать каждому LANA, используя события. Ждет

//пока событие не сработает, а затем обрабатывает клиентское соединение.

int main(int argc, char **argv)

{

PNCB pncb=NULL;

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