Программирование в сетях Windows
.pdf298 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
|
Листинг 10-1. {продолжение) |
,,v, |
|
II |
|
«"» |
II Задаем IPX-адрес нашей службы |
|
|
// |
|
|
memset(sa_ipx.sa_netnum, 0, sizeof(sa_ipx.sa_netnum)); memset(sa_ipx.sa_nodenum, 0, sizeof(sa_ipx.sa_nodenum)); sa_ipx.sa_family = AF_IPX;
sa_ipx.sa_socket = 0;
socks[1] = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
ret = bind(socks[1], (SOCKADDR *)&sa_ipx, sizeof(sa_ipx)); |
|
cb = sizeof(IPX_ADDRESS_DATA); |
|
memset (&ipx_data, 0, cb); |
|
ipx_data.adapternum = 0; |
|
ret = getsockopt(socks[1], NSPROTO_IPX, IPX_ADDRESS, |
|
(char *)&ipx_data, &cb); |
|
cb = sizeof(SOCKADDR_IPX); |
|
getsockname(socks[1], (SOCKADDR *)sa_ipx, &cb); |
|
memcpy(sa_ipx.sa_netnum, ipx_data.netnum, sizeof(sa_ipx.sa_netnum)); |
er |
memcpy(sa_ipx.sa_nodenum, ipx_data.nodenum, sizeof(sa_ipx.sa_nodenum)); |
*? |
lpCSAddr[1].iSocketType = SOCK_DGRAM; |
|
lpCSAddr[1].iProtocol = NSPROTO_IPX; |
|
lpCSAddr[1].LocalAddr.lpSockaddr = (struct sockaddr *)&sa_ipx; |
Ff |
lpCSAddr[1].LocalAddr.iSockaddrLength = sizeof(sa_ipx); |
eP |
lpCSAddr[1].RemoteAddr.lpSockaddr = (struct sockaddr •)&sa_ipx; |
* |
lpCSAddr[1].RemoteAddr.iSockaddrLength = sizeof(sa_ipx); |
ч |
qs.dwNumberOfCsAddrs = 2 ; |
|
ret = WSASetService(&qs, RNRSERVICE.REGISTER, OL); |
' n> *"' |
В листинге 10-1 показано, как настроить экземпляр службы, чтобы ее клиент мог найти адреса, необходимые для взаимодействия с ней. Прежде всего, следует инициализировать структуру WSAQUERYSET. Кроме того, необходимо задать имя экземпляра службы, назовем его Widget Server. Другой важный шаг — использовать тот же GUID, который применялся для регистрации нашего класса службы. Здесь мы работаем с классом Widget Service Class (определение дано в предыдущем разделе), GUID которого — SVCID_NETWARE(200). Следующий этап — задать интересующие нас пространства имен. Наша служба выполняется по протоколам IPX и UDP, и поэтому мы указываем NS_ALL. Поскольку мы задаем уже существующее пространство имен, присвоим параметру ipNSProviderld значение NULL.
Затем следует настроить структуры SOCKADDR в массиве CSADDRJNFO, которые функция WSASetService передает в качестве поля ipcsaBuffer структу-
Г Л А ВА 10 Регистрация и разрешение имен |
299 |
ры WSAQUERYSET. Как видите, перед настройкой структуры SOCKADDR мы действительно создаем сокеты и связываем их с локальным адресом. Это обусловлено тем, что нам необходимо узнать точный локальный адрес, к которому будут подключаться клиенты. Например, мы связываем создаваемый для сервера UDP-сокет с INADDR^ANY, таким образом, получить реальный IP-адрес без вызова функции getsockname невозможно. На основе полученной от функции getsockname информации можно создать структуру SOCKADDRIN. В структуре CSADDR_INFO задаем тип и протокол сокета. Два других поля содержат локальный (с которым должен быть связан сервер) и удаленный адрес (который клиент будет использовать для подключения к службе).
Следующий шаг — настроить службу, выполняющуюся по протоколу IPX. Из материалов главы б вы знаете, что серверы должны быть связаны с номером внутренней сети, для этого номер сети и узла должны быть нулевыми. Опять же, таким образом вы не сможете получить необходимый клиентам адрес. Чтобы получить его, вызовите параметр сокета IPX_ADDRESS. Заполняя структуру CSADDRJNFO для протокола IPX, укажите SOCKJDGRAM и NSPROTOJPX в качестве типа сокета и протокола соответственно.
Завершающий этап — присвоить полю dwNumberOfCsAddrs структуры WSAQUERYSET значение 2, поскольку для установления соединения клиенты могут использовать два адреса — UDP и IPX. Затем вызовите функцию WSASetService, передав ей структуру WSAQUERYSET, флаг RNRSERVICE_REGISTER и
не передавая управляющих флагов. Управляющий флаг SERVICE_MULTIPLE не указывается, чтобы при удалении сведений о службе удалялась информация обо всех ее экземплярах (IPX- и UDP-адреса).
В приведенном примере не учитывается один случай: компьютеры с несколькими сетевыми адаптерами. Если вы создадите сервер на основе протокола UDP, привязывающийся KINADDRANYHZL компьютерах с несколькими сетевыми адаптерами, клиент сможет подключаться к этому серверу, используя любой из доступных интерфейсов. В протоколе IP функции getsockname недостаточно: вам потребуется получить все локальные IP-адреса. Это можно осуществить несколькими способами, в зависимости от платформы, на которой вы работаете. Один из распространенных методов, применимый на всех платформах — вызвать функцию gethostbyname, которая вернет список IP-адресов для имени. В Winsock можно также вызвать ioctl-команду SIOJGETJNTERFACEJJST. Для Windows 2000 доступна ioctl-команда SIO_ADDRESSJJST_QUERY.
Кроме того, можно воспользоваться функциями IP helper (приложение В). Простое разрешение имен TCP/IP и функция обсуждаются в главе 6, а команды ioctl — в главе 9- На прилагаемом компакт-диске содержится пример (файл Rnrcs.c), в котором реализована работа с компьютерами с несколькими сетевыми адаптерами.
Запрос к службе
Теперь рассмотрим, как клиент может запросить пространство имен для данной службы и получить информацию, необходимую для установления связи. Разрешение имен несколько проще, чем регистрация служб. Для выпол-
300 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
нения запросов используются три функции: WSALookupServiceBegin, WSALookupServiceNext и WSALookupServiceEnd.
Первый этап — вызвать функцию WSALookupServiceBegin, которая инициирует запрос, задавая ограничения для его выполнения:
INT WSALookupServiceBegin ( LPWSAQUERYSET lpqsRestrictions, DWORDdwControlFlags,
LPHANDLE lphLookup
);
Первый параметр — структура WSAQUERYSET, ограничивающая запрос, например в части количества опрашиваемых пространств имен. Второй параметр — dwControlFlags, определяет глубину поиска. Модель поведения запроса и то, какие данные он вернет, определяют следующие флаги.
II LUPDEEP — в иерархичных пространствах имен задает глубину запроса по отношению к первому уровню.
ШLUPCONTAINERS — вернуть только объекты-контейнеры. Этот флаг действителен лишь в иерархичных пространствах имен.
ШLUPNOCONTAINERS — не возвращать какие-либо контейнеры. Флаг также действителен лишь в иерархичных пространствах имен.
•LUPFLUSHCACHE — игнорировать кэш и опрашивать непосредственно пространство имен. Заметьте: не все поставщики пространств имен кэшируют запросы.
ШLUPFLUSHPREVIOUS — указать поставщику пространства имен отбросить ранее возвращенный набор сведений. Обычно используется после того, как WSALookupServiceNext вернет WSA_NOT_ENOUGH_MEMORY. Набор, не помещающийся в предоставленный буфер, отбрасывается, после чего извлекается следующий.
•LUPNEAREST — вернуть результаты, упорядочив их по расстоянию. Мера расстояния определяется поставщиком имен, поскольку при регистрации службы соответствующие сведения не указываются. Поставщикам имен не требуется поддерживать данную концепцию.
•LUPRESSERVICE — указывает, что локальные адреса должны быть возвращены в структуре CSADDRJNFO.
U LUP_RETURN_ADDR — вернуть адреса как ipcsaBuffer.
• LUP_RETURN_ALIASES — получить только сведения о псевдонимах. Каждый псевдоним будет возвращаться при успешных вызовах функции WSALookupServiceNext и для него будет задан флаг RESULT JS_AUAS.
ШLUP_RETURN_ALL — вернуть все доступные сведения.
ШLUPRETURNBLOB — вернуть все частные данные как ipBlob.
ШLUPRETURNCOMMENT — вернуть комментарий как ipszComment.
ЖLUPRETURNNAME — вернуть имя как ipszServicelnstanceName.
ШLUP_RETURN_TYPE — вернуть тип как ipServiceClassId.
ЖLUP_RETURN_VERSION — вернуть версию как ipVersion,
Г Л А ВА 10 Регистрация и разрешение имен |
301 |
Последний параметр имеет тип HANDLE и инициализируется при возвращении функции. В случае успеха возвращенное значение равно 0, иначе — SOCKETJERROR. Если один или несколько параметров не действительны, функция WSAGetLastError возвращает WSAEINVAL. Если имя найдено в пространстве имен, но заданным ограничениям не соответствуют какие-либо данные, будет возвращен код ошибки WSANOJDATA. Если службы не существует,функцияWSAGetLastErrorвозвращаетWSAEINVAL.
После вызова функция возвращает дескриптор WSALookupServiceBegin, которыйпередается функции WSALookupServiceNext, возвращающей информацию:
INT WSALookupServiceNext (
HANDLE hLookup,
DWORD dwControlFlags,
LPDWORD lpdwBufferLength,
LPWSAQUERYSET lpqsResults
ФункцияWSALookupServiceBeginвозвращаетдескрипторhLookup.Параметр dwControlFlags имеет в функции WSALookupServiceBegin то же самое значение, ноподдерживаетсялишьLUP_FLUSHPREVIOUS.ПараметрlpdwBufferLength—
длина буфера, переданного в качестве lpqsResults. Поскольку структура WSAQUERYSET может содержать данные больших двоичных объектов, часто требуется передать буфер, объем которого превосходит объем структуры. Если размер буфера не достаточен для возвращенных данных, произойдет сбой и функциявернетWSA_NOT_ENOUGH_MEMORY.
ИнициировавзапросспомощьюфункцииWSALookupServiceBegin,вызывайте функцию WSALookupServiceNext до тех пор, пока система не выдаст сообщениеобошибкеWSA_E_NO_MORE(10110).Помните,чтовпредыдущихверсиях Winsock при отсутствии данных возвращалась ошибка WSAENOMORE (10102), поэтому надежное приложение должно проверять оба кода. Получив вседанныеилизавершивопрос,вызовитефункциюWSALookupServiceEnd, передав ей использовавшуюся в запросах переменнуюHANDLE:
INT WSALookupServiceEnd ( HANDLE hLookup );
Создание запроса
Рассмотрим, какопроситьзарегистрированнуювпредыдущемразделеслуж* бу.Преждевсего,необходимонастроитьструктуруWSAQUERYSET,опреде» ляющую запрос:
WSAQUERYSET qs; |
|
|
GUID |
fluid |
= SVCID_NETWARE(200); |
AFPROTOCOLS |
afp[2] * {{AF_IPX, NSPROTO.IPX}, {AF.INET, IPPROTOJJDP}}; |
|
HANDLE |
|
hLookup; |
int |
ret; |
|
memset(&qs, 0, sizeof(qs)); qs.dwSlze = sizeof (WSAQUERYSET);
304 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
В этом случае параметру ipdwBufferLength присваивается значение, соответствующее требуемому размеру буфера В примере используется буфер с фиксированным размером, равным размеру структуры WSAQUERYSETплюс 2000 байт Поскольку вам необходимы только имена и адреса службы, такого размера должно хватить Конечно, серьезные приложения должны уметь обрабатыватьошибкуWSAEFAULT
После успешного вызова функции WSALookupServiceNext в буфер WSAQUERYSET помещается структура, содержащая результаты В запросе требовалось получить имена и адреса, и поэтому наиболее интересные для нас поля структуры WSAQUERYSET — это ipszServicelnstanceName и ipcsaBuffer Поле IpszServicelnstanceName содержит имя службы, а поле IpcsaBuffer —
представляет массив структур CSADDRJNFO с ее адресами Параметр dwNumberOfCsAddrs указывает, сколько адресов возвращено В коде примера мы просто выводим все адреса Убедитесь, что будут выводиться только IP- и IPXадреса, поскольку это единственные семейства адресов, указанные при открытии запроса
Если в запросе в качестве имени службы указана звездочка (*), при каждом вызове функции будет возвращен конкретный экземпляр этой службы, выполняющийся на одном из компьютеров сети (конечно, при условии, что несколько экземпляров действительно зарегистрированы и выполняются) После того, как функция WSA_E_NO_MORE вернет все экземпляры службы, будет сгенерирована ошибка и цикл прекратится Последнее, что необходимо сделать — вызвать функцию WSALookupServiceEnd, передав ей дескриптор запроса При этом будут освобождены все выделенные запросу ресурсы.
Запрос к DNS
Как уже упоминалось, пространство имен DNS статично, то есть не позволяет динамически зарегистрировать собственную службу Тем не менее, для запросов к DNS можно воспользоваться функциями разрешения имен Winsock Выполнить DNS-запрос сложнее, чем обычный запрос о наличии зарегистрированной службы, поскольку поставщик пространства имен DNS возвращает информацию в форме BLOB Почему' В главе 6, где обсуждалась функция gethostbyname, мы говорили, что при поиске по имени возвращается структура HOSTENT, содержащая не только IP-адреса, но и их псевдонимы Зачастую эта информация не умещается в поля структуры WSAQUERYSET. Формат BLOB-данных плохо документирован, поэтому для прямых запросов к DNS приходится изобретать обходные пути Прежде всего рассмотрим, как открыть запрос В файле Dnsqueryc на прилагаемом компакт-дис- ке содержится полный код для прямого запроса к DNS, а здесь мы рассмот-
рим его поэтапно Следующий код инициализирует DNS-запрос
WSAQUERYSET |
qs; |
AFPROTOCOLS |
afp [2] = {{AF_INET, IPPROTOJJDP},{AF_INET, IPPROTO_TCP}}\ |
GUID |
hostnameguid = SVCID_INET_HOSTADDRBYNAME, |
DWORD |
dwLength = sizeof(WSAQUERYSET) + sizeof(HOSTENT) + 2048; |
HANDLE |
hQuery; |
ГЛАВА 10 Регистрация и разрешение имен |
305 |
qs = (WSAQUERYSET *)buff; memset(&qs, 0, sizeof(qs));
qs dwSize = sizeof(WSAQUERYSET); qs.lpszServicelnstanceName = argv[1]; qs.lpServiceClassId = ihostnameguid; qs.dwNameSpace = NS_DNS; qs.dwNumberOfProtocols = 2; qs.lpafProtocols = afp;
ret = WSALookupServiceBegin(&qs, LUP_RETURN_NAME | LUP_RETURN_BLOB, ihQuery);
if (ret == SOCKET.ERROR) II Ошибка
Настройка запроса осуществляется почти так же, как и в предыдущем примере Наиболее значительное отличие — используется предопределенный GUID SVCIDJNETJiOSTADDRBYNAME, он идентифицирует запросы имен компьютеров Параметр ipszServicelnstanceName — имя компьютера, которое требуется разрешить Поскольку имена разрешаются с использованием DNS,
вкачестве параметра dwNameSpace следует передать лишь NSJDNS Наконец,
впараметре ipaJProtocols передается массив из двух структур AFPROTOCOLS, которые определяют используемые запросом протоколы TCP/IP и UDP/IP.
После создания запроса вызовите функцию WSALookupServiceNext, чтобы
вернуть данные |
-j |
|
char |
buff[sizeof(WSAQUERYSET) + sizeof(HOSTENT) + 2048)]; |
|
DWORD |
dwLength = sizeof(WSAQUERYSET) + sizeof(HOSTENT) + 2048; |
|
WSAQUERYSET *pqs; |
|
|
HOSTENT |
*hostent; |
|
pqs = (WSAQUERYSET »)buff; pqs->dwSize = sizeof(WSAQUERYSET);
ret = WSALookupServiceNext(hQuery, 0, idwLength, pqs); if (ret == SOCKET_ERROR)
// Ошибка WSALookupServiceEnd(hQuery);
hostent = pqs->lpBlob->pBlobData;
Поскольку поставщик пространства имен DNS возвращает сведения о компьютере в форме BLOB, необходимо выделить буфер достаточного размера Именно поэтому используется буфер, равный по объему сумме «структура WSAQUERYSET + структура HOSTENT + дополнительные 2048 байт» Если и такого размера окажется не достаточно, при вызове функции произойдет сбой и будет возвращено значение WSAEFAULT В DNS-запросе все сведения о компьютере возвращаются в структуре HOSTENT, даже если имя компьютера связано с несколькими IP-адресами Таким образом, не требуется многократно вызывать функцию WSALookupServiceNext
306 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Теперь наступает самый сложный этап — расшифровка BLOB, возвращенного запросом Из главы 6 вы знаете, что структура HOSTENT определена так
typedef struct hostent |
{ |
|
char FAR • h_name, |
' |
|
char FAR * FAR * h_aliases, |
||
short |
h_addrtype, |
l |
short |
h_length, |
|
char FAR * FAR * h_addr_list, } HOSTENT,
Когда структура HOSTENT возвращается в виде BLOB-данных, указатели внутри нее представляют собой смещения по адресам памяти, где хранятся данные Смещение отсчитывается от начала BLOB-данных В связи с этим для доступа к данным требуется зафиксировать указатели и сделать так, чтобы они ссылались на абсолютные адреса памяти На рис 10-1 изображена структура HOSTENT и карта возвращенной памяти
Массивспискаадресов Массив списка псевдонимов
«Ой8
HOSTENT
Рис. 10-1. BLOB-формат структуры HOSTENT
DNS-запрос выполняется по имени компьютера Riven, связанного с одним IP-адресом и не имеющего псевдонимов У каждого поля структуры имеется значение смещения Чтобы поля ссылались на действительное расположение данных, необходимо прибавить значение смещения к адресу в заголовке структуры HOSTENT Подобную операцию следует выполнить для полей hjname, h_ahases и h_addrjist Кроме того, поля h_ahases и h_addrjist
представляют собой массивы указателей Итак, получен верный указатель на массив указателей каждое 32-битное
поле в этом массиве ссылается на область памяти, содержащую указатели В поле h_addr_hst (рис 10-1) начальное смещение составляет 16 байт — это ссылка на байт, следующий за структурой HOSTENT, массив указателей на четырехбайтный IP-адрес Тем не менее, смещение первого указателя в массиве составляет 28 байт Для ссылки на действительное расположение данных прибавьте к адресу структуры HOSTENT 28 байт и вы прлучите ссылку на четырехбайтную область с данными 0x9D36B9BA, представляющими собой IP-адрес 157 54 185 186 Затем можно взять 4 байта после записи со смещением 28 байт, получив в результате 0
Если бы с этим именем компьютера было связано несколько IP адресов, присутствовало бы и другое смещение, и потребовалось бы по аналогии с первым случаем изменить указатель Точно такая же операция позволяет
ГЛАВА 10 Регистрация и разрешение имен |
307 |
исправить указатель и массив указателей, на который он ссылается В данном примере у компьютера нет псевдонимов Первая запись массива 0, и это означает, что в отношении данного поля какие-либо дополнительные действия не нужны Последнее поле — hjiame, исправить которое достаточно просто Следует лишь добавить смещение к адресу структуры HOSTENT, и поле будет указывать на начало оканчивающейся нулем строки
Код, превращающий смещения в реальные адреса, прост, хотя и включает некоторые арифметические действия с указателями Для корректировки поля Ьпате подойдет следующая процедура настройки смещения
hostent->h_name = (PCHAR)((DWORD_PTR)hostent->h_name) + (PCHAR)hostent,
Чтобы изменить массив указателей (например, поля h_ahases и h_addrjtst), необходим более сложный код, который будет просматривать массив и изменять ссылки, пока не достигнет нулевой записи
PCHAR «addr, |
! |
if (hostent->h_aliases) |
; |
< |
I |
addr=hostent->h_aliases=(PCHAR)((DWORD_PTR)hostent»h_aliases+ |
, |
(PCHAR)hostent), |
|
while (addr) |
|
{ |
Л |
addr = (PCHAR)((DWORD_PTR)addr + (PCHAR Ohostent), |
! |
addr++, |
; |
Этот код переходит от одной записи массива к другой и добавляет начальный адрес структуры HOSTENT к заданному смещению, в результате получается новое значение текущей записи Конечно, по достижении нулевой записи просмотр массива прекращается Данную операцию следует выполнить и для поля b_addr_hst После того как смещения будут изменены, со структурой HOSTENT можно работать в обычном порядке
Резюме
Функции регистрации и разрешения имен могут показаться сложными, однако они обеспечивают значительную гибкость при разработке клиент-сер- верных приложений Реальные ограничения на регистрацию имен связаны непосредственно с пространством имен Удивительно, что при всей популярности пакета протоколов TCP/IP доступен единственный метод разрешения имен — DNS, который не обеспечивает требуемой гибкости В доменных пространствах Windows 2000 и Windows NT доступен постоянный, не зависящий от протокола метод разрешения имен, обеспечивающий необходимую гибкость для разработки устойчивых приложений Кроме того, приложениям на основе протокола IPX/SPX доступны другие пространства имен (например, SAP), предоставляющие большинство из возможностей NTDS (за исключением независимости от протокола)