Section 9.2. Using the NSNetServices API in Cocoa


9.2. Using the NSNetServices API in Cocoa

Objective-C and Cocoa programming are a good fit for the asynchronous DNS-SD programming model. The notion of delegates makes it easy to set up asynchronous calls. All of the Cocoa Bonjour functionality is implemented in the classes NSNetService and NSNetServiceBrowser. When you initiate browsing, for example, you pass in a handle to an object that will act as the delegate. The appropriate delegate methods are called on this object to report on relevant activity that results from browsing. This is very similar to the listener interfaces in Java. As in Java, it's usual for the object making the NSNetServices call to specify self as the delegate to receive event notifications. One difference, though, is that in Java it is mandatory for the listener object to implement all of the methods required by the interface definition, and the Java compiler enforces this. In Objective-C, there's no compile-time check that the delegate object implements the required methods. If you accidentally specify the wrong object as the delegate, the compiler won't warn you that it implements none of the relevant methods. If you specify the right object as the delegate, but when implementing the required methods you mistype one of the method names, the compiler won't complain about that either. Your program won't crash; it just won't work as expected. This is a deliberate feature of the Objective-C languageif you choose not to implement a particular method, then calls to that method automatically become non-operationsbut it does mean you have to be careful.

9.2.1. Advertising a Service in Cocoa

To publish a service, you will create an instance of an NSNetService and pass in the information about the domain, service type, service name, and port with initWithDomain:type:name:port:. You next set the delegate for the NSNetService. Finally, you call the publish method. This process looks something like this:

 NSNetService *service = [[NSNetService alloc] initWithDomain:@""                     type:@"_example._tcp."                     name:@"sample"                     port:thePort];     [service setDelegate:self];     [service publish]; 

There are four delegate methods, which you can implement as you see fit. The netServiceDidPublish: method is new in Mac OS X 10.4.

 netServiceWillPublish: netServiceDidPublish: netService:didNotPublish: netServiceDidStop: 

Example 9-4 shows a simple example of publishing a service using the Cocoa API.

Example 9-4. Publishing a service in Cocoa
 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #import <Foundation/Foundation.h> @interface MyPublisher : NSObject     {     NSNetService *service;     } @end @implementation MyPublisher - (void)publishService:(UInt16)thePort     {     service = [[NSNetService alloc]         initWithDomain:@"" type:@"_example._tcp." name:@"Cocoa Example" port:thePort];     [service setDelegate:self];     [service publish];     } - (void)netServiceWillPublish:(NSNetService *)s     {     NSLog(@"WillPublish: %@.%@%@\n", [s name], [s type], [s domain]);     } - (void)netServiceDidPublish:(NSNetService *)s     {     NSLog(@"DidPublish: %@.%@%@\n", [s name], [s type], [s domain]);     } - (void)netService:(NSNetService *)s didNotPublish:(NSDictionary *)errorDict     {     NSLog(@"didNotPublish: %@.%@%@\n", [s name], [s type], [s domain]);     } @end int main(int argc, char *argv[])     {     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     // Get a new listening socket...     int s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);     struct sockaddr_in sa = { sizeof(sa), AF_INET };     int size = sizeof(sa);     bind(s, (struct sockaddr *)&sa, sizeof(sa));     getsockname(s, (struct sockaddr *)&sa, &size);     // ... and advertise it     [[[MyPublisher alloc]init] publishService: ntohs(sa.sin_port)];     [[NSRunLoop currentRunLoop] run];     [pool release];     return 0;     } 

This code can easily be built in Xcode by selecting File New Project... and then choosing Foundation Tool (near the bottom of the list of options). Open main.m, paste in the source code, and click the button to build and run the project. Your program should now be publishing a service of type _example._tcp with name Cocoa Example. You should be able to discover your registered service using the code listed in Example 9-5, or by running the command dns-sd -B _example._tcp.

Example 9-5. Browsing for services in Cocoa
 #import <Foundation/Foundation.h> @interface MyBrowser : NSObject     {     NSNetServiceBrowser *serviceBrowser;     } - (void)browseForServices; @end @implementation MyBrowser - (void)browseForServices     {     serviceBrowser = [[NSNetServiceBrowser alloc] init];     [serviceBrowser setDelegate:self];     [serviceBrowser searchForServicesOfType:@"_example._tcp." inDomain:@""];     } - (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)aNetServiceBrowser     {     NSLog(@"Starting to search . . .\n");     } - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser         didFindService:(NSNetService *)s moreComing:(BOOL)moreComing     {     NSLog(@"Add %@.%@%@\n", [s name], [s type], [s domain]);     } - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser         didRemoveService:(NSNetService *)s moreComing:(BOOL)moreComing     {     NSLog(@"Rmv %@.%@%@\n", [s name], [s type], [s domain]);     } @end int main(int argc, char *argv[])     {     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     [[[MyBrowser alloc]init] browseForServices];     [[NSRunLoop currentRunLoop] run];     [pool release];     return 0;     } 

9.2.2. Browsing in Cocoa

To begin browsing in Cocoa, you first instantiate NSNetServiceBrowser and assign a delegate to the object with the method setDelegate:. You can then begin browsing by calling the method searchForSevicesOfType: inDomain:, like this:

 serviceBrowser = [[NSNetServiceBrowser alloc] init]; [serviceBrowser setDelegate:self]; [serviceBrowser searchForServicesOfType:@"_example._tcp." inDomain:@""]; 

