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

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

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

1 38

ЧАСТЬ II Интерфейс прикладного программирования Winsock

char

snb_name[NETBIOS_NAME_LENGTH];

} SOCKADDR_NB, «PSOCKADDRJJB, FAR «LPSOCKADDRJIB;

Поле snbjamily задает семейство адресов этой структуры, поэтому оно всегда должно быть равно AFJVETBIOS. В поле snbjype указывается тип имени: уникальное или групповое. Для этого поля можно использовать следующие определения:

«define

NETBIOSJJNIQUE.NAME

(0x0000)

«define

NETBIOS_GROUP_NAME

(0x0001)

Наконец, в поле snbjiame содержится собственно имя NetBIOS.

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

«define SET_NETBIOS_SOCKADDR(_snb, „type, _name, _port)

int _i;

(_snb)->snb_family = AF_NETBIOS; (_snb)->snb_type = (_type);

for (_i = 0; _i < NETBIOS_NAME_LENGTH - 1; (_snb)->snb_name[_i] = ' ';

}

for (_i = 0 ;

•((.name) + _i) != '\0'

&& _i < NETBIOS_NAME_LENGTH - 1;

(_snb)->snb_name[_i] = *((_name)+_i);

}

(_snb)->snb_name[NETBIOS_NAME_LENGTH - 1] = (_port);

Первый параметр макроса — _snb, адрес заполняемой вами структуры SOCKADDRNB. Как видите, полю snbjamily автоматически присваивается значение AFJVETBIOS. Для параметра Jype задайте значение NETBIOSJJNIQUE_NAME или NETBIOS_GROUPJSfAME. Параметр jiame - это имя NetBIOS; предполагается, что оно состоит либо из NETBIOSJ4AMEJENGTH - 1 символов, либо содержит меньшее число символов и является строкой с 0 в конце. Заметьте, что поле snbname сначала заполняется пробелами, а в конце макроса 1б-й символ строки snbjiame получает значение jport.

Как видите, определить структуру имени NetBIOS в Winsock достаточно просто. В отличие от TCP и IrDA, разрешение имени в NetBIOS скрыто от вас, поэтому не нужно сопоставлять имени физический адрес перед началом работы. Дело в том, что NetBIOS использует несколько протоколов в качестве протоколов нижнего уровня, и каждый из них имеет собственную схему адресации. В следующем разделе мы приведем пример простого клиентсерверного приложения, использующего интерфейс NetBIOS в Winsock.

Г Л А ВА 6 Семейства адресов и разрешение имен

»139

Создание сокета

При создании NetBIOS-сокета очень важно верно задать номер LANA. Как и для собственного API NetBIOS, нужно знать, какие номера LANA доступны приложению. Помните, что клиент и сервер NetBIOS должны использовать общий транспортный протокол для прослушивания и соединения. Существует два способа создания сокета NetBIOS. Первый — вызвать функцию socket или WSASocket:

s = WSASocket(AF_NETBIOS, SOCK.DGRAM | SOCK.SEQPACKET, -lana, NULL, 0, WSA_FLAG_OVERLAPPED);

Чтобы использовать дейтаграммный сокет, назначте параметру type функции WSASocket значение SOCK_DGRAM, а сокету с установлением соедине- н и я _ значение SOCK_SEQPACKET (но не оба одновременно). Третий пара- M e T p j _ protocol, номер LANA, на котором нужно создать сокет (он должен быть отрицательным). Значение четвертого параметра — NULL, так как вы задаете свои собственные параметры, не используя структуру WSAPROTOCOL_ INFO. Пятый параметр не используется. Наконец, параметр dwFlags равен WSAFIAG OVERLAPPED. Это значение следует использовать при всех обращениях к функции WSASocket.

