TweetFollow Us on Twitter

Using Rendezvous

Volume Number: 19 (2003)
Issue Number: 7
Column Tag: Cocoa

Using Rendezvous

How to discover and publish services on the LAN

by Wolfgang Ante

What is Rendezvous?

To start with a buzzword: Rendezvous is zero-config networking. That means using Rendezvous you should not have to set up any server addresses, user names, and such to access and use services on the local network. Just use them. Apple users not switching over from Windows recently already know this: AppleTalk supported zero-config networking from the beginning. So why is Rendezvous better?

First, Rendezvous is based on IP networking. IP is the industry standard, the Internet protocol, and has far less overhead than most other protocols. On the other hand AppleTalk is a proprietary protocol with a lot of overhead, making it more expensive and slower.

Second, Rendezvous is open. The source code is available for download and you may include it into your software for free. You will not need this as long as your program is running on Mac OS X only. But for developers on Linux and Windows this means they can use Rendezvous, too. If you take a look at the Rendezvous mailing list you can easily see that they do.

Publishing and browsing services

When your applications wants to provide a service on the network you will have to publish it, when you want to use a service published by someone else you will have to browse for services. A service is whatever you want it to be. Rendezvous is just helping to announce and find services. It gives you all you need to connect, namely the IP address and port number, but doesn't setup the connection itself.

Cocoa gives you a very powerful way to connect to other Cocoa applications on the network called Distributed Objects. My next article will be on Distributed Objects, but for now let's get into the details of using Rendezvous in code.

Goals

I noticed that Rendezvous is not widely adopted yet, and after working with it myself I think I know why: You still have to care about a lot of things you are not very much interested in, even in Cocoa. Don't get me wrong, I think the Cocoa API to Rendezvous is very good, but for 90% of the applications that could use Rendezvous it would be very handy to have something that gives you just two things: The ability to publish a service and to browse for others who published the same type of service in one easy to use class.

This article will show you how Rendezvous works and additionally will give you a simplified interface (the ARendezvousController class) that you are free to use in your software. Using Rendezvous will open your application for teamwork. Think about it, in a lot of applications it makes sense to connect and communicate!

Overview

Cocoa gives you two classes for the two main objectives: NSNetService to publish services and NSNetServiceBrowser to browse published services.

Because network operations may take some time to finish, both classes use the concept of delegation to keep your application responsive. You ask for what you want and continue. As soon as the information is available your delegate will be notified about the found information. This also solves the problem of dynamic changes, when services are published or removed later on. Every time the situation changes your delegate will be notified.

There are quite a lot of delegation methods that make understanding the API confusing at first. I will try to concentrate on a typical flow of information, if you are interested in the complete list of delegation methods later, I recommend using the freeware "AppKiDo" from Andy Lee. It is a very good Cocoa API browser, I could not live without it anymore. It is available at <http://homepage.mac.com/aglee/downloads>.

Publishing

Publishing a service is quite easy. You create a service by creating an object of type NSNetService, set the delegate and publish the service. Listing 1 shows this:

Listing 1:

Publishing

NSNetService  *service;
NSString      *publishedName;
Int           portNumber;

service = [[NSNetService alloc] initWithDomain:@""
  type:@"_servicetype._tcp."
  name:publishedName
  port:portNumber];
[service setDelegate:delegate];
[service publish];

To publish on the default domain (which is the 'local' domain, i.e. your LAN) you pass an empty string to initWithDomain. type is an identifier for the kind of service you are publishing. The naming convention for this is "_servicetype._tcp." where you replace servicetype with your identifier. The type is usually not visible to the user, so be as clear or obscure as you like. name identifies your service to the network and must be unique. The name usually is visible to the user. Finally port must contain a port number you already acquired for the service.

setDelegate sets the delegate, this is the object that will be notified later. publish will start the process. As soon as you publish your service it will broadcast its presence on the network.

The only interesting delegate method at this point is netService:didNotPublish:. This will be sent to your delegate when the service could not be published, otherwise you can assume that your service has been published and is visible to others now.

Browsing

Browsing for services is a bit more complicated. You start by creating an object of type NSNetServiceBrowser, set the delegate and start browsing. Listing 2 shows this:

