10名字注册和解析 - 范文中心

10名字注册和解析

10/21

第10章名字注册和解析

本章,我们将全面论述Winsock 2中引入的名字注册和解析模型,它们都是与协议无关的。由于现在已经废弃了Winsock 1中引入的名字注册和解析方法,所以我们将不再对它进行讨论。首先,介绍名字注册和解析的重要性及其用法的背景知识,然后步步深入现有的各种不同的名字注册模型,最后说明Winsock 2中用于解析名字的函数。另外,还谈谈如何注册自己的服务,以供他人查询。

10.1 背景知识

名字注册是一个过程,把一个用户好用的名和具体协议地址关联在一起。主机名及其I P地址便是例证。人们发现要记住一个工作站的地址(比如1 57.54.185.186)非常麻烦。所以他们宁愿把自己的机器命名为一个更容易记的地址,比如“a jones1”。在I P中,一项名为“域名命名系统”(D NS)会把I P地址映射成相应的名字。我们将在下一节详细地讨论名字空间。

人们不仅希望能够注册和解析主机名,还希望能够映射自己的Wi nsock服务器地址,以便于在客户机打算和服务器连接时,可获得服务器的地址。比方说,你有一个服务器,它运行的机器地址为1 57.64.185.186,端口为5 000。如果它只在那台机器上运行,就可以把这台服务器的地址硬编码到客户机应用程序中。但如果你需要一个更为动态的方法,即在若干台机器运行的服务器时,就要考虑采用一个容错的分布式应用程序。如果一个服务器崩溃或过于繁忙,另一个应用就开始接替它,为客户机提供服务。这种情况下,要找到服务器事实上在哪个地址运行,是非常令人头疼的。最理想的情况是用若干个地址来注册自己的服务器—命名为“容错分布式服务器”。另外,大家也许还希望动态更新一个已注册的服务及其地址。这便是名字注册和解析的核心,而本章将着重讨论Wi nsock提供的一些适用于分布式服务器注册和名字解析的设计。

10.2 名字空间模型

深入Wi nsock函数之前,需要为大家讲讲大多数协议附带的各种名字空间模型。名字空间提供了一种能力,用一个友好名把具体的协议及其定址属性关联在一起。最常见的名字空间是针对I P的D NS和N ovell针对I PX开发的N etWa re目录服务(N DS)。这些名字空间在组成和实施各不相同,但它们的有些属性特别有助于我们理解如何通过Wi nsock注册和解析名字。

名字空间有三种类型:动态的、静态的和固定的。动态名字空间允许人们即时注册服务。另外,还意味着客户机可以在运行时对这个服务进行查看。一般说来,动态名字空间依赖于周期性地广播服务信息,表示该服务可继续使用。动态命名空间有:“服务声明协议”(S AP)(用于N etWa re环境)和A ppleTa lk的“名字绑定协议”(N BP)名字空间。

这三类名字空间中,静态名字空间的灵活性最小。在静态名字空间内注册一个服务,需要在规定时间内进行手工注册。这意味着无法通过Wi nsock用静态名字空间注册一个服务名,因为它只有一种解析法。D NS是一个静态名字空间。举个例子来说,你可以用D NS手工把I P

地址和主机名输入一个文件,D NS服务利用这个文件来处理解析请求。

固定名字空间和动态名字空间一样,允许即时注册服务。但和动态名字空间不同的是,固定名字空间把注册信息保留在固定的地方上,比如说磁盘上的一个文件中。只有在服务请求被删除时,固定名字空间才会把这项服务条目删除。它的优点在于灵活,不会连续不断地广播任何一种类型的有用信息。缺点就是如果一个服务行为不佳(或者说编得糟糕),该服务便在不通知名字空间提供者删除其服务条目的情况下,不知所终。从而导致客户机错误地认为该服务仍然可用。N DS是一个固定名字空间。

名字空间的列举

现在,大家已经知道名字空间的各种属性,但一台机器上可用哪些名字空间呢?我们来看看。多数预先定义的名字空间的声明都在N spapi.h头文件中。每个名字空间都有一个分配所得的整数值。表1 0-1列出了一些比较常见的名字空间,它们已获支持,并可用于Wi n32平台。返回的名字空间由工作站上安装的协议决定。比方说,如果一个工作站上没有安装I PX/SPX,就不会返回N S_SAP名字空间。

表10-1 已获支持的名字空间

名字空间

N S_SAP

N S_NDS

N S_DNS

N D_NTDS值12113 2说明S AP名字空间;用于I PX网络N DS名字空间;也用于I PX网络D NS名字空间;多见于T CP/IP网络和互联网Windows NT域名空间;运行于Windows 2000的与协议无关的命名空间在一台机器上安装I PX/SPX时,只支持S AP名字空间查询。如果想注册自己的服务,还需要安装“S AP代理服务”。某些情况下,需要“N etWa re的客户机服务”(Client Service ofN etWa re)把本地的I PX接口地址准确无误地显示出来。如果没有这个服务,本地地址全部以0的形式出现。另外还必须增加一个N DS客户机,以便利用N DS名字空间。所有这些协议和服务都可通过“控制面板”得以增添。

