Программирование в сетях Windows
.pdf1 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_