Listing 2:

Start browsing

NSNetServiceBrowser *browser;

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

First you create the object and set the delegate to be notified, then you start browsing by asking for services of the type you are interested in. As before pass an empty string to browse in the default domain.

Again the requested information will be sent to your delegate later. The interesting ones that will be sent to you at this point are netServiceBrowser:didFindService:moreComing: and netServiceBrowser:didRemoveService:moreComing:. Rendezvous is dynamic, so services will show up and go away from time to time. As long as you do not stop browsing, your delegate will receive these messages every time something changes about the availability of services of the requested type on the network.

Lets start by looking at netServiceBrowser:didFindService:moreComing:. Listing 3 shows an implementation:

Listing 3:

Service found

- (void)netServiceBrowser:
  (NSNetServiceBrowser *)aNetServiceBrowser 
  didFindService:(NSNetService *)aNetService 
  moreComing:(BOOL)moreComing
{
    [aNetService setDelegate:self];
    [aNetService resolve];
}

You receive three parameters: aNetServiceBrowser is the browser that sent the message, aNetService is the service that has been found and moreComing tells you if there are more queued service objects. The moreComing parameter looks a bit strange at first, but can be used as a hint to your user interface code to delay updating.

The information you are probably most interested in comes as the second parameter and it looks like this is already everything you need. Unfortunately you only get one third of the information you are interested in. You can send the service object a name message to get the name of the service. To obtain the IP address and port number you have to set a delegate and send a resolve message to the service. Don't forget to set the delegate, since this time the service will sent you messages, not the browser. When you send resolve this will again result in messages sent to the delegate. The interesting one this time is netServiceDidResolveAddress:.

Following the successful path lets start by looking at netServiceDidResolveAddress:. Listing 4 shows an implementation:

Listing 4:

Addresses resolved

- (void)netServiceDidResolveAddress:(NSNetService *)sender
{
    NSString           *name = nil;
    NSData             *address = nil;
    struct sockaddr_in *socketAddress = nil;
    NSString           *ipString = nil;
    int                port;
    
    for (i = 0; i < [[sender addresses] count]; i++)
    {
        name = [sender name];
        address = [[sender addresses] objectAtIndex:i];
        socketAddress = (struct sockaddr_in *)
          [address bytes];
        ipString = [NSString stringWithFormat: @"%s",
          inet_ntoa (socketAddress->sin_addr)];
        port = socketAddress->sin_port;
        [...]
     }
}

The only parameter is the service object that sent the message. Now the service object holds all the needed information to connect to the service. You obtain the name by sending a name message to the service. To find the IP address and port number you send an addresses message to the service. You receive an array containing all addresses. This array usually contains exactly one address, but to be on the save side loop through all addresses in the array. An address is of type sockaddr_in. This is a standard networking structure of Unix. See the above code for the details. After you have the name, IP and port you are ready to use that information. You will probably store the information in an array so you can access it any time you need it.

Lets go one step back to netServiceBrowser:didRemoveService:moreComing:. This message will be sent to you every time a service has been removed. If you stored the information in an array you have to remove the entry for that service to keep the array up-to-date.

Browsing problems

On Mac OS X 10.2.3 and before there is a problem with service browsing and switching locations. When you switch the location from the Apple menu (or change your IP address in any other way) the browser gets seriously confused. Your old and new IP may or may not show up. You may get entries that are simply wrong, pointing to your old IP address.

This happens with Apple's Rendezvous-enabled products, too. I saw myself showing up in iChat double after switching back and forth and Apple's AFPServer (the 'Personal File Sharing' server) also has problems with it, when you look at your logs.

It might be that this is already fixed when you read this, since Apple has released fixes to Rendezvous with every of the last three system updates. I read it is much better with Mac OS X 10.2.5, but to be nice to your users you should try to offer them a workaround on systems before 10.2.5 at least.

My suggestion is to offer refresh functionality. A refresh starts a complete new search forgetting about the cached entries.

If you want to automate this, you could regularly check if your IP address has changed (use [[NSHost currentHost] address] for this) and then refresh. The ARendezvousController class does not do this, but it offers a refreshBrowsing method that you can hook up to a button in your user interface easily.

Making things easier