Winsock 2提供了一种方法,即如何通过程序获得一份列表,表上列出系统上所有可用的

名字空间。这是通过调用

W S

AEnumNameSpaceProviders函数来完成的。该函数的定义如下:

第一个参数作为l pnspBuff er提交的缓冲区的长度,它是由多个W SANAMESPA CE_INFO结构组成的一个大型数组。如果该函数是通过一个不充裕的缓冲区调用的,就会失败,把l pdwBuff erLength设为所需要的最小长度,则会导致W SAGetLastError返回W SAEFA ULT错误。这个函数将返回的W S A N A M E S PA C E _ I N F O结构数的多少返回,或在错误之后出现S OCKET_ERROR。W SANAMESPA CE_INFO结构描述指定机器上安装的一个独立名字空间。它的格式如下:

事实上,这个结构有两种格式

—U n

i

code和A NSI。Winsock 2头文件针对具体需要,灵活地定义了为W SANAMESPA CE_INFO的相应结构。实际应用中,所有结构和Winsock 2注册和名字解析函数都有两个版本:U nicode和A NSI。该结构的第一个成员N SProviderId,是一个通用的唯一识别符(G UID),它对这个特殊的名字空间进行描述。d WNameSpace字段是这个名字空间的整数常量,比如N S_DNS或N S_NAP。f Active成员是一个布尔值,若是真,就表明该名字空间可用,并准备发出请求;反之则表示提供者未激活,不能发出特别引用提供者的请求。d wVe rsion字段只对这个提供者的版本进行识别。最后,l pszIdentifier是该提供者的一个描述性的字串识别符。

10.3 服务的注册

下一步便是如何设置自己的服务,并使其有用,让网络上的其他机器都知道它。这就是利用名字空间提供者注册一个服务,这样一来,打算与之通信的客户机就可以对它进行声明或请求。注册一个服务实际上只有两步。第一步是安装一个描述服务特征的service class(服务类)。

弄清楚服务类和事实上的服务本身之间的区别非常重要的。服务类和服务对两个不相同的概念。比方说,服务类描述哪些名字空间可用来注册自己的服务,该服务的特征有哪些(它是面向连接的,还是无连接的)。至于客户机如何才能建立一个连接,该服务类是无法将其描述出来的。服务类一旦注册,便可注册事实上的服务了,事实上的服务引用的是它所属的那个准确的服务类。一旦发生这种情况,客户机就可执行请求,在服务实例运行的地方进行查找,从而与别的机器通信。

10.3.1 安装服务类

在注册一个服务实例时,需要定义你的服务属于哪个服务类。用哪个名字空间注册这个服务类中的服务,由服务类定义决定。注册服务类的Wi nsock函数是W SAInstallServiceClass,它的定义如下:

唯一的参数是l pServiceClassInfo,它指向一个W SASERV ICECLASSINFO结构,这个结构定义了这个服务类的属性。它的格式如下:

第一个字段是G UID,它对这个特殊的服务类进行唯一性识别。要在这里使用G UID,有两种方法可生成它。一是利用公用程序U uidgen.exe,并为这个服务器类建立一个G UID。采用这种方法的问题就是:如果需要再次引用这个G UID,就必须把它的值硬编码到头文件中的

某个地方。鉴于这一不便,第二种方法应运而生。在头文件S vcguid.h内,有几个宏可生成基于一个简单属性的G UID。比方说,如果你安装S AP服务类(将声明你的I PX应用程序),就可用S VCID_NETWA RE宏。唯一的参数便是你为自己的应用程序类分配的SAP ID编号。S API D编号是在N etWa re中预先定义好的,比如说,0 x4表示文件服务器,而0 x7则表示打印服务器。若采用后一种方法,只需一个易于记忆的SAP ID,利用它生成指定服务类的G UID。另外,有几个宏把端口号当作一个参数来接收,并返回相应服务的G U I D。现在来看看头文件S vcguide.h,其中包含一些针对逆向操作的宏,这些宏都是有用的—从G UID中取出服务端口号。G UID是利用宏通过诸如端口号或SAP ID之类的属性生成的,表1 0-2列出了其中最常用的几个宏。头文件中还包括了一些众所周知的端口号(为F TP和Te lnet之类的服务预留的)的常量。

表10-2 常用的服务I D宏

S VCID_TCP(P ort)

S VCID_DNS(R ecordTy pe)

S VCID_UDP(P ort)

S VCID_NETWA RE(S apId)说明通过T CP端口号生成一个G UID通过一个D NS记录类型生成一个G UID通过U DP端口号生成一个G UID通过SAP ID编口号生成一个G UID

