Mac OS X Internals: A Systems Approach

9.14. Distributed Objects

The Objective-C runtime environment provides an IPC mechanism called Distributed Objects that allows one application to call a remote object, which could be in another application, in a different thread in the same application, or even in an application running on another computer. In other words, Distributed Objects supports intramachine or intermachine remote messaging.

Distributed Objects makes it rather simple to make a remote object locally available, although the usual distributed computing caveats related to latency, performance, and reliability still apply. Let us look at a client-server system implemented using Distributed Objects.

In our system, the server object (DOServer) will implement the ClientProtocol Objective-C protocol, whose methods will be called by the client as follows.

  • (void)helloFromClient:(id)client The client calls this method to "say hello" to the server.

  • (void)setA:(float)arg sThe client calls this method to set the value of the server variable A to the given floating-point value.

  • (void)setB:(float)arg The client calls this method to set the value of the server variable B to the given floating-point value.

  • (float)getSum The client calls this method to retrieve the sum of server variables A and B.

The client object (DOClient) will implement a single method as part of the ServerProtocol Objective-C protocol. This methodwhoAreYouwill be called from the server's implementation of the helloFromClient method. This is to demonstrate that both the client and the server can remotely call each other's methods.

Objective-C Protocols

An Objective-C formal protocol is a list of method declarations not attached to a class definition. The methods can be implemented by any class, which is then said to adopt the protocol. In a class declaration, a comma-separated list of adopted protocols can be specified within angled brackets after the superclass specification.

@interface class_name : superclass_name <protocol1, protocol2, ...>

A remote object's implementation advertises the messages it responds to in a formal Objective-C protocol. In our example, the DOClient class adopts the ServerProtocol protocol, whereas DOServer adopts the ClientProtocol protocol.

The server will create an instance of the NSConnection class for receiving client requests on a well-known (to our client) TCP port. It will then call the setRootObject method of NSConnection to attach the server object to the connection. Thereafter, the object will be available to other processes through the connection. This is referred to as vending an object.

Note that although we use sockets for communication, Distributed Objects can use other communication mechanisms too, such as Mach ports. Moreover, remote messaging can be synchronous, blocking the sender until a reply is received, or it can be asynchronous, in which case no reply is desired.

The client will begin by establishing a connection to the serveragain, an instance of the NSConnection class. It will then call the rootProxy method of NSConnection to retrieve the proxy for the root object of the server end of the connection. This way, the client will have a proxy objectan instance of the NSDistantObject classin its address space representing the vended object in the server's address space. Thereafter, the client can send messages to the proxy object with reasonable transparencyas if it were the real object. Figure 947 shows the working of our client and server.

Figure 947. Communication in a Distributed Objects client-server system

In Objective-C, a local object's methods are invoked by sending the appropriate messages to the object. In Figure 947, when a method of the remote object is invoked, the Objective-C message goes to the NSDistantObject instance, which renders the message static by turning it into an instance of the NSInvocation class. The latter is a container for carrying all constituents of a messagesuch as target, selector, arguments, return value, and data types of the arguments and the return value. The NSConnection instance encodes the NSInvocation object into a low-level, platform-independent formNSPortMessagethat contains a local port, a remote port, a message ID, and an array of encoded data components. The encoded data is eventually transmitted across the connection. The reverse process occurs on the server: When the NSPortMessage is received by the server, it is decoded and given to NSConnection as an NSPortMessage. NSConnection converts the NSPortMessage to an NSInvocation and finally sends the corresponding message, along with arguments, to the target (the vended object). This is referred to as dispatching theNSInvocation.

Our implementation of the client-server system consists of a common header file (do_common.h, shown in Figure 948), the server source (do_server.m, shown in Figure 949), and the client source (do_client.m, shown in Figure 950).

Figure 948. Common header file for the Distributed Objects client-server example

// do_common.h #import <Foundation/Foundation.h> #include <sys/socket.h> #define DO_DEMO_PORT 12345 #define DO_DEMO_HOST "localhost" @protocol ClientProtocol - (void)setA:(float)arg; - (void)setB:(float)arg; - (float)getSum; - (void)helloFromClient:(id)client; @end @protocol ServerProtocol - (bycopy NSString *)whoAreYou; @end