By now you have a good overview of how to use Rendezvous. But like me, I guess you will think that you don't want to weave that all into your code every time you want to use Rendezvous.

Therefore I came up with ARendezvousController. It has a simplified interface and still provides everything to publish and browse services. An ARendezvousController object cares about one service type, publishing and browsing it. It provides start/stop methods for publishing and browsing. It has exactly one delegate method that is called every time something changes. It keeps track of the found services and on request returns an array with all found services, ready with name, IP address and port number. It even creates the socket others will connect to for you.

Listing 5:

Public interface of ARendezvousController

// public interface
@interface ARendezvousController : NSObject

- (id)initWithName:(NSString *)name type:(NSString *)type
  port:(int)port;
- (void)dealloc;
- (id)delegate;
- (void)setDelegate:(id)object;
- (NSString *)name;
- (BOOL)setName:(NSString *)name;
- (NSSocketPort *)socket;

- (void)activateBrowsing:(BOOL)flag;
- (BOOL)isBrowsing;
- (void)refreshBrowsing;
- (void)activatePublishing:(BOOL)flag;
- (BOOL)isPublished;
- (NSArray *)discoveredServicesWithInfo;

- (NSString *)ipForName:(NSString *)name;
- (int)portForName:(NSString *)name;

@end

// to be implemented by the delegate
@interface NSObject (RendezvousControllerDelegate)

- (void)discoveredServicesDidChange:(id)sender;

@end

Using ARendezvousController

Instead of going through the list method by method, lets start with a real world example. The following code will fill an NSTableView with all available services of type @"_demo._tcp." on your local network. The table view will be updated dynamically every time services are published and removed without user intervention. Just like you know it from iChat.

The complete demo project including ARendezvousController can be downloaded from <http://www.artissoftware.com/rendezvous>. This is probably the best way to proceed, since you don't have to create the .nib file in Interface Builder yourself.

If you are reading this without access to the Internet, here is what you need to create in Interface Builder: The main window contains an NSTableView. The NSTableView has three columns. The identifiers of the three columns should be "name", "ip" and "port".

Listing 6:

ADemoController.h

@interface ADemoController : NSObject
{
    IBOutlet NSWindow     *_mainWindow;
    IBOutlet NSTableView  *_tableView;
    ARendezvousController    *_rendezvousController;
    NSArray                *_lastState;
}
- (void)awakeFromNib;
- (void)discoveredServicesDidChange:(id)sender;
- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView
  objectValueForTableColumn:(NSTableColumn *)tableColumn
  row:(int)row;
@end

In Interface Builder import this header file and connect the _mainWindow outlet to your window and the _tableView outlet to your table view.

Lets start by looking at awakeFromNib. This methods contains all the necessary setup:

Listing 7:

awakeFromNib

- (void)awakeFromNib
{
    _lastState = [[NSArray alloc] init];
    _rendezvousController = [[ARendezvousController alloc]
      initWithName:NSFullUserName() type:@"_demo._tcp."
      port:12345];
    [_rendezvousController setDelegate:self];
    [_rendezvousController activateBrowsing:YES];
    [_rendezvousController activatePublishing:YES];
    [_mainWindow center];
    [_mainWindow makeKeyAndOrderFront:self];
}

_lastState will contain a copy of the last state returned by the rendezvousController. In the beginning there are no services, so it is initialized as an empty array. After that the ARendezvousController is initialized with a type of @"_demo._tcp." and with port 12345. The port number is just a suggestion. If this port is already in use the number is increased as long as a socket can be created. This is no problem, since the port number is published. At this time the socket will be created. To obtain the socket you would send a socket message to the rendezvousController. Finally the delegate is set to self so we are notified of changes, browsing is started by sending activateBrowsing: and the service is published by sending activatePublishing: to the rendezvousController.

The next method is the delegate method.

Listing 8:

The delegate method

- (void)discoveredServicesDidChange:(id)sender
{
    [_lastState autorelease];
    _lastState = [[rendezvousController
      discoveredServicesWithInfo] retain];
    [_tableView reloadData];
}

It simply releases the old copy of states, asks the rendezvousController for an up-to-date array of states and retains that. After that it tells the table view to reload its data.

