Zero Configuration Networking: The Definitive Guide

7.4. Event Handling with Cocoa RunLoop or Core Foundation CFRunLoop

So far in this chapter, you have used the select( ) loop to receive the asynchronous event notifications central to DNS-SD. In this last section, you will see alternative event delivery solutions. First, without any changes to the code presented in Examples 7-3 through 7-6, you can swap out the cross-platform code presented in Example 7-2 for a Mac OS X specific run loop that uses Core Foundation classes. Second, you will see how to alter the code to take advantage of the Windows-specific event loop.

If you're writing a Cocoa or Core Foundation application, you'll probably be using a Cocoa RunLoop or Core Foundation CFRunLoop (which are actually the same thing under the covers). You'll want to add a Cocoa- or Core Foundation-compatible event-generating object to your RunLoop. To do that, you extract the Unix Domain Socket from the DNSServiceRef, construct a CFSocket from that, and then construct a CFRunLoopSourceRef from that. The rough outline is shown in Example 7-7.

Example 7-7. Skeleton of CFRunLoop

typedef struct MyDNSServiceState { DNSServiceRef service; CFSocketRef socket; CFRunLoopSourceRef source; } MyDNSServiceState; void HandleEvents(DNSServiceRef serviceRef) { // . . . // Access the underlying Unix domain socket and create a CFSocket sock = DNSServiceRefSockFD(ref->service); ref->socket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, MySocketCallBack, &context); // . . . // Create a CFRunLoopSource from the CFSocket, add to run loop and start. ref->source = CFSocketCreateRunLoopSource(NULL, ref->socket, 0); CFRunLoopAddSource(CFRunLoopGetCurrent( ), ref->source, kCFRunLoopCommonModes); // . . . CFRunLoopRun( ); }

DNSServiceRefSockFD( ) extracts the Unix Domain Socket from the DNSServiceRef.

CFSocketCreateWithNative( ) makes a CFSocket from a native Unix socket.

CFSocketCreateRunLoopSource( ) makes a CFRunLoopSource from the CFSocket.

Once added to the RunLoop, MySocketCallBack( ) will be called every time there is data waiting to be read. You can use a single MySocketCallBack( ) routine for all of your DNS-SD operations. All it has to do is call DNSServiceProcessResult( ) on the right DNSServiceRef, and DNS-SD will do the rest to invoke the right callbacks.

static void MySocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void * data, void * info) { //. . . cast the context info to initialize ref MyDNSServiceState * ref = (MyDNSServiceState *)info; //...use the service discovery reference for callback err = DNSServiceProcessResult(ref->service); // handle error . . . }

Example 7-8 shows the full listing with all of the details.

Example 7-8. Core Foundation RunLoop example

// Simple example of how to handle DNSServiceDiscovery events using a CFRunLoop #include <dns_sd.h> #include <CoreFoundation/CoreFoundation.h> // Structure to hold CFRunLoop-related state typedef struct MyDNSServiceState { DNSServiceRef service; // Active DNSServiceDiscovery operation CFSocketRef cfsocket; // CFSocket for this operation CFRunLoopSourceRef source; // RunLoopSource for this CFSocket } MyDNSServiceState; // Remove a DNSServiceDiscovery operation from a CFRunLoop's // set of active event sources static void DNSServiceRemoveSource(CFRunLoopRef rl, MyDNSServiceState *ref) { assert(rl != NULL); assert(ref != NULL); // Remove the CFRunLoopSource from the current run loop. CFRunLoopRemoveSource(rl, ref->source, kCFRunLoopCommonModes); CFRelease(ref->source); // Invalidate the CFSocket. CFSocketInvalidate(ref->cfsocket); CFRelease(ref->cfsocket); // Workaround to give time to CFSocket's select( ) thread // so it can remove the socket from its FD set before we // close the socket by calling DNSServiceRefDeallocate. usleep(1000); // Terminate the connection with the daemon, which cancels the operation. DNSServiceRefDeallocate(ref->service); free(ref); } // Helper function: When CFRunLoop indicates an interesting event, // this function calls DNSServiceProcessResult( ) to handle it static void MySocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { #pragma unused(s) #pragma unused(type) #pragma unused(address) #pragma unused(data) DNSServiceErrorType err; MyDNSServiceState *ref = (MyDNSServiceState *)info; assert(ref != NULL); // Read a reply from the daemon, which will call the appropriate callback. err= DNSServiceProcessResult(ref->service); if (err != kDNSServiceErr_NoError) { fprintf(stderr, "DNSServiceProcessResult returned %d\n", err); // Terminate the discovery operation and release everything. DNSServiceRemoveSource(CFRunLoopGetCurrent( ), ref); } } // Add a DNSServiceDiscovery operation to a CFRunLoop's // set of active event sources static void DNSServiceAddSource(CFRunLoopRef rl, MyDNSServiceState *ref) { CFSocketContext context = { 0, ref, NULL, NULL, NULL }; CFSocketNativeHandle sock = DNSServiceRefSockFD(ref->service); assert(sock != -1); // Create a CFSocket from the underlying Unix Domain socket. ref->cfsocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, MySocketCallBack, &context); // Prevent CFSocketInvalidate from closing DNSServiceRef's socket. CFOptionFlags f = CFSocketGetSocketFlags(ref->cfsocket); CFSocketSetSocketFlags(ref->cfsocket, f & ~kCFSocketCloseOnInvalidate); // Create a CFRunLoopSource from the CFSocket. ref->source = CFSocketCreateRunLoopSource(NULL, ref->cfsocket, 0); // Add the CFRunLoopSource to the current run loop. CFRunLoopAddSource(rl, ref->source, kCFRunLoopCommonModes); } // Simple example: Here we just add a single DNSServiceDiscovery event source, // and then call CFRunLoopRun( ) to handle the events. In a program that already // has a main RunLoop, you'd just keep that as is, and use DNSServiceAddSource/ // DNSServiceRemoveSource to add and remove event sources from that RunLoop. void HandleEvents(DNSServiceRef serviceRef) { MyDNSServiceState ref = { serviceRef }; DNSServiceAddSource(CFRunLoopGetCurrent( ), &ref); CFRunLoopRun( ); }

Save this as DNSServiceCallbackCF.c. Compile and run this with any of the files from Examples 7-3 to 7-6 in place of DNSServiceCallbackSelect.c. The results should be the same as before.

Note that because a Cocoa RunLoop and a Core Foundation CFRunLoop are actually the same thing (Cocoa and Core Foundation just provide their own APIs to access the same underlying object), the code shown above can also be used in an Objective-C program. It may not look much like Objective-C (no square brackets all over the place), but that's no problem. The Objective-C compiler also fully supports standard C, and the code will compile and do exactly what you need.

DNSServiceDiscovery's context parameter helps us interface with Objective-C's object-oriented paradigm. When calling one of the DNSServiceDiscovery API routines, pass in the address of your C-style callback function and, for the context parameter, pass a reference to the object (usually self) that you want to handle the events, like this:

DNSServiceBrowse(&ref, 0, 0, srvtype, "", BrowseReplyFn, self);

Then, in your C-style callback function, you recover the object context by writing something like MyObjectType *self = (MyObjectType *)context, as shown here:

void BrowseReplyFn(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { MyObjectType *self = (MyObjectType *)context; [self doThis]; [self doThat]; [self addName: [NSString stringWithUTF8String:serviceName]]; //... and so on }

Категории