Zero Configuration Networking: The Definitive Guide
7.2. Event Handling with a select( ) Loop
It is important to understand the structure for working with the socket-based DNSServiceDiscovery APIs . First, you call a function that might, for example, initiate browsing for a service or register a service. Along with the other parameters, you pass in the address of an uninitialized DNSServiceRef (which the call initializes) and also pass in the address of the function that should be called back when interesting events happen. You then call DNSServiceRefSockFD( ), passing in the newly initialized DNSServiceRef, to extract the file descriptor for the Unix Domain Socket connection to the mdnsd daemon running in the background, and add this file descriptor to your select( ) loop. When the mdnsd daemon sends a message over the Unix Domain Socket connection to your process, your select( ) call will wake up, indicating that there is data waiting to be read on the file descriptor. You then call DNSServiceProcessResult( ); the DNS-SD code decodes the message and calls the appropriate callback function you previously specified when starting the operation. This section covers the three functions used to access sockets and to perform the callback: DNSServiceRefSockFD( ), DNSServiceProcessResult( ), and DNSServiceRef-Deallocate( ). A listing of the DNSServiceDiscovery Error Codes is also provided. 7.2.1. Event Callbacks
The use of asynchronous callbacks is essential for DNS-SD. Recall from your experience with the command-line tool that there was often a delay in discovering or registering services. You don't want to block the application while waiting for a reply to the function call. In addition, there may be more than one reply to a particular query. If you are browsing for services of a given type, there may be multiple instances of that type of service on the local network. Finally, you may wish to leave a service browser running so that you can track service instances as they come and go on the network. The skeleton code in Example 7-1 provides an overview of the process. (As you will see later in this chapter, you follow each call to a core DNSServiceDiscovery function, such as DNSServiceBrowse( ), with some code that enables the asynchronous callback.) Example 7-1. Skeleton of select( ) loop
void HandleEvents(DNSServiceRef serviceRef) { int dns_sd_fd = DNSServiceRefSockFD(serviceRef); // . . . while (!stopNow) { FD_ZERO(&readfds); FD_SET(dns_sd_fd, &readfds); // . . . tv.tv_sec = timeOut; tv.tv_usec = 0; int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv); if (result > 0) { // . . . if (FD_ISSET(dns_sd_fd, &readfds)) err = DNSServiceProcessResult(serviceRef); if (err) stopNow = 1; } } } You can see the basic structure in the HandleEvents( ) example function shown in Example 7-1. First, you pass the initialized service discovery reference to DNSService-RefSockFD( ), to get the file descriptor for the Unix Domain Socket that's being used to communicate with the mdnsd daemon running in the background. The file descriptor is added to the set of file descriptors the process is watching. After the select( ) call returns, if the bit is set to indicate that the DNSServiceDiscovery file descriptor has data available for reading, then you call DNSServiceProcessResult( ), which reads the message from the file descriptor, decodes it, and calls the appropriate callback function. Note that a real networking program would probably be watching more file descriptors than just the DNS-SD one(s) and may also have time-based operations it's performing, too. Example 7-2 shows a complete working example of a simple single-purpose HandleEvents( ) function. It runs until the user presses Ctrl-C to terminate the program, or one of the callback functions sets the stopNow variable. (If you're adding DNS-SD functionality to an existing select( )-based application, you'd probably add the DNS-SD file descriptors to your existing select( ) loop rather than changing your existing code to use the example select( ) loop shown here.) Example 7-2. HandleEvents using select( )
#include <dns_sd.h> #include <stdio.h> // For stdout, stderr #include <string.h> // For strlen(), strcpy( ), bzero( ) #include <errno.h> // For errno, EINTR #include <time.h> #ifdef _WIN32 #include <process.h> typedef int pid_t; #define getpid _getpid #define strcasecmp _stricmp #define snprintf _snprintf #else #include <sys/time.h> // For struct timeval #include <unistd.h> // For getopt( ) and optind #include <arpa/inet.h> // For inet_addr( ) #endif // Note: the select( ) implementation on Windows (Winsock2) //fails with any timeout much larger than this #define LONG_TIME 100000000 static volatile int stopNow = 0; static volatile int timeOut = LONG_TIME; void HandleEvents(DNSServiceRef serviceRef) { int dns_sd_fd = DNSServiceRefSockFD(serviceRef); int nfds = dns_sd_fd + 1; fd_set readfds; struct timeval tv; int result; while (!stopNow) { FD_ZERO(&readfds); FD_SET(dns_sd_fd, &readfds); tv.tv_sec = timeOut; tv.tv_usec = 0; result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv); if (result > 0) { DNSServiceErrorType err = kDNSServiceErr_NoError; if (FD_ISSET(dns_sd_fd, &readfds)) err = DNSServiceProcessResult(serviceRef); if (err) stopNow = 1; } else { printf("select( ) returned %d errno %d %s\n", result, errno, strerror(errno)); if (errno != EINTR) stopNow = 1; } } }
7.2.2. Accessing the Underlying Unix Domain Sockets
Your code interacts only indirectly with the Unix Domain Socket connection . DNSServiceRefSockFD( ) lets you get the raw file descriptor so you can add it to the set of file descriptors your select( ) loop is watching. When data arrives on this socket, you call DNSServiceProcessResult( ) to let the DNS-SD code decode the message and call your callback function. When you've finished what you're doing (say the user closes the browsing window), you call DNSServiceRefDeallocate( ) to stop the operation, close the Unix Domain Socket connection to the daemon, and free the resources and memory used to perform the operation. 7.2.2.1. DNSServiceRefSockFD( )
In Example 7-2, the DNSServiceRefSockFD( ) function was called like this: int dns_sd_fd = DNSServiceRefSockFD(serviceRef);
dns_sd_fd is given the value returned by DNSServiceRefSockFD( ). This value is the underlying file descriptor of the service discovery reference specified by serviceRef. The signature of DNSServiceRefSockFD( ) is: int DNSServiceRefSockFD (DNSServiceRef serviceRef);
7.2.2.2. DNSServiceProcessResult( )
The function DNSServiceProcessResult( ) is used to call the appropriate callback function when there is a response from the mdnsd daemon running in the background. Its signature is deceptively simple: DNSServiceErrorType DNSServiceProcessResult(DNSServiceRef serviceRef);
For proper use, start by referring again to Example 7-2, where a call to DNSServiceProcessResult( ) was made like this: result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv); if (result > 0) { DNSServiceErrorType err = kDNSServiceErr_NoError; if (FD_ISSET(dns_sd_fd, &readfds)) err = DNSServiceProcessResult(serviceRef); if (err) stopNow = 1; } One important thing to note in this small snippet is that you only want to call DNSServiceProcessResult( ) if select( ), or some similar system call, tells you data is ready. If you call DNSServiceProcessResult( ) when there is no data waiting to be read, it will block and wait until there is. 7.2.2.3. DNS Service Discovery error codes
Table 7-1 contains a listing of the possible error codes that arise when calling functions in the DNSServiceDiscovery APIs. Some errors, like kDNSServiceErr_BadParam, may be returned as an immediate result if you pass invalid parameters to a function. Others, like kDNSServiceErr_NameConflict, may be passed asynchronously to your callback function if an error condition occurs later. The DNSServiceDiscovery error names are all of the form kDNSServiceErr_NoErr, kDNSServiceErr_Unknown, and so on. In Table 7-1, the initial kDNSServiceErr_ part of each error name is omitted.
7.2.2.4. DNSServiceRefDeallocate( )
In Example 7-2, the DNSServiceRef was created elsewhere and passed into the HandleEvents routine. You get a DNSServiceRef when you call one of the core DNSService functions:
When this reference is no longer needed, it should be deallocated using : void DNSServiceRefDeallocate(DNSServiceRef serviceRef);
When you call DNSServiceRefDeallocate( ), the associated operation is stopped, the application's connection with the mdnsd daemon is terminated, the connecting socket is closed, and the memory associated with the reference is released. Before calling DNSServiceRefDeallocate( ), make sure you've removed the socket from your select( ) loop, or you'll have a select( ) loop with a dead socket in it, which can cause confusing results, especially in multithreaded programs. |
Категории