W SASERV ICECLASSINFO结构的第二个字段是l pszServiceClassName,它仅仅是这个特定服务类的一个字串名。最后两个字段是描述性的:d wCount字段引用一个数值,表示投递给l pClassInfos字段的W SANSCLASSINFO结构有多少,这些结构对事实上服务所用的名字空间

和协议特征进行定义。事实上的服务是指在这个服务类下注册的服务。它的格式如下:l pszName字段定义服务类处理属性。表1 0-3列出了可用的各种属性。其中每个属性都有一个R EG_DWORD类的值。

表10-3 服务类型

字串值

“S apId”

“C onnectionOriented”

“Tc pPort”

“U dpPort”定义的常量S ERV ICE_TYPE_VA LUE_SAPIDS ERV ICE_TYPE_VA LUE_CONNS ERV ICE_TYPE_VA LUE_TCPPORTS ERV ICE_TYPE_VA LUE_UDPPORT名字空间N S_SAP任何一种N S_DNSN S_NTDSN S_DNS

N S_NTDS说明SAP ID指明服务是面向连接的,还是无连接的T CP端口U DP端口

d wNameSpace是这个属性所用的名字空间。表1 0-3列出了各种服务类型通常使用的名字空间。后三个字段d wVa lueTy pe、d wVa lueSize和l pVa lue,都对真正与服务类型关联的那个值进行了描述。d wVa lueTy pe字段标识与这个条目关联的数据的类型,所以称之为注册类型值。比如,这个值如果是D WORD,其类型就应该是R EG_DWORD。下一个字段d wVa lueSize,仅

仅是被当作l pVa lue投递的数据之长度,l pVa lue是一个数据指针。

至于如何安装一个名为“Widget Server Class”的服务类,下面的代码示例对此进行了解释:

首先要注意的是,这段代码采用了一个G UID,上面的服务类将在这个G UID下注册。你设计的服务都属于Widget Server Class这个类,而这个服务类描述的则是常见的属性,这些属性又属于一个服务实例。这个示例中,我们选用N etWareSAP ID of 200来注册服务类。这样做

只是为了方便。其实,应该用一个任意的G UID或基于U DP端口号的G UID。另外,当客户机正在端口5 150上监听时,该服务可以使用U DP协议。

下一个注意点是W SASERV ICECLASSINFO结构的d wCount字段被设为4。这个示例中,将用S AP名字空间(N S_SAP)和Windows NT 域名空间(N S_NTDS)来注册服务类。其余需要注意的是:即使是在只用两个名字空间注册这个服务类,但这里却用了四个W SANSCLASSINFO结构。因为我们为每个名字空间都定义了两个属性,而每个属性都需要一个独立的

W SANSCLASSINFO结构。为每个名字空间定义了服务是否面向连接。这个示例中,名字空间是无连接的,因为我们把S ERV ICE_TYPE_VA LUE_CONN的值设成了一个布尔值0。我们还针对Windows NT域名空间,通过使用服务类型SERVICE_TYPE_VALUE_UDPPORT,设置了U DP端口号,这个服务一般在这个端口下运行。针对S AP名字空间,我们用服务类型SERVICE_T YPE_VA LUE_SAPID设置了自己的服务SAP ID。

针对每一个W SANSCLASSINFO条目,在设置服务类型和值长度时,还必须设置名字空间识别符,服务类型将在应用于这一名字空间。表1 0-3中包括服务类型所需的类型,这一示例中结果都是D WORD。最后一步是简单调用W SAInstallServiceClass,并把它当作一个参数投给W SASERV ICECLASSINFO结构。如果W SAInstallServiceClass调用成功,就返回0;反之,则返回S O C K E T _ E R R O R。如果W S A S E RV I C E C L A S S I N F O无效或排列有误,W SAGetLastError就会返回W SAEINCAL。如果这个服务类已经存在,W SAGetLastError则返

RemoveServiceClass,删掉一个服务器类。

回W SAEALREADY。这种情况下,就可以调用W SA

它的声明如下:

这个函数的唯一参数是指向G U

I

D的指针。这个G UID就是定义具体服务类的G UID。10.3.2 服务的注册

服务类一旦安装(说明你的服务有哪些常见属性),就可以注册自己的服务实例,这样一来,远程机器上的其他客户机就可使用这一服务了。注册一个服务实例的

W SASetService。Wi n s o c k函数是

第一个参数l pqsRegInfo,是指向W SAQUERY SET结构的指针,该指针定义特定的服务。我们简要说明这个结构。e ssOperation参数指定即将发生的行为,比如注册或取消注册。表1 0-4对这三个有效标志进行了说明。

