Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
ЧАСТЬ 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 (за исключением независимости от протокола) |