1@paragraphindent 0 2 3@node Distributed Objects 4@chapter Distributed Objects 5@cindex distributed objects 6 7Until now we have been concentrating on using the Objective-C language 8to create programs that execute in a single process. But what if you 9want your program to interact with objects in other processes, perhaps 10running on different machines? 11 12As a simple example, we may have a client process that needs to access a 13telephone directory stored on a remote server. The client process could 14send a message to the server that contained a person's name, and the 15server could respond by returning that person's number. 16 17The GNUstep base library provides a powerful set of classes that make 18this type of remote messaging not only possible, but easy to program. So 19what do these classes do and how can we use them? To answer that we must 20first look at the way code interacts with objects in a single process, 21and then look at how we can achieve the same interaction with objects 22that exist in different processes. 23 24@section Object Interaction 25@cindex object interaction, remote objects 26 27To continue with the example above, if the telephone directory existed 28in the same process as the code that was accessing it, then a simple 29message would return the wanted telephone number. 30 31@example 32 NSString *wantedNumber = [telephoneDirectory teleNumber: personName]; 33@end example 34 35Now object and method names just hold pointers to memory addresses. The 36code executed at run time in response to the @code{teleNumber} message 37is located at an address held by the name of the responding method (a 38variable), while data in the telephone directory is located at an 39address held by the @code{telephoneDirectory} variable. 40 41In a single process these addresses can be accessed by the client code 42at run time, but if the telephone directory is located on a remote 43server, then the address of the remote object is not known in the client 44process (the @code{telephoneDirectory} object and its responding method 45are said to exist in a separate 'address space'). 46 47The Objective-C run-time library was not designed for this inter-process 48communication or 'remote messaging'. 49 50@section The GNUstep Solution 51@cindex distributed objects 52@cindex remote objects 53@cindex client/server processes 54@cindex NSConnection class 55@cindex NSProxy class 56@cindex NSRunLoop class 57 58GNUstep overcomes these limitations by providing you with classes that 59form what is known as a 'distributed objects' architecture that 60extends the capabilities of the run-time system. 61 62With the addition of a few lines of code in the client and server 63programs, these extensions allow you to send a message to a remote 64process by constructing a simple Objective-C statement. In the telephone 65directory example, the statement to retrieve the telephone number would 66now look something like this: 67 68@example 69 NSString *wantedNumber = [proxyForDirectory teleNumber: personName]; 70@end example 71 72Compare this to the original statement: 73 74@example 75 NSString *wantedNumber = [telephoneDirectory teleNumber: personName]; 76@end example 77 78Notice that the only difference between the two statements is the name 79of the object receiving the message, i.e. @code{proxyForDirectory} 80rather than @code{telephoneDirectory}. GNUstep makes it as simple as 81this to communicate with an object in another process. 82 83The variable @code{proxyForDirectory} is known as a 'proxy' for the 84remote @code{telephoneDirectory} object. A proxy is simply a substitute 85for the remote object, with an address in the 'address space' of the 86local client process, that receives messages and forwards them on to the 87remote server process in a suitably coded form. 88 89Let us now take a look at the additional lines of code required to make 90this 'remote messaging' possible. 91 92@subsection Code at the Server 93@cindex distributed objects, client code 94 95In order to respond to client messages, the responding server object must be 96set as the 'root object' of an instance of the @code{NSConnection} class, and 97this @code{NSConnection} must be registered with the network by name. Making 98an object available to client processes in this way is known as 'vending' the 99object. The registered name for the @code{NSConnection} is used by the client 100when obtaining a proxy for the responding server object over the network. 101 102The only other code you need to consider is the code that listens for incoming 103messages. This 'runloop', as it is known, is started by sending a @code{run} 104message to an instance of the @code{NSRunLoop} class. Since an 105@code{NSRunLoop} object is created automatically for each process, there is no 106need to create one yourself. Simply get the default runloop, which is returned 107by the @code{+currentRunLoop} class method. 108 109When the runloop detects an incoming message, the message is passed to the 110root object of the @code{NSConnection}, which performs a method in response to 111the message and returns a variable of the appropriate type. The 112@code{NSConnection} manages all inter-process communication, decoding incoming 113messages and encoding any returned values. 114@comment{Sequence diagram would be really useful here.} 115 116The code to vend the @code{telephoneDirectory} object and start the 117runloop would look something like this: 118 119@example 120/* 121 * The main() function: Set up the program 122 * as a 'Distributed Objects Server'. 123 */ 124int main(void) 125@{ 126 /* 127 * Remember, create an instance of the 128 * NSAutoreleasePool class. 129 */ 130 CREATE_AUTORELEASE_POOL(pool); 131 132 /* 133 * Get the default NSConnection object 134 * (a new one is automatically created if none exists). 135 */ 136 NSConnection *connXion = [NSConnection defaultConnection]; 137 138 /* 139 * Set the responding server object as 140 * the root object for this connection. 141 */ 142 [connXion setRootObject: telephoneDirectory]; 143 144 /* 145 * Try to register a name for the NSConnection, 146 * and report an error if this is not possible. 147 */ 148 if ([connXion registerName: @@"DirectoryServer"] == NO) 149 @{ 150 NSLog(@@"Unable to register as 'DirectoryServer'"); 151 NSLog(@@"Perhaps another copy of this program is running?"); 152 exit(1); 153 @} 154 155 /* Start the current runloop. */ 156 [[NSRunLoop currentRunLoop] run]; 157 158 /* Release the pool */ 159 RELEASE(pool); 160 return 0; 161@} 162@end example 163 164These additional lines of code turn a program into a distributed objects 165server, ready to respond to incoming client messages. 166 167@subsection Code at the Client 168@cindex distributed objects, client code 169 170At the client, all you need do is obtain a proxy for the responding 171server object, using the name that was registered for the @code{NSConnection} 172at the server. 173 174@example 175 /* Create an instance of the NSAutoreleasePool class */ 176 CREATE_AUTORELEASE_POOL(pool); 177 178 /* Get the proxy */ 179 id proxy = [NSConnection 180 rootProxyForConnectionWithRegisteredName: @i{registeredServerName}]; 181 182 /* The rest of your program code goes here */ 183 184 /* Release the pool */ 185 RELEASE(pool); 186@end example 187 188The code that obtains the proxy automatically creates an NSConnection 189object for managing the inter-process communication, so there is no need 190to create one yourself. 191 192The above example serves to establish a secure connection between processes 193which are run by the same person and are both on the same host. 194 195If you want your connections to work between different host or between 196programs being run by different people, you do this slightly differently, 197telling the system that you want to use 'socket' ports, which make TCP/IP 198connections over the network. 199 200@example 201int main(void) 202@{ 203 CREATE_AUTORELEASE_POOL(pool); 204 205 /* 206 * Create a new socket port for your connection. 207 */ 208 NSSocketPort *port = [NSSocketPort port]; 209 210 /* 211 * Create a connection using the socket port. 212 */ 213 NSConnection *connXion = [NSConnection connectionWithReceivePort: port 214 sendPort: port]; 215 216 /* 217 * Set the responding server object as 218 * the root object for this connection. 219 */ 220 [connXion setRootObject: telephoneDirectory]; 221 222 /* 223 * Try to register a name for the NSConnection, 224 * and report an error if this is not possible. 225 */ 226 if ([connXion registerName: @@"DirectoryServer" 227 withNameServer: [NSSocketPortNameServer sharedInstance]] == NO) 228 @{ 229 NSLog(@@"Unable to register as 'DirectoryServer'"); 230 NSLog(@@"Perhaps another copy of this program is running?"); 231 exit(1); 232 @} 233 234 [[NSRunLoop currentRunLoop] run]; 235 236 RELEASE(pool); 237 return 0; 238@} 239@end example 240 241In the above example, we specify that the socket port name server is used 242to register the name for the connection ... this makes the connection name 243visible to processes running on other machines. 244 245The client side code is as follows 246 247@example 248 /* Create an instance of the NSAutoreleasePool class */ 249 CREATE_AUTORELEASE_POOL(pool); 250 251 /* Get the proxy */ 252 id proxy = [NSConnection 253 rootProxyForConnectionWithRegisteredName: @i{registeredServerName} 254 host: @i{hostName} 255 usingNameServer: [NSSocketPortNameServer sharedInstance]]; 256 257 /* The rest of your program code goes here */ 258 259 /* Release the pool */ 260 RELEASE(pool); 261@end example 262 263If the @i{hostName} in this statement is 'nil' 264or an empty string, then only the local host will be searched to find the 265@i{registeredServerName}. If @i{hostName} is "*", then all hosts on the 266local network will be searched. 267 268In the telephone directory example, the code to obtain the proxy from 269any host on the network would be: 270 271@example 272 id proxyForDirectory = [NSConnection 273 rootProxyForConnectionWithRegisteredName: @@"DirectoryServer" 274 host: @@"*" 275 usingNameServer: [NSSocketPortNameServer sharedInstance]]; 276@end example 277 278With this additional line of code in the client program, you can now 279construct a simple Objective-C statement to communicate with the remote 280object. 281 282@example 283 NSString *wantedNumber = [proxyForDirectory teleNumber: personName]; 284@end example 285 286 287@subsection Using a Protocol 288@cindex protocol for distributed objects 289@cindex distributed objects, using a protocol 290 291A client process does not need to know the class of a remote server 292object to avoid run-time errors, it only needs to know the messages to 293which the remote object responds. This can be determined by the client 294at run-time, by asking the server if it responds to a particular message 295before the message is sent. 296 297If the methods implemented at the server are stated in a formal 298protocol, then the client can ask the server if it conforms to the 299protocol, reducing the network traffic required for the individual 300message/response requests. 301 302A further advantage is gained at compile time, when the compiler will 303issue a warning if the server fails to implement any method declared in 304the protocol, or if the client contains any message to which the server 305cannot respond. 306 307The protocol is saved to a header file and then included in both client 308and server programs with the usual compiler @code{#include} 309directive. Only the server program needs to implement the methods 310declared in the protocol. To enable compiler checking in the client 311program, extend the type declaration for the proxy to this protocol, and 312cast the returned proxy object to the same extended type. 313 314In the telephone directory example, if the declared protocol was 315@code{TelephoneDirectory}, declared in header file 316@code{protocolHeader.h}, then the client code would now look like this: 317 318@example 319 #include "protocolHeader.h"; 320 321 /* Extend the type declaration */ 322 id<TelephoneDirectory> proxyForDirectory; 323 324 /* Cast the returned proxy object to the extended type */ 325 proxyForDirectory = (id<TelephoneDirectory>) [NSConnection 326 rootProxyForConnectionWithRegisteredName: @@"DirectoryServer" 327 usingNameServer: [NSSocketPortNameServer sharedInstance]]; 328@end example 329Since class names and protocol names do not share the same 'address 330space' in a process, the declared protocol and the class of the 331responding server object can share the same name, making code easier to 332understand. 333 334For example, @code{proxyForDirectory} at the client could be a proxy for 335an instance of the @code{TelephoneDirectory} class at the server, and 336this class could implement the @code{TelephoneDirectory} protocol. 337 338 339@subsection Complete Code for Telephone Directory Application 340 341Here we provide the rest of the code needed for client and server to actually 342run the above example. 343 344@b{Code At Server} 345 346@example 347#include <Foundation/Foundation.h> 348 349/* Include the TelephoneDirectory protocol header file */ 350#include "TelephoneDirectory.h" 351 352/* 353 * Declare the TelephoneDirectory class that 354 * implements the 'teleNumber' instance method. 355 */ 356@@interface TelephoneDirectory : NSObject <TelephoneDirectory> 357@@end 358 359/* 360 * Define the TelephoneDirectory class 361 * and the instance method (teleNumber). 362 */ 363@@implementation TelephoneDirectory : NSObject 364- (char *) teleNumber: (char *) personName 365@{ 366 if (strcmp(personName, "Jack") == 0) return " 0123 456"; 367 else if (strcmp(personName, "Jill") == 0) return " 0456 789"; 368 else return " Number not found"; 369@} 370@@end 371 372 /* main() function: Set up the program as a 'Distibuted Objects Server'. */ 373 /* [use code from server example above ...] */ 374@end example 375 376@b{Code at Client} 377 378 379@example 380#include <Foundation/Foundation.h> 381 382/* Include the TelephoneDirectory protocol header file */ 383#include "TelephoneDirectory.h" 384 385/* 386 * The main() function: Get the telephone number for 387 * 'personName' from the server registered as 'DirectoryServer'. 388 */ 389int main(int argc, char *argv[]) 390@{ 391 char *personName = argv[1]; 392 char *returnedNumber; 393 id<TelephoneDirectory> proxyForDirectory; 394 CREATE_AUTORELEASE_POOL(pool); 395 396 /* Acquire the remote reference. */ 397 proxyForDirectory = (id<TelephoneDirectory>) [NSConnection 398 rootProxyForConnectionWithRegisteredName: @@"DirectoryServer" 399 host: @@"*" 400 usingNameServer: [NSSocketPortNameServer sharedInstance]]; 401 402 if (proxyForDirectory == nil) 403 printf("\n** WARNING: NO CONNECTION TO SERVER **\n"); 404 else printf("\n** Connected to server **\n"); 405 406 if (argc == 2) // Command line name entered 407 @{ 408 returnedNumber = (char *)[proxyForDirectory teleNumber: personName]; 409 printf("\n%s%s%s%s%s\n", "** (In client) The telephone number for ", 410 personName, " is:", 411 returnedNumber, " **"); 412 @} 413 else printf("\n** No name entered **\n"); 414 printf("\n%s\n\n", "** End of client program **"); 415 RELEASE(pool); 416 return 0; 417@} 418@end example 419 420To get this running, all you need do is create two directories, one for the 421client and one for the server. Each directory will hold a makefile, the client 422or server source code, and a copy of the protocol header file. When the files 423compile, first run the server and then the client. You can try this on the 424same machine, or on two different machines (with GNUstep installed) on the 425same LAN. What happens when you run the client without the server? How would 426you display a "No Server Connection" warning at the client? 427 428 429@subsection GNUstep Distributed Objects Name Server 430@cindex gdomap 431@cindex Distributed Objects Name Server, GNUstep 432 433You might wonder how the client finds the server, or, rather, how it finds the 434directory the server lists itself in. 435 436For the default connection type (a connection only usable on the local host 437between processes run by the same person), a private file (or the registry 438on ms-windows) is used to hold the name registration information. 439 440For connections using socket ports to communicate between hosts, 441an auxiliary process will 442automatically be started on each machine, if it isn't running already, that 443handles this, allowing the server to register and the client to send a query 444behind the scenes. This @i{GNUstep Distributed Objects Name Server} runs as 445'@code{gdomap}' and binds to port 538. See the manual page or the HTML 446``GNUstep Base Tools'' @uref{../../Tools/Reference/index.html, 447documentation} for further information. 448 449 450@subsection Look Ma, No Stubs! 451 452One difference you may have noticed in the example we just looked at from 453other remote method invocation interfaces such as CORBA and Java RMI was that 454there are @i{no stub classes}. The source of this great boon is described at 455the end of this chapter: @ref{Distributed Objects, , Language Support for 456Distributed Objects}. 457 458 459@section A More Involved Example 460@cindex distributed objects, example (no error checking) 461@cindex game server example 462 463Now we will look at an example called GameServer that uses distributed objects 464in a client/server game. 465 466Actually the game itself is not implemented, just its distributed support 467structure, and while the code to vend an object and connect to a remote 468process is similar to that already shown, the code does show a number of 469additional techniques that can be used in other client/server programs. Here 470are the requirements we will implement: 471 472@itemize @bullet 473@item 474When the client attempts to join the game, the server checks that the client 475is entitled to join, based on the last time the client played. The rule is: 476if the client lost the last game, then they cannot re-play for the next 2 477hours; but if the client won the last game, then they can re-play the game at 478any time (a reward for winning).@* 479 480@item 481The server also makes sure the client is not already connected and playing the 482game (i.e. they cannot play two games at the same time - that would be 483cheating).@* 484 485@item 486In addition to a proxy for the server being obtained at the client, a proxy 487for the client is received at the server. This allows two-way messaging, where 488the client can send messages to the server and the server can send messages to 489the client (e.g. the state of play may be affected by the actions of other 490players, or by other events at the server).@* 491 492Two protocols will therefore be required, one for the methods 493implemented at the server and one for those implemented at the client. 494@end itemize 495 496Have a look at the program code in the following sections and added 497comments. Can you work out what is happening at the server and client? If you 498have any difficulties then refer to the relevant sections in this manual, or 499to class documentation @uref{../Reference/index.html, here} or at the Apple 500web site. 501 502 503@subsection Protocol Adopted at Client 504 505We have chosen @code{GameClient} as the name of both the protocol 506adopted at the client and the class of the responding client object. The 507header file declaring this protocol will simply declare the methods that 508the class must implement. 509 510@example 511@@protocol GameClient 512- (void) clientMessage: (bycopy NSString *)theMessage; 513- (int) clientReply; 514 515// Other methods would be added that 516// reflect the nature of the game. 517 518@@end 519@end example 520 521The protocol will be saved as @code{GameClient.h}. 522 523@subsection Protocol Adopted at Server 524 525We have chosen @code{GameServer} as the name of both the protocol 526adopted at the server and the class of the responding server object. The 527header file declaring this protocol will simply declare the methods that 528the class must implement. 529 530@example 531@@protocol GameServer 532- (BOOL) mayJoin: (id)client asPlayer: (bycopy NSString*)name; 533- (int) startGame: (bycopy NSString*)name; 534- (BOOL) endGame: (bycopy NSString*)name; 535 536// Other methods would be added that 537// reflect the nature of the game. 538 539@@end 540@end example 541 542The protocol will be saved as @code{GameServer.h}. 543 544@subsection Code at the Client 545 546The client code contains the @code{main} function and the 547@code{GameClient} class declaration and implementation. 548 549The @code{main()} function attempts to connect to the server, while the 550@code{GameClient} class adopts the @code{GameClient} protocol. 551 552@example 553#include <Foundation/Foundation.h> 554#include "GameServer.h" 555#include "GameClient.h" 556 557/* 558 * GameClient class declaration: 559 * Adopt the GameClient protocol. 560 */ 561@@interface GameClient : NSObject <GameClient> 562@@end 563 564/* 565 * GameClient class implementation. 566 */ 567@@implementation GameClient 568 569/* 570 * Implement clientMessage: as declared in the protocol. 571 * The method simply prints a message at the client. 572 */ 573- (void) clientMessage: (NSString*)theMessage 574@{ 575 printf([theMessage cString]); 576@} 577 578/* 579 * Implement clientReply: as declared in the protocol. 580 * The method simply returns the character entered 581 * at the client keyboard. 582 */ 583- (int) clientReply 584@{ 585 return getchar(); 586@} 587@@end // End of GameClient class implementation. 588 589/* 590 * The main function of the client program. 591 */ 592int main(int argc, char **argv) 593@{ 594 CREATE_AUTORELEASE_POOL(pool); 595 id<GameServer> server; 596 int result; 597 NSString *name; 598 id client; 599 600 /* 601 * The NSUserName() function returns the name of the 602 * current user, which is sent to the server when we 603 * try to join the game. 604 */ 605 name = NSUserName(); 606 607 /* 608 * Create a GameClient object that is sent to 609 * the server when we try to join the game. 610 */ 611 client = AUTORELEASE([GameClient new]); 612 613 /* 614 * Try to get a proxy for the root object of a server 615 * registered under the name 'JoinGame'. Since the host 616 * is '*', we can connect to any server on the local network. 617 */ 618 server = (id<GameServer>)[NSConnection 619 rootProxyForConnectionWithRegisteredName: @@"JoinGame" 620 host: @@"*" 621 usingNameServer: [NSSocketPortNameServer sharedInstance]]; 622 if (server == nil) 623 @{ 624 printf("\n** No Connection to GameServer **\n"); 625 result = 1; 626 @} 627 628 /* 629 * Try to join the game, passing a GameClient object as 630 * the client, and our user-name as name. The 'client' 631 * argument will be received as a proxy at the server. 632 */ 633 else if ([server mayJoin: client asPlayer: name] == NO) 634 @{ 635 result = 1; // We cannot join the game. 636 @} 637 else 638 @{ 639 /* 640 * At this point, we would actually start to play the game. 641 */ 642 [server startGame: name]; // Start playing game. 643 [server endGame: name]; // Finally end the game. 644 result = 0; 645 @} 646 RELEASE(pool); 647 return result; 648@} 649@end example 650 651To summarise the code at the client: 652 653@itemize @bullet 654@item 655We obtained a proxy for the server and can now communicate with the server 656using the methods declared in the @code{GameServer} protocol.@* 657 658@item 659We passed a @code{GameClient} object and our user-name to the server (the 660@code{GameClient} object is received as a proxy at the server). The server 661can now communicate with the client using the methods declared in the 662@code{GameClient} protocol.@* 663 664@item 665When the game is in progress, the server can alter the state of the client 666object to reflect the success of the player. 667@end itemize 668 669@subsection Code at the Server 670 671The server code contains the @code{main} function and the 672@code{GameServer} class declaration and implementation. 673 674The @code{main()} function vends the server's root object and starts the 675runloop, while the @code{GameServer} class adopts the @code{GameServer} 676protocol. The class also implements methods that initialise and 677deallocate the root object's instance variables (dictionaries that hold 678player information). 679 680@example 681#include <Foundation/Foundation.h> 682#include "GameServer.h" 683#include "GameClient.h" 684 685/* 686 * GameServer class declaration: 687 * Adopt the GameServer protocol and declare 688 * GameServer instance variables. 689 */ 690@@interface GameServer : NSObject <GameServer> 691@{ 692 NSMutableDictionary *delayUntil; // Delays to re-joining GameServer. 693 NSMutableDictionary *currentPlayers; // Proxies to each client. 694 NSMutableDictionary *hasWon; // Success in game for each player. 695@} 696@@end 697 698/* 699 * GameServer class implementation. 700 */ 701@@implementation GameServer 702 703/* Initialise GameServer's instance variables. */ 704- (id) init 705@{ 706 self = [super init]; 707 if (self != nil) 708 @{ 709 /* 710 * Create a dictionary for a maximum of 711 * 10 named players that will hold a 712 * re-joining time delay. 713 */ 714 delayUntil = [[NSMutableDictionary alloc] 715 initWithCapacity: 10]; 716 /* 717 * Create a dictionary that will hold the 718 * names of these players and a proxy for 719 * the received client objects. 720 */ 721 currentPlayers = [[NSMutableDictionary alloc] 722 initWithCapacity: 10]; 723 724 /* 725 * Create a dictionary that will record 726 * a win for any of these named players. 727 */ 728 hasWon = [[NSMutableDictionary alloc] 729 initWithCapacity: 10]; 730 @} 731 return self; 732@} 733 734/* Release GameServer's instance variables. */ 735- (void) dealloc 736@{ 737 RELEASE(delayUntil); 738 RELEASE(currentPlayers); 739 RELEASE(hasWon); 740 [super dealloc]; 741@} 742 743/* 744 * Implement mayJoin:: as declared in the protocol. 745 * Adds the client to the list of current players. 746 * Each player is represented at the server by both 747 * name and by proxy to the received client object. 748 * A player cannot join the game if they are already playing, 749 * or if joining has been delayed until a later date. 750 */ 751- (BOOL) mayJoin: (id)client asPlayer: (NSString*)name 752@{ 753 NSDate *delay; // The time a player can re-join the game. 754 NSString *aMessage; 755 756 if (name == nil) 757 @{ 758 NSLog(@@"Attempt to join nil user"); 759 return NO; 760 @} 761 762 /* Has the player already joined the game? */ 763 if ([currentPlayers objectForKey: name] != nil) 764 @{ 765 /* Inform the client that they cannot join. */ 766 aMessage = @@"\nSorry, but you are already playing GameServer!\n"; 767 [client clientMessage: aMessage]; 768 return NO; 769 @} 770 771 /* Get the player's time delay for re-joining. */ 772 delay = [delayUntil objectForKey: name]; 773 774 /* 775 * Can the player join the game? Yes if there is 776 * no restriction or if the time delay has passed; 777 * otherwise no, they cannot join. 778 */ 779 if (delay == nil || [delay timeIntervalSinceNow] <= 0.0) 780 @{ 781 /* Remove the old restriction on re-joining the game. */ 782 [delayUntil removeObjectForKey: name]; 783 784 /* Add the player to the list of current players. */ 785 [currentPlayers setObject: client forKey: name]; 786 [hasWon setObject: @@"NO" forKey: name]; // They've not won yet. 787 788 /* Inform the client that they have joined the game. */ 789 aMessage = @@"\nWelcome to GameServer\n"; 790 [client clientMessage: aMessage]; 791 return YES; 792 @} 793 else 794 @{ 795 /* Inform the client that they cannot re-join. */ 796 aMessage = @@"\nSorry, you cannot re-join GameServer yet.\n"; 797 [client clientMessage: aMessage]; 798 return NO; 799 @} 800@} 801 802/* 803 * Implement startGame: as declared in the protocol. 804 * Simply ask the player if they want to win, and get 805 * there reply. 806 */ 807- (int) startGame: (NSString *)name 808@{ 809 NSString *aMessage; 810 id client; 811 int reply; 812 813 client = [currentPlayers objectForKey: name]; 814 815 aMessage = @@"\nDo you want to win this game? (Y/N <RET>) ... "; 816 [client clientMessage: aMessage]; 817 818 reply = [client clientReply]; 819 if (reply == 'y' || reply == 'Y') 820 [hasWon setObject: @@"YES" forKey: name]; // They win. 821 else [hasWon setObject: @@"NO" forKey: name]; // They loose. 822 return 0; 823@} 824 825/* 826 * Implement endGame: as declared in the protocol. 827 * Removes a player from the game, and either sets 828 * a restriction on the player re-joining or removes 829 * the current restriction. 830 */ 831- (BOOL) endGame: (NSString*)name 832@{ 833 id client; 834 NSString *aMessage, *yesOrNo; 835 NSDate *now, *delay; 836 NSTimeInterval twoHours = 2 * 60 * 60; // Seconds in 2 hours. 837 838 if (name == nil) 839 @{ 840 NSLog(@@"Attempt to end nil user"); 841 return NO; 842 @} 843 844 now = [NSDate date]; 845 delay = [now addTimeInterval: twoHours]; 846 client = [currentPlayers objectForKey: name]; 847 yesOrNo = [hasWon objectForKey: name]; 848 849 if ([yesOrNo isEqualToString: @@"YES"]) // Has player won? 850 @{ 851 /* 852 * Player wins, no time delay to re-joining the game. 853 * Remove any re-joining restriction and send 854 * a message to the client. 855 */ 856 [delayUntil removeObjectForKey: name]; 857 aMessage = @@"\nWell played: you can re-join GameServer at any time.\n"; 858 [client clientMessage: aMessage]; 859 860 @} 861 else // Player lost 862 @{ 863 /* 864 * Set a time delay for re-joining the game, 865 * and send a message to the client. 866 */ 867 [delayUntil setObject: delay forKey: name]; 868 aMessage = @@"\nYou lost, but you can re-join GameServer in 2 hours.\n"; 869 [client clientMessage: aMessage]; 870 @} 871 872 /* Remove the player from the current game. */ 873 [currentPlayers removeObjectForKey: name]; 874 [hasWon removeObjectForKey: name]; 875 return YES; 876@} 877 878@@end // End of GameServer class implementation 879 880/* 881 * The main function of the server program simply 882 * vends the root object and starts the runloop. 883 */ 884int main(int argc, char** argv) 885@{ 886 CREATE_AUTORELEASE_POOL(pool); 887 GameServer *server; 888 NSSocketPort *port; 889 NSConnection *connXion; 890 891 server = AUTORELEASE([GameServer new]); 892 port = [NSSocketPort port]; 893 connXion = [NSConnection connectionWithReceivePort: port sendPort: port]; 894 [connXion setRootObject: server]; 895 [connXion registerName: @@"JoinGame" 896 withNameServer: [NSSocketPortNameServer sharedInstance]]; 897 [[NSRunLoop currentRunLoop] run]; 898 RELEASE(pool); 899 return 0; 900@} 901@end example 902 903To summarise the code at the server: 904 905@itemize @bullet 906@item 907We vend the server's root object and start a runloop, allowing clients to 908connect with the server.@* 909 910@item 911When we receive a proxy for a client object, we communicate with that client 912using methods declared in the @code{ClientServer} protocol.@* 913 914@item 915We create three dictionary objects, each referenced by player 916name. @code{currentUsers} holds proxies for each of the current players; 917@code{delayUntil} holds times when each player can re-join the game; and 918@code{hasWon} holds a string for each player, which is set to "YES" if the 919player wins.@* 920 921@item 922When the game is in progress, the server can alter the state of each client 923object to reflect the success of each player. 924@end itemize 925 926I hope you managed to understand most of the code in this example. If 927you are reading the on-screen version, then you can copy and paste the 928code to suitably named files, create makefiles, and then make and run 929each program. What message is displayed if you immediately try to 930re-join a game after losing? And after winning? 931 932@i{Exercise: Modify the server code so that the server records the 933number of wins for each player, and displays this information at both 934the start and end of each game.} 935 936 937@section Language Support for Distributed Objects 938 939Objective-C provides special 'type' qualifiers that can be used in a 940protocol to control the way that message arguments are passed between 941remote processes, while at run time, the run-time system transparently 942uses what is known as 'forward invocation' to forward messages to a 943remote process. (See @ref{Advanced Messaging, , Forwarding}.) 944 945 946@subsection Protocol Type Qualifiers 947@cindex protocol type qualifiers 948@cindex in, out, and inout type qualifiers 949@cindex out, type qualifier 950@cindex oneway, type qualifier 951@cindex bycopy and byref type qualifiers 952 953When message arguments are passed by value then the receiving method can 954only alter the copy it receives, and not the value of the original 955variable. When an argument is passed by reference (as a pointer), the 956receiving method has access to the original variable and can alter that 957variable's data. In this case the argument is effectively passed 'in' to 958the method, and then passed 'out' of the method (on method return). 959 960When an argument is passed by reference to a remote object, the network 961must handle this two-way traffic, whether or not the remote object 962modifies the received argument. 963 964Type qualifiers can be used in a protocol to control the way these 965messages are handled, and to indicate whether or not the sending process 966will wait for the remote process to return. 967 968@itemize @bullet 969@item The @b{oneway} qualifier is used in conjunction with a 970@code{void} return type to inform the run-time system that the sending 971process does not need to wait for the receiving method to return (known 972as 'asynchronous' messaging). The protocol declaration for the receiving 973method would look something like this:@*@* 974 975@code{- (@b{oneway} void)noWaitForReply;}@*@* 976 977@item The @b{in, out } and @b{inout} qualifiers can be used with pointer 978arguments to control the direction in which an argument is passed. The 979protocol declaration for the receiving methods would look something like 980this:@* 981 982@example 983/* 984 * The value that 'number' points to will be passed @b{in} to the remote process. 985 * (No need to return the argument's value from the remote process.) 986 */ 987@code{- setValue: (@b{in} int *)number;} 988 989/* 990 * The value that 'number' points to will be passed @b{out} of the remote process. 991 * (No need to send the argument's value to the remote process.) 992 */ 993@code{- getValue: (@b{out} int *)number;} 994 995/* 996 * The value that 'number' points to is first passed @b{in} to the remote 997 * process, but will eventually be the value that is passed @b{out} of the 998 * remote process. (Send and return the argument's value.) 999 */ 1000@code{- changeValue: (@b{inout} int *)number;} 1001@end example 1002 1003Passing of arguments by reference is very restricted in Objective-C. 1004it applies only to pointers to C data types, not to objects, and except 1005for the special case of a pointer to a nul terminated C string (@b{char*}) 1006the pointer is assumed to refer to a single data item of the specified 1007type. 1008 1009@example 1010/* 1011 * A method passing an unsigned short integer by reference. 1012 */ 1013@code{- updateCounter: (@b{inout} unsigned shortn *)value;} 1014 1015/* 1016 * A method passing a structure by reference. 1017 */ 1018@code{- updateState: (@b{inout} struct stateInfo *)value;} 1019 1020/* 1021 * As a special case, a char (or equivalent typedef) passed by reference 1022 * is assumed to be a nul terminated string ... there is no way to pass 1023 * a single character by reference: 1024 */ 1025@code{- updateBuffer: (@b{inout} char *)str;} 1026 1027@end example 1028 1029@item The @b{bycopy} and @b{byref} qualifiers can be used in a protocol 1030when the argument or return type is an object.@*@* 1031 1032An object is normally passed by reference and received in the remote 1033process as a proxy. When an object is passed by copy, then a copy of 1034the object will be received in the remote process, allowing the remote 1035process to directly interact with the copy. Protocol declarations would 1036look something like this:@* 1037 1038@example 1039/* 1040 * Copy of object will be received in the remote process. 1041 */ 1042- sortNames: (@b{bycopy} id)listOfNames; 1043 1044/* 1045 * Copy of object will be returned by the remote process. 1046 */ 1047- (@b{bycopy} id)returnNames; 1048@end example 1049 1050By default, large objects are normally sent @b{byref}, while small 1051objects like @code{NSStrings} are normally sent @b{bycopy}, but you 1052cannot rely on these defaults being adopted and should explicitly state 1053the qualifier in the protocol.@*@* 1054 1055 1056The @b{bycopy} qualifier can also be used in conjunction with the 1057@b{out} qualifier, to indicate that an object will be passed @b{out} of 1058the remote process by copy rather than by proxy (no need to send the 1059object).@* 1060 1061@example 1062/* 1063 * The object will not be received in the remote process, but the object 1064 * will be returned @b{bycopy}. 1065 */ 1066- sortAndReturn: (@b{bycopy out} id *)listOfNames; 1067@end example 1068 1069You should be aware that some classes ignore the @b{bycopy} qualifier 1070and the object will be sent by reference. The @b{bycopy} qualifier will 1071also be ignored if the remote process does not have the class of the 1072object in its address space, since an object's instance variables are 1073accessed through the object's methods.@*@* 1074 1075When a copy of an object is sent to a remote process, only the object's 1076instance variables are sent and received (an object's methods exist in 1077the address space of the object's class, not in the address space of the 1078individual object). 1079 1080@end itemize 1081 1082@subsection Message Forwarding 1083@cindex message forwarding, distributed objects 1084@cindex forward invocation, distributed objects 1085 1086If you have used other remote invocation mechanisms such as CORBA or Java 1087RMI, you may have noticed a big difference from these in the GNUstep 1088Distributed Object paradigm -- there are no ``stub'' classes, either on the 1089client or the server. This tremendously simplifies the use of remote 1090invocation and is possible due to the Objective-C message-forwarding facility 1091(@ref{Advanced Messaging, , Forwarding}). 1092 1093In GNUstep, there are proxies on the client and server side that handle 1094network communications and serialization/deserialization of arguments and 1095return values just as in CORBA and RMI, but when it comes to responding to the 1096client and server protocol method calls themselves, they are intercepted 1097through the use of the @code{forwardInvocation:} method, where they can be 1098passed on to the registered client and server objects through the ordinary 1099Objective-C message sending mechanism. 1100 1101 1102@section Error Checking 1103@cindex error checking, distributed objects 1104@cindex distributed objects, error checking 1105 1106When dealing with distributed objects your code must be able to handle 1107the following situations: failure to vend the server object, exceptions 1108raised at run-time, and failure of the network connection. 1109 1110@subsection Vending the Server Object 1111When vending the server object, your code must be able to handle the 1112situation in which the network does not accept the proposed registered 1113name for the server. 1114 1115@subsection Catching Exceptions 1116There are two situations to consider. 1117@itemize @bullet 1118@item An @code{NSPortTimeoutException} is raised.@*@* 1119This exception is raised if a message takes too long to arrive at the 1120remote process, or if a reply takes too long to return. This will happen 1121if the remote process is busy, has hung, or if there is a problem with 1122the network. The best way to handle the exception is to close the 1123connection to the remote process.@*@* 1124 1125@item An exception is raised in the remote process while the remote 1126process is executing a method.@*@* 1127In most cases you can deal directly with these exceptions in the process 1128in which they were raised; i.e. without having to consider the network 1129connection itself. 1130@end itemize 1131 1132@subsection The Connection Fails 1133You can register an observer object to receive a notification, in the 1134form of a @code{connectionDidDie:} message, when a registered connection 1135fails. The argument to this message will be an @code{NSNotification} 1136object that returns the failed connection when it receives an 1137@code{object} message. See @ref{Base Library, , Event-Based Communications} 1138for more information on notifications. 1139 1140To receive this 'notification' the observer must implement the 1141@code{connectionDidDie:} method, but can be an instance of any class. The 1142observer can then handle the failure gracefully, by releasing any 1143references to the failed connection and releasing proxies that used the 1144connection. Registering an object to receive this notification is 1145described in more detail in the @code{NSConnection} class documentation. 1146 1147@comment{Need some comments on reference counting and disposal.} 1148 1149 1150@page 1151 1152