第三个参数d wControlFlags,不是0就是S ERV ICE_MULT IPLE这个标志。如果要在具体的服务实例下注册若干个地址,用这个标志即可。比如,你有一个服务,想在五台机器上运行它。投入W SAService的W SAQUERY结构将引用五个C SADDR_INFO结构,一一对该服务的实例进行描述。这时便需要设置S ERV ICE_MULT IPLE标志。稍后,可取消注册一个单一的服务实例,这是通过利用R NRSERV ICE_DELETE标志来完成的。表1 0-5给出了操作和控制标志的可能组合,并根据服务的存在与否,对命令结果进行了描述。

操作标志

R NRSERV ICE_REGISTER

R NRSERV ICE_DEREGISTER表10-4 设置服务标志含义

R NRSERV ICE_DELETE注册一个服务名。对动态名字提供者而言,这个标志的意思是开始动态地声明这个服务。对固定的名字提供者来说,无意义从注册表中删除整个服务。对动态名字提供者而言,意味着中止声明服务。对固定的名字提供者来说,意味着从数据库中删除这个服务。对静态名字提供者来说,无意义只将具体的服务实例从注册表中删除。一个注册服务可能包含若干个

实例(这是在注册之后,紧接着利用S ERV ICE_MULT IPLE标志来完成

的)。再次提醒大家注意,这个标志只能应用于动态和固定名字提供者

表10-5 WSASetService标志的组合

R NRSERV ICE_REGISTER

标志含义

若服务已存在若服务不存在

在具体的地址上增加一条新的服务条目

在具体的地址上增加一条新的服务条目n oneS ERV ICE_MULT IPLE覆盖现成的服务实例通过增加新地址的方式,更新服务实例R NRSERV ICE_DEREGISTER

标志含义

若服务已存在若服务不存在

这是一个错误,将返回W SASERV ICE_

N OT_FOUND

这是一个错误,并返回W SASERV ICE_

N OT_FOUNDn oneS ERV ICE_MULT IPLE删除所有的服务实例,但不删除服务(一般)说来,是保留W SAQUERY, 但C SADDR_INFO结构数是0通过删除具体地址的方式,对这个服务进行更新。即便无地址,这个服务仍

然会存在

R NRSERV ICE_DELETE

标志含义

若服务已存在若服务不存在

这是一个错误,并返回W SASERV ICE_

N OT_FOUND

这是一个错误,并返回WSASERVICE_NOT_

FOUNDn oneS ERV ICE_MULT IPLE把这个服务彻底从名字空间删除通过删除具体地址的方式,对这个服务进行更新。如果不保留地址,服

务就会彻底从名字空间删除

至此,大家已知道了W SASetService的用法。接下来看看W SAQUERY SET结构。这个结

构需要填充并投入函数。它的格式如下:

d wSi

ze字段应该设为W SAQUERY SET结构的长度。l pszServiceInstanceName字段中包含一个名字服务实例的字串识别符。l pServiceClassId字段是服务实例所属的那个服务类的G UID。l p Ve r s i o n字段是可选的。在客户机请求一个服务时,可利用它获得有用的版本信息。l pszComment字段也是可选的。可在这里指定一个任何类型的注释字串。d wNameSpace字段指定准备用来注册服务的名字空间。如果你只用一个名字空间,就只能用那个值;反之,就用N S_ALL。另外,可以引用一个定制的名字空间提供者(关于怎样编写自己的名字空间,将在第1 4章论述)。在定制的命名空间提供者这种情况下,

(NDS )中的查询起点。

d wNumberOfProtocols和l pafpProtocols字段都是可选的参数,用于限制搜索,只返回所提供协议。d wNumberOfProtocols字段对A FPROTO COLS结构的数目进行引用,这些结构包含在l pafpProtocols数组中。它的格式如下:d w N a m e S p a c e字段设为0,而l pNSProviderId指定代表定制名字空间提供者的G UID。l pszContext字段指定分层式名字空间

第一个参数i AddressFamily,是A F_INET和A F_IPX之类的地址家族常量。第二个参数i Protocol是源于指定地址家族的协议,比如I PPROTO _TCP和N SPROTO _IPX。

W SAQUERY SET结构中的下一个字段l pszQueryString是可选的,只供支持丰富结构化查询语言(S QL)的名字空间所用,比如说W hois++。这个参数用来指定字串。

注册一个服务时,接下来的这两个参数是最重要的。d wNumberOfCsAddrs字段只提供了投到l pcsaBuff er中的C SADDR_INFO结构的数目。C SADDR_INFO结构定义地址家族和服务事实上定位的地址。如果出现多个结构,就可以用多个服务实例。该结构的定义如下:

