Next: 6 Putting them together Up: GNUstep Distributed Objects Previous: 4 The server

Subsections


5 The client

The client is implemented in the Client.m file; it is composed by the main function of the original program, with some simple yet very interesting changes.

5.1 The FileReader protocol

Because the FileReader class is not compiled into the client (as we moved it into the server), we can't refer to the FileReader class in the client. And still, if we need to access the remote FileReader object, we need a way to declare which methods it supports.

To manage this situation, we use a protocol. If you know Java, this is similar to an interface in Java. A protocol declares some methods, leaving the implementation unspecified. The language then allows you to declare that a certain object conforms to a certain protocol; this means that the object implements the methods listed in the protocol. In our example, this allows us to declare that we can send the getFile: method to the reader object, without actually knowing the implementation of the method nor actually knowing the class of the reader object.

The declaration of the protocol is as follows:

@protocol FileReader
- (NSString *) getFile: (NSString *)fileName;
@end
This declares the protocol FileReader to have a single method, getFile:. Objects conform to this protocol if and only if they have a getFile: method taking a NSString * argument, and returning an NSString *.

The reader object, which we used to declare to be of class FileReader,

FileReader *reader;
is now declared more generically to conform to the FileReader protocol:
id <FileReader> reader;
id means a generic object; <FileReader> means that it must conform to the FileReader protocol; in this case this simply means that reader is an object and you can send the message getFile: to it.

5.2 Accessing the remote FileReader object

To access the reader object, where we used to create it directly,
reader = [FileReader new];
we now ask the gnustep-base library to give us the object registered with the name FileReader on a remote machine:
reader = (id <FileReader>)[NSConnection
               rootProxyForConnectionWithRegisteredName: @"FileReader" 
               host: @"*"];
strictly speaking, reader is a local proxy to the remote object - but the whole thing is made so that you can forget about this distinction, and think of reader simply as the remote object. Using * for the host argument means that gnustep-base will look for an object registered with name FileReader anywhere on the network; if you know the host on which you want to access the FileReader object, you should better use your specific host name, such as localhost or 192.14.29.1.

We need a cast to id <FileReader> because the call to NSConnection returns a generic object, while we know the FileReader object implements getFile:. A more robust application could check at execution time that the remote object in the server actually can respond to getFile: messages before doing the cast (for example by using the method respondsToSelector:); we skip this little complication in this first example.

But we need to check that we have a real reader object - if it is nil, it is because for some reason the gnustep-base library couldn't connect to an object registered as FileReader on the network. Usually this is because the server is not running; there is nothing we can do in the client in these cases, so we simply print an error message and exit.

As promised, the rest of the function is unchanged; in particular, when we send the getFile: method to the remote object, that starts a network connection to the server, and returns the result - but the nice thing is that we don't need to do anything special to perform this remote call: we just call the method normally, as if the object were our old friendly local object.

Here is the source code:

#include <Foundation/Foundation.h>

/* This tells us how the reader object behaves */

@protocol FileReader
- (NSString *)getFile: (NSString *)fileName;
@end


int 
main (void)
{
  NSAutoreleasePool *pool;
  NSArray *args;
  int count;
  id <FileReader> reader;
  NSString *filename;
  NSString *file;

  pool = [NSAutoreleasePool new];

  /* Create our FileReader object */
  reader = (id <FileReader>)[NSConnection
             rootProxyForConnectionWithRegisteredName: 
                   @"FileReader" 
             host: @"*"];

  if (reader == nil)
    {
      NSLog (@"Error: could not connect to server");
      exit (1);
    }
  
  /* From now on the code is the same, whether reader is 
     in the local process or in a remote one */

  /* Get program arguments */
  args = [[NSProcessInfo processInfo] arguments];

  /* the first string in args is the program name; 
     get the second one if any */
  if ([args count] == 1)
    {
      NSLog (@"Error: you should specify a filename");
      exit (1);
    }
  
  filename = [args objectAtIndex: 1];

  /* Ask the reader object to get the file */
  file = [reader getFile: filename];

  /* If the reader object could get the file, show it */
  if (file != nil)
    {
      printf ("%s\n", [file lossyCString]);
    }
  else
    {
      NSLog (@"Error: could not read file `%@'", filename);
      exit (1);
     }

  return 0;
}


Next: 6 Putting them together Up: GNUstep Distributed Objects Previous: 4 The server
Nicola 2002-02-24