本文翻译整理自:DNS Service Discovery Programming Guide(更新日期:2013-08-08
https://developer.apple.com/library/archive/documentation/Networking/Conceptual/dns_discovery_api/Introduction.html#//apple_ref/doc/uid/TP30000964
文章目录
一、DNS服务发现简介
本文档介绍如何在您的程序中使用DNS服务发现API。
重要提示: iOS和OS X中的大多数应用程序不应该使用此API,而是应该使用更高级别的API,例如NSNetService
类。
有关详细信息,请参阅 NSNetServices和CFNetServices编程指南 。
DNS服务发现API可帮助您执行三项主要任务:
- 注册服务
- 浏览服务
- 将服务名称解析为主机名
为了支持这些主要任务,此API可以直接帮助您执行两个辅助任务:
- 枚举域(查找推荐的服务域)
- 更新注册(动态更改您的DNS注册数据)
DNS服务发现API是Bonjour的一部分,Bonjour是Apple的零配置网络(ZEROCONF)实现。
有关Bonjour工作原理的概述,请在使用DNS服务发现之前阅读 Bonjour概述 。
注意:本文档描述了基于套接字的DNS服务发现API,这是OS X v10.3及更高版本以及所有iOS版本中可用的推荐API。
不推荐使用基于马赫的替代API(并且在iOS中不受支持)。
1、谁应该阅读此文档?
本文档适用于希望在其应用程序中使用Bonjour的开发人员。
在网络服务API(NSNetServices和CFNetServices)上使用DNS服务发现API的原因有很多:
- 您正在编写不需要链接到更高级别框架的BSD风格的应用程序。
- 您正在编写跨平台程序(DNS Service Discovery API可在iOS、OS X、Windows和其他POSIX兼容操作系统上使用)。
- 您正在编写一个需要特殊用途的低级例程的应用程序,例如注册单个记录或只能使用特定的网络接口。
如果您的程序不需要这些功能,强烈建议您先查看NSNetService和CFNetService。
2、文档组织
本文档包含以下内容:
- 注册和终止服务说明了如何使用
mDNSResponder
守护程序注册和终止服务。 - 浏览网络服务描述了如何浏览服务。
- 解析服务的当前地址说明如何根据服务名称、注册类型和域获取服务信息。
- 枚举域有助于您了解如何查找推荐注册和浏览的域。
- 在Windows中使用DNS服务发现概述了如何在基于Windows的应用程序中实现DNS服务发现。
3、在你开始之前
接下来的几段描述了在尝试任何任务之前您应该了解的有关此API的一些信息。
此API中的大多数函数不会使用其函数返回或参数块返回所有数据。
相反,它们要求您提供一个可以处理异步发送的数据的回调函数。
您的回调函数可能会被多次调用,以响应您的单个函数调用。
例如,您可能会请求可用服务列表。
对于与您的请求匹配的每个可用服务,您的回调将被调用一次,然后在添加或删除匹配服务时再次调用。
一些函数以通常的方式返回错误代码,但许多函数没有。
在这些情况下,任何错误代码和状态标志都作为异步回复的一部分发送到您的回调函数,连同或代替任何返回的数据。
此API中的大多数函数使用一组通用参数来描述服务。
您将需要提供部分或全部这些参数,具体取决于您调用的目的。
在许多情况下,您将提供一些参数,例如服务的域和类型,并且您的回调函数将接收与其他参数对应的数据,例如匹配服务的服务名称。
以下是DNS Service Discovery API所需的常用参数列表:
-
名称-服务的人类可读名称,例如
Sales Laser Printer
。 -
注册类型-服务类型后跟协议名称并用点(
.
)分隔;_printer._tcp
就是一个例子。 -
域-服务的域,通常为
NULL
。 -
完整域名-唯一标识服务的名称。
完整域名是名称、注册类型和域的串联。
因为点(.
)字符用作分隔符,所以完整域名名称部分中的任何点字符都必须用反斜杠字符(\
)转义。
如果名称包含文字反斜杠,反斜杠也必须用反斜杠字符转义。
以下是完整域名的示例:Dr\.Smith’s Home\\Office Server._http._tcp.local
. -
端口-按网络字节顺序排列的服务端口号。
-
文本记录-包含使用服务可能需要的任何附加信息的可选记录,例如打印队列名称。
4、要求
DNS Service Discovery API需要mDNSResponder
守护程序的服务。
OS X(版本10.2及更高版本)和iOS包含一个mDNSResponder
守护程序作为操作系统的一部分。
Apple还在Apple的开源站点上提供了其mDNSResponder
守护程序的源代码。
此API也可在Bonjour for Windows和(如果安装了mDNSResponder
)Linux、Solaris和FreeBSD操作系统中使用。
**注意:**Apple鼓励硬件开发人员在其硬件中嵌入DarwinmDNSResponder
守护程序代码。
5、限制
DNS Service Discovery API不对服务执行网络访问设置。
同样,DNS Service Discovery API不提供从应用程序到服务的网络连接。
它允许应用程序浏览服务,或按名称请求服务,并提供IP地址、端口等。
您可以使用BSD套接字通过网络连接到服务。
6、更多信息
有关Bonjour的其他信息,包括标准、规范和资源的链接,请参阅http://developer.apple.com/networking/bonjour并阅读 Bonjour概述 。
有关基于Java的DNS服务发现API的文档,请参阅DNS服务发现Java参考。
有关动态更新和共享机密的信息,请分别参阅http://www.ietf.org/rfc/rfc2136.txt和http://www.ietf.org/rfc/rfc2845.txt。
有关网络地址转换(NAT)的信息,请参见http://www.ietf.org/rfc/rfc3022.txt,有关自动NAT端口映射的信息,请参见http://files.dns-sd.org/draft-nat-port-mapping.txt。
有关示例代码,请在Apple的开源站点下载mDNSResponder
源代码。
二、注册和终止服务
当您的服务启动时,您需要向mDNSResponder
守护程序注册,以便应用程序可以发现您的服务。
本节提供该过程的一般概述,然后是一组分步说明。
1、注册服务
要注册您的服务,请调用DNSServiceRegister
。
进行此调用的参数包括以下内容:
- 未初始化的服务发现引用。
- 要在其上注册服务的接口的索引;传递
0
以在所有可用接口上注册,传递–1
以仅在本地计算机上注册(远程主机无法使用您的服务),或传递表示要在其上注册的接口的编号(使用if_nametoindex
系列调用获取编号)。 - 指示您希望如何处理名称冲突的标志。
默认情况下,如果发生名称冲突,(
n)
会自动附加到您的服务名称,其中n是一个数字。
要覆盖此行为,请设置kDNSServiceFlagsNoAutoRename
标志,这将导致调用您的注册回调函数,以便您可以处理名称冲突。
kDNSServiceFlagsNoAutoRename
标志仅在您还显式指定服务名称时有效。 - 服务的名称;您可以指定
NULL
以使用计算机的名称作为服务的名称。 - 服务的注册类型。
- SRV目标主机名;通常,您将传递
NULL
以使用计算机的默认主机名。
几乎在所有情况下,传递NULL
都是所需的行为。
但是,代理应用程序可能会传递计算机主机名以外的显式SRV目标。 - 服务接受连接的网络字节顺序的端口号。
端口传递0
将注册占位符服务。
使用占位符服务,浏览不会发现该服务,但如果另一个客户端尝试注册相同的名称,则会发生名称冲突。
大多数应用程序不需要使用占位符服务。 - 要调用的回调函数,用于提供有关注册成功或失败的信息,或
NULL
。 - 调用回调函数时将传递给回调函数的用户定义上下文值,
NULL
。
需要TXT记录的服务也传递TXT记录的原始数据和原始数据的长度作为参数。
大多数服务不需要TXT记录,因此这些参数分别传递NULL
和0
。
您可以传递NULL
,而不是提供回调函数,在这种情况下,您将不会收到代表您选择的默认值的通知,也不会收到任何可能阻止您的服务注册的异步错误的通知。
如果您为此参数传递NULL
,则不能传递kDNSServiceFlagsNoAutoRename
作为标志参数。
您可以通过调用DNSServiceRefDeallocate
.
如果注册可以启动,DNSServiceRegister
初始化服务发现引用并创建一个套接字,用于与mDNSResponder
守护进程通信。
使用服务发现引用调用DNSServiceRefSockFD
并获取服务引用的套接字描述符。
使用套接字描述符设置运行或select
循环。
当循环指示mDNSResponder
守护进程的回复可用时,调用DNSServiceProcessResult
并向其传递由DNSServiceRegister
初始化的服务发现引用。
DNSServiceProcessResult
将调用与服务发现引用关联的回调函数。
您可以调用DNSServiceRegister
并立即调用DNSServiceProcessResult
,而不是设置运行循环或select
循环。
DNSServiceProcessResult
函数将阻塞,直到mDNSResponder
守护进程有响应,此时将调用调用DNSServiceRegister
时指定的回调(如果有)。
除了当前未使用的服务发现引用和标志之外,您的回调将使用以下参数调用:
- 指示注册是否成功的错误代码;如果注册成功,则其余参数包含有效数据
- 传递给
DNSServiceRegister
的服务名称,或者在传递给DNSServiceRegister
作为服务名称时选择NULL
的名称 - 传递给
DNSServiceRegister
- 注册服务的域
- 传递给DNSServiceRegister的用户定义
DNSServiceRegister
如果服务名称、注册类型和域名的组合导致完整域名已在本地使用并且您指定了kDNSServiceFlagsNoAutoRename
,则需要解除分配服务发现引用,选择另一个服务名称并重试,直到可以注册本地唯一名称。
成功注册后,您的服务将被通知到本地网络,并且可以使用多播DNS(按名称或浏览服务)找到其访问信息(IP地址、端口等)。
使用初始化的服务发现引用,您可以与mDNSResponder
守护程序通信,以便在服务运行时向服务的注册信息添加记录、更新添加的记录或删除添加的记录。
但是,您可能永远不需要更改注册信息,因为Bonjour处理常见情况,例如唤醒、睡眠、关闭和更改IP地址。
一个罕见的例外是需要更新与服务关联的文本记录。
例如,如果文本字段包含队列名称,并且队列名称发生更改,则需要更新服务的文本记录。
只要期望调用回调函数,就必须将套接字描述符保留在运行循环或select
循环中。
2、终止服务注册
要终止服务的注册,请从运行循环或select
循环中删除套接字描述符并调用DNSServiceRefDeallocate
,将注册服务时初始化的服务发现引用传递给它。
除了使服务发现引用无效并释放与之关联的内存之外,任何已添加的资源记录都将取消注册并且它们的引用将无效。
与mDNSResponder
守护程序连接的基础套接字将关闭,从而终止应用程序与守护程序的连接。
三、浏览网络服务
使用此API浏览服务相当简单。
您可以通过单个函数调用找出给定域中可用的给定类型的服务。
1、使用DNSServiceBrowse
要浏览可用服务,请调用DNSServiceBrowse
。
进行此调用的参数包括以下内容:
- 未初始化的服务发现引用。
- 要浏览的接口的索引;传递
0
以浏览所有可用接口,传递–1
以仅浏览本地主机上的服务,或传递表示要浏览的接口的编号(使用if_nametoindex
系列调用获取编号)。 - 要浏览的服务的注册类型;注册类型是服务类型,后跟一个点,后跟协议(例如,
_printer._tcp
)。 - 要浏览的域;传递
NULL
以浏览用户指定为可浏览的域,或传递域名以仅浏览该域。 - 要调用的回调函数,以提供有关浏览成功或失败的信息并提供搜索结果,例如已找到的服务或不再可用的服务。
- 调用回调函数时将传递给回调函数的用户定义上下文值,
NULL
。
如果可以启动浏览,DNSServiceBrowse
会初始化服务发现引用并创建一个套接字,用于与mDNSResponder
守护程序通信。
使用服务发现引用调用DNSServiceRefSockFD
并获取套接字的套接字描述符。
使用套接字描述符设置一个运行循环或select
循环,该循环将指示何时来自mDNSResponder
守护进程的响应变得可用。
该响应可能指示已找到与指定类型、域和接口匹配的服务实例,或者先前找到的服务实例不再可用。
当循环指示mDNSResponder
守护进程已响应时,调用DNSServiceProcessResult
并向其传递由DNSServiceBrowse
初始化的服务发现引用。
DNSServiceProcessResult
将调用与服务发现引用关联的回调函数。
您的回调将使用以下参数调用:
- 由
DNSServiceBrowse
初始化的服务发现引用。 - 提供已找到或不再可用的服务和浏览状态信息的标志。
例如,kDNSServiceFlagsAdd
表示服务参数包含已找到的服务的名称;您应该将其添加到您的可用服务列表中。
如果kDNSServiceFlagsAdd
未设置,则服务参数指定的服务不再可用,应从您的可用服务列表中删除。
浏览状态由kDNSServiceFlagsMoreComing
标志指示。
设置时,您的回调函数将立即再次调用,因此您不应该更新您的用户交互界面。
当kDNSServiceFlagsMoreComing
未设置时,您的回调函数将不会立即再次调用,因此您有时间更新您的用户交互界面。 - 发现服务的接口的索引。
- 指示浏览是否成功的错误代码;如果浏览成功,其余参数包含有效数据。
- 如果浏览成功,则找到的服务的名称。
- 注册类型,如果浏览成功。
- 如果浏览成功,则发现服务的域。
- 传递给
DNSServiceBrowse
的用户定义上下文信息。
2、浏览多个域
要浏览多个域或多个服务类型,请为感兴趣的每个域和服务类型调用DNSServiceBrowse
。
您的应用程序负责跟踪响应。
**注意:**您可以通过调用DNSServiceEnumerateDomains
获取要搜索的推荐域列表。
有关详细信息,请参阅枚举域。
如果您的应用程序需要在应用程序运行的整个过程中保持浏览器界面可见,就像iTunes和iChat所做的那样,那么您通常会在每个会话中调用一次DNSServiceBrowse
。
每当有新服务可用或现有服务不可用时,数据都会发送到您的回调函数,因此您只需保持回调处于活动状态,您的服务列表将始终是最新的。
此信息通常不经常更改,因此回调通常不会使用太多CPU时间。
但是,如果您的应用程序不需要在打印机对话框等情况下不断显示可用服务列表,那么您应该调用DNSServiceBrowse
并在完成后终止浏览。
当您调用DNSServiceBrowse
时,它会初始化一个服务发现引用,并打开一个基于套接字的连接与mDNSResponder
守护程序。
因此,如果您选择停用回调并根据需要重复搜索,请务必在再次调用DNSServiceBrowse
之前调用DNSServiceRefDeallocate
以释放引用。
否则,每次搜索都会泄漏内存和套接字。
给定服务实例的实际IP地址和端口将比服务名称更频繁地更改。
每次使用服务时,您都应该在使用服务之前解析服务实例的当前地址。
请参阅下一节,解析服务的当前地址。
3、终止浏览
要终止浏览,请从运行循环或select
循环中删除套接字描述符并调用DNSServiceRefDeallocate
,将调用DNSServiceBrowse
时初始化的服务发现引用传递给它。
浏览停止,服务发现引用无效,与引用关联的内存被释放。
与mDNSResponder
守护程序连接的基础套接字关闭,从而终止应用程序与守护程序的连接。
四、解析服务的当前地址
本文介绍如何使用DNSServiceResolve
根据名称、类型和域获取有关服务的信息。
1、使用DNSServiceResolve
一旦获得了服务的名称、注册类型和域,您就可以通过调用DNSServiceResolve
来获取有关服务的信息,例如注册服务的接口、服务的完整域名、提供服务的主机的名称以及服务的主TXT记录的内容。
警告: DNSServiceResolve
适用于获取有关具有单个SRV记录和单个TXT记录(可能为空)的服务的信息。
要解析具有多个SRV或TXT记录的服务,您应该使用DNSServiceQueryRecord
您还应该使用DNSServiceQueryRecord
来监视TXT记录内容,而不是DNSServiceResolve
。
要将服务名称解析为其主机名和端口,请调用DNSServiceResolve
。
进行此调用的参数包括以下内容:
- 未初始化的服务发现引用
- 要解析服务的接口的索引;传递传递给
DNSServiceBrowse
回调函数的值,或0
以在所有可用接口上解析 - 要解析的服务名称;传递传递给DNSServiceBrowse回调函数的
DNSServiceBrowse
- 要解析的服务的注册类型;将传递给DNSServiceBrowse回调函数的值
DNSServiceBrowse
- 注册服务的域;传递传递给DNSServiceBrowse回调函数的
DNSServiceBrowse
- 要调用的回调函数,以提供有关解析成功或失败的信息
- 调用时将传递给回调函数的用户定义上下文值,或
NULL
如果解析可以启动,DNSServiceResolve
会初始化服务发现引用并创建一个套接字,用于与mDNSResponder
守护进程通信。
使用服务发现引用调用DNSServiceRefSockFD
并获取套接字的套接字描述符。
2、设置回调函数
如果DNSServiceResolve
返回无错误,您需要让mDNSResponder
解析服务发现引用,并在收到响应时运行回调函数。
设置回调函数有两种技术:异步和同步。
若要从mDNSResponder
异步获取响应,请使用套接字描述符设置运行或select
循环。
每当来自mDNSResponder
守护进程的响应可用时,将通知该循环。
当循环指示响应可用时,调用DNSServiceProcessResult
并将由DNSServiceResolve
初始化的服务发现引用传递给它。
DNSServiceProcessResult
将调用与服务发现引用关联的回调函数。
mDNSResponder
守护进程将为它基于每个接口解析的每个服务提供响应。
如果您想同步运行回调函数,而不是设置运行循环或select
循环,您可以调用DNSServiceResolve
并立即调用DNSServiceProcessResult
。
DNSServiceProcessResult
函数将阻塞,直到mDNSResponder
守护进程有响应,此时将调用调用DNSServiceResolve
时指定的回调。
对于您希望解析的每个服务,整个过程可能应该在其自己的循环中运行。
除了当前未使用的服务发现引用和标志之外,您的回调将使用以下参数调用:
- 解析服务的接口索引;使用
if_nametoindex
系列调用将索引与接口名称相关联 - 指示解析是否成功的错误代码;如果解析成功,则其余参数包含有效数据
- 服务的完整域名,适合传递给以完整域名为参数的特殊用途函数
- 提供服务的机器的主机名,适合传递给
gethostbyname
或DNSServiceQueryRecord
获取主机的IP地址 - 服务接受连接的网络字节顺序的端口号
- 服务的TXT记录长度
- 标准TXT记录格式的服务的主TXT记录(即一个长度字节后跟数据,后跟一个长度字节,后跟数据,依此类推)
- 传递给DNSServiceResolve的用户定义
DNSServiceResolve
重要提示:服务的IP地址和端口号可以动态更改,因此您应该在每次使用服务时获取当前地址,就在使用之前。
将为解析服务的每个接口以及与服务关联的每个TXT记录通知您的运行循环或select
循环。
获得所需结果后,您必须终止解析。
从运行循环或select
循环中删除套接字描述符并调用DNSServiceRefDeallocate
,将调用DNSServiceResolve
时初始化的服务发现引用传递给它。
服务发现引用无效,与该引用关联的内存被释放。
与mDNSResponder
守护程序连接的基础套接字关闭,从而终止应用程序与守护程序的连接。
五、枚举域
该DNSServiceEnumerateDomains
函数查找建议注册和浏览的域。
每次调用回调时,都会提供有关一个域的信息,以及指示是否从域列表中添加或删除该域或指示该域是默认域或不再是默认域的标志。
用于调用DNSServiceEnumerateDomains
的参数包括:
- 未初始化的服务发现引用
- 一个标志,指示是否要枚举推荐的浏览或注册域
- 指定要枚举的接口的接口索引;传递
0
来枚举所有接口上的域,或者传递正整数来指定要枚举域的接口(使用if_nametoindex
系列调用来获取要枚举的接口的索引) - 要调用的回调函数以提供有关枚举成功或失败的信息
- 调用时将传递给回调函数的用户定义上下文值,或
NULL
如果可以启动枚举,DNSServiceEnumerateDomains
初始化服务发现引用并创建用于与mDNSResponder
守护程序通信的套接字。
使用服务发现引用调用DNSServiceRefSockFD
并获取套接字描述符。
使用套接字描述符设置运行循环或select
循环。
当循环指示来自mDNSResponder
守护进程的响应可用时,调用DNSServiceProcessResult
并将由DNSServiceEnumerateDomains
初始化的服务发现引用传递给它。
DNSServiceProcessResult
将调用与服务发现引用关联的回调函数。
无需设置运行或select
循环,您可以调用DNSServiceEnumerate
并立即调用DNSServiceProcessResult
。
DNSServiceProcessResult
函数将阻塞,直到mDNSResponder
守护进程有响应,此时将调用调用DNSServiceEnumerate
时指定的回调。
您的回调将使用以下参数调用:
- 被传递到的服务发现引用
DNSServiceEnumerateDomains
- 指示是否将立即再次调用您的回调以传递有关已找到的另一个域的信息、是否从应用程序维护的列表中添加或删除此域以及是否将该域作为默认域添加或删除的标志
- 找到域的接口的索引
- 指示枚举是否成功的错误代码;如果枚举成功,则其他参数包含有效数据
- 找到的域的名称
- 传递给的用户定义的上下文信息
DNSServiceEnumerateDomains
运行循环或select
循环将根据每个接口枚举的每个推荐域以及添加或删除域时得到通知。
您负责将守护程序的响应组装到当前推荐域的列表中。
注意:即使标志指示列表已完成,如果添加或删除域、设置为默认值或不再是默认值,您的回调也会再次被调用。
要终止枚举,请从运行循环或select
循环中删除套接字描述符并调用DNSServiceRefDeallocate
,将调用DNSServiceEnumerateDomains
时初始化的服务发现引用传递给它。
服务发现引用无效,与该引用关联的内存被释放。
与mDNSResponder
守护程序连接的基础套接字关闭,从而终止应用程序与守护程序的连接。
六、在Windows中使用DNS服务发现
DNS Service Discovery在编写时考虑到了跨平台兼容性。
因此,所有在OS X和iOS中有效的DNS Service Discovery API调用在Windows中也有效。
这两个平台之间的区别在于每个句柄如何运行循环。
接下来的两节将解释编写利用Windows中的DNS Service Discovery的程序需要进行哪些更改。
在阅读这些部分之前,如果您还没有熟悉DNS Service Discovery API和Microsoft Foundation类,您将希望熟悉它们。
1、Windows图形用户界面
要在Windows图形用户交互界面中正确合并DNS Service Discovery,请使用WinSockWSAAsyncSelect
函数。
WSAAsyncSelect
函数将基于套接字的网络事件集成到Windows消息循环中。
要在Windows代码中使用它,您应该首先创建并初始化一个DNSServiceRef
对象。
然后,调用函数WSAAsyncSelect
将您的DNSServiceRef
对象的套接字与Windows消息循环相关联。
WSAAsyncSelect
需要四个参数:DNSServiceRef
对象的套接字、接收消息的窗口、事件发生时发送的消息以及您感兴趣的网络事件的位掩码。
下面提供了一个简单的示例。
在示例中,您可以看到如何创建NULL
DNSServiceRef
对象,使用DNSServiceBrowse
初始化该引用,然后使用WSAAsyncSelect
将其添加到工作循环。
// create blank DNSServiceRef
e = new ServiceHandlerEntry;
...
// initialize the DNSServiceRef for browsing
err = DNSServiceBrowse( &e->ref, 0, 0, e->type, NULL, BrowseCallBack, e );
// add browsing to the work loop with WSAAsyncSelect
// where m_hWnd is the window, WM_PRIVATE_SERVICE_EVENT is the message and
// FD_READ and FD_CLOSE are bitmasks for reading and closing sockets
err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(e->ref),
m_hWnd,
WM_PRIVATE_SERVICE_EVENT,
FD_READ|FD_CLOSE);
2、Windows命令行界面
使用DNS服务发现创建Windows命令行程序类似于为OS X或iOS创建命令行程序。
Windows与OS X和iOS一样,支持select
系统调用。
此函数用于确定DNS服务发现API函数的结果何时可用。
有关将select
循环与DNS服务发现一起使用的更多信息,请参阅注册和终止服务、浏览网络服务和解析服务的当前地址。
2024-06-18(二)