这一结构中还还包括了S OCKET_ADDRESS的定义。在注册一个服务时,可指定本地和远程地址。本地地址字段(L ocalAddr)用于指定这个服务实例应该绑定的地址,而远程地址字段(R emoteAddr)则用于指定客户机在c onnect或s endto调用中,应该使用的那个地址。其他两个字段(i S o c k e t Ty p e和i P r o t o c o l)则是为具体的地址指定套接字类型(比方说

S OCK_STREAM和S OCK_DGRAM)和协议家族(比方说A F_INET和A F_IPX)。

W SAQUERY SET结构的最后两个字段是d wOutputFlags和l pBlob。一般说来,服务注册不需要这两个字段;不过在查询服务实例时,它们是非常有用的(将在下一节讨论)。只有名字空间提供者才可以返回一个B LOB结构。也就是说,在注册一个服务时,你不能增加自己的

B LOB结构,令其在客户机查询中返回。

表1 0-6列出了W SAQUERY SET结构的字段,并根据执行查询还是注册,判断哪些字段是要求的,哪些又是可选的。

表10-6 WSAQUERY SE T字段

字段查询注册

d wSize

l pszServiceInstanceName

l pServiceClassId

l pVe rsion

l pszComment

d wNameSpace

l pNSProviderId

l pszContext

d wNumberOfProtocols

l pafpProtocols

l pszQueryString

d wNumberOfCsAddrs

l pcsaBuff er

d wOutputFlags

l pBlob要求要求字串或“*”要求可选忽略二选一必须指定的字段可选0或0以上可选可选忽略忽略忽略忽略,可通过查询返回要求要求要求可选可选二选一必须指定字段可选0或0以上可选忽略要求要求可选忽略

10.3.3 服务注册示例

这一小节中,将向大家展示如何在S A P和N T D S这两个名字空间下注册自己的服务。Windows NT域名空间相当有用,这就是我们把它包含到示例中的原因。尽管如此,必须了解它的下面这些特性。首先,Windows NT域名空间需要Windows 2000,因为它是以“活动目录”(Active Directory)为基础的。另外,对你打算在上面注册和(/或)查找服务的Windows 2000工作站而言,还意味着必须有一个账号,可以在这个域内访问“活动目录”。另一个需要注意的特性是Windows NT域名空间能够注册源于任何一个协议家族的套接字地址。这意味着你的I P和I PX服务均可以注册在同一个名字空间内。程序清单1 0-1解释了注册一个服务实例的所需要的基本步骤。为了简单起见,代码中没有执行错误检查。

程序清单10-1 WSASetService示例

10.4 服务的查询

现在,大家已经知道如何在名字空间内注册服务了,下面来看看客户机是如何向名字空间查询具体服务,进而获得通信服务的有关信息的。尽管名字解析采用的查询函数有三个之多:W SALookupServiceBegin、W SALookupServiceNext和W SALookupServiceEnd,但和服务注册比起来,仍然要简单些。执行查询的第一步是调用W SALookupServiceBegin,这个函数通过设置查询限制来开始查询。它的原型如下:

第一个参数是一个

W S

AQUERY结构,它把限制加到查询上,比如说限定查询哪些名字空间。第二个参数d wControlFlags,决定查询的深度。表1 0-7中包含了各种可能用到的标志及其含义。这些标志都会影响查询的行为和查询返回的数据。最后一个参数是H ANDLE类型的,在函数返回之后利用。若成功,返回的值就是0;否则,就返回S OCKET_ERROR。如果一个或一个以上的参数无效,W SAGetLastError就会返回W SAEINVA I。如果在名字空间内找到该服务名,但没有可与所给限制匹配的数据,错误就是W SANO_DATA。若指定服务不存在,则返回W SASERV ICE_NOT_FOUND错误。

表10-7 控制标志

标志含义

L UP_DEEP

L UP_CONTA INERS

L UP_NOCONTA INERS

L UP_FLUSHCACHE

L UP_FLUSHPREVIOUS

L UP_NEAREST

L UP_RES_SERV ICE

L UP_RETURN_ADDR

L UP_RETURN_ALIASES

L UP_RETURN_ALL

L UP_RETURN_BLOB

L UP_RETURN_COMMENT

L UP_RETURN_NAME

L UP_RETURN_TYPE

L UP_RETURN_VERSION在分层式名字空间中,请求深度与第一级相对应只获得容器对象。该标志只适合分层式名字空间不返回任何容器。该标志只适合分层式名字空间忽略隐藏信息,直接查询名字空间。注意并非所有的名字提供者都隐藏查询令名字提供者丢弃前一次返回的信息集。这个标志一般在W SALookupS erviceNext返回W SA_NOT_ENOUGH_MEMORY之后才用。如果所提供的缓冲区不够,信息就会被丢弃,并检索下一个信息集按距离顺序检索结果。注意,这由计算距离公制的名字提供者来决定,因为注册服务时没有为该信息作准备。不要求名字提供者支持这一概念指定本地地址在C SADDR_INFO结构中返回获得l pcsaBuff er形式的地址只获得别名信息。每个别名都会在成功调用W SALookupService中返回获得所有有用的信息获得l pBlob形式的私有数据获得l pszComment形式的注释获得l pszServiceInstanceName形式的名字获得l pServiceClassId形式的类型获得l pVe rsion形式的版本号