Figure 949. Server source for the Distributed Objects client-server example

// do_server.m #import "do_common.h" @interface DOServer : NSObject <ClientProtocol> { float a; float b; } @end // server @implementation DOServer - (id)init { [super init]; a = 0; b = 0; return self; } - (void)dealloc { [super dealloc]; } - (void)helloFromClient:(in byref id<ServerProtocol>)client { NSLog([client whoAreYou]); } - (oneway void)setA:(in bycopy float)arg { a = arg; } - (oneway void)setB:(in bycopy float)arg { b = arg; } - (float)getSum { return (float)(a + b); } @end // server main program int main(int argc, char **argv) { NSSocketPort *port; NSConnection *connection; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSRunLoop *runloop = [NSRunLoop currentRunLoop]; DOServer *server = [[DOServer alloc] init]; NS_DURING port = [[NSSocketPort alloc] initWithTCPPort:DO_DEMO_PORT]; NS_HANDLER NSLog(@"failed to initialize TCP port."); exit(1); NS_ENDHANDLER connection = [NSConnection connectionWithReceivePort:port sendPort:nil]; [port release]; // vend the object [connection setRootObject:server]; [server release]; [runloop run]; [connection release]; [pool release]; exit(0); }

Figure 950. Client source for the Distributed Objects client-server example

// do_client.m #import "do_common.h" @interface DOClient : NSObject <ServerProtocol> { id proxy; } - (NSString *)whoAreYou; - (void)cleanup; - (void)connect; - (void)doTest; @end // client @implementation DOClient - (void)dealloc { [self cleanup]; [super dealloc]; } - (void)cleanup { if (proxy) { NSConnection *connection = [proxy connectionForProxy]; [connection invalidate]; [proxy release]; proxy = nil; } } - (NSString *)whoAreYou { return @"I am a DO client."; } - (void)connect { NSSocketPort *port; NSConnection *connection; port = [[NSSocketPort alloc] initRemoteWithTCPPort:DO_DEMO_PORT host:@DO_DEMO_HOST]; connection = [NSConnection connectionWithReceivePort:nil sendPort:port]; [connection setReplyTimeout:5]; [connection setRequestTimeout:5]; [port release]; NS_DURING proxy = [[connection rootProxy] retain]; [proxy setProtocolForProxy:@protocol(ClientProtocol)]; [proxy helloFromClient:self]; NS_HANDLER [self cleanup]; NS_ENDHANDLER } - (void)doTest { [proxy setA:4.0]; [proxy setB:9.0]; float result = [proxy getSum]; NSLog(@"%f", result); } @end // client main program int main(int argc, char **argv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; DOClient *client = [[DOClient alloc] init]; [client connect]; [client doTest]; [client release]; [pool release]; exit(0); }

Let us now test the Distributed Objects client-server system.

$ gcc -Wall -o do_server do_server.m -framework Foundation $ gcc -Wall -o do_client do_client.m -framework Foundation $ ./do_server # another shell prompt $ ./do_client ... do_client[4741] 13.000000 ... do_server[4740] I am a DO client.

Note that we use the NS_DURING, NS_HANDLER, and NS_ENDHANDLER macros in our client and server implementations. These demarcate an exception-handling domain and a local exception handler. Specifically, if an exception is raised in the section of code between NS_DURING and NS_HANDLER, the section of code between NS_HANDLER and NS_ENDHANDLER is given a chance to handle the exception. Figure 951 shows a code excerpt with the expanded forms of these macros.

Figure 951. Exception-handling macros in the Foundation framework

NS_DURING { NS_DURING NSHandler2 _localHandler; NS_DURING _NSAddHandler2(&_localHandler); NS_DURING if (!_NSSETJMP(_localHandler._state, 0)) { // section of code ... NS_HANDLER _NSRemoveHandler2(&_localHandler); NS_HANDLER } else { NS_HANDLER NSException *localException = NS_HANDLER _NSExceptionObjectFromHandler2(&_localHandler); // local exception-handler code ... NS_ENDHANDLER localException = nil; NS_ENDHANDLER } NS_ENDHANDLER }

Категории