| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- DNS-SD Tuturial And User Guide
- DNS-SD
- !!!Introduction
- The POCO DNS-SD POCO library provides an easy-to-use and
- unified programming interface for integrating Zeroconf features
- (service discovery and host name resolution) into a C++ application.
- The Applied Informatics DNS-SD library does not
- implement its own mDNS and DNS-SD protocol stacks, but rather uses
- an existing Zeroconf implementation for that purpose. Apple's Bonjour
- and Avahi can be used as backend for the DNS-SD library.
- A great advantage of the library is that it provides a unified
- programming interface. For the programmer, it's completely
- transparent whether Avahi or Bonjour is used as backend.
- !!!Programming Basics
- The DNS-SD library provides an asynchronous programming interface.
- This means that the application starts a browse or resolve operation
- by calling a member function of the Poco::DNSSD::DNSSDBrowser class,
- and this function returns immediately. The actual browse or resolve
- operation (which involves sending queries over the network and
- receiving responses from other hosts) is carried out in a separate
- thread, and as soon as results become available, these are reported
- to the application via events.
- !!Event Handlers
- The event handlers registered by the application should
- complete their work and return as quick as possible, otherwise they may
- interfere with DNS-SD processing. Event handlers should never
- wait for other events to happen. Specifically, they must never
- wait for an other DNSSDBrowser event to happen, as this will
- result in a deadlock.
- !!Service Types
- Service types (or registration types) are the most important concept when
- handling with DNS Service Discovery. A service type consists of a
- short name (maximum of 15 characters) specifying the protocol implemented
- by the service (prepended by an underscore), followed by a dot, followed
- by a name identifying the primary transport protocol, which is either
- "_tcp" or "_udp". Service types should be registered at <[dns-sd.org]>.
- A list of currently registered service types, as well as information on how
- to register a new service type can be found at the
- [[http://www.dns-sd.org/ServiceTypes.html DNS-SD website]].
- Examples for service types are "_daap._tcp" (Digital Audio Access Protocol,
- the protocol used by iTunes music sharing), "_http._tcp" (web server)
- or "_printer._tcp" for a printing service.
- Service names are not case sensitive.
- !!!Programming Tasks
- In the following sections, the basic programming Tasks
- that need to be performed when working with the DNS-SD library
- are described.
- !!Initializing the DNS-SD Library
- The DNS-SD core library only defines the classes that are part
- of the programming interfaces, it does not provide an actual
- implementation of these interfaces. So, in addition to the DNS-SD
- core library, a backend library must be linked with the application
- as well. Depending on which backend library is used, either Apple's
- Bonjour or Avahi will be used.
- Before the DNS-SD library can be used it must be initialized.
- This is done by calling the Poco::DNSSD::initializeDNSSD() function.
- This function is actually defined implemented in a backend library,
- so the backend libraries's header file must be included.
- It is good practice to control which backend header is being
- included via the preprocessor. For a cross-platform application,
- one would use Avahi on Linux platforms and Bonjour on Mac OS X and
- Windows platforms.
- Typically, the <[#include]> statements for the DNS-SD library
- would be as follows:
- #include "Poco/DNSSD/DNSSDResponder.h"
- #include "Poco/DNSSD/DNSSDBrowser.h"
- #if POCO_OS == POCO_OS_LINUX && !defined(POCO_DNSSD_USE_BONJOUR)
- #include "Poco/DNSSD/Avahi/Avahi.h"
- #else
- #include "Poco/DNSSD/Bonjour/Bonjour.h"
- #endif
- ----
- These statements will include the header files for the Poco::DNSSD::DNSSDResponder
- and Poco::DNSSD::DNSSDBrowser classes, as well as the core header
- file for a backend. Note that an application that only registers a service,
- but does not browse for services, does not need to include the
- <*Poco/DNSSD/DNSSDBrowser.h*> header file.
- The Poco::DNSSD::initializeDNSSD() function must be called before an
- instance of the Poco::DNSSD::DNSSDResponder class is created. If the
- application uses the Poco::Util::Application class (or its server
- counterpart), this can happen in the constructor of the application
- subclass. It is also good practice to uninitialize the DNS-SD library
- when the application exits by calling Poco::DNSSD::uninitializeDNSSD(),
- which can be done in the application class destructor.
- After initializing the DNS-SD library, the application should
- create an instance of the Poco::DNSSD::DNSSDResponder class.
- This class provides the main entry point into the DNS-SD library.
- Although it is possible to create more than one instance of
- the Poco::DNSSD::DNSSDResponder class, application programmers
- should refrain from doing so. The Poco::DNSSD::DNSSDResponder
- object should be kept alive during the entire lifetime of
- the application, or at least as long as DNS-SD services
- are required.
- After the responder object has been created, its <[start()]> method
- must be called. This will start a thread that handles all
- DNS-SD related network activity for the application.
- Similarly, when the application terminates, or when DNS-SD
- services are no longer required, the <[stop()]> method should
- be called to orderly shut-down the background thread.
- !!Registering A Service
- Registering a service, and thus making it discoverable to other
- DNS-SD capable applications, is a two step process.
- First, an instance of Poco::DNSSD::Service must be created
- and properly initialized. At least the following information
- must be specified:
- - the service type (e.g., "_http._tcp"), and
- - the port number of the service.
- Other information can be specified, but defaults will be
- used if not. This includes:
- - The service name, which defaults to the local hosts's machine name.
- - The network interface (by its interface index), on which the
- service shall be announced. The default is to announce the
- service on all interfaces (interface index is zero).
- - The domain, on which the service will be announced.
- - The domain name of the host providing the service.
- - Service properties, which will be announced in the TXT
- record of the service.
- The service properties (basically, a list of key-value pairs)
- can be used to provide additional information
- necessary for invoking the service. These will be announced along
- with the basic service information (host name, port number, etc.).
- The content of the service properties is specific to the application
- or network protocol the application uses.
- When specifying service properties, a few restrictions must be
- considered:
- - The total length of a key-value pair must not exceed 254 bytes.
- - The total length of all key-value pairs, including two additional
- bytes for each pair, must not exceed 65535 bytes.
- - The length of the key should not exceed nine bytes.
- - Values can contain text strings or arbitrary binary values, and
- can also be empty.
- The following code shows how to register and publish a HTTP server
- (running on port 8080) on a Zeroconf network:
- Poco::DNSSD::DNSSDResponder dnssdResponder;
- dnssdResponder.start();
- Poco::DNSSD::Service service("_http._tcp", 8080);
- Poco::DNSSD::ServiceHandle serviceHandle = dnssdResponder.registerService(service);
- ----
- Another example, the following code shows how to register
- and publish a network postscript-capable printer on a Zeroconf network.
- Poco::DNSSD::DNSSDResponder dnssdResponder;
- dnssdResponder.start();
- Poco::DNSSD::Service::Properties props;
- props.add("txtvers", "1");
- props.add("pdl", "application/postscript");
- props.add("qtotal", "1");
- props.add("rp", "ThePrinter");
- props.add("ty", "A Postscript Printer");
- Poco::DNSSD::Service service(0, "The Printer", "", "_printer._tcp", "", "", 515, props);
- Poco::DNSSD::ServiceHandle serviceHandle = dnssdResponder.registerService(service);
- ----
- !Unregistering A Service
- The <[registerService()]> method returns a Poco::DNSSD::ServiceHandle object.
- This object is used to unregister the service when it's no longer available,
- by passing it as argument to the <[unregisterService()]> method.
- dnssdResponder.unregisterService(serviceHandle);
- ----
- !Handling Registration Errors
- The above code examples don't do a very good job of error handling.
- Registration on the network may fail for various reasons.
- The Poco::DNSSD::DNSSDResponder class provides two events that can be
- used to check the status of a service registration.
- If the registration was successful, the <[serviceRegistered]> event
- will be fired. If registration failed, the <[serviceRegistrationFailed]>
- event will be fired. Please see the class documentation for a
- description of the available event arguments.
- Usually, there's not much an application can do when service registration
- fails. This is especially true for embedded devices, which often don't
- even have a way to communicate this error to the user. However, an application
- should at least log a registration error in a log file, to help with
- error diagnostics.
- Handling the <[serviceRegistered]> event is only necessary if the application
- needs to know the actual service name used to announce the service.
- In case of a name conflict (duplicate service names on the network), the
- name specified when registering the service may have been changed by
- the Bonjour or Avahi backend.
- The following example shows an event handler (delegate) function for handling
- registration errors.
- void onError(const void* sender, const Poco::DNSSD::DNSSDResponder::ErrorEventArgs& args)
- {
- std::cerr
- << "Service registration failed: "
- << args.error.message()
- << " (" << args.error.code() << ")"
- << std::endl;
- }
- ----
- To register this function as delegate for the <[serviceRegistrationFailed]> event:
- dnssdResponder.serviceRegistrationFailed += Poco::delegate(onError);
- ----
- !!Browsing For Services
- To discover available services of a specific type on the network, a browse operation
- for a specific service type must be initiated. For this purpose, the
- Poco::DNSSD::DNSSDBrowser class is used. An instance of this class can be obtained
- from the Poco::DNSSD::DNSSDResponder object, by calling the <[browswer()]> method.
- After a browse operation for a service type has been started, services becoming
- available or unavailable will be reported via events. A service that has been
- discovered will be reported via the <[serviceFound]> event.
- If a service is no longer available, it will be reported via the <[serviceRemoved]>
- event. The name, type and domain of the discovered service can be obtained from the
- Poco::DNSSD::Service object passed as event argument.
- The following sample shows how to write a delegate function for
- the <[serviceFound]> event.
- void onServiceFound(const void* sender, const Poco::DNSSD::DNSSDBrowser::ServiceEventArgs& args)
- {
- std::cout << "Service Found: \n"
- << " Name: " << args.service.name() << "\n"
- << " Domain: " << args.service.domain() << "\n"
- << " Type: " << args.service.type() << "\n"
- << " Interface: " << args.service.networkInterface() << "\n" << std::endl;
- }
- ----
- The next sample shows how to start a browse operation:
- Poco::DNSSD::DNSSDResponder dnssdResponder;
- dnssdResponder.start();
- dnssdResponder.browser().serviceFound += Poco::delegate(onServiceFound);
- Poco::DNSSD::BrowseHandle bh = dnssdResponder.browser().browse("_printer._tcp", "");
- ----
- Poco::DNSSD::DNSSDBrowser::browse() returns a Poco::DNSSD::BrowseHandle object, which can
- later be used to cancel a browse operation, by passing it to the <[cancel()]> method, as
- shown in the following example:
- dnssdResponder.browser().cancel(bh);
- ----
- After a service has been discovered, the next step is resolving the service, to obtain
- its host name, port number and properties, so that the service can be invoked.
- !!Resolving A Service
- Like browsing for services, resolving a service is an asynchronous operation.
- A resolve operation is started with a call to <[resolve()]>, passing the
- Poco::DNSSD::Service object obtained from the <[serviceFound]> event as
- argument. Once the service has been resolved, the result is reported via
- the <[serviceResolved]> event. The resolve operation can be started
- directly from the <[serviceFound]> event handler, as shown in the
- following sample:
- void onServiceFound(const void* sender, const Poco::DNSSD::DNSSDBrowser::ServiceEventArgs& args)
- {
- std::cout << "Service Found: \n"
- << " Name: " << args.service.name() << "\n"
- << " Domain: " << args.service.domain() << "\n"
- << " Type: " << args.service.type() << "\n"
- << " Interface: " << args.service.networkInterface() << "\n" << std::endl;
- reinterpret_cast<Poco::DNSSD::DNSSDBrowser*>(const_cast<void*>(sender))->resolve(args.service);
- }
- ----
- After a successful resolve, the service host name, port number and properties
- are available through the Poco::DNSSD::Service object passed to the event handler,
- as shown in the sample below:
- void onServiceResolved(const void* sender, const Poco::DNSSD::DNSSDBrowser::ServiceEventArgs& args)
- {
- std::cout << "Service Resolved: \n"
- << " Name: " << args.service.name() << "\n"
- << " Full Name: " << args.service.fullName() << "\n"
- << " Domain: " << args.service.domain() << "\n"
- << " Type: " << args.service.type() << "\n"
- << " Interface: " << args.service.networkInterface() << "\n"
- << " Host: " << args.service.host() << "\n"
- << " Port: " << args.service.port() << "\n"
- << " Properties: \n";
- for (Poco::DNSSD::Service::Properties::ConstIterator it = args.service.properties().begin(); it != args.service.properties().end(); ++it)
- {
- std::cout << " " << it->first << ": " << it->second << "\n";
- }
- std::cout << std::endl;
- }
- ----
- Of course, the event delegate for the <[serviceResolved]> event must be registered:
- dnssdResponder.browser().serviceResolved += Poco::delegate(onServiceResolved);
- ----
- !!Resolving A Service's Host Name
- The last step necessary before invoking a service is to resolve the service's host name
- into an IP address. On systems where mDNS is integrated into the DNS resolver (e.g.,
- Mac OS X, Windows with Bonjour or most Linux distributions with Avahi), this
- can simply be done by creating a Poco::Net::SocketAddress instance from the service's
- host name and port number. However if the systems's DNS resolver cannot handle
- Multicast DNS queries, the host name must be resolved through the
- Poco::DNSSD::DNSSDBrowser::resolveHost() method. Like resolving a service, resolving
- a host name is an asynchronous operation, and the result will be reported via
- an event -- the <[hostResolved]> event.
- The following sample shows how to implement the delegate function for the
- <[hostResolved]> event:
- void onHostResolved(const void* sender, const Poco::DNSSD::DNSSDBrowser::ResolveHostEventArgs& args)
- {
- std::cout << "Host Resolved: \n"
- << " Host: " << args.host << "\n"
- << " Interface: " << args.networkInterface << "\n"
- << " Address: " << args.address.toString() << "\n"
- << " TTL: " << args.ttl << "\n" << std::endl;
- }
- ----
- Like with resolving a service, it is possible to initiate resolving a host name directly
- from within the event delegate for the <[serviceResolved]> event.
- !!!Advanced Programming Tasks
- !!Enumerating Domains
- Available domains for browsing and registration can be enumerated by calling the
- Poco::DNSSD::DNSSDBrowser::enumerateBrowseDomains() and
- Poco::DNSSD::DNSSDBrowser::enumerateRegistrationDomains() methods. As usual,
- results are reported via events.
- !!Registering And Browsing For Records
- Additional DNS records for a specific service can be published on a Zeroconf network
- by invoking the Poco::DNSSD::DNSSDResponder::addRecord() method. It is also possible
- to alter a published record, or remove it. Records can be queried by invoking
- Poco::DNSSD::DNSSDBrowser::queryRecord(). Results are reported via
- events.
- !!Enumerating Available Service Types
- It is possible to enumerate all available services types on a domain by
- browsing for the special service type "_services._dns-sd._udp".
- Results will be reported via the <[serviceDiscovered]> event.
- The service type (without the primary transport protocol part,
- as in "_http") can be obtained from the service name stored
- in the Poco::DNSSD::Service object passed as event argument.
- The primary transport protocol and the domain can be obtained
- from the service type.
|