在调用W SALookupServiceBegin时,返回的查询句柄是你投给W SALookupServiceNext的,它会返回你需要的数据。该函数的定义如下:

句柄h

Lookup通过W SALookupServiceBegin返回。对d wControlFlags参数而言,除了只支持L U P _ F L U S H P R E V I O U S外,其含义和W S A L o o k u p S e r v i c e B e g i n是相同的。参数l pdwBuff erLength被当作l pqsResults投递,是一个缓冲区长度。由于W SAQUERY SET结构中应该包含二进制的大型对象(B LOB)数据。它通常要求你投递一个大于结构本身的缓冲区。对即将返回的数据而言,如果提供的缓冲区长度不够,函数调用就会失败,并出现W SA_NOT_ENOUGH_MEMORY错误。

一旦利用W SALookupServiceBegin开始查询,就要在生成W SA_E_NO_MORE(1011 0)之前,调用W SALookupServiceNext。特别需要注意的是:早期的Wi nsock实施中,“无过多数据”的错误代码是W SAENOMORE(10102),因此,对一个健壮的代码而言,应该还要对这两个错误代码都进行检查。一旦所有数据都返回,或已经完成查询,就调用查询过程中所用的、带有H ANDLE变量的W SALookupServiceEnd。该函数的定义如下:

10.4.1 怎样对服务进行查询

如何对前一小节中注册的服务进行查询呢?第一件事是设置定义查询所用的W SAQUERY结构。我们来看看下面的代码:

记住,所有的服务查询都在服务类G UID的基础上进行,你查询的服务就是在这个服务类基础上注册的。变量g uid设为服务器的服务类I D。先把q s初始化成0,在把d wSize字段设为结构的长度。下一步是给出准备查询的服务名。服务名可以是服务的真名,也可以指定一个通配(*),通配将返回具体的服务类G UID的所有服务。接下来,要求查询利用N S_ALL常量搜索所有的名字空间。最后,设置协议(I PX和U DP/IP),以便我们的客户机能与之建立连接。这是利用一个由两个A FPROTO COLS结构组成的数组来完成的。

现在,准备开始查询,首先必须调用W SALookupServiceBegin。它的第一个参数是我们的W SAQUERY SET结构,而后面的几个参数则是标志,这些标志定义找到相符的服务时应该返回呢数据。这里,你应该对L UP_RETURN_ADDR和L UP_RETURN_NAME这两个标志执

行按位和运算,藉此说明自己需要定址和服务名;只有在用通配符(*)来指代服务名时,才需要L UP_RETURN_NAME标志;否则就是已经知道服务名了。最后一个参数是一个识别这个特殊查询的H ANDLE变量。成功返回之后,它就会被初始化。

一旦成功打开查询,就可在W SA_E_NO_MORE返回之前,调用W SALookupServiceNext。每个成功调用都会返回符合查询标准的那个服务的相关信息。这一过程的代码如下:

尽管这段示例代码有点简单,但非常明晰。调用W S

ALookupServiceNext只需要一个有效的查询句柄、返回缓冲区的长度和返回缓冲区本身。不需要指定任何控制标志,因为唯一可用于该函数标志只有一个:L UP_FLUSHPREVIOUS。如果我们提供的缓冲区太小,若设置这个标志,该函数返回的结果就会被丢弃。然而,在我们的示例中,我们没有采用

L UP_FLUSHPREVIOUS,如果我们提供的缓冲区太小,就会生成W SAEFA ULT错误。发生这种情况时,就要把l pdwBuff erLength设为所需要的长度。我们的例子中采用了一个固定长度的缓冲区,相当于W SAQUERY SET结构再加2 000个字节的长度。由于你只要求了所有的服务名和地址,所以这个长度绰绰有余。当然,如果要面向广大用户,你的应用程序中还应该能对W SAEFA ULT错误进行处理。

一旦成功调用W SALookupServiceNext,缓冲区内就会填入一个W SAQUERY SET结构,这个结构中包含了返回的结果。我们的查询中,需要服务名和地址;W SAQUERY SET结构中,最重要的字段是l p s z S e r v i c e I n s t a n c e N a m e和l p c s a B u ff e r。前者包含服务名,后者则是一个

C SADDR_INFO结构数组,其中包含服务的定址信息。d wNumberOfCsAddrs参数会准确地告诉我们已返回多少地址。上面的示例代码中,我们只是打印了地址。只检查并打印了I PX和I P地址,因为在你打开查询时,只要求了这两个地址家族。

