Section 9.3. A Cocoa Bonjour Extended Example


9.3. A Cocoa Bonjour Extended Example

Let's use the Cocoa NSNetService API to create a simple application. When the example is complete, you will start up the application and see the window shown in Figure 9-1.

Figure 9-1. Startup screen for Bonjour Mood Ring


This is one of the rare examples of an application where there is no actual application protocol. The entire functionality of the application consists of browsing to discover peers on the network and monitoring their TXT record status. All communication happens as a result of updates to the TXT record status, and, consequently, there is no application-layer protocol.

Because TXT record updates have a cost on the network, the mdnsd daemon limits how rapidly a program may update its TXT records in order to help limit the bad impact buggy application software could have on the network. In Mac OS X 10.4, the TXT record may be updated up to 10 times per minute. If you drag the slider around rapidly and exceed this rate, you'll see messages appear in system.log saying "Excessive update rate...delaying announcement." Your new mood will be announced eventually, but before sending out the packet, the mdnsd daemon will sit and wait a few seconds to see if you're going to move the slider again. After you've decided what your new mood will be and not changed your mind for a few seconds, the mdnsd daemon will then send out the packet to notify your peers on the local network.

Note that this example application uses some of the newer utility functions, such as NSNetService dataFromTXTRecordDictionary: and NSNetService startMonitoring, introduced in Mac OS X 10.4. It won't work on Mac OS X 10.3.x.

Press the Start Service button and a progress indicator will start to spin until your service is registered. Then the Start Service button will be replaced by a rounded text box containing your full name, with the background color matching your mood. A slider bar also appears, allowing you to change your mood. A list of yours and other discovered services is provided at the bottom of the window, with each name displayed in a text color that matches their mood, as shown in Figure 9-2.

Figure 9-2. Bonjour Mood Ring with service running


The application is built on two classes. MoodBeacon advertises the service and is responsible for updating the TXT record when there is a change to the slider position. MoodBrowser discovers other services and is responsible for updating the UI to reflect the current slider setting of other services.

This program is based on an example Ken Arnold demonstrated as a Jini-based application at MacHack 2003, and it is presented with his permission. The noteworthy difference is that with Jini, someone somewhere on the network needs to be running a network service called the "Jini Lookup Service." Clients discover the local Jini Lookup Service by sending a UDP packet to the IP multicast address 224.0.1.85 on port 4160 and waiting for a response. The clients then register their presence with that Jini Lookup Service and query the Jini Lookup Service to find the list of other peers that have registered. For Jini to work, that vital piece of network infrastructure needs to be present and working. In contrast, the Bonjour version needs no infrastructure. For two Bonjour Mood Ring programs to communicate over Ethernet, the only network infrastructure you need is a length of Ethernet cable with a plug at each end. If you use 802.11 wireless, you don't even need the cable. Work is being done to marry the benefits of Jini with the server-less nature of Zeroconf, by defining a mapping from Jini interface specifications to DNS-SD service subtypes. A Jini client can then just perform a DNS-SD browsing query for the specific DNS-SD service subtype that corresponds to the Jini interface specification it's looking for and discover a list of Jini services on the network that implement that desired interface.

9.3.1. Creating the GUI

