An Example Discover Service Types
I've written a demo program that browses the network for details on every service instance it can find and presents the results in a tree format. Figure 2 shows the program running on my home network, where I have drilled down to get information about an instance of a print service.
This program has to start by doing a top-level iteration of all the service types currently seen on my network. In my blog post, I noted that I can use a special browse command to accomplish this. If you've installed the Bonjour SDK, you can run the dns-sd program and browse for service type _services._dns-sd._udp, which should give results something like this:
C:\Users\Mark>dns-sd -B _services._dns-sd._udp Browsing for _services._dns-sd._udp Timestamp A/R Flags if Domain Service Type Instance Name 17:38:09.407 Add 3 13 . _tcp.local. _smb 17:38:09.407 Add 3 13 . _tcp.local. _printer 17:38:09.408 Add 3 13 . _tcp.local. _pdl-datastream 17:38:09.408 Add 3 13 . _tcp.local. _http 17:38:09.408 Add 3 13 . _tcp.local. _tivo-videos 17:38:09.409 Add 2 13 . _tcp.local. _csco-sb
Looking to do the same thing in my demo program, I turn to the header file dns_sd.h. This file ships with the Bonjour SDK and contains not only the API definition, but what passes for documentation. In that header file, I see that there is a function called DNSServiceBrowse, and it looks like it does exactly what I want. In my sample program, my call to this routine is shown below:
DNSServiceRef client = NULL;
DNSServiceErrorType err;
err = DNSServiceBrowse( &client,
0,
0,
"_services._dns-sd._udp",
"",
IterateServiceTypes,
this );
You can get some exposition on each of the arguments I pass to the function from the header file, my brief comments on each are given below:
sdRef: Every call to the Bonjour API creates a newDNSServiceRefhandle. It is initialized by the function call, and used later to retrieve the results.flags: This parameter is not used in this version of the SDK.- interfaceIndex: This argument is used to select a specific network interface. For this particular function I want to browse on all available interfaces, so a value of 0 is used.
regType: The type of service being browsed for. Normally when you callDNSServiceBrowseyou will use this parameter to specify a specific service type that you are interested in, such as_http._tcp. I'm using the special type of_services._dns-sd._udpin order to get a list of all published service types, not specific instances.domain: By passing an empty string, I tell the service that I want to see advertisements from all domains.callback: The pointer to a callback function that will receive the responses to this request. The argument I pass in,IterateServiceTypes, is a static member of my MCFDialogclass.context: The context variable is an opaque pointer type that is passed in to the Bonjour service. When it performs a callback, it will include a copy of the context pointer for the use of the callback function. I always pass in a pointer to my MFCDialogclass, so my callback functions have full access to the class members.
The important thing to note here is that the call to the API function doesn't return any important data. All I get back is an error code indicating that the request is being processed, and a DNSServiceRef handle that I use to track that progress.
The real action comes when my callback function is invoked. IterateServiceTypes is a static member of my Dialog class. Apple kept things simple by having one callback type for C and C++, which means no member functions. You could easily build shims to make it appear as though the DLL was calling member functions it would just take a small modification to the code I'll show you here.
The function definition has to follow exactly the declaration given in dns_sd.h. My implementation starts like this:
void DNSSD_API CServiceBrowserDlg::IterateServiceTypes( DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *domain,
void *context )
{
CServiceBrowserDlg *p = (CServiceBrowserDlg *) context;
It's worth walking through a look at each of the parameters in the callback:
sdRef: This is the sameDNSServiceRefvalue that was created when the call was made toDNSServiceBrowse. You don't need to make any use of it in the callback, but it does provide a good way to correlate results with function calls, particularly if you are using a single callback function to process many different results.flags: There are two important flag bits to check in this value. The first value,kDNSServiceFlagsMoreComingis used to indicate that there are definitely more callbacks coming. If that bit is cleared, there is no more pending data. The second flag bit,kDNSServiceFlagsAddis used to indicate whether this service is being added or deleted. When you first start browsing, all the callbacks will be for services added. As services are added and removed from the system, additional callbacks will be generated with this bit both set and cleared.interfaceIndex: In the callback, this index will be set to the index of the network interface where the advertisement was found. When it comes time to resolve this service, you need to pass in the correct index.errorCode: If this value is not zero, the callback is indicating an error. As long as it is zero, your code can process the input safely.serviceName: This value contains the name of a discovered service it is the whole point of the callback. Normally, this will contain the name of an instance of a service. However, when browsing for the special name_services._dns-sd._udp, the instance name is actually a service type.regtype: The type of the service you may already know this information by the time you reach the callback, but if the callback is handling the results from multiple queries, it can be helfpul.domain: The domain of the discovered service. Like the interface index, you need use the domain when you are attempting to resolve the service.context: A copy of the context variable passed in when the browse call was made.
If you look at the first line of the aforementioned code sample, the first thing I do is cast the context pointer to its correct type, a pointer to my MFC Dialog class. Then I can make full use of all the members of the class, albeit via a call through a pointer instead of directly.
So what do I do with these services once I receive them? Well, for each service type that I find, I kick off a new browse process, looking for specific instances of the service. As an example, in my callback IterateServiceTypes, one of the callbacks returns a service type of _printer._tcp. In order to find all instances of this service, I have to call DNSServiceBrowse again, with that service name and the correct interface and domain. After inserting the service type into the tree, I make that call so I can start adding those instances:
HTREEITEM item = p->m_Tree.InsertItem( CA2T(service_type.c_str()), TVI_ROOT, TVI_SORT );
DNSServiceRef client = NULL;
DNSServiceErrorType err;
err = DNSServiceBrowse( &client,
0,
0,
service_type.c_str(),
"",
IterateServiceInstances,
context );
The key point to note about this call is that the callback function, IterateServiceInstances, is a different member function one that expects to get the results of my browsing for instances of a specific service.
Driving the Callbacks
So how do these callbacks actually get generated? Does the DLL asynchronously make calls into my code whenever events occur?
The Bonjour SDK lets your program control when callbacks occur by giving you the handle to the message pump. When you call DNSServiceProcessResult() with a single argument of a DNSServiceRef, you will generate a single callback message for the given reference. The callback will occur within the context of the call to DNSServiceProcessResult().
When you call DNSServiceProcessResult(), the Bonjour DLL will block if there are no messages ready to process. So how do you know when there are messages ready?
The indicator that messages are ready is given by a file descriptor associated with the DNSServiceRef. You can get a copy of the file descriptor by calling DNSServiceRefSockFD(), passing in a copy of the reference. When the file descriptor has data ready to read, you have callbacks pending. The easiest way to check this condition is to use the select() function, which can check multiple references in one fell swoop.
In my implementation of the callback message pump, I rely on an unordered_map called m_ClientToFdMap that contains a copy of all the DNSServiceRef references currently waiting for responses. I create the necessary data structure used by select(), then call it to get a list of all references that have callbacks pending. The core of this code looks like this:
int result = select(0, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
if ( result > 0 ) {
//
// While iterating through the loop, the callback functions might delete
// the client pointed to by the current iterator, so I have to increment
// it BEFORE calling DNSServiceProcessResult
//
for ( auto ii = m_ClientToFdMap.cbegin() ; ii != m_ClientToFdMap.cend() ; ) {
auto jj = ii++;
if (FD_ISSET(jj->second, &readfds) )
DNSServiceErrorType err = DNSServiceProcessResult(jj->first);
}
}
This generates my callbacks efficiently, and because they are in the context of my main program's UI thread, I avoid a lot of unpleasantness.


