oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

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


Sockets are the topic of the next column, but I want to touch on them here because setting up a socket is the first step to creating and publishing a service. Sockets are, for programmers, endpoints of network communications. When a connection between a server and a client has been established, there is a pair of sockets associated with each other: one on the client and one on the server. Data is transferred from the client to the server when the client writes data to its socket representing the server. The server obtains the data sent by the client by reading available data from its socket representing the client. This chain of events is true in the opposite direction as well.

When a server starts, it creates what is known as a listening socket. This is a socket that is "listening" for connections from clients. The listening socket for an application is what we need to set up for our net service.

One could look at NSNetService and Rendezvous as doing nothing more than advertising the presence of a listening socket. This socket is given a name, which is the service name, and a type, which is the service type discussed above. Browsers searching for services of a specific type will then get, in response, the name of our particular service, and when the service is resolved, the client gets the address and port number for the server's listening socket to which it can connect. So you can see how, from the programmer's perspective, NSNetService really only advertises the existence of a socket to which interested clients may connect.

NSFileHandle is used to communicate over a socket. Again, the details of this will be discussed in the next column, but for the purposes of setupSocket, we're going to create one now.

- (void)setupSocket
    socketPort = [[NSSocketPort alloc] initWithTCPPort:12345];
    listeningSocket = [[NSFileHandle alloc]
                         initWithFileDescriptor:[socketPort socket]];

In this code the variables socketPort and listeningSocket are both instance variables in Controller. So in the class interface we have to add the following:

NSSocketPort *socketPort;
NSFileHandle *listeningSocket;

In setupSocket, we do two things. We use NSSocketPort to create a socket bound to the port 12345 using the initializer initWithTCPPort:. In the next line we instantiate and initialize an NSFileHandle using the method initWithFileDescriptor:. The file descriptor we pass in this method is the socket file descriptor returned by a socket message to our NSSocketPort object. Again, we will talk more about this in the next column, in addition to an alternative to using NSSocketPort here.

This method is a pretty simple method. NSNetService will advertise the existence of the socket we create in this method. Of course, right now our socket does absolutely nothing, but that will change in the next column. For now, we'll pretend that our socket on port 12345 is set up to do really important things that makes advertising it with Rendezvous worthwhile (although, since Rendezvous is so easy to use, even if the service behind your socket is a paragon of mediocrity, it would still be worthwhile to advertise it with Rendezvous).

Interestingly, all of the documentation regarding Rendezvous states that creating a socket is a necessary first step to creating a service. The truth, however, is that we can advertise a service without creating a socket. This is totally pointless, since clients won't have anything to which to connect. The point is that we are on the honor system to make sure we have a functioning service provider before we advertise it. NSNetService doesn't perform any check to make sure that there is a live socket on the port number we specify.


The next thing we do after setting up the socket is to set up an actual instance of NSNetService. This is done in the method setupService, which appears below:

- (void)setupService
    service = [[NSNetService alloc] initWithDomain:@""
					                          name:[nameField stringValue]
	   [service setDelegate:self];
    [service publish];

The variable service is another instance variable that must be added to the class interface:

NSNetService *service;

Initializing an instance of NSNetService is done with the method initWithDomain:type:name:port:. The first argument, initWithDomain:, is the domain in which we wish to register our service. Currently, this class only supports registration in the local domain, .local. Rather than explicitly specify .local as the domain, we pass an empty string that NSNetService takes to mean "register this service in the default domain," which is .local.

The next argument, type:, is a string that identifies the service type and transport layer. For this parameter we pass _rce._tcp. The NSNetService object will advertise our service as type rce. The reason these elements are prepended by an underscore is so that the mDNS responder can identify them as information about the service, rather than as being part of the host name. (The type: string is actually part of a larger string used by the mDNS responder that contains the host name and service description: for example, Mike._rce._tcp.southpark.local..)

In the next parameter, name:, we specify the name of the service. This name should be a name that has meaning to human users, rather than some indecipherable incantation of characters that computers really like to use. This is, in fact, the name that will appear in the browser lists of other instances of RCE running on the network. For our service, we set the name of the service to the string contained in the text field nameField.

Finally, we supply the port number to which our socket is bound. Supplying the port number in the initialization is actually the only contact NSNetService has with the networking functionality of our application. All NSNetService does is say to those who care (i.e. apps searching for _rce._tcp service instances) that "I'm running a service over here on port 12345 and it's of type _rce._tcp. If you ask, I'll give you the address of the socket that's serving this service and you can hook up with it." Your service is the pretty girl at the party, and NSNetService is her drunk friend giving out her name and phone number to anyone who asks.

NSNetService has two areas of functionality: it functions to publish services that we create, and it also works to resolve the addresses of discovered services. In the current section, we are concerned with the publication side of business. NSNetService declares delegate methods that are relevant to both publication and resolution, all of which are listed here:

  • Publication Appropriate
    • netServiceWillPublish:
    • netServiceDidStop:
    • netService:didNotPublish:
  • Resolution Appropriate
    • netServiceWillResolve:
    • netService:didNotResolve:
    • netServiceDidResolveAddress:

The functionality of the two sets of methods are largely parallel, and serve to notify the delegate of the progress of a publish or resolution operation, and of any errors that may arise during these operations. We will discuss the resolution-specific delegate methods below, and here we will say a word about the publication-relevant delegate methods.

Each of these delegate methods passes as a parameter the net service object that invoked the method. In netServiceWillPublish: and netServiceDidStop:, the only argument is the net service object that sent the message. The method netService:didNotPublish: passes the NSNetService object in the first argument, and an error dictionary in the second argument.

These first two methods can be used to update the user interface to reflect the status of service publication. We won't do anything too fancy here, just print a message to the standard output with NSLog indicating where we are in publishing, as shown here:

- (void)netServiceWillPublish:(NSNetService *)sender
    NSLog( @"Publishing service %@", [sender name] );

- (void)netServiceDidStop:(NSNetService *)sender
    NSLog( @"Stopping service %@", [sender name] );

NSNetService objects notify their delegates of errors by invoking their respective didNotPublish: or didNotResolve: methods. Information about the error is passed in an error dictionary as the last argument to this method. The error dictionary contains objects for the keys NSNetServicesErrorCode and NSNetServicesErrorDomain. The object for NSNetServicesErrorDomain tells us whether the error occurred in the mach network layer or in the NSNetService object. This information is usually less useful than the constant passed for NSNetServicesErrorCode. The possible error codes are listed in the table below.

Constant Description
NSNetServicesUnknownError An unknown error occurred.
NSNetServicesCollisionError The service could not be published because the name is already in use either locally or on another system.
NSNetServicesNotFoundError The service could not be found on the network.
NSNetServicesActivityInProgress The net service cannot process the request at this time.
NSNetServicesBadArgumentError An invalid argument was used when creating the NSNetService object.
NSNetServicesCancelledError The client cancelled the action.
NSNetServicesInvalidError The net service was improperly configured.

Pages: 1, 2, 3, 4

Next Pagearrow