If this is your first Cocoa application, the description below may go a little fast for you. You may want to consult the book Learning Cocoa (O'Reilly). The key is that with Cocoa applications, there is a strong separation of the View from the Model and Control layers. The result is that there is a lot less actual code in the resulting application than what you might be used to from other languages. You create the view using the application Interface Builder. You create this in two parts: you design the GUI using graphical tools and you create a skeleton outline of the classes that will interact with the GUI.

To begin, double-click on MainMenu.nib to open it in Interface Builder. You should see a window. Select the menu item Tools Show Info to open up the inspector window. You can change the size of the window and text in the title bar of the window and set various other attributes. You can also add other components to the window and configure them. Figure 9-3 gives you an idea of what the final window should look like.

Figure 9-3. GUI for the BonjourMoods application


At the top, you can see an NSButton labeled Start Service, which is initially visible. In the same location is a rounded NSTextField and to the right an NSProgressIndicator, both of which are initially invisible. Below is an NSSlider with five markers with values from 0 to 4. The discovered services will be listed in an NSTableView titled Moody Friends.

Create two classes to implement the service. The MoodBeacon class will be used to advertise a service and the MoodBrowser class will be used to discover other services of the same type. In the class view, you will add actions and outlets to your classes. Actions correspond to methods you will create in your class to respond to user input. For example, when the user clicks on the Start Service button, you would like to publish the service. You create an action in the MoodBeacon class named publishService and create an instance of the MoodBeacon. All that remains is for you to implement this method when you are editing the source code. Outlets correspond to variables in your source code that are handles to the GUI object. For example, if you are going to make the Start Service button invisible, you need an outlet in order to refer back to it.

MoodBeacon has five outlets: groupMood (an NSTextField), happinessSlider (an NSSlider), progressIndicator (an NSProgressIndicator), serviceStarter (an NSButton), and userName (an NSTextField). MoodBeacon also has two actions: publishService: and updateMood:. On the other hand, MoodBrowser has no actions and only one outlet, friendView (an NSTableView). The data source and delegate for the NSTableView is the MoodBrowser instance associated with the view. Complete the GUI by using Interface Builder to wire the appropriate GUI component to the corresponding outlet or action and save your work.

9.3.2. The Generated MoodBeacon and MoodBrowser Header Files

The work you did above in Interface Builder created header files for the two classes. Example 9-7 shows the header file for MoodBeacon. Note the five outlets you created and wired up, followed by the signatures for the two methods you specified as actions.

Example 9-7. MoodBeacon.h
 /* MoodBeacon */ #import <Cocoa/Cocoa.h> @interface MoodBeacon : NSObject {     IBOutlet NSSlider *happinessSlider;     IBOutlet NSProgressIndicator *progressIndicator;     IBOutlet NSButton *serviceStarter;     IBOutlet NSTextField *userName; } - (IBAction)publishService:(id)sender; - (IBAction)updateMood:(NSSlider *)sender; @end 

The header file for MoodBrowser is shown in Example 9-8 and contains the outlet corresponding to the NSTableView named friendView. You will also need to add the two NSMutableArray objects friendsList and moodsList as well as the NSNetServiceBrowser object serviceBrowser.

Example 9-8. MoodBrowser.h
 /* MoodBrowser */ #import <Cocoa/Cocoa.h> @interface MoodBrowser : NSObject {     IBOutlet NSTableView *friendView;     NSMutableArray * friendsList;     NSNetServiceBrowser * serviceBrowser;     NSMutableArray * moodsList; } @end 

9.3.3. Advertising the Service with MoodBeacon

The MoodBeacon class can respond to two actions: publishService: and updateMood:. As you saw above, when the user presses the Start Service button, the publishService: method is called. The first action taken in the publishService: method is to allocate and initialize an NSNetService object of type _moodring._tcp with name equal to the user's full name, as returned by a call to the method NSFullUserName( ). The MoodBeacon object is then registered as the delegate, the service is published, and the updateMood: method is called.

Before discussing the updateMood: method, let's look at how to use the delegate methods to communicate progress to the end user. When the netServiceWillPublish: method is called, the NSProgressIndicator begins to spin to indicate that an action is being taken by the system. Here is all that is required to accomplish this:

 - (void)netServiceWillPublish:(NSNetService *)sender {     [progressIndicator startAnimation:self]; } 

Similarly, when the method netServiceDidPublish: is called, the NSProgressIndicator stops spinning and the NSButton appears to be replaced with a text area containing the username. The slider with which the user can set his happiness level also appears. If, instead, the didNotPublish: method is called, an error is reported to the user.

The updateMood: method takes the value of the NSSlider, updates the TXT record for the service, and sets the background color of the NSTextField. To update the TXT record, first create an NSDictionary of entries with keys given by the NSStrings @"txtvers" and @"mood". The value corresponding to txtvers is 1 and the value corresponding to mood is the string value of the NSSlider. The method setTXTRecordData: dataFromTXTRecordDictionary: is called to update the TXT record. Example 9-9 shows the complete listing for MoodBeacon.m.

Example 9-9. MoodBeacon.m
 #import "MoodBeacon.h" @implementation MoodBeacon NSNetService * service; - (void)netServiceWillPublish:(NSNetService *)sender {     [progressIndicator startAnimation:self]; } - (void)netServiceDidPublish:(NSNetService *)sender {     [progressIndicator stopAnimation:self];     [userName setEnabled:YES];     [serviceStarter setHidden:YES];     [userName setHidden:NO];     [happinessSlider setHidden:NO];     [userName setStringValue:[sender name]]; } - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict {     [userName setStringValue:@"Error: did not publish"]; } - (IBAction)publishService:(id)sender {     // Get us a unique listening socket number to advertise     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);     service = [[NSNetService alloc] initWithDomain:@""                     type:@"_moodring._tcp."                     name:NSFullUserName(  )                     port:ntohs(sa.sin_port)];     if ( [[serviceStarter title] isEqualToString:@"Start Service"])     {         [service setDelegate:self];         [service publish];         [self updateMood:happinessSlider];     } } - (IBAction)updateMood:(NSSlider *)sender {     NSMutableDictionary * moodDictionary = [NSMutableDictionary dictionaryWithCapacity:2];     NSString * txtversKey = @"txtvers";     NSString * moodKey = @"mood";     NSString * txtversValue = @"1";     NSString * moodValue = [sender stringValue];     [moodDictionary setObject:txtversValue forKey:txtversKey];     [moodDictionary setObject:moodValue forKey:moodKey];     [service setTXTRecordData:[NSNetService dataFromTXTRecordDictionary:moodDictionary]];     float currentValue = [sender floatValue]/4;     [userName setBackgroundColor:[NSColor colorWithCalibratedRed:3.3 * (1- currentValue)                                   green:2.0 * (currentValue)                                   blue:0.0 alpha:1.0]]; } @end 

The MoodBeacon class is used to announce the availability of the new service and the current state of the mood of the person who the service represents. The next section will detail the actions performed by services receiving messages from MoodBeacon objects.

9.3.4. Finding and Using the Service with MoodBrowser

As you saw in the MoodBrowser.h code listing in Example 9-8, the MoodBrowser class has two NSArrayList objects: friendsList and moodsList. When a service is discovered, the following callback method is called:

 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser                           didFindService:(NSNetService *)aNetService                           moreComing:(BOOL)moreComing {     [friendsList addObject:aNetService];     [moodsList addObject:[NSColor blackColor]];     [aNetService setDelegate:self];     [aNetService startMonitoring];     if(!moreComing)         [friendView reloadData]; } 

The discovered service is added to friendsList and initially the color assigned to represent the mood of this service is black. Similarly, when a service is removed, the corresponding entry is removed from both friendsList and moodsList.

The view is only reloaded once there are no more services coming. This makes the program run a lot faster on a network where there are lots of other MoodRing participants. Instead of updating the window a hundred times, it adds a large batch of participants to friendsList, and only when there are no more results waiting to be handled does it update the window.

For active services, the MoodBrowser object listens for changes to the TXT record. If there is a change, then the appropriate entry in moodsList is updated and the text color of the name of the corresponding entry in friendsList is changed to reflect the updated mood:

 - (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data {   NSString * temp = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];     float currentValue = [[temp substringFromIndex:[temp length]-1] floatValue]/4;     [moodsList replaceObjectAtIndex:[friendsList indexOfObject:sender]                          withObject:[NSColor colorWithCalibratedRed:3.3 * (1- currentValue)                                      green:2.0 * (currentValue)                                      blue:0.0 alpha:1.0]];     [friendView reloadData]; } 

Example 9-10 shows the complete listing of MoodBrowser.m.

Example 9-10. MoodBrowser.m
 #import "MoodBrowser.h" @implementation MoodBrowser - (void) setUpServiceBrowser {     serviceBrowser = [[NSNetServiceBrowser alloc] init];     [serviceBrowser setDelegate:self];     [serviceBrowser searchForServicesOfType:@"_moodring._tcp."                     inDomain:@""]; } - (id) init {     self = [super init];     friendsList = [[NSMutableArray alloc] init];     moodsList = [[NSMutableArray alloc] init];     [self setUpServiceBrowser];     return self; } - (void) dealloc {     [friendsList release];     [super dealloc]; } - (int) numberOfRowsInTableView:(NSTableView *)tableView {     return [friendsList count]; } - (id)tableView:(NSTableView *)aTableView         objectValueForTableColumn:(NSTableColumn *)aTableColumn             row:(int)rowIndex {     return [[friendsList objectAtIndex:rowIndex] name]; } - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser                           didFindService:(NSNetService *)aNetService                           moreComing:(BOOL)moreComing {     [friendsList addObject:aNetService];     [moodsList addObject:[NSColor blackColor]];     [aNetService setDelegate:self];     [aNetService startMonitoring];     if(!moreComing)         [friendView reloadData]; } - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser                           didRemoveService:(NSNetService *)aNetService                           moreComing:(BOOL)moreComing {     [moodsList removeObjectAtIndex:[friendsList indexOfObject:aNetService]];     [friendsList removeObject:aNetService];     [aNetService stopMonitoring];     if(!moreComing)         [friendView reloadData]; } - (void)tableView:(NSTableView *)inTableView      willDisplayCell:(id)inCell       forTableColumn:(NSTableColumn *)inTableColumn               row:(int)inRow {     [inCell setTextColor:[moodsList objectAtIndex:inRow]];     } - (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data {   NSString * temp = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];     float currentValue = [[temp substringFromIndex:[temp length]-1] floatValue]/4;     [moodsList replaceObjectAtIndex:[friendsList indexOfObject:sender]                          withObject:[NSColor colorWithCalibratedRed:3.3 * (1- currentValue)                                      green:2.0 * (currentValue)                                      blue:0.0 alpha:1.0]];     [friendView reloadData]; } @end 

In this simple example, you saw how to publish and discover services. You also saw how to update and respond to updates of TXT records. This example shows you how you might Bonjour-enable your own Cocoa projects.




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