Недостаток этого способа создания сокетов в том, что вы должны знать доступные номера LANA. К сожалению, в Winsock не существует простого способа нумерации LANA. Конечно, для выяснения свободных номеров LANA можно использовать функцию Netbios с параметром NCBENUM. Но Winsock предлагает альтернативу — перечисление всех транспортных протоколов с помощью функции WSAEnumProtocols (см. главу 5). В следующем примере перечисляются все транспортные протоколы, обнаруживаются транспорты NetBIOS и создаются сокеты для каждого из них.

dwNum = WSAEnumProtocols(NULL, lpProtocolBuf, &dwBufLen); if (dwNum == SOCKET_ERROR)

{

// Ошибка

}

for (i = 0; i < dwNum; i++)

{

// Поиск записей в семействе адресов AF_NETBIOS if (lpProtocolBuf[i].iAddressFamily == AF_NETBIOS)

{

//

поиск сокетов с типом SOCK_SEQPACKET или SOCK_DGRAM

if

(lpProtocolBuf[i].iSocketType == SOCK.SEQPACKET)

{

 

 

= WSASocket(FROM_PROTOCOL_INFO,

 

FROM_PROTOCOL_INFO, FROM_PROTOC0L_INF0,

 

&lpProtocolBuf[i], 0, WSA_FLAG OVERLAPPED);

av

1 40

ЧАСТЬ II Интерфейс прикладного программирования Winsock

В данном псевдокоде все доступные протоколы сначала нумеруются, а затем в цикле выявляются те, которые относятся к семейству адресов AF^NETBIOS. Затем идет поиск сокетов с типом SOCK_SEQPACKET. Если бы мы хотели передавать дейтаграммы, следовало бы искать сокеты с типом SOCKJDGRAM. Когда тип сокета совпадет с желаемым — найден доступный для использования транспорт NetBIOS. Если нужен помер LANA, возьмите абсолютное значение поля iProtocol структуры WSAPROTOCOLINFO Единственное исключение — номер 0. Поле iProtocol для этого LANA равно 0x80000000, так как номер 0 зарезервирован для Winsock. Количество действительных транспортов содержится в переменной/

ПротоколAppleTalk

Winsock поддерживает AppleTalk уже давно, хотя знают об этом немногие. Вероятнее всего, вы не захотите использовать протокол AppleTalk, если только вам не нужно соединяться с компьютерами Macintosh. AppleTalk похож на NetBIOS: сервер динамически регистрирует определенное имя, по которому с ним могут соединяться клиенты. Впрочем, имена AppleTalk значительно сложнее имен NetBIOS

Адресация

Имя AppleTalk фактически основано на трех отдельных именах: собственно имени, типе и зоне. Длина каждого из этих имен не может быть больше 32 символов. Имя идентифицирует процесс и связанный с ним сокет на компьютере. Тип — это механизм группировки для зон. Обычно зона представляет собой сеть компьютеров, поддерживающих AppleTalk и расположенных в одной петле (loop). Реализация протокола AppleTalk от Microsoft позволяет компьютеру под управлением Windows указать зону, в которой он находится. Несколько сетей можно соединить мостами. Номеру сокета, узла и сети соответствуют дружественные имена. Протокол Name Binding Protocol (NBP) требует, чтобы имя AppleTalk было уникальным для данного типа и зоны Для проверки уникальности имени этот протокол применяет широковещательные запросы. Чтобы динамически определить маршруты к соединенным сетям, AppleTalk использует Routing Table Maintenance Protocol (RTMP).

В основе адресации узлов AppleTalk из Winsock лежит структура:

typedef struct sockaddr_at

{

USHORT

sat_family;

USHORT

sat.net;

UCHAR

sat_node;

UCHAR

sat_socket;

} SOCKADDR_AT, *PSOCKADDR_AT;

Заметьте: эта структура содержит только символы и короткие целые числа, но не дружественные имена. Структура SOCKADDR_ATпередается в таких функциях Winsock, как bind, connect и WSAConnect, однако для трансляции

Г Л А ВА 6 Семейства адресов и разрешение имен

,141

пужественного имени необходимо сначала разрешить или зарегистрироть это имя функциями getsockopt и setsockopt соответственно.

РегистрацияимениAppleTalk

Зарегистрировать сервер под определенным именем, которое будут использовать клиенты, позволяет функция setsockopt с параметром SO_REGISTER_NAME. Пдя всех параметров сокетов, связанных с именами AppleTalk, используйте структуруWSH_NBP_NAME:

typedef struct

CHAR

ObjectNameLen;

CHAR

ObjectName[MAX_ENTITY];

CHAR

TypeNameLen;

CHAR

TypeName[MAX_ENTITY];

CHAR

ZoneNameLen;

CHAR

ZoneName[MAX_ENTITY];

} WSH_NBP_NAME, «PWSH_NBP_NAME;

Ряд типов: например, WSH_REGISTER_NAME, WSH_DEREG1STER_NAME и WSH_REMOVE_NAME, — определены на основе структуры WSH_NBP_NAME.

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

Вот как зарегистрировать имя AppleTalk:

 

«define

MY_ZONE

 

 

 

 

«define

MY_TYPE

"Winsock-Test-App"

 

«define

MY_0BJECT

 

"AppleTalk-Server"

 

WSH_REGISTER_NAME

atname;

 

SOCKADDFi_AT

ataddr;

 

SOCKET

 

 

s;

 

 

// Впишите регистрируемое имя

 

//

 

 

 

 

 

strcpy(atname.ObjectName, MY.OBJECT);

 

atname.ObjectNameLen = strlen(MY_OBJECT);

 

strcpy(atname.TypeName,

MY_TYPE);

 

atname.TypeNameLen

=

strlen(MY TYPE);

 

strcpy(atname.ZoneName,

 

MY_Z0NI);

 

atname.ZoneNameLen = strlen(MY_ZONE);

 

s = socket(AF_APPLETALK,

SOCK.STREAM, ATPROTOJUWP&b<

i,

if (s == INVALID_SOCKET)

 

// Ошибка

 

 

 

 

ataddr.sat^socket = О-

 

 

a ? a t - f a m i l y

=

A F

 

 

 

(s, (SOCKADDR

.)&ataddr, sizeof(at*Alie5/r« SOCKET.ERROR)

142

ЧАСТЬ II Интерфейс прикладного программирования Wmsock

// Невозможно открыть конечную точку в сети AppleTalk

}

if (setsockopt(s, SOL_APPLETALK, SO_REGISTER_NAHE,

(char O&atname, sizeof(WSH_NBP_NAME)) == SOCKET_ERROR)

{

// Ошибка при регистрации имени1

Первое, на что следует обратить внимание, — строки MYZONE, MYJTYPE YL MY OBJECT Как вы помните, имя AppleTalk трехуровневое В рассматриваемом примере вместо имени зоны стоит звездочка (*) Она обозначает текущую зону, то есть зону, в которой находится ваш компьютер Затем создается сокет с типом SOCK_STREAM и вызывается функция bind со структурой адреса, где значение поля sat socket обнулено и только полю семейства протоколов присвоено значение Это важно, поскольку в результате в сети AppleTalk создается конечная точка, откуда ваше приложение сможет выполнять запросы Хотя вызов функции bind и позволяет выполнять простейшие операции в сети, сам по себе он не дает возможности приложению принимать входящие клиентские запросы Для этого необходимо сделать следующий шаг — зарегистрировать имя в сети

Сделать это не так уж сложно вызовите функцию setsockopt, передав в нее в качестве параметра level SOL_APPLETALK, а в качестве параметра optname SO_REGISTER_NAME Эти параметры — указатели на структуру WSHJREGISTERJSIAME и ее размер Успешный вызов данной функции означает, что имя сервера было зарегистрировано Если при вызове произошла ошибка, вероятнее всего, имя уже используется кем-то другим Код этой ошибки Winsock

WSAEADDR1NUSE (10048 или 0x02740h) Заметьте, что для получения данных процесс должен зарегистрировать имя, вне зависимости от того, обмениваетесь ли вы дейтаграммами или устанавливаете соединение

РазрешениеименAppleTalk

Клиентское приложение обычно знает дружественное имя сервера и для вызова функций Winsock должно разрешить его в номер сети, узла и сокета Эта задача решается путем вызова функции getsockopt с параметром SO_ LOOKUP_NAME Для поиска имени применяется структура WSH_ LOOKUP^ NAME, а также используемая в ней структура WSH_NBP_TUPLE

typedef struct

WSH_ATALK_ADDRESS

Address; f_ ЛЗЯЧТЛ

,ir

. „ , <

USHORT

Enumerator;

 

 

WSH_NBP_NAME

NbpName;

 

 

} WSH_NBP_TUPLE, *PWSH_NBP_TUPLE;

 

 

typedef struct _WSH_LOOKUP_NAME

 

 

// Массив структур WSH_NBP_TUPLE

ж*лш

.1Ь»ьч,г>{- „;W)XiS/

WSH_NBP_TUPLE

LookupTuple;

 

 

 

Г Л А ВА 6

Семейства адресов и разрешение имен

143

ULONG

NoTuples;

 

 

> WSH_L0OKUP_NAME,

*PWSH_LO0KUP_NAME;

f

 

При вызове функции getsockopt с параметром SO LOOKUP_NAME мы пеедаем в нее в качестве буфера структуру WSH_LOOKUP_NAME, а в поле \\ S#_ ЫВР NAME — задаем первый член массива LookupTuple В случае успешного вызова функция getsockopt возвращает массив элементов WSHJVBP_TUPLE, содержащих информацию о физическом адресе для данного имени Поиск имени проиллюстрирован в листинге 6-1 (файл Atalknmc) Кроме того, в примере показано, как перечислить все «обнаруженные» зоны AppleTalk и найти текущую зону Информация о зонах содержится в параметрах SO_

LOOKUP_ZONES и SO_LOOKUP_MYZONE функции getsockopt.

Листинг 6-1. Поиск имени и зоны AppleTalk

«include

<winsock.h>

 

«include

<atalkwsh.h>

U][t

«include <stdio.h>

 

«include <stdlib.h>

 

«define

DEFAULT.ZONE

 

«define

DEFAULT_TYPE

"Windows Sockets"

«define

0EFAULT_0BJECT

"AppleTalk-Server"

char szZone[MAX_ENTITY], szType[MAX_ENTITY], szObject[MAX_ENTITY];

BOOL bFmdName = FALSE,

bListZones = FALSE,

bListMyZone = FALSE;

void usage()

<

printf("usage. atlookup [options]\n");

printf("

 

Name Lookup \n"),

Printf("

 

-z-ZONE-NAME\n");

printf("

 

-t TYPE-NAME\n");

p n n t f C

к

-o-OBJECT-NAME\n");

p n n t f C

 

List All Zones.\n");

Pnntf("

 

-lz\n");

printf("

 

List My Zone:\n");

P n n t f C

 

-lm\n");

ExitProcessd);

}

«•>

;>

,о>.<и и

(v

void ValidateArgsdnt argc, char "argv)

xnt

1 ;

Ti ,

 

 

 

jut» •

iq

aj

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

и -интерфейс прикладного программирования Winsock

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

strcpy(szZone, DEFAULT_ZONE); strcpy(szType, DEFAULT_TYPE); strcpy(szObject, DEFAULT_OBJECT);

for(i = 1; i < argc; i++)

{

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

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

{

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

{

 

 

case

'z':

//

Указание

имени зоны

 

 

 

if (strlen(argv[i])

> 3)

 

 

 

 

strncpy(szZone, &argv[i][3], MAX_ENTITY);

 

 

 

bFindName = TRUE;

 

 

 

 

 

break;

 

 

 

 

 

case

't':

// Указание

имени типа

 

 

 

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

 

 

 

 

strncpy(szType, &argv[i][3], MAX_ENTITY);

 

 

 

bFindName = TRUE;

 

 

 

 

 

break;

 

 

 

 

 

case

'о':

// Указание

имени объекта

 

 

 

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

 

 

 

 

strncpyCszObject, &argv[i][3], MAX_ENTITY);

 

 

 

bFindName = TRUE;

 

 

 

 

 

break;

 

 

 

 

 

case

'1';

// Просмотр информации о зонах

 

 

 

if (strlen(argv[i])

== 3)

 

 

 

 

//

List all zones

 

 

 

 

if

(tolower(argv[i][2]) == 'z')

 

 

 

 

bListZones = TRUE;

 

 

 

 

//

List my zone

 

 

%i

 

 

 

else if (tolower(argv[i][2]) == 'm')

«*

 

 

 

bListMyZone

= TRUE;

 

 

break;

 

 

 

; ( ' *

 

default:

 

 

у.: (

 

 

usage();

 

 

l

 

 

 

.(

int main(int a^b, char **argv)

{fc,

WSADATA

 

wsd;

(vgie»« lerto ,.-s

°har

J-

cLookupBuffer[16000J,

 

 

•pTupleBuffer

= NULL;

PWSH_NBP_TBR6

pTuples =

NULL;

 

 

Г Л А ВА 6

Семейства адресов и разрешение имен

145

Листинг 6-1.

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

 

 

PWSH_LOOKUP_NAME

atlookup;

 

 

PWSH_L00KUP_Z0NES zonelookup;

 

SOCKET

 

s;

 

 

DWORD

 

dwSize = sizeof(cLookupBuffer);

 

SOCKADDR_AT

 

ataddr;

 

 

. int

 

i'-

 

 

// Загрузка

библиотеки Winsock

 

 

//

 

 

 

 

if (WSAStartup(MAKEW0RD(2, 2),

&wsd) != 0)

 

<

 

 

 

 

printf("Unable to load Winsock library!\n"); return 1;

ValidateArgs(argc, argv);

atlookup = (PWSH_LOOKUP_NAME)cLookupBuffer; zonelookup = (PWSH_LOOKUP_ZONES)cLookupBuffer; if (bFindName)

// Ввод искомого имени

//

strcpy(atlookup->LookupTuple.NbpName.ObjectName, szObjeot); atlookup->LookupTuple.NbpName.ObjectNameLen =

strlen(szObject); strcpy(atlookup->LookupTuple.NbpName.TypeName, szType); atlookup->LookupTuple.NbpName.TypeNameLen = strlen(szType); strcpy(atlookup->LookupTuple.NbpName.ZoneName, szZone); atlookup->LookupTuple.NbpName.ZoneNameLen = strlen(szZone);

}

// Создание сокета AppleTalk

//

s = socket(AF_APPLETALK, SOCK_STREAM, ATPROTO_ADSP); if (s == INVALID_SOCKET)

{

 

printf("socket() failed: Xd\n", WSAGetLastErrorO);

 

return 1;

}

 

//

Для создания в сети AppleTalk конечной точки и выполнения запросов,

//

необходимо осуществить привязку

ZeroMemory(&ataddr, sizeof(ataddr)); ataddr.sat_family = AF.APPLETALK; ataddr.sat_socket = 0;

if (bind(s, (SOCKADDR »)&ataddr, sizeof(ataddr)) == INVALID_SOCKET)

.imp

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

4A(JIь II Интерфейсприкладного программированияWinsock

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

printf("bind() failed: Xd\n", WSAGetLastErrorO); return 1;

if (bFmdName)

{

printf("Looking up %s: Xs@Xs\n", szObject, szType, szZone); if (getsockopt(s, SOL.APPLETALK, SO_LOOKUP_NAM£,

(char Oatlookup, idwSize) == INVALID.SOCKET)

{

pnntf("getsookopt(SO_LOOKUP_NAME) failed: Xd\n", WSAGetLastErrorO);

return 1;

>

pnntfC'Lookup returned: %d entries\n", atlookup->NoTuples);

//

//Символьный буфер содержит массив структур

//WSH_NBP_TUPLE, вслед за структурой WSH_L00KUP_NAME

pTupleBuffer = (char *)cLookupBuffer + sizeof(WSH_LOOKUP_NAME);

pTuples = (PWSH_NBP_TUPLE) pTupleBuffer;

<*

for(i = 0;

atlookup->NoTuples;

 

ataddr.sat.family = AF_APPLETALK,

 

ataddr.sat.net

=

pTuples[i].Address.Network;

ataddr.sat_node

=

pTuples[i].Address.Node,

ataddr.sat_socket

= pTuplesfi].Address. Socket;

printf("server address = Xlx.Xlx Xlx.\n", ataddr.sat_net,

ataddr.sat_node, ataddr.sat_socket);

}

JAV,-

else if (bListZones)

 

//Для этого параметра необходимо выделить достаточный размер буфера,

//иначе в Windows NT 4 SP3 появляется ошибка "синяя смерть" (blue screen)

if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_ZONES,

ом-

(char *)atlookup, &dwSize)

== INVALID_SOCKET)8*

(

 

«7

printf("getsockopt(SO_LOOKUP_NAME) f a i l e d - Xd\n",

«e

WSAGetLastErrorO);

 

>

r e t u r n 1;

 

.JAVKA

>

 

 

p n n t f C ' L o o k u p r e t u r n e d : %6 zones\n",

zonelookup->NoZonee);

Г Л А ВА 6 Семейства адресов и разрешение имен

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

II

II Буфер содержит список разделенных нулями строк вслед // за структурой WSH_LOOKUP_NAME

//

pTupleBuffer = (char OcLookupBuffer + sizeof(WSH_LOOKUP_ZONES);

for(i = 0; l < zonelookup->NoZones; i++)

{

printf("X3d: 'Xs'\n", i+1, pTupleBuffer); while (*pTupleBuffer++);

else

if (bListMyZone)

ш

II

Этот параметр возвращает простую строку

if (getsockopt(s, SOL.APPLETALK, S0_LOOKUP_MYZ0NE,

(char OcLookupBuffer, &dwSize) == INVALID_SOCKET)

{

printf("getsockopt(SO_LOOKUP_NAME) failed: Xd\n",

WSAGetLastErrorO);

return 1;

 

}

 

printf("My Zone: 'J(s'\n",

cLookupBuffer);

}

 

else

"!

usage();

 

WSACleanup();

 

return 0;

}

Для большинства параметров сокетов AppleTalk (например, SO_ LOOKUP'JMYZONE, SO_LOOKUP_ZONES или SO_LOOKUP_NAME) при вызове функции getsockopt необходимо выделить достаточно большой символьный буфер Если в качестве параметра требуется указать структуру, то она должна находиться в начале буфера При успешном вызове функции getsockopt возвращаемые данные помещаются в буфер после этой структуры.

Рассмотрим раздел с SO_LOOKUP_NAME в листинге 6-1. Для вызова функции getsockopt используется переменная cLookupBuffer, являющаяся простым массивом символов Сначала мы помещаем туда структуру PWSH_LOOKUP_ * с информацией об искомом имени. Затем передаем буфер в функцию 8 sockopt, после чего увеличиваем на 1 указатель на символьный runpTuple- Лег> чтобы он указывал на символ, находящийся после структуры WSH_ KUP_NAME. Далее присваиваем этому указателю адрес структуры PWSH_