查询中,如果用通配符(*)来代表服务名,那么每次调用W SALookupServiceNext,都会返回一个特定的服务实例,它运行于网络上某个地方—当然,前提是实际上已注册了多个服务实例,并且正处于运行状态。一旦服务的所有实例都已返回,就会产生W S A _ E _ N O _ M O R E错误,这样你就会中断循环。最后一件事是在查询句柄上调用W SALookupServiceEnd。这样便可释放为查询分配的所有资源。

10.4.2 查询DNS

前面,我们曾提过D NS名字空间是静态的,这意味着你不能动态地注册自己的服务;但仍可用Wi nsock名字解析函数来执行D NS查询。实际上,执行D NS查询比执行普通的已注册服务查询要复杂得多,因为D NS名字空间提供者是以B LOB的形式返回查询信息。为什么会这样呢?还记得第6章中对g ethostname的讨论吗?“名字检索返回的H OSTNAME结构中不仅包含了I P地址,还包含了别名”。W SAQUERY SET结构偏偏就例外。

关于B LOB数据,需要注意的一点是:它的格式尚未很好地编入帮助文档,这样就使得直接查询D NS显得有些困难。我们首先来看看如何打开查询。本书附带光盘上的D nsquery. c文件包含了直接查询D NS所用的完整的示例代码;我们逐段地对它们进行分析。下面的代码解释了如何初始化D NS查询:

D NS查询的设置非常类似于我们的前一个例子。最显著的变化是我们这里采用的是G UIDSVCD_INET_HOSTADDRBYNAME。这是一个识别主机名查询的GUID。lpszServiceInstanceName是我们准备解析的主机名。由于正在通过D NS解析主机名,所以我们只需为d wNameSpace指定N S_DNS。最后,把l pafProtocols设成一个数组,该数组由两个A FPROTO COLS结构组成,它把T CP/IP和U DP/IP协议定义成我们的查询感兴趣的协议。

查询一旦建立,就可调用W SALookupServiceNext,返回数据:

因为D NS名字空间提供者以B LOB的形式返回主机信息,所以需要你提供一个足够大的缓冲区。这就是为什么要用一个非常大的缓冲区的原因,其长度相当于一个W SAQUERY SET加上一个H OSTENT,再加上2 048个字节。再次提醒大家注意,如果长度还不够,函数调用就会失败,出现W SAEFA ULT错误。在D NS查询中,所有的主机信息都返回在H OSTENT结构内,即使主机名与多个I P地址关联在一起。这就是不必多次调用W SALookupServiceNext的原因。

这个地方需要特别注意—对查询返回的B LOB结构进行解码。通过第6章的学习,大家已知道H OSTENT结构的定义是这样的:

H OSTENT结构以B LOB数据的形式返回时,结构内的指针对应的是实际是内存中的偏移位置,实际数据是自那个位置起开始存放的。这个偏移位置是自B LOB数据的起始处开始算起的。这样一来,我们必须对指针作一番修正,使其指向绝对的内存位置,否则不能实际地访

问到数据。在图1 0-1中,我们向大家展示了H OSTENT结构以及返回的内存布局。D NS查询是主机名“r iven”上执行的,该主机有一个对应的I P地址,但没有“别名”。结构中的每个字段都有一个偏移值。为纠正这个问题,使字段能引用到正确位置,我们需要将偏移值加到H OSTENT结构的头地址上。这一计算需要针对h _name,h _aliases和h _addr_list字段进行。此外,h _aliases和h _addr_list字段指定的是一个指针数组。一旦取得了指向指针数组的正确指针,引用位置中的每个3 2位字段都会由偏移值构成。看看图1 0-1展示的h _addr_list字段,便会发现初始偏移量是1 6字节,它指定的是H OSTENT结构末尾之后的字节数。这是指向4字节I P地址的一个指针数组。然而,数组中的每一个指针偏移距离是2 8字节。要想令其指向正确的位置,需要先取得H OSTENT结构的地址,再在它的基础上加4字节位置。2 8字节,指向一个实际的

在那个位置,含有数据0 x9D36B9BA,亦即I P地址1 57.54.185.186。随后,便可自那个位置开始,在第2 8个字节的偏移量之后,取得总长为4个字节的数据(全部为0)它标志着指针数组的结束。假如该主机名同时对应着几个I P地址,那么必然存在着其他的偏移量。如法炮制,便能修正指针,得到正确的数据。针对h _aliases指针以及它所引用的那个指针数组,也要采取同样的做法,将它们修正为正确的值。就本例来说,我们的主机并未设置别名。数组的第一个条目是0,表明不必再对那个字段采取任何多余的操作。最后一个字段是h _name字段,非常容易纠正—只需将偏移量加到H OSTENT结构地址上面就可以了,它指向的是一个空中止

字串的起始处。地址列表数组别名列表数组

0x9D36-

B9BAh_name=0x20h_aliasesAF_h_addr_list0x4=0x18INE_T=0x100x1c0x00x0riven/0

