Главная страница

Программирование в сетях Windows. Э. Джонс, Д. Оланд


Скачать 2.88 Mb.
НазваниеЭ. Джонс, Д. Оланд
АнкорПрограммирование в сетях Windows.pdf
Дата12.10.2017
Размер2.88 Mb.
Формат файлаpdf
Имя файлаПрограммирование в сетях Windows.pdf
ТипКнига
#9346
страница27 из 50
1   ...   23   24   25   26   27   28   29   30   ...   50
ЧАСТЬ II Интерфейс прикладного программирования Winsock ляющих флагов и флагов операций, а также описаны результаты выполне- ния команд в зависимости от того, существует уже служба или нет.
Табл. 10-2. Комбинации флагов WSASetService
Флаги
Служба существует Службы не существует
RNRSERVICE_ He заданы
REGISTER
SERVICE^MULTIPLE
RNRSERVICE_ He заданы
DEREGISTER
pi < •*•<*»*!
SERVICEJAULTIPLE
RNRSERVICE_ He заданы
DELETE
SERVICE^MULTIPLE
Перезаписать текущий экземпляр службы
Обновить экземп- ляр службы, доба- вив новый адрес
Удалить все экземп- ляры службы, но не сведения о ней (обыч- но WSAQUERYSET ос- тается, однако число структур CSADDRJNFO
равно 0)
Обновить службу, уда- лив указанный адрес.
Сведения о службе не будут удалены, даже если не останется адресов.
Удалить из прост- ранства имен все сведения о службе
Обновить службу, уда- лив указанный адрес.
Если адресов не останется, сведения о службе будут удалены.
Добавить по данному адресу новый экземпляр службы
Добавить по данному адресу новый экземпляр службы
Ошибка, функция вернет
WSASERV1CE_NOT_
FOUND
Ошибка, функция вернет
WSASERVICE_NOT_
FOUND
Ошибка, функция вернет
WSASERVICE_NOT_
FOUND
Ошибка, функция вернет
WSASERVICE_NOT_
FOUND
Теперь рассмотрим структуру WSAQUERYSET —• ее необходимо заполнить и передать функции WSASetService:
typedef struct _WSAQuerySetW {
DWORD
LPTSTR
LPGUID
LPWSAVERSION
LPTSTR
DWORD
LPGUID
LPTSTR
DWORD
1PAFPR0T0C0LS
LPTSTR
DWORD
LPCSADDR.INFO
DWORD
dwSize;
lpszServicelnstanceName
lpServiceClassId;
lpVersion;
lpszComment;
dwNameSpace;
lpNSProviderld;
lpszContext;
dwNumberOfProtocols;
lpafpProtocols;
lpszQueryString;
dwNumberOfCsAddrs;
lpcsaBuffer;
dwOutputFlags;

Г Л А В А 10 Регистрация и разрешение имен 295
LPBLOB lpBlob;
} WSAQUERYSETW, «PWSAQUERYSETW, «LPWSAQUERYSETW;
Полю dwSize следует присвоить значение, соответствующее размеру струк- туры WSAQUERYSET. Поле ipszServicelnstanceName содержит строковый иден- тификатор, задающий имя данного экземпляра сервера. Поле ipServiceClas-
sld — GUID класса, к которому принадлежит данный экземпляр службы. Поле
ipVersion является необязательным. Оно позволяет указать сведения о версии,
которые могут оказаться полезными клиенту при запросе к службе. Поле
ipszComtnent также является необязательным и позволяет задать любой стро- ковый комментарий. Поле dwNameSpace указывает пространства имен, в которых будет зарегистрирована служба.
При работе с одним пространством имен укажите только одно значение,
в противном случае присвойте полю значение NS_ALL. Кроме того, можно ссылаться на собственный поставщик пространства имен (подробней о со- здании собственного пространства имен — в главе 14). Для собственного поставщика пространства имен полю dwNameSpace присваивается значение
0, а поле ipNSProviderld задает GUID, представляющий собственный постав- щик. Поле ipszContext указывает начальную точку запроса в иерархичном пространстве имен, например, DNS.
Поля dwNumberOJProtocols и ipaJpProtocols — дополнительные параметры,
позволяющие сузить поиск и вернуть сведения только о необходимых прото- колах. Поле dwNumberOfProtocols ссылается на число структур AFPROTOCOLS,
имеющихся в массиве ipafpProtocols. Синтаксис структуры AFPROTOCOLS сле- дующий:
typedef struct .AFPROTOCOLS {
INT iAddressFamily;
INT lProtocol;
} AFPROTOCOLS, «PAFPROTOCOLS, «LPAFPROTOCOLS;
Первое поле — iAddressFamily, это константа семейства адресов, напри- мер, AFINET или AF_IPX. Второе поле — iProtocol, протокол из данного се- мейства адресов, например IPPROTOJTCP или NSPROTOJPX.
Следующее поле структуры WSAQUERYSET ipszQueryString, является нео- бязательным и используется только теми пространствами имен, которые поддерживают расширенные SQL-запросы, например, Whois++. Это поле за- дает строку расширенного запроса.
Два следующих поля наиболее важны при регистрации службы. Поле
dwNumberOfCsAddrs указывает число структур CSADDRJNFO, переданных в параметре ipcsaBuffer. Структура CSADDRJNFO определяет семейство адре- сов и фактический адрес, по которому находится служба. Если имеется не- сколько структур, будут доступны несколько экземпляров службы. Синтак- сис структуры CSADDRJNFO следующий:
typedef struct _CSADDR_INFO {
SOCKET_ADDRESS LocalAddr;
SOCKET.ADDRESS RemoteAddr;
INT iSocketType;
INT iProtocol;

296
ЧАСТЬ II Интерфейс прикладного программирования Winsock
} CSADDR_INFO,
typedef s t r u c t _SOCKET_ADDRESS {
LPSOCKADDR lpSockaddr,
INT iSockaddrLength,
} SOCKET_ADDRESS, *PSOCKET_ADDRESS, FAR
LPSOCKET_ADDRESS,
Сюда также включено определение SOCKET_ADDRESS При регистрации службы вы можете задать локальные и удаленные адреса Поле локального адреса (LocalAddr) позволяет указать адрес, с которым должен связываться экземпляр данной службы, поле удаленного адреса (RemoteAddr) — адрес, ко- торый клиент должен использовать при вызовах connect и sendto Два других поля указывают тип сокета (например, SOCK_STREAM или SOCKJDGRAM) и се- мейство протоколов (например, AFJNET или AFJPX) для данного адреса
Последние два поля структуры WSAQUERYSET — dwOutputFlags и ipBlob
При регистрации службы они обычно не требуются, и применяются в запросе к экземпляру службы Структура BLOB возвращает лишь поставщик простран- ства имен, то есть при регистрации службы нельзя добавить собственную структуру BLOB, которая будет возвращаться в клиентских запросах
В табл 10-3 перечислены поля структуры WSAQUERYSET, и указано, какие из них являются обязательными, а какие нет — в зависимости от выполняе- мого действия запроса или регистрации службы
Табл. 10-3. Поля WSAQUERYSET
Поле
Запрос
Регистрация
dwSize
ipszSetvicelnstanceNatne
ipServiceClassId
ipVersion
ipszComment
dwNameSpace,
ipNSProvtderld
ipszContext
dwNumberOJProtocols
ipafpProtocols
ipszQueryStnng
dwNumberOfCsAddrs
ipcsaBuffer
dwOutputFlags
IpBlob
Обязательное
Требуется строка или <*»
Обязательное
Необязательное
Игнорируется
Должно быть указано одно из этих полей
Необязательное
О или больше
Необязательное
Необязательное
Игнорируется
Игнорируется
Игнорируется
Игнорируется, может возвращаться запросом
Обязательное
Обязательное ».
Обязательное
Необязательное
Необязательное
Должно быть указано одно из этих полей
Необязательное
О или больше
Необязательное
Игнорируется
Обязательное
Обязательное
Необязательное
Игнорируется ко
Пример регистрации службы
А теперь рассмотрим на примере, как зарегистрировать собственную служ-,
бу в пространствах имен SAP и NTDS одновременно Пространство домена
Windows NT предоставляет достаточно мощные возможности Тем не менее,