Because you have set the delegate to self, you also implement the delegate methods for browsing in the same class. You can track the lifecycle of the search and react to services that have been added or removed using the following methods:

 netServiceBrowserWillSearch: netServiceBrowserDidStopSearch: netServiceBrowser:didNotSearch: netServiceBrowser:didFindService:moreComing: netServiceBrowser:didRemoveService:moreComing:

You need to decide whether or not you are interested in each of these events and write the code that performs the actions you want. As a trivial example, you may wish to report on services that have been discovered. Here is a possible implementation of netServiceBrowser:didFindService: moreComing:

 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser         didFindService:(NSNetService *)s moreComing:(BOOL)moreComing     {     NSLog(@"Add %@.%@%@\n", [s name], [s type], [s domain]);     } 

A message is printed listing the name, type, and domain of the discovered service. In Example 9-5, you see that other delegate methods are implemented as well.

To run the code given in Example 9-5, run Xcode, select File New Project..., and then choose Foundation Tool. Open main.m, paste in the source code, and click the button to build and run the project. Your program should now be browsing for services of type _example._tcp. To test it, use the code in Example 9-4 to advertise a service, or use the command dns-sd -R "Cocoa Example" _example._tcp "" 123.

Your browser should discover the advertised service and print out:

 Add Cocoa Example._example._tcp.local.

Kill off the command-line process and your browser should report:

 Rmv Cocoa Example._example._tcp.local. 

9.2.3. Resolving in Cocoa

To resolve a DNS-SD service, you begin by creating an NSNetService instance using alloc( ) and initWithDomain:type:name: or by using a service discovered using NSNetServiceBrowser, as shown when browsing for services in Example 9-5. If you start a resolve running on an NSNetService object handed to you in your didFindService: delegate method, you must be sure to retain it first, or when your didFindService: delegate method returns, the object will be disposed and your program will crash.

Remember that, unlike registering and browsing, to resolve you do need to specify a domain. To work asynchronously, you next have to assign a delegate that is used for the callback methods. Finally, you begin to resolve the service by calling resolve. Here's a simple example of how you might resolve a service:

 NSNetService *service = [[NSNetService alloc] initWithDomain:@"local."                                       type:@"_example._tcp."                                       name:@"sample"]; [service setDelegate:self]; [service resolve]; 

The delegate methods for resolving services are:

 netServiceDidResolveAddress: netService:didNotResolve: 

Example 9-6 shows a simple example of how you might resolve the service named Cocoa Example of type _example._tcp.

Example 9-6. Resolving a service in Cocoa
 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #import <Foundation/Foundation.h> @interface MyResolver : NSObject     {     NSNetService *service;     } @end @implementation MyResolver - (void)resolveService     {     service = [[NSNetService alloc]         initWithDomain:@"local." type:@"_example._tcp." name:@"Cocoa Example"];     [service setDelegate:self];     [service resolve];     } - (void)netServiceWillResolve:(NSNetService *)sender     {     NSLog(@"netServiceWillResolve: %@.%@%@\n",         [sender name], [sender type], [sender domain]);     } - (void)netServiceDidResolveAddress:(NSNetService *)s     {     NSLog(@"DidResolve: %@.%@%@\n", [s name], [s type], [s domain]);     // May get more than one reply     NSArray *addresses = [s addresses];     int count;     for (count = 0; count < [addresses count]; count++)         {         char addr[256];         struct sockaddr_in *sa = (struct sockaddr_in *)             [[addresses objectAtIndex:count] bytes];         if (inet_ntop(sa->sin_family, &sa->sin_addr, addr, sizeof(addr)))             NSLog(@"DidResolve: %s:%d \n", addr, ntohs(sa->sin_port));         }     } - (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict     {     NSLog(@"didNotResolve: %@.%@%@\n",       [sender name], [sender type], [sender domain]);     } @end int main(int argc, char *argv[])     {     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     [[[MyResolver alloc]init] resolveService];     [[NSRunLoop currentRunLoop] run];     [pool release];     return 0;     } 

Just as with the other examples, make a new Foundation Tool, paste in the code, and then compile and run it.

Now advertise a service called Cocoa Example of type _example._tcp:

 dns-sd -R "Cocoa Example" _example._tcp "" 123 

As you do, you'll see that your resolve call succeeds and prints out the list of possible addresses for this service. From this point on, you're no longer using the Cocoa NSNetService API. The Cocoa API has done its job. It has resolved the named service to its address(es) and port number. Connecting to the service and using it are done using any of the many networking APIs already available.

There's no guarantee that all the addresses you see will be reachable. Also, some may offer faster performance than otherse.g., an address on Gigabit Ethernet is likely to give a lot faster connection than an address on AirPort. In an ideal program, you'd attempt connections to all of the possible addresses simultaneously to see which one succeeds fastest, and then as soon as one succeeds, cancel the other outstanding attempts.

If you're writing an application that only needs to run on Mac OS X 10.4 or later, then there's a new resolveWithTimeout: method you can use that will give up and return an error after the specified interval has elapsed.




Zero Configuration Networking. The Definitive Guide
Zero Configuration Networking: The Definitive Guide
ISBN: 0596101007
EAN: 2147483647
Year: 2004
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net