macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Incorporating Rendezvous into Your Cocoa Applications, Part 2
Pages: 1, 2, 3, 4

If you look at the NSNetService class reference you will see an initializer in addition to the one that we have already discussed. This additional initializer is initWithDomain:type:name:, and it is used to initialize resolution-appropriate instances of NSNetService. Note that it is identical to the publication-appropriate initializer we used earlier, except we can't specify a port. If you attempt to send a publish message to a net service object initialized with this method, then the delegate's netService:didNotPublish: method will be invoked with an NSNetServicesBadArgumentError. We never have to create resolution-appropriate net service objects ourselves, since it is all taken care of by NSNetServiceBrowser, but I thought it was worth mentioning.



Typically, when a service is discovered and reported by didFindService:moreComing:, we immediately send a resolve message to the net service object passed to us. This takes care of determining the connection information before it is needed by the user. The concern is that resolving the address and port number of a service may take a non-trivial amount of time. Since we can begin the resolution process in the background, we do so to avoid delaying the connection when the user actually requests it.

The final pair of methods, didFindDomain:moreComing: and didRemoveDomain:moreComing:, are used to notify the delegate when domains have been discovered, and when previously-discovered domains have disappeared. NSNetServiceBrowser has two methods for searching for domains: searchForAllDomains and searchForRegistrationDomains. A registration domain is one that we have the authority to register a service on: .local is a registration domain.

In the last four methods we've discussed, I have yet to mention anything about the moreComing: argument, which is a BOOL. When a service browser object is searching, it may get responses from several hosts running services within a short period of time. The service browser, however, can only report discovered domains or services one at a time.

The idea behind the moreComing: flag is that when the service browser is reporting discovered services, it uses this flag to notify the delegate whether or not there are additional services to report that were discovered at the same time. When the moreComing: flag is NO, the method should update the user interface. By checking the value of this flag, the delegate can cut down on the number of expensive user interface updates that may need to be performed.

Let's go through and implement our net service browser delegate methods now. Since we don't have a user interface for displaying discovered domains, we will simply log any discovered and removed domains:

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
	    didFindDomain:(NSString *)domainString 
	    moreComing:(BOOL)moreComing
{
    NSLog( @"Discovered the domain %@", domainString );
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
	    didRemoveDomain:(NSString *)domainString 
	    moreComing:(BOOL)moreComing
{
    NSLog( @"Removing the domain %@", domainString );
}

Discovered services will be displayed in the table discoveredServicesList. The net services whose names are displayed in this list are stored in the array discoveredServices. When a service is found, it is resolved and added to the array. When a service is removed from the network, it is removed from the array. These operations are done in the didFindService:moreComing: and didRemoveService:moreComing: methods shown here:

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
	    didFindService:(NSNetService *)aNetService 
	    moreComing:(BOOL)moreComing
{
    [discoveredServices addObject:aNetService];
    
    [aNetService setDelegate:self];
    [aNetService resolve];
    
    if ( moreComing == NO )
	       [discoveredServicesList reloadData];
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
	    didRemoveService:(NSNetService *)aNetService 
	    moreComing:(BOOL)moreComing
{
    [discoveredServices removeObject:aNetService];
    
    if ( moreComing == NO )
	       [discoveredServicesList reloadData];
}

Notice in these methods how we use the moreComing flag to control when the table's data is reloaded: only when there are no more changes to report do we update the interface. Also note in didFindService:moreComing: that we set self as the delegate of the discovered service. This is so that the controller object can be notified of the progress of resolving the net service. For example, we can add implementations for the NSNetService delegate methods netServiceDidResolveAddress:, netService:didNotResolve:, and netServiceWillResolve:. The first notifies the delegate of a successful address resolution, the second is invoked in the event of an error, and the third tells us when the network is prepared for resolving. For the sake of completeness, we will add the following simple implementations for these methods:

- (void)netService:(NSNetService *)sender 
        didNotResolve:(NSDictionary *)errorDict
{
    NSLog( @"There was an error while attempting to resolve %@.", 
                                                      [sender name] );
}

- (void)netServiceDidResolveAddress:(NSNetService *)sender
{
    NSLog( @"successfully resolved address for %@.", [sender name] );
}

- (void)netServiceWillResolve:(NSNetService *)sender
{
    NSLog( @"Attempting to resolve address for %@.", [sender name] );
}

The next method we need to implement is netServiceBrowserDidStopSearch:. In this method we do two things: we remove all previously-discovered net service objects from the discoveredServices array, and we reload the table view to reflect the new state of the discoveredServices array:

- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser
{
    if ( aNetServiceBrowser == serviceBrowser ) {
	       [discoveredServices removeAllObjects];
	       [discoveredServicesList reloadData];
    }    
}

We have two objects performing two different searches, both with the same delegate. Since this method will be invoked by both objects when they are sent stop messages, we need to check which browser object is invoking this method. If the argument browser is the serviceBrowser object, only then do we remove the services and update the UI.

Finally, after all of this work, we have a working Rendezvous service publisher and browser. You should now be able to compile it, run it, and see your own name appear when you publish the service. If you have access to additional Macs on your network, try dropping a copy of this app on them and starting it up. When testing RCE, you must be connected to a network for your own service name to appear. The current mDNS responder implementation in Jaguar has a bug that does not recognize the loopback interface as a valid network interface, preventing your service from working properly in the absence of another network interface.

As always, a complete project folder is available for download here.

Final Thoughts

The ZeroConf working group has the following to say with respect to the technology they are developing:

It is important to understand that the purpose of Zeroconf is not solely to make current personal computer networking easier to use, though this is certainly a useful benefit. The long-term goal of Zeroconf is to enable the creation of entirely new kinds of networked products, products that today would simply not be commercially viable because of the inconvenience and support costs involved in setting up, configuring, and maintaining a network to allow them to operate.

This is an interesting perspective that should make all developers pause and take a look at this technology. Stop and think about that. Apple says that if you have a product that does networking, it should advertise its service using Rendezvous. This, I think, is where we will see Rendezvous and ZeroConf networking take root: in existing applications that we already know and use. Once developers get a feel for Rendezvous, then we will begin to see apps be built that were too difficult to support previously. Already we are seeing collaboration apps, like the recently-released iStorm, that use Rendezvous. Peer-to-peer apps will become more common. I suppose the end result of all of this is to make the flow of information between peers and colleagues and friends as easy as flipping the power switch.

What we saw here was how to use Rendezvous for Cocoa. My hope is that you will take this and run with it. If your thing isn't Cocoa, but perhaps cross-platform Unix applications, or programming on any platform, then download the Rendezvous source code and play around with it -- it's well worth a look if you have had any exposure to Unix network programming. You might see something you like. Right now, I'm working on a side project to embed a multicast DNS responder in the software I work on at my day job. If everything goes as planned (which appears to be the case thus far) I'll write something up about it to share. This is really cool stuff, the kind of thing I always imagined that computers should be able to do.

Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.


Read more Programming With Cocoa columns.

Return to the Mac DevCenter.