2

我有一个使用 MFC 构建的应用程序,我需要将 Bonjour/Zeroconf 服务发现添加到其中。我在弄清楚如何最好地做到这一点时遇到了一些麻烦,但我决定使用 mDNSresponder 源代码中提供的 DLL 存根并将我的应用程序链接到由它生成的静态库(反过来使用系统dnssd.dll)。

但是,我仍然遇到问题,因为回调似乎并不总是被调用,所以我的设备发现停止了。让我感到困惑的是,在 OSX 下,使用 OSX dns-sd 终端服务和在 Windows 下使用 dns-sd 命令行服务,这一切都可以正常工作。在此基础上,我排除了客户端服务的问题,并试图找出我的 Windows 代码出了什么问题。

我基本上是在调用 DNSBrowseService(),然后在该回调中调用 DNSServiceResolve(),最后调用 DNSServiceGetAddrInfo() 来获取设备的 IP 地址,以便我可以连接到它。

所有这些调用都基于使用 WSAAsyncSelect,如下所示:

DNSServiceErrorType err = DNSServiceResolve(&client,kDNSServiceFlagsWakeOnResolve,
                                                    interfaceIndex,
                                                    serviceName,
                                                    regtype,
                                                    replyDomain,
                                                    ResolveInstance,
                                                    context);

    if(err == 0) 
    {
        err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(client), p->m_hWnd, MESSAGE_HANDLE_MDNS_EVENT, FD_READ|FD_CLOSE);
    }

但有时即使服务在那里,回调也不会被调用,并且使用命令行会确认这一点。

我完全不知道为什么这不是 100% 可靠,但如果我在命令行中使用相同的 DLL,那就是这样。我唯一可能的解释是,DNSServiceResolve 函数尝试在 WSAAsyncSelect 为套接字注册处理消息之前调用回调函数,但我看不到任何解决方法。

我在这上面花了很长时间,现在完全没有想法了。任何建议都会受到欢迎,即使它们是“这是一种非常愚蠢的方法,你为什么不做 X、Y、Z”。

4

1 回答 1

1

DNSServiceBrowse用“共享连接”(参见dns_sd.h文档)调用 ,如下所示:

DNSServiceCreateConnection(&ServiceRef);
// Need to copy the main ref to another variable.
DNSServiceRef BrowseServiceRef = ServiceRef;
DNSServiceBrowse(&BrowseServiceRef,               // Receives reference to Bonjour browser object.
                 kDNSServiceFlagsShareConnection, // Indicate it's a shared connection.
                 kDNSServiceInterfaceIndexAny,    // Browse on all network interfaces.
                 "_servicename._tcp",             // Browse for service types.
                 NULL,                            // Browse on the default domain (e.g. local.).
                 BrowserCallBack,                 // Callback function when Bonjour events occur.
                 this);                           // Callback context.

这是在一个run名为 的线程类的主要方法中ServiceDiscoveryServiceRef是 的成员ServiceDiscovery

然后紧跟上面的代码,我有一个主事件循环,如下所示:

while (true)
{
   err = DNSServiceProcessResult(ServiceRef);
   if (err != kDNSServiceErr_NoError)
   {
      DNSServiceRefDeallocate(BrowseServiceRef);
      DNSServiceRefDeallocate(ServiceRef);
      ServiceRef = nullptr;
   }
}

然后,BrowserCallback您必须设置解析请求:

void DNSSD_API ServiceDiscovery::BrowserCallBack(DNSServiceRef inServiceRef,
                                                 DNSServiceFlags inFlags,
                                                 uint32_t inIFI,
                                                 DNSServiceErrorType inError,
                                                 const char* inName,
                                                 const char* inType,
                                                 const char* inDomain,
                                                 void* inContext)
{
   (void) inServiceRef; // Unused

   ServiceDiscovery* sd = (ServiceDiscovery*)inContext;
   ...
   // Pass a copy of the main DNSServiceRef (just a pointer).  We don't
   // hang to the local copy since it's passed in the resolve callback,
   // where we deallocate it.
   DNSServiceRef resolveServiceRef = sd->ServiceRef;
   DNSServiceErrorType err =
      DNSServiceResolve(&resolveServiceRef,
                        kDNSServiceFlagsShareConnection, // Indicate it's a shared connection.
                        inIFI,
                        inName,
                        inType,
                        inDomain,
                        ResolveCallBack,
                        sd);

那么ResolveCallback你应该拥有你需要的一切。

// Callback for Bonjour resolve events.
void DNSSD_API ServiceDiscovery::ResolveCallBack(DNSServiceRef inServiceRef,
                                                 DNSServiceFlags inFlags,
                                                 uint32_t inIFI,
                                                 DNSServiceErrorType inError,
                                                 const char* fullname,
                                                 const char* hosttarget,
                                                 uint16_t port,        /* In network byte order */
                                                 uint16_t txtLen,
                                                 const unsigned char* txtRecord,
                                                 void* inContext)
{
   ServiceDiscovery* sd = (ServiceDiscovery*)inContext;
   assert(sd);

   // Save off the connection info, get TXT records, etc.
   ...

   // Deallocate the DNSServiceRef.
   DNSServiceRefDeallocate(inServiceRef);
}

hosttargetport包含您的连接信息,并且可以使用 DNS-SD API(例如TXTRecordGetCountTXTRecordGetItemAtIndex)获取任何文本记录。

使用共享连接引用,您必须在使用完它们后根据父引用(或从父引用复制)取消分配每个引用。我认为当您将共享引用的副本传递给它们的一个函数时,DNS-SD API 会进行一些引用计数(以及父/子关系)。同样,有关详细信息,请参阅文档。

一开始我尝试不使用共享连接,而我只是传递下来ServiceRef,导致它在回调中被覆盖,并且我的主循环变得混乱。我想如果您不使用共享连接,您需要维护一个需要进一步处理(并处理每个)的引用列表,然后在完成后销毁它们。共享连接方法似乎要容易得多。

于 2013-08-26T20:39:53.060 回答