HOSTENT

图10-1 HOSTENT BLOB数据

要把这些偏移修正为真正的地址,尽管涉及到大量的指针运算,但所需代码并不复杂。

要修正

h _name字段,进行简单的偏移调整即可,具体如下:

要修正指针数组(比如h_aliases h_addr_list字段中的数组),需要的代码则要多一些,但也只需要在这个数组中完整地走一遍,依次修正每一个引用,直到碰到一个空条目为止。代码如下:

这段代码只是逐步浏览了各个数组条目,把

H OSTENT结构的起始地址加到具体的偏移量上,这个偏移量即后来的条目的值。当然,一旦碰到了一个其值为0的数组条目,你自然就会停下来。需要对h _addr_list字段如法炮制。一旦偏移得到了修正,就可以正常使用H OSTENT结构了。

10.5 小结

R NR函数看起来过于复杂,但是,它们为编写客户机/服务器应用程序提供了非常大的灵活性。名字注册的真正局限在于名字空间。随着T CP/IP的风行,D NS曾一度独领风骚,但

D NS又欠灵活。直到有了Windows 2000和Windows NT域名空间,我们才有了一个稳定的、与协议无关的名字解析方法,它为编写强健的应用程序提供了充分的灵活性。另外,其他名字空间(比如S AP)也可用于以I PX/SPX为基础的应用程序,它们提供了许多类似于N TDS的能力(与协议无关这一点除外)。


相关内容

  • 20**年执业药师[药事管理与法规]押题
    <药事管理与法规>押题 一.A型题(最佳选择题)共40题,每题1分.每题的备选项中只有一个最佳答案. 1.执业药师应当在其注册的执业单位执业,下列需要注册执业药师是( ). A.药品监管部门工作人员刘某 B.药品科研单位研究员关 ...
  • 第四章 基金管理人-基金管理公司市场准入
    2015年证券从业资格考试内部资料 2015证券投资基金 第四章 基金管理人 知识点:基金管理公司市场准入 ● 定义: 我国相关法规规定,基金管理公司的注册资本应不低于1亿元人民币且股东要符合相应条件 ● 详细描述: 1.主要股东条件 (1 ...
  • 广州工商行政管理局:公司注册流程解析
    广州工商行政管理局:公司注册流程解析 广州工商行政管理局是广东省工商局下的分局.注册广州公司,根据<广东省人民政府办公厅转发国务院办公厅关于加快推进"三证合一"登记制度改革意见的通知>(粤府办[2015]51 ...
  • Dubbo路由模块设计说明书
    Dubbo 路由模块 设计说明书 修改记录 1 目录 1. 1.1. 1.2. 引言 ............................................................................. ...
  • 亚马逊美国站测试题目B
    [1.0]亚马逊全球开店美国站点上线测试题 成绩单 您的得分:68 分 答对题数:17 题 问卷满分:100 分 测试题数:25 题 答案解析 现在,请正确填写您美国站的"注册邮箱": [填空题] * 您的回答为: 1. ...
  • 20XX年报检员代理报检单位考试试题及答案解析
    报检员代理报检单位考试试题及答案解析 一.单选题(本大题8小题.每题0.5分,共4.0分.请从以下每一道考题下面备选答案中选择一个最佳答案,并在答题卡上将相应题号的相应字母所属的方框涂黑.) 第1题 代理报检单位注册信息发生变更,企业向检验 ...
  • 证券市场基本法律法规20**年真题
    www.lekaowang.cn 一.单项选择题 1.公司发起人.股东在公司成立后,抽逃其出资的,由公司登记机关责令改正,处以所抽逃出资金额()的罚款. A.百分之五以上百分之二十以下 B.百分之二以上百分之十以下 C.百分之五以上百分之十 ...
  • 20**年注册会计师-经济法真题及答案
    2010注会考试专业阶段<经济法>真题及参考答案 一.单项选择题(本题型共20小题,每小题l分,共20分.每小题只有一个正确答案,请从每小题的备选答案中选出一个你认为正确的答案,在答题卡相应位置上用2B铅笔填涂相应的答案代码.答 ...
  • 20**年陕西省会计证考资料
    1.处于更新前改造过程的固定资产,下列说法正确的是[ ]. A.继续作为固定资产 B.账面价值转入在建工程 C.照提折旧 D.不再计提折旧 [答案解析]处于更新前改造过程的固定资产停止计提折旧,并且账面价值转入在建工程. 2."应 ...
  • 注会经济法第十二章
    一.单选题 1.下列有关中外合资经营企业与中外合作经营企业共同特点的表述中,符合外商投资企业法律制度规定的是( ). A. 二者的中外投资者均可以是公司.企业.其他经济组织或者个人 B. 二者的中外投资者均以其投资额为限对企业的债务承担有限 ...