Finally these two methods fill the table view with data:

Listing 9:

Tableview methods

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    return [_lastState count];
}
- (id)tableView:(NSTableView *)tableView
  objectValueForTableColumn:(NSTableColumn *)tableColumn
  row:(int)row
{
    return [[_lastState objectAtIndex:row]
      valueForKey:[tableColumn identifier]];
}

The last one is a wonderful demonstration of applied Cocoa magic, but to understand it you should know first that the array returned by discoveredServicesWithInfo contains an NSDictionary for every service. Such a dictionary contains three key/value pairs:

Key "name" contains an NSString with the name of the service

Key "ip" contains an NSString with the IP address (like @"192.168.0.1")

Key "port" contains an NSNumber with the port number

By setting the column identifiers of the table view to these keys the data can be fed to the table view with this one-liner.

That's it! It is that simple. In my next article I will show how to send messages over the network just like you would send it to your objects. This is called Distributed Objects. Stay tuned!

Listing 10:

ARendezvousController.h

#import <Foundation/Foundation.h>
// interface of the rendezvous class
@interface ARendezvousController : NSObject
{
    NSNetService  *_service;
    NSString      *_serviceName;
    NSString      *_serviceType;
    BOOL          _browsing;
    BOOL          _publishing;
    NSNetServiceBrowser  *_serviceBrowser;
    NSNetServiceBrowser  *_domainBrowser;
    NSSocketPort         *_socketPort;
    int                  _portNumber;
    NSMutableArray       *_discoveredServicesWithInfo;
    id                   _delegate;
}
- (id)initWithName:(NSString *)name type:(NSString *)type
  port:(int)port;
- (void)dealloc;
- (id)delegate;
- (void)setDelegate:(id)object;
- (NSString *)name;
- (BOOL)setName:(NSString *)name;
- (NSSocketPort *)socket;
- (void)activateBrowsing:(BOOL)flag;
- (BOOL)isBrowsing;
- (void)refreshBrowsing;
- (void)activatePublishing:(BOOL)flag;
- (BOOL)isPublished;
- (NSArray *)discoveredServicesWithInfo;
- (NSString *)ipForName:(NSString *)name;
- (int)portForName:(NSString *)name;
@end
// to be implemented by the delegate
@interface NSObject (RendezvousControllerDelegate)
- (void)discoveredServicesDidChange:(id)sender;
@end

Listing 11:

ARendezvousController.m

#import "ARendezvousController.h"
#include <netinet/in.h>
#include <arpa/inet.h>
@interface ARendezvousController (ARendezvousControllerInternal)
// creation
- (void)createSocket;
- (void)createBrowser;
- (void)createService;
// management
- (BOOL)addInfoService:(NSNetService *)
  service name:(NSString *)name ip:(NSString *)ip
  port:(int)port;
- (BOOL)removeInfoService:(NSNetService *)service;
// other
- (void)startBrowsing;
@end
@interface ARendezvousController (NSNetServiceDelegation)
// publication
- (void)netService:(NSNetService *)sender
  didNotPublish:(NSDictionary *)errorDict;
- (void)netServiceWillPublish:(NSNetService *)sender;
- (void)netServiceDidStop:(NSNetService *)sender;
// resolution
- (void)netService:(NSNetService *)sender
  didNotResolve:(NSDictionary *)errorDict;
- (void)netServiceDidResolveAddress:(NSNetService *)sender;
- (void)netServiceWillResolve:(NSNetService *)sender;
@end
@interface ARendezvousController
  (NSNetServiceBrowserDelegation)
// browsing
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didFindService:(NSNetService *)
  aNetService moreComing:(BOOL)moreComing;
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict;
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didRemoveService:(NSNetService *)
  aNetService moreComing:(BOOL)moreComing;
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didFindDomain:(NSString *)domainString
  moreComing:(BOOL)moreComing;
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didRemoveDomain:(NSString *)domainString 
  moreComing:(BOOL)moreComing;
- (void)netServiceBrowserDidStopSearch:
  (NSNetServiceBrowser *)aNetServiceBrowser;
- (void)netServiceBrowserWillSearch:
  (NSNetServiceBrowser *)aNetServiceBrowser;
