У меня есть приложение, созданное с использованием MFC, к которому нужно добавить обнаружение службы Bonjour / Zeroconf. У меня возникли некоторые проблемы с выяснением, как лучше всего это сделать, но я остановился на использовании заглушки DLL, предоставленной в исходном коде mDNSresponder, и связал мое приложение со статической библиотекой, сгенерированной этим (которая, в свою очередь, использует систему 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».
Я звоню 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
метод класса потока называется ServiceDiscovery
, ServiceRef
является членом 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);
}
hosttarget
а также port
содержать информацию о вашем соединении, и любые текстовые записи могут быть получены с помощью API DNS-SD (например, TXTRecordGetCount
а также TXTRecordGetItemAtIndex
).
При использовании общих ссылок на соединения вы должны освободить каждую из них на основе (или скопированной) родительской ссылки, когда вы закончите с ними. Я думаю, что DNS-SD API выполняет подсчет ссылок (и отношения родитель / потомок), когда вы передаете копии общей ссылки одной из их функций. Снова, см. Документацию для деталей.
Сначала я пытался не использовать общие подключения, а просто передавал ServiceRef
, заставляя это быть перезаписано в обратных вызовах и моем главном цикле, чтобы быть перепутанным. Я полагаю, что если вы не используете общие соединения, вам нужно вести список ссылок, которые нуждаются в дальнейшей обработке (и обрабатывать каждую), а затем уничтожить их, когда вы закончите. Подход с общим подключением казался намного проще.
Других решений пока нет …