Г Л А В А 10 Регистрация и разрешение имен
297
кое-что следует п о м н и т ь Во-первых, п р о с т р а н с т в о домена Windows NT тре- бует Windows 2000, поскольку о н о о с н о в а н о на Active Directory Эго также означает, что у р а б о ч е й с т а н ц и и Windows 2000, на к о т о р о й вы с о б и р а е т е с ь з а р е г и с т р и р о в а т ь и (или) искать службу, должна иметься учетная з а п и с ь в д а н н о м домене, в п р о т и в н о м случае система не сможет обратиться к Active
Directory
Кроме того, п р о с т р а н с т в о домена Windows NT может р е г и с т р и р о в а т ь ад- реса сокетов из л ю б о г о семейства п р о т о к о л о в Это означает, ч т о все IP- и
IPX-службы м о ж н о з а р е г и с т р и р о в а т ь в о д н о м п р о с т р а н с т в е имен, а удале- ние и добавление IP-адресов — осуществлять д и н а м и ч е с к и Для п р о с т о т ы из кода и с к л ю ч е н к о н т р о л ь о ш и б о к
) Ч
Листинг 10-1. Пример функции WSASetService
к socks[2],
qs lpCSAddr[2],
sa_in,
sa_ipx,
ipx_data, i
guid = SVCID_NETWARE(200), /
ret, cb,
SOCKET
WSAQUERYSET
CSADDR_INFO
SOCKADDR_IN
SOCKADDR_IPX
IPX_ADDRESS_DATA
GUID
int memset(&qs, 0, sizeof(WSAQUERYSET)),
qs dwSize = sizeof(WSAQUERYSET),
qs lpszServicelnstanceName = (LPSTR) Widget Server ,
qs lpServiceClassId = &guid,
qs dwNameSpace = NS_ALL,
qs lpNSProviderld = NULL,
qs lpcsaBuffer = lpCSAddr,
qs lpBlob = NULL,
// Задаем IP-адрес нашей службы memset(&sa_in, 0, sizeof(sa_m)),
sa_in sin_family = AF_INET,
sa_m sin_addr s_addr = htonl(INADDR_ANY),
sa_m sin_port = 5150,
socks[0] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP),
ret = bmd(socks[0], (SOCKADDR *)&sa_m, sizeof(sa_in)),
cb = sizeof(sa_in),
getsockname(socks[0], (SOCKADDR *)&sa_m, &cb),
lpCSAddr[O] iSocketType = SOCK_DGRAM,
lpCSAddr[O] lProtocol = IPPROTOJJDP
lpCSAddr[O] LocalAddr lpSockaddr = (SOCKADDR O&sa_in,
lpCSAddr[O] LocalAddr iSockaddrLength = sizeof(sa_in),
lpCSAddr[O] RemoteAddr lpSockaddr = (SOCKADDR *)&sa_in lpCSAddr[O] RemoteAddr iSockaddrLength = sizeof(sa_in),
ш

298 ЧАСТЬ 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;
F
f lpCSAddr[1].LocalAddr.iSockaddrLength = sizeof(sa_ipx);
e
P
lpCSAddr[1].RemoteAddr.lpSockaddr = (struct sockaddr •)&sa_ipx; *
lpCSAddr[1].RemoteAddr.iSockaddrLength = sizeof(sa_ipx);
ч qs.dwNumberOfCsAddrs = 2 ;
v
'
ret = WSASetService(&qs, RNRSERVICE.REGISTER, OL); '
n>
*"'
В листинге 10-1 показано, как настроить экземпляр службы, чтобы ее кли- ент мог найти адреса, необходимые для взаимодействия с ней. Прежде все- го, следует инициализировать структуру WSAQUERYSET. Кроме того, необхо- димо задать имя экземпляра службы, назовем его Widget Server. Другой важ- ный шаг — использовать тот же GUID, который применялся для регистрации нашего класса службы. Здесь мы работаем с классом Widget Service Class (оп- ределение дано в предыдущем разделе), GUID которого — SVCID_NET-
WARE(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. Затем вызовите функцию WSA-
SetService, передав ей структуру WSAQUERYSET, флаг RNRSERVICE_REGISTER и не передавая управляющих флагов. Управляющий флаг SERVICE_MULTIPLE не указывается, чтобы при удалении сведений о службе удалялась информация обо всех ее экземплярах (IPX- и UDP-адреса).
В приведенном примере не учитывается один случай: компьютеры с не- сколькими сетевыми адаптерами. Если вы создадите сервер на основе про- токола UDP, привязывающийся KINADDRANYHZL компьютерах с нескольки- ми сетевыми адаптерами, клиент сможет подключаться к этому серверу, ис- пользуя любой из доступных интерфейсов. В протоколе IP функции getsoc-
kname недостаточно: вам потребуется получить все локальные IP-адреса. Это можно осуществить несколькими способами, в зависимости от платформы,
на которой вы работаете. Один из распространенных методов, применимый на всех платформах — вызвать функцию gethostbyname, которая вернет спи- сок IP-адресов для имени. В Winsock можно также вызвать ioctl-команду
SIOJGETJNTERFACEJJST. Для Windows 2000 доступна ioctl-команда SIO_AD-
DRESSJJST_QUERY.
Кроме того, можно воспользоваться функциями IP helper (приложение В).
Простое разрешение имен TCP/IP и функция обсуждаются в главе 6, а коман- ды ioctl — в главе 9- На прилагаемом компакт-диске содержится пример
(файл Rnrcs.c), в котором реализована работа с компьютерами с нескольки- ми сетевыми адаптерами.
Запрос к службе
Теперь рассмотрим, как клиент может запросить пространство имен для дан- ной службы и получить информацию, необходимую для установления свя- зи. Разрешение имен несколько проще, чем регистрация служб. Для выпол-

3 0 0 ЧАСТЬ II Интерфейс прикладного программирования Winsock нения запросов используются три функции: WSALookupServiceBegin, WSA-
LookupServiceNext и WSALookupServiceEnd.
Первый этап — вызвать функцию WSALookupServiceBegin, которая иници- ирует запрос, задавая ограничения для его выполнения:
INT WSALookupServiceBegin (
LPWSAQUERYSET lpqsRestrictions,
DWORD dwControlFlags,
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 — получить только сведения о псевдонимах. Каж- дый псевдоним будет возвращаться при успешных вызовах функции WSA-
LookupServiceNext и для него будет задан флаг 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. Поскольку структура WSA-
QUERYSET может содержать данные больших двоичных объектов, часто тре- буется передать буфер, объем которого превосходит объем структуры. Если размер буфера не достаточен для возвращенных данных, произойдет сбой и функция вернет 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);

302 ЧАСТЬ II Интерфейс прикладного программирования Winsock qs.lpszServicelnstanceName = "Widget Server";
qs.lpServiceClassId = &guid;
%
qs.dwNameSpace = NS_ALL; /
qs.dwNumberOfProtocols = 2 ;
Л
qs.lpafpProtocols = afp;
ret = WSALookupServiceBegin(&qs, LUP_RETURN_ADDR | LUP_RETURN_NAME,
&hLookup);
if (ret == SOCKET_ERROR)
// Ошибка
Помните, что все операции поиска служб основаны на GUID класса служ- бы, на котором построена искомая служба. Переменной guid присваивается идентификатор класса службы сервера. Сначала вы инициализируете пере- менную qs со значением 0 и сохраняете в поле dwSize размер структуры. Сле- дующий шаг — указать имя искомой службы. Это может быть как точное имя,
так и звездочка (*); в последнем случае функция вернет все службы с данным
GUID класса службы. Далее с помощью константы NS_ALL указано, что поиск должен вестись во всех пространствах имен. Последний этап — настройка протоколов, по которым может соединяться клиент, в нашем случае это IPX
и UDP/IP. Для этого используется массив из двух структур AFPROTOCOLS.
Теперь можно начать опрос: вызовите функцию WSALookupServiceBegin.
Первый параметр — структура WSAQUERYSET, последующие — флаги, опре- деляющие, какие данные будут возвращены при обнаружении искомой служ- бы. Здесь вы указываете, что требуются сведения об адресах и имя службы;
для этого создается логическое условие «ИЛИ» из флагов LUP_RETURN_ADDR
и LUP_RETURN_NAME. Флаг LUPJRETURN_NAME необходим, только если в ка- честве имени службы вы указали звездочку (*), в противном случае имя служ- бы уже известно. Последний параметр — переменная HANDLE, идентифици- рующая данный конкретный запрос. Она инициализируется при успешном возвращении данных.
После успешного открытия запроса вызывайте функцию WSALookupSer-
viceNext, пока не будет возвращен код WSA_E_NO_MORE. При каждом успеш- ном вызове функции возвращаются сведения о службе, соответствующие заданным критериям:
+ 2000];
char
DWORD
WSAQUERYSET
SOCKADDR
int
buff[sizeof(WSAQUERYSET)
dwLength, dwErr;
*pqs = NULL;
*addr;
I;
pqs = (WSAQUERYSET *)buff;
dwLength = sizeof(WSAQUERYSET) + 2000;
while (1)
{
ret = WSALookupServiceNext(hLookup, 0, idwLength, pqs);
if (ret == SOCKET_ERR0R)
J

Г Л А В А 10 Регистрация и разрешение имен 303
I
if ((dwErr = WSAGetLastErrorO) == WSAEFAULT) ,
чЭ
,
printf("Buffer too small; required size is: Xd\n", dwLength);
jf1|
break;
}
else if ((dwErr == WSA_E_NO_MORE) || (dwErr = WSAENOMORE)) '*
break; "
else
{ «a printf("Failed with error: Xd\n", dwErr); ч break; IT
} .[1
}
for (i = 0; i < pqs->dwNumberOfCsAddrs; i++)
{
addr = (SOCKADDR *)pqs->lpcsaBuffer[i].RemoteAddr.lpSockaddr;
if (addr->sa_family == AF_INET)
{ "
SOCKADDR_IN *ipaddr = (SOCKADDR_IN *)addr;
printf("IP address:port = Xs:Xd\n", inet_ntoa(addr->sin_addr), f
addr->sin_port); '«
} ,A
else if (addr->sa_family == AF_IPX)
{ J
SOCKAODR.IPX «ipxaddr = (SOCKADDR.IPX «)addr; i;t')OM
printf("X02XX02XX02XX02X.X02XX02XX02XX02XX02XX02X:X04X",
v f
,
Г
^
Ш
£
(unsigned char)ipxaddr->sa_netnum[O],
(unsigned char)ipxaddr->sa_netnum[1], j й ЭООПбв
(unsigned char)ipxaddr->sa_netnum[2],
(unsigned char)ipxaddr->sa_netnum[3], 'i
(unsigned char)ipxaddr->sa_nodenum[0], Ь
(unsigned char)ipxaddr->sa_nodenum[1], \
(unsigned char)ipxaddr->sa_nodenum[2],
(unsigned char)ipxaddr->sa_nodenum[3], у
(unsigned char)ipxaddr->sa_nodenum[4], ,^
(unsigned char)ipxaddr->sa_nodenum[5], ,'
}
ntohs(ipxaddr->sa_socket));
M
WSALookupServiceEnd(hLookup);
Этот код вполне понятен, хотя и упрощен. При вызове функции WSA-
LookupServiceNext требуются только действительный дескриптор запроса,
непосредственно возвращаемый буфер и его длина. Указывать какие-либо управляющие флаги не нужно, поскольку единственный допустимый флаг
Данной функции — LUPJFLUSHPREVIOUS. Если размер переданного буфера не достаточен, и данный флаг задан, результаты вызова функции отбрасывают- ся. Тем не менее, в примере флаг LUP_FLUSHPREVIOUS не используется, и по- этому при недостаточном размере буфера генерируется ошибка WSAEFAULT.

3 0 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock
В этом случае параметру ipdwBufferLength присваивается значение, соответ- ствующее требуемому размеру буфера В примере используется буфер с фик- сированным размером, равным размеру структуры WSAQUERYSETплюс 2000
байт Поскольку вам необходимы только имена и адреса службы, такого раз- мера должно хватить Конечно, серьезные приложения должны уметь обра- батывать ошибку WSAEFAULT
После успешного вызова функции WSALookupServiceNext в буфер WSA-
QUERYSET помещается структура, содержащая результаты В запросе требо- валось получить имена и адреса, и поэтому наиболее интересные для нас поля структуры WSAQUERYSET — это ipszServicelnstanceName и ipcsaBuffer
Поле IpszServicelnstanceName содержит имя службы, а поле IpcsaBuffer —
представляет массив структур CSADDRJNFO с ее адресами Параметр dw-
NumberOfCsAddrs указывает, сколько адресов возвращено В коде примера мы просто выводим все адреса Убедитесь, что будут выводиться только 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,
ihQuery);
if (ret == SOCKET.ERROR)
II Ошибка
LUP_RETURN_NAME | LUP_RETURN_BLOB,
Настройка запроса осуществляется почти так же, как и в предыдущем примере Наиболее значительное отличие — используется предопределен- ный 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;
0, idwLength, pqs);
pqs = (WSAQUERYSET »)buff;
pqs->dwSize = sizeof(WSAQUERYSET);
ret = WSALookupServiceNext(hQuery,
if (ret == SOCKET_ERROR)
s
// Ошибка М
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 (за исключением независимости от протокола)

1   ...   23   24   25   26   27   28   29   30   ...   50


написать администратору сайта