@end
@implementation ARendezvousController
- (id)initWithName:(NSString *)name type:(NSString *)type
  port:(int)port
{
    self = [super init];
    if (self)
    {
        // store name, type and port for later
        [self setName:name];
        _serviceType = [type retain];
        _portNumber = port;
        
        // initialize array
        _discoveredServicesWithInfo =
          [[NSMutableArray alloc] init];
        
        // create the socket and the browser
        [self createSocket];
        [self createBrowser];
    }
    return self;
}
- (void)dealloc
{
    // clean up
    [self activatePublishing:NO];
    [_serviceBrowser stop];
    [_domainBrowser stop];
    [_socketPort release];
    [_discoveredServicesWithInfo release];
    [super dealloc];
}
- (id)delegate
{
    return _delegate;
}
- (void)setDelegate:(id)object
{
    // find if delegate supports the delegation message
    if (![object respondsToSelector:
      @selector(discoveredServicesDidChange:)])
        NSLog (@"Delegate does not respond to 
          'discoveredServicesDidChange:'!");
    
    _delegate = object;
}
- (NSString *)name
{
    return _serviceName;
}
- (BOOL)setName:(NSString *)name
{
    // change name only when not already published
    if (!_publishing)
    {
        [name retain];
        [_serviceName release];
        _serviceName = name;
        return YES;
    }
    else
    {
        NSLog (@"Cannot change name while service
          is published!");
        return NO;
    }
}
- (NSSocketPort *)socket
{
    return _socketPort;
}
- (void)activateBrowsing:(BOOL)flag
{
    // if requested and actual state match don't proceed
    if (flag == _browsing)
        return;
    
    if (flag)
    {
        // activate browsing
        _browsing = YES;
        [_serviceBrowser searchForServicesOfType:
          _serviceType inDomain:@""];
    }
    else
    {
        // deactivate browsing
        _browsing = NO;
        [_serviceBrowser stop];
        [_discoveredServicesWithInfo removeAllObjects];
        [_delegate discoveredServicesDidChange:self];
    }
}
- (BOOL)isBrowsing
{
    return _browsing;
}
- (void)startBrowsing
{
    // should only be called when browsing off
    if (_browsing)
    {
        NSLog (@"Browing already started!");
        return;
    }
    
    // start browsing
    _browsing = YES;
    [_serviceBrowser searchForServicesOfType:
      _serviceType inDomain:@""];
}
- (void)refreshBrowsing
{
    // don't refresh if not browsing
    if (!_browsing)
        return;
    
    // start/stop
    // (stop is deferred to the end of message queue)
    [self activateBrowsing:NO];
    [self performSelector:@selector(startBrowsing)
      withObject:nil afterDelay:0.0];
}
- (void)activatePublishing:(BOOL)flag
{
    // if already activated then don't do anything
    if (_publishing == flag)
        return;
    
    if (flag)
    {
        // activate service
        [self createService];
    }
    else
    {
        // deactivate service
        [_service stop];
        _service = nil;
    }
    
    // set new state
    _publishing = flag;
}
- (BOOL)isPublished
{
    return _publishing;
}
- (NSArray *)discoveredServicesWithInfo
{
    // return (autoreleased) copy
    return [NSArray 
      arrayWithArray:_discoveredServicesWithInfo];
}
- (NSString *)ipForName:(NSString *)name
{
    NSEnumerator    *e = nil;
    NSDictionary    *dict = nil;
    
    // find the corresponding ip to the given name
    e = [_discoveredServicesWithInfo objectEnumerator];
    while (dict = [e nextObject])
        if ([[dict objectForKey:@"name"]
          isEqualToString:name])
            return [dict objectForKey:@"ip"];
    
    // return nil when not found
    return nil;
}
- (int)portForName:(NSString *)name
{
    NSEnumerator    *e = nil;
    NSDictionary    *dict = nil;
    
    // find the corresponding ip to the given name
    e = [_discoveredServicesWithInfo objectEnumerator];
    while (dict = [e nextObject])
        if ([[dict objectForKey:@"name"]
          isEqualToString:name])
            return [[dict objectForKey:@"port"] intValue];
    
    // return 0 when not found
    return 0;
}
@end
@implementation ARendezvousController
  (RendezvousControllerInternal)
- (void)createSocket
{
    // already there
    if (_socketPort)
        return;
    
    // look for free port
    while (!_socketPort)
    {
        _socketPort = [[NSSocketPort alloc]
          initWithTCPPort:_portNumber];
        _portNumber++;
    }
    _portNumber--;
}
- (void)createService
{
    // already there
    if (_service)
        return;
    
    // create service, make self the delegate and publish
    _service = [[NSNetService alloc] initWithDomain:@""
      type:_serviceType name:_serviceName port:_portNumber];
    [_service setDelegate:self];
    [_service publish];
}
- (void)createBrowser
{
    // setup service browser
    if (!_serviceBrowser)
    {
        _serviceBrowser = [[NSNetServiceBrowser alloc] init];
        [_serviceBrowser setDelegate:self];
    }
}
- (BOOL)addInfoService:(NSNetService *)service name:(NSString *)name ip:(NSString *)ip port:(int)port
{
    NSMutableDictionary    *dict = nil;
    NSEnumerator           *e = nil;
    
    // if already there then don't add
    e = [_discoveredServicesWithInfo objectEnumerator];
    while (dict = [e nextObject])
        if ([[dict objectForKey:@"ip"] isEqualToString:ip])
            if ([[dict objectForKey:@"port"] intValue] ==
              port)
                return NO;
    
    // add if not found in array
    dict = [NSMutableDictionary dictionary];
    [dict setObject:ip forKey:@"ip"];
    [dict setObject:[NSNumber numberWithInt:port]
      forKey:@"port"];
    [dict setObject:name forKey:@"name"];
    [dict setObject:service forKey:@"service"];
    [_discoveredServicesWithInfo addObject:dict];
    return YES;
}
- (BOOL)removeInfoService:(NSNetService *)service
{
    NSDictionary    *dict = nil;
    NSEnumerator    *e = nil;
    NSMutableArray  *delete = nil;
    
    // look for object with this service and mark for delete
    delete =[NSMutableArray array];
    e = [_discoveredServicesWithInfo objectEnumerator];
    while (dict = [e nextObject])
        if ([[dict objectForKey:@"service"] isEqual:service])
            [delete addObject:dict];
    
    // delete marked objects
    e = [delete objectEnumerator];
    while (dict = [e nextObject])
        [_discoveredServicesWithInfo removeObject:dict];
    
    return NO;
}
@end
@implementation ARendezvousController
  (NSNetServiceDelegation)
- (void)netService:(NSNetService *)sender
  didNotPublish:(NSDictionary *)errorDict
{
    // publishing failed
    _publishing = NO;
    NSLog (@"Publishing the service %@failed.",
      [sender name]);
    [_delegate discoveredServicesDidChange:self];
}
- (void)netServiceWillPublish:(NSNetService *)sender
{
    // does nothing for now,
    // implemented for your possible additions
}
- (void)netServiceDidStop:(NSNetService *)sender
{
    // does nothing for now,
    // implemented for your possible additions
}
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
{
    // resolving failed
    NSLog (@"Resolving of address for service %@ failed.",
      [sender name]);
}
- (void)netServiceDidResolveAddress:(NSNetService *)sender
{
    NSData              *address = nil;
    struct sockaddr_in  *socketAddress = nil;
    NSString            *ipString = nil;
    int                 port, i;
    
    for (i = 0; i < [[sender addresses] count]; i++)
    {
        // gather data about this published service
        address = [[sender addresses] objectAtIndex:i];
        socketAddress = (struct sockaddr_in *)
          [address bytes];
        ipString = [NSString stringWithFormat: @"%s",
          inet_ntoa (socketAddress->sin_addr)];
        port = socketAddress->sin_port;
        
        // published localhost is a Rendezvous strangeness:
        // ignore that!
        if ([ipString isEqualToString:@"127.0.0.1"])
            continue;
        
        // notify delegate of change
        if ([self addInfoService:sender name:[sender name]
          ip:ipString port:port])
            [_delegate discoveredServicesDidChange:self];
    }
}
- (void)netServiceWillResolve:(NSNetService *)sender
{
    // does nothing for now,
    // implemented for your possible additions
}
@end
@implementation ARendezvousController
  (NSNetServiceBrowserDelegation)
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didFindService:(NSNetService *)
  aNetService moreComing:(BOOL)moreComing
{
    // add to dicovered services and resolve it
    [aNetService setDelegate:self];
    [aNetService resolve];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didRemoveService:(NSNetService *)
  aNetService moreComing:(BOOL)moreComing
{
    // remove and notify
    [self removeInfoService:aNetService];
    [_delegate discoveredServicesDidChange:self];
}
- (void)netServiceBrowserDidStopSearch:
  (NSNetServiceBrowser *)aNetServiceBrowser
{
    // empty the arrays and notify delegate
    if (aNetServiceBrowser == _serviceBrowser)
    {
        [_discoveredServicesWithInfo removeAllObjects];
        [_delegate discoveredServicesDidChange:self];
    }
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict
{
    NSLog (@"Unable to search.");
}
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)
  aNetServiceBrowser
{
    // does nothing for now,
    // implemented for your possible additions
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didFindDomain:(NSString *)domainString
  moreComing:(BOOL)moreComing
{
    // does nothing for now,
    // implemented for your possible additions
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)
  aNetServiceBrowser didRemoveDomain:(NSString *)domainString 
  moreComing:(BOOL)moreComing
{
    // does nothing for now,
    // implemented for your possible additions
}
@end

Bibliography and References

Apple Computer. "Rendezvous Network Services" <http://developer.apple.com/techpubs/macosx/...>

Michael Beam. "Incorporating Rendezvous into Your Cocoa Applications"

<http://www.macdevcenter.com/pub/a/mac/2002/11/08/cocoa.html>


Wolfgang Ante is the founder of ARTIS Software (http://www.artissoftware.com). In the last 14 years he worked on Macintosh products for ARTIS and lots of other companies, including a product that won the Macworld's Editors Choice Award. At the time he is trying hard to transform ARTIS into a successful Mac OS X shareware company. Beside that he is always happy to offer his experience for working on your project. You can reach him at wolfgang@artissoftware.com.

 
AAPL
$116.47
Apple Inc.
+0.16
MSFT
$47.98
Microsoft Corpora
-0.72
GOOG
$537.50
Google Inc.
+2.67

MacTech Search:
Community Search:

Software Updates via MacUpdate

StatsBar 1.9 - Monitor system processes...
StatsBar gives you a comprehensive and detailed analysis of the following areas of your Mac: CPU usage Memory usage Disk usage Network and bandwidth usage Battery power and health (MacBooks only)... Read more
Cyberduck 4.6 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Evernote 6.0.1 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
calibre 2.11 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital... Read more
Herald 5.0.1 - Notification plugin for M...
Note: Versions 2.1.3 (for OS X 10.7), 3.0.6 (for OS X 10.8), and 4.0.8 (for OS X 10.9) are no longer supported by the developer. Herald is a notification plugin for Mail.app, Apple's Mac OS X email... Read more
Firetask 3.7 - Innovative task managemen...
Firetask uniquely combines the advantages of classical priority-and-due-date-based task management with GTD. Stay focused and on top of your commitments - Firetask's "Today" view shows all relevant... Read more
TechTool Pro 7.0.6 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PhotoDesk 3.0.1 - Instagram client for p...
PhotoDesk lets you view, like, comment, and download Instagram pictures/videos! (NO Uploads! / Image Posting! Instagram forbids that! AND you *need* an *existing* Instagram account). But you can do... Read more
SuperDuper! 2.7.3 - Advanced disk clonin...
SuperDuper! is an advanced, yet easy to use disk copying program. It can, of course, make a straight copy, or "clone" -- useful when you want to move all your data from one machine to another, or do... Read more

Latest Forum Discussions

See All

Ubisoft Gives Everyone Two New Ways to E...
Ubisoft Gives Everyone Two New Ways to Earn In-Game Stuff for Far Cry 4 Posted by Jessica Fisher on November 21st, 2014 [ permalink ] | Read more »
Golfinity – Tips, Tricks, Strategies, an...
Dig this: Would you like to know what we thought of being an infinite golfer? Check out our Golfinity review! Golfinity offers unlimited ways to test your skills at golf. Here are a few ways to make sure your score doesn’t get too high and your... | Read more »
Dark Hearts, The Sequel to Haunting Meli...
Dark Hearts, The Sequel to Haunting Melissa, is Available Now Posted by Jessica Fisher on November 21st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Meowza! Toyze Brings Talking Tom to Life...
Meowza! | Read more »
Square Enix Announces New Tactical RPG f...
Square Enix Announces New Tactical RPG for Mobile, Heavenstrike Rivals. Posted by Jessica Fisher on November 21st, 2014 [ permalink ] With their epic stories and gorgeous graphics, | Read more »
Quest for Revenge (Games)
Quest for Revenge 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: The great Kingdom of the west has fallen. The gods ignore the prayers of the desperate. A dark warlord has extinguished... | Read more »
Threadz is a New Writing Adventure for Y...
Threadz is a New Writing Adventure for You and Your Friends Posted by Jessica Fisher on November 21st, 2014 [ permalink ] In the tradition of round-robin storytelling, | Read more »
SteelSeries Stratus XL Hardware Review
Made by: SteelSeries Price: $59.99 Hardware/iOS Integration Rating: 4 out of 5 stars Usability Rating: 4.5 out of 5 stars Reuse Value Rating: 4.25 out of 5 stars Build Quality Rating: 4.5 out of 5 stars Overall Rating: 4.31 out of 5 stars | Read more »
ACDSee (Photography)
ACDSee 1.0.0 Device: iOS iPhone Category: Photography Price: $1.99, Version: 1.0.0 (iTunes) Description: Capture, perfect, and share your photos with ACDSee. The ACDSee iPhone app combines an innovative camera, a powerful photo... | Read more »
ProTube for YouTube (Entertainment)
ProTube for YouTube 2.0.2 Device: iOS Universal Category: Entertainment Price: $1.99, Version: 2.0.2 (iTunes) Description: ProTube is the ultimate, fully featured YouTube app. With it's highly polished design, ProTube offers ad-free... | Read more »

Price Scanner via MacPrices.net

Save up to $400 with Apple refurbished 2014 1...
The Apple Store has restocked Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and shipping... Read more
New 13-inch 1.4GHz MacBook Air on sale for $8...
 Adorama has the 2014 13″ 1.4GHz/128GB MacBook Air on sale for $899.99 including free shipping plus NY & NJ tax only. Their price is $100 off MSRP. B&H Photo has the 13″ 1.4GHz/128GB MacBook... Read more
Apple Expected to Reverse Nine-Month Tablet S...
Apple and Samsung combined accounted for 62 percent of the nearly 36 million branded tablets shipped in 3Q 2014, according to early vendor shipment share estimates from market intelligence firm ABI... Read more
Stratos: 30 Percent of US Smartphone Owners t...
Stratos, Inc., creator of the Bluetooth Connected Card Platform, has announced results from its 2014 Holiday Mobile Payments Survey. The consumer survey found that nearly one out of three (30 percent... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has lowered their price on the new 1.4GHz Mac mini to $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new... Read more
Check Apple prices on any device with the iTr...
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more
64GB iPod touch on sale for $249, save $50
Best Buy has the 64GB iPod touch on sale for $249 on their online store for a limited time. Their price is $50 off MSRP. Choose free shipping or free local store pickup (if available). Sale price for... Read more
15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale for $1799.99 for a limited time. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of... Read more
New Logitech AnyAngle Case/Stand Brings Flexi...
Logitec has announced the newest addition to its suite of tablet products — the Logitech AnyAngle. A protective case with an any-angle stand for iPad Air 2 and all iPad mini models, AnyAngle is the... Read more
Notebook PC Shipments Rise Year-Over-Year as...
According to preliminary results from the upcoming DisplaySearch Quarterly Mobile PC Shipment and Forecast Report, the global notebook PC market grew 10 percent year-over-year in Q3’14 to 49.4... Read more

Jobs Board

*Apple* Solutions Consultant (ASC)- Retail S...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Store Leader Program - College Gradu...
Job Description: Job Summary As an Apple Store Leader Program agent, you can continue your education as you major in the art of leadership at the Apple Store. You'll Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.