Bonjour enables dynamic discovery of network services on IP networks without a centralized directory server. The Foundation framework’s NSNetService class represents instances of Bonjour network services. This article describes the process for publishing Bonjour network services with NSNetService.
Bonjour network services use standard DNS information to advertise their existence to potential clients on a network. In Cocoa, the NSNetService class handles the details of service publication.
Typically, you use NSNetService to publish a service provided by a socket owned by the same process. This is not a requirement, so you can use the class to advertise on behalf of another process’s service—for example, an FTP server process that has not yet been updated to support Bonjour. However, if you are creating an IP network service, you should include Bonjour publication code as part of its startup process.
Because network activity can sometimes take some time, NSNetService objects process publication requests asynchronously, delivering information through delegate methods. To use NSNetService correctly, your application must assign a delegate to each NSNetService instance it creates. Because the identity of the NSNetService object is passed as a parameter in delegate methods, you can use one delegate for multiple NSNetService objects.
Publishing a Bonjour network service takes four steps:
Set up a valid socket port for communication.
Initialize an NSNetService instance with name, type, domain and socket information, and assign a delegate to the object.
Publish the NSNetService instance.
Respond to messages sent to the NSNetService object’s delegate.
The following sections describe these steps in detail.
Bonjour network services are set up as standard TCP or UDP socket ports over IP. Mac OS X provides a number of APIs to help you manage sockets:
CFNetwork, part of the Core Services framework, for HTTP sockets
NSSocketPort, part of the Foundation framework
CFStream and CFSocket
Raw BSD sockets, using the API provided in <sys/socket.h> and <netinet/in.h>.
Using NSSocketPort or CFStream and CFSocket generally means also interacting with the lower-level BSD sockets layer. NSSocketPort objects can also be used as endpoints for Cocoa distributed objects communication advertised as a Bonjour network service. CFNetwork also provides an abstraction over low-level sockets, but only for HTTP transactions.
For more detailed information, see the CFNetwork Programming Guide, the documentation for NSSocketPort and the many available web and print resources about BSD sockets.
To initialize an NSNetService instance for publication, use the initWithDomain:type:name:port: method. This method sets up the instance with appropriate socket information and adds it to the current run loop.
The service type expresses both the application protocol (FTP, HTTP, and so on) and the transport protocol (TCP or UDP). The format is as described in Domain Naming Conventions, for example, _printer._tcp for a printer over TCP.
The service name can be any NSString. This is the name that should be presented to users, so it should be human-readable and descriptive of the specific service instance. You should let the user override any default name you provide.
It is recommended that you use the computer name as the service name. If you pass the empty string (@"") for the service name parameter, the system automatically advertises your service using the computer name as the service name. Another possibility is to retrieve the computer name to concatenate it with another string. In Mac OS X on the desktop, calling the SCDynamicStore function from the System Configuration framework returns the computer name so you can manipulate it like any other string. In iPhone OS, you can obtain the same information from the name property of the UIDevice class.
If you want to use a run loop other than the current run loop, you can call the removeFromRunLoop:forMode: and scheduleInRunLoop:forMode: methods. The object must be scheduled in a run loop to operate.
Once the initialization is complete and valid, assign a delegate to the NSNetService object with the setDelegate: method. Finally, publish the service with the publish method, which returns immediately. It performs publication asynchronously and returns results through delegate methods.
Listing 1 demonstrates the initialization and publication process for Bonjour network services. An explanation of the code follows it. Also see the PictureSharing application in /Developer/Examples/Foundation/PictureSharing for a good example of service publication.
local.arpa., but it changed to local. before the operating system shipped. Use local. to specify the link-local network.Listing 1 Initializing and publishing a Bonjour network service
#import <netinet/in.h> |
#import <sys/socket.h> |
// ... |
id delegateObject; // Assume this exists. |
NSSocketPort *socket; |
NSNetService *service; |
struct sockaddr *addr; |
int port; |
socket = [[NSSocketPort alloc] init];// 1 |
if(socket) |
{ |
[self setUpSocket:socket];// 2 |
addr = (struct sockaddr *)[[socket address] bytes];// 3 |
if(addr->sa_family == AF_INET) |
{ |
port = ntohs(((struct sockaddr_in *)addr)->sin_port); |
} |
else if(addr->sa_family == AF_INET6) |
{ |
port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port); |
} |
else |
{ |
[socket release]; |
socket = nil; |
NSLog(@"The family is neither IPv4 nor IPv6. Can't handle."); |
} |
} |
else |
{ |
NSLog(@"An error occurred initializing the NSSocketPort object."); |
} |
if(socket) |
{ |
service = [[NSNetService alloc] initWithDomain:@""// 4 |
type:@"_music._tcp" |
name:@"" port:port]; |
if(service) |
{ |
[service setDelegate:delegateObject];// 5 |
[service publish];// 6 |
} |
else |
{ |
NSLog(@"An error occurred initializing the NSNetService object."); |
} |
} |
else |
{ |
NSLog(@"An error occurred initializing the NSSocketPort object."); |
} |
Here’s what the code does:
Initializes an NSSocketPort instance with the init method, which sets up an available TCP/IP socket port.
Calls a setup method defined elsewhere in this object. This method might connect the socket to a custom BSD sockets-based service, a distributed objects connection, or other TCP/IP service.
Extracts the port number from the sockaddr structure returned by the NSSocketPort address method. Finds out whether the structure is IPv4-based or IPv6-based, and extracts the appropriate part of the structure.
If the socket initialization succeeds, initializes the NSNetService object. This example uses the default domain(s) for publication and a hypothetical TCP/IP music service.
Sets the delegate for the NSNetService object. This object handles all results from the NSNetService object, as described in “Implementing Delegate Methods for Publication.”
Finally, publishes the service to the network.
To stop a service that is already running or in the process of starting up, use the stop method.
NSNetService returns publication results to its delegate. If you are publishing a service, your delegate object should implement the following methods:
netServiceWillPublish:
netService:didNotPublish:
netServiceDidStop:
The netServiceWillPublish: method notifies the delegate that the network is ready to publish the service. When this method is called, the service is not yet visible to the network, and publication may still fail. After this method is called, however, you can assume the service is visible unless you receive an error message.
The netService:didNotPublish: method is called when publication fails for any reason. Publication can fail even after the netServiceWillPublish: method is called. If the delegate receives a netService:didNotPublish: message, you should extract the type of error from the returned dictionary using the NSNetServicesErrorCode key and handle the error accordingly.
One common error is NSNetServicesCollisionError, which is received when the service name is already in use. If your application receives this error, it should inform the user and ask for a different name. See NSNetService for a complete list of possible errors.
The netServiceDidStop: method gets called as a result of the stop message being sent to the NSNetService object. If this method gets called, the service is no longer running.
Listing 2 shows the interface for a class that acts as a delegate for multiple NSNetService objects, and Listing 3 shows its implementation. You can use this code as a starting point for more sophisticated tracking of published services.
Listing 2 Interface for an NSNetService delegate object (publication)
#import <Foundation/Foundation.h> |
@interface NetServicePublicationDelegate : NSObject |
{ |
// Keeps track of active services or services about to be published |
NSMutableArray *services; |
} |
// NSNetService delegate methods for publication |
- (void)netServiceWillPublish:(NSNetService *)netService; |
- (void)netService:(NSNetService *)netService |
didNotPublish:(NSDictionary *)errorDict; |
- (void)netServiceDidStop:(NSNetService *)netService; |
// Other methods |
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service; |
@end |
Listing 3 Implementation for an NSNetService delegate object (publication)
#import "NetServicePublicationDelegate.h" |
@implementation NetServicePublicationDelegate |
- (id)init |
{ |
self = [super init]; |
services = [[NSMutableArray alloc] init]; |
return self; |
} |
- (void)dealloc |
{ |
[services release]; |
[super dealloc]; |
} |
// Sent when the service is about to publish |
- (void)netServiceWillPublish:(NSNetService *)netService |
{ |
[services addObject:netService]; |
// You may want to do something here, such as updating a user interface |
} |
// Sent if publication fails |
- (void)netService:(NSNetService *)netService |
didNotPublish:(NSDictionary *)errorDict |
{ |
[self handleError:[errorDict objectForKey:NSNetServicesErrorCode] withService:netService]; |
[services removeObject:netService]; |
} |
// Sent when the service stops |
- (void)netServiceDidStop:(NSNetService *)netService |
{ |
[services removeObject:netService]; |
// You may want to do something here, such as updating a user interface |
} |
// Error handling code |
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service |
{ |
NSLog(@"An error occurred with service %@.%@.%@, error code = %@", |
[service name], [service type], [service domain], error); |
// Handle error here |
} |
@end |
Last updated: 2009-10-09