00200-DNSSDTutorialAndUserGuide.page 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. DNS-SD Tuturial And User Guide
  2. DNS-SD
  3. !!!Introduction
  4. The POCO DNS-SD POCO library provides an easy-to-use and
  5. unified programming interface for integrating Zeroconf features
  6. (service discovery and host name resolution) into a C++ application.
  7. The Applied Informatics DNS-SD library does not
  8. implement its own mDNS and DNS-SD protocol stacks, but rather uses
  9. an existing Zeroconf implementation for that purpose. Apple's Bonjour
  10. and Avahi can be used as backend for the DNS-SD library.
  11. A great advantage of the library is that it provides a unified
  12. programming interface. For the programmer, it's completely
  13. transparent whether Avahi or Bonjour is used as backend.
  14. !!!Programming Basics
  15. The DNS-SD library provides an asynchronous programming interface.
  16. This means that the application starts a browse or resolve operation
  17. by calling a member function of the Poco::DNSSD::DNSSDBrowser class,
  18. and this function returns immediately. The actual browse or resolve
  19. operation (which involves sending queries over the network and
  20. receiving responses from other hosts) is carried out in a separate
  21. thread, and as soon as results become available, these are reported
  22. to the application via events.
  23. !!Event Handlers
  24. The event handlers registered by the application should
  25. complete their work and return as quick as possible, otherwise they may
  26. interfere with DNS-SD processing. Event handlers should never
  27. wait for other events to happen. Specifically, they must never
  28. wait for an other DNSSDBrowser event to happen, as this will
  29. result in a deadlock.
  30. !!Service Types
  31. Service types (or registration types) are the most important concept when
  32. handling with DNS Service Discovery. A service type consists of a
  33. short name (maximum of 15 characters) specifying the protocol implemented
  34. by the service (prepended by an underscore), followed by a dot, followed
  35. by a name identifying the primary transport protocol, which is either
  36. "_tcp" or "_udp". Service types should be registered at <[dns-sd.org]>.
  37. A list of currently registered service types, as well as information on how
  38. to register a new service type can be found at the
  39. [[http://www.dns-sd.org/ServiceTypes.html DNS-SD website]].
  40. Examples for service types are "_daap._tcp" (Digital Audio Access Protocol,
  41. the protocol used by iTunes music sharing), "_http._tcp" (web server)
  42. or "_printer._tcp" for a printing service.
  43. Service names are not case sensitive.
  44. !!!Programming Tasks
  45. In the following sections, the basic programming Tasks
  46. that need to be performed when working with the DNS-SD library
  47. are described.
  48. !!Initializing the DNS-SD Library
  49. The DNS-SD core library only defines the classes that are part
  50. of the programming interfaces, it does not provide an actual
  51. implementation of these interfaces. So, in addition to the DNS-SD
  52. core library, a backend library must be linked with the application
  53. as well. Depending on which backend library is used, either Apple's
  54. Bonjour or Avahi will be used.
  55. Before the DNS-SD library can be used it must be initialized.
  56. This is done by calling the Poco::DNSSD::initializeDNSSD() function.
  57. This function is actually defined implemented in a backend library,
  58. so the backend libraries's header file must be included.
  59. It is good practice to control which backend header is being
  60. included via the preprocessor. For a cross-platform application,
  61. one would use Avahi on Linux platforms and Bonjour on Mac OS X and
  62. Windows platforms.
  63. Typically, the <[#include]> statements for the DNS-SD library
  64. would be as follows:
  65. #include "Poco/DNSSD/DNSSDResponder.h"
  66. #include "Poco/DNSSD/DNSSDBrowser.h"
  67. #if POCO_OS == POCO_OS_LINUX && !defined(POCO_DNSSD_USE_BONJOUR)
  68. #include "Poco/DNSSD/Avahi/Avahi.h"
  69. #else
  70. #include "Poco/DNSSD/Bonjour/Bonjour.h"
  71. #endif
  72. ----
  73. These statements will include the header files for the Poco::DNSSD::DNSSDResponder
  74. and Poco::DNSSD::DNSSDBrowser classes, as well as the core header
  75. file for a backend. Note that an application that only registers a service,
  76. but does not browse for services, does not need to include the
  77. <*Poco/DNSSD/DNSSDBrowser.h*> header file.
  78. The Poco::DNSSD::initializeDNSSD() function must be called before an
  79. instance of the Poco::DNSSD::DNSSDResponder class is created. If the
  80. application uses the Poco::Util::Application class (or its server
  81. counterpart), this can happen in the constructor of the application
  82. subclass. It is also good practice to uninitialize the DNS-SD library
  83. when the application exits by calling Poco::DNSSD::uninitializeDNSSD(),
  84. which can be done in the application class destructor.
  85. After initializing the DNS-SD library, the application should
  86. create an instance of the Poco::DNSSD::DNSSDResponder class.
  87. This class provides the main entry point into the DNS-SD library.
  88. Although it is possible to create more than one instance of
  89. the Poco::DNSSD::DNSSDResponder class, application programmers
  90. should refrain from doing so. The Poco::DNSSD::DNSSDResponder
  91. object should be kept alive during the entire lifetime of
  92. the application, or at least as long as DNS-SD services
  93. are required.
  94. After the responder object has been created, its <[start()]> method
  95. must be called. This will start a thread that handles all
  96. DNS-SD related network activity for the application.
  97. Similarly, when the application terminates, or when DNS-SD
  98. services are no longer required, the <[stop()]> method should
  99. be called to orderly shut-down the background thread.
  100. !!Registering A Service
  101. Registering a service, and thus making it discoverable to other
  102. DNS-SD capable applications, is a two step process.
  103. First, an instance of Poco::DNSSD::Service must be created
  104. and properly initialized. At least the following information
  105. must be specified:
  106. - the service type (e.g., "_http._tcp"), and
  107. - the port number of the service.
  108. Other information can be specified, but defaults will be
  109. used if not. This includes:
  110. - The service name, which defaults to the local hosts's machine name.
  111. - The network interface (by its interface index), on which the
  112. service shall be announced. The default is to announce the
  113. service on all interfaces (interface index is zero).
  114. - The domain, on which the service will be announced.
  115. - The domain name of the host providing the service.
  116. - Service properties, which will be announced in the TXT
  117. record of the service.
  118. The service properties (basically, a list of key-value pairs)
  119. can be used to provide additional information
  120. necessary for invoking the service. These will be announced along
  121. with the basic service information (host name, port number, etc.).
  122. The content of the service properties is specific to the application
  123. or network protocol the application uses.
  124. When specifying service properties, a few restrictions must be
  125. considered:
  126. - The total length of a key-value pair must not exceed 254 bytes.
  127. - The total length of all key-value pairs, including two additional
  128. bytes for each pair, must not exceed 65535 bytes.
  129. - The length of the key should not exceed nine bytes.
  130. - Values can contain text strings or arbitrary binary values, and
  131. can also be empty.
  132. The following code shows how to register and publish a HTTP server
  133. (running on port 8080) on a Zeroconf network:
  134. Poco::DNSSD::DNSSDResponder dnssdResponder;
  135. dnssdResponder.start();
  136. Poco::DNSSD::Service service("_http._tcp", 8080);
  137. Poco::DNSSD::ServiceHandle serviceHandle = dnssdResponder.registerService(service);
  138. ----
  139. Another example, the following code shows how to register
  140. and publish a network postscript-capable printer on a Zeroconf network.
  141. Poco::DNSSD::DNSSDResponder dnssdResponder;
  142. dnssdResponder.start();
  143. Poco::DNSSD::Service::Properties props;
  144. props.add("txtvers", "1");
  145. props.add("pdl", "application/postscript");
  146. props.add("qtotal", "1");
  147. props.add("rp", "ThePrinter");
  148. props.add("ty", "A Postscript Printer");
  149. Poco::DNSSD::Service service(0, "The Printer", "", "_printer._tcp", "", "", 515, props);
  150. Poco::DNSSD::ServiceHandle serviceHandle = dnssdResponder.registerService(service);
  151. ----
  152. !Unregistering A Service
  153. The <[registerService()]> method returns a Poco::DNSSD::ServiceHandle object.
  154. This object is used to unregister the service when it's no longer available,
  155. by passing it as argument to the <[unregisterService()]> method.
  156. dnssdResponder.unregisterService(serviceHandle);
  157. ----
  158. !Handling Registration Errors
  159. The above code examples don't do a very good job of error handling.
  160. Registration on the network may fail for various reasons.
  161. The Poco::DNSSD::DNSSDResponder class provides two events that can be
  162. used to check the status of a service registration.
  163. If the registration was successful, the <[serviceRegistered]> event
  164. will be fired. If registration failed, the <[serviceRegistrationFailed]>
  165. event will be fired. Please see the class documentation for a
  166. description of the available event arguments.
  167. Usually, there's not much an application can do when service registration
  168. fails. This is especially true for embedded devices, which often don't
  169. even have a way to communicate this error to the user. However, an application
  170. should at least log a registration error in a log file, to help with
  171. error diagnostics.
  172. Handling the <[serviceRegistered]> event is only necessary if the application
  173. needs to know the actual service name used to announce the service.
  174. In case of a name conflict (duplicate service names on the network), the
  175. name specified when registering the service may have been changed by
  176. the Bonjour or Avahi backend.
  177. The following example shows an event handler (delegate) function for handling
  178. registration errors.
  179. void onError(const void* sender, const Poco::DNSSD::DNSSDResponder::ErrorEventArgs& args)
  180. {
  181. std::cerr
  182. << "Service registration failed: "
  183. << args.error.message()
  184. << " (" << args.error.code() << ")"
  185. << std::endl;
  186. }
  187. ----
  188. To register this function as delegate for the <[serviceRegistrationFailed]> event:
  189. dnssdResponder.serviceRegistrationFailed += Poco::delegate(onError);
  190. ----
  191. !!Browsing For Services
  192. To discover available services of a specific type on the network, a browse operation
  193. for a specific service type must be initiated. For this purpose, the
  194. Poco::DNSSD::DNSSDBrowser class is used. An instance of this class can be obtained
  195. from the Poco::DNSSD::DNSSDResponder object, by calling the <[browswer()]> method.
  196. After a browse operation for a service type has been started, services becoming
  197. available or unavailable will be reported via events. A service that has been
  198. discovered will be reported via the <[serviceFound]> event.
  199. If a service is no longer available, it will be reported via the <[serviceRemoved]>
  200. event. The name, type and domain of the discovered service can be obtained from the
  201. Poco::DNSSD::Service object passed as event argument.
  202. The following sample shows how to write a delegate function for
  203. the <[serviceFound]> event.
  204. void onServiceFound(const void* sender, const Poco::DNSSD::DNSSDBrowser::ServiceEventArgs& args)
  205. {
  206. std::cout << "Service Found: \n"
  207. << " Name: " << args.service.name() << "\n"
  208. << " Domain: " << args.service.domain() << "\n"
  209. << " Type: " << args.service.type() << "\n"
  210. << " Interface: " << args.service.networkInterface() << "\n" << std::endl;
  211. }
  212. ----
  213. The next sample shows how to start a browse operation:
  214. Poco::DNSSD::DNSSDResponder dnssdResponder;
  215. dnssdResponder.start();
  216. dnssdResponder.browser().serviceFound += Poco::delegate(onServiceFound);
  217. Poco::DNSSD::BrowseHandle bh = dnssdResponder.browser().browse("_printer._tcp", "");
  218. ----
  219. Poco::DNSSD::DNSSDBrowser::browse() returns a Poco::DNSSD::BrowseHandle object, which can
  220. later be used to cancel a browse operation, by passing it to the <[cancel()]> method, as
  221. shown in the following example:
  222. dnssdResponder.browser().cancel(bh);
  223. ----
  224. After a service has been discovered, the next step is resolving the service, to obtain
  225. its host name, port number and properties, so that the service can be invoked.
  226. !!Resolving A Service
  227. Like browsing for services, resolving a service is an asynchronous operation.
  228. A resolve operation is started with a call to <[resolve()]>, passing the
  229. Poco::DNSSD::Service object obtained from the <[serviceFound]> event as
  230. argument. Once the service has been resolved, the result is reported via
  231. the <[serviceResolved]> event. The resolve operation can be started
  232. directly from the <[serviceFound]> event handler, as shown in the
  233. following sample:
  234. void onServiceFound(const void* sender, const Poco::DNSSD::DNSSDBrowser::ServiceEventArgs& args)
  235. {
  236. std::cout << "Service Found: \n"
  237. << " Name: " << args.service.name() << "\n"
  238. << " Domain: " << args.service.domain() << "\n"
  239. << " Type: " << args.service.type() << "\n"
  240. << " Interface: " << args.service.networkInterface() << "\n" << std::endl;
  241. reinterpret_cast<Poco::DNSSD::DNSSDBrowser*>(const_cast<void*>(sender))->resolve(args.service);
  242. }
  243. ----
  244. After a successful resolve, the service host name, port number and properties
  245. are available through the Poco::DNSSD::Service object passed to the event handler,
  246. as shown in the sample below:
  247. void onServiceResolved(const void* sender, const Poco::DNSSD::DNSSDBrowser::ServiceEventArgs& args)
  248. {
  249. std::cout << "Service Resolved: \n"
  250. << " Name: " << args.service.name() << "\n"
  251. << " Full Name: " << args.service.fullName() << "\n"
  252. << " Domain: " << args.service.domain() << "\n"
  253. << " Type: " << args.service.type() << "\n"
  254. << " Interface: " << args.service.networkInterface() << "\n"
  255. << " Host: " << args.service.host() << "\n"
  256. << " Port: " << args.service.port() << "\n"
  257. << " Properties: \n";
  258. for (Poco::DNSSD::Service::Properties::ConstIterator it = args.service.properties().begin(); it != args.service.properties().end(); ++it)
  259. {
  260. std::cout << " " << it->first << ": " << it->second << "\n";
  261. }
  262. std::cout << std::endl;
  263. }
  264. ----
  265. Of course, the event delegate for the <[serviceResolved]> event must be registered:
  266. dnssdResponder.browser().serviceResolved += Poco::delegate(onServiceResolved);
  267. ----
  268. !!Resolving A Service's Host Name
  269. The last step necessary before invoking a service is to resolve the service's host name
  270. into an IP address. On systems where mDNS is integrated into the DNS resolver (e.g.,
  271. Mac OS X, Windows with Bonjour or most Linux distributions with Avahi), this
  272. can simply be done by creating a Poco::Net::SocketAddress instance from the service's
  273. host name and port number. However if the systems's DNS resolver cannot handle
  274. Multicast DNS queries, the host name must be resolved through the
  275. Poco::DNSSD::DNSSDBrowser::resolveHost() method. Like resolving a service, resolving
  276. a host name is an asynchronous operation, and the result will be reported via
  277. an event -- the <[hostResolved]> event.
  278. The following sample shows how to implement the delegate function for the
  279. <[hostResolved]> event:
  280. void onHostResolved(const void* sender, const Poco::DNSSD::DNSSDBrowser::ResolveHostEventArgs& args)
  281. {
  282. std::cout << "Host Resolved: \n"
  283. << " Host: " << args.host << "\n"
  284. << " Interface: " << args.networkInterface << "\n"
  285. << " Address: " << args.address.toString() << "\n"
  286. << " TTL: " << args.ttl << "\n" << std::endl;
  287. }
  288. ----
  289. Like with resolving a service, it is possible to initiate resolving a host name directly
  290. from within the event delegate for the <[serviceResolved]> event.
  291. !!!Advanced Programming Tasks
  292. !!Enumerating Domains
  293. Available domains for browsing and registration can be enumerated by calling the
  294. Poco::DNSSD::DNSSDBrowser::enumerateBrowseDomains() and
  295. Poco::DNSSD::DNSSDBrowser::enumerateRegistrationDomains() methods. As usual,
  296. results are reported via events.
  297. !!Registering And Browsing For Records
  298. Additional DNS records for a specific service can be published on a Zeroconf network
  299. by invoking the Poco::DNSSD::DNSSDResponder::addRecord() method. It is also possible
  300. to alter a published record, or remove it. Records can be queried by invoking
  301. Poco::DNSSD::DNSSDBrowser::queryRecord(). Results are reported via
  302. events.
  303. !!Enumerating Available Service Types
  304. It is possible to enumerate all available services types on a domain by
  305. browsing for the special service type "_services._dns-sd._udp".
  306. Results will be reported via the <[serviceDiscovered]> event.
  307. The service type (without the primary transport protocol part,
  308. as in "_http") can be obtained from the service name stored
  309. in the Poco::DNSSD::Service object passed as event argument.
  310. The primary transport protocol and the domain can be obtained
  311. from the service type.