home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2001 / MacHack 2001.toast / pc / Papers / Garrison / Code / SchmoozingExamples / ForkingServer.m < prev    next >
Encoding:
Text File  |  2001-06-23  |  7.9 KB  |  225 lines

  1. //
  2. //  ForkingServer.m
  3. //  SchmoozingExamples
  4. //
  5. //  Created by garrison on Fri Apr 20 2001.
  6. //  Copyright (c) 2001 Standard Orbit Software, LLC. All rights reserved.
  7. //
  8. //  Permission is granted to use this code for any purpose, at your own risk.
  9. //  No warranties are expressed or implied.
  10. //
  11.  
  12. /* An Algorithm for a Concurrent Server [Comer93]
  13.  
  14. Master 1) Create a socket for the server and bind it to the well known port 
  15.           for the service being offered.
  16. Master 2) Establishing a listener on the socket.
  17. Master 3) Repeatedly accept incoming requests and create new slaves to handle the response.
  18. Slave 1) Receive a connection request/socket upon creation.
  19. Slave 2) Interact with the client using the socket, reading requests and sending back replies.
  20. Slave 3) Close the connection and exit.  The slave exits after handling all requests 
  21.          from one client.
  22. */
  23.  
  24. #import <OmniNetworking/OmniNetworking.h>
  25. #import <Connection.h>
  26. #import "ForkingServer.h"
  27. #include <unistd.h> // for fork()
  28. #include <sys/wait.h> // for waitpid()
  29.  
  30. static void reaper();
  31.  
  32. @implementation ForkingServer
  33.  
  34. - (id) initWithLocalPort:(unsigned short) aPort
  35. {
  36.     self = [super init];
  37.     
  38.     if ( self )
  39.     {
  40.         NS_DURING {
  41.             
  42.             // Establish a handler for the SIGCHLD signal so that we can
  43.             // do cleanup on forked processes.
  44.             if ( signal( SIGCHLD, reaper ) == SIG_ERR )
  45.             {
  46.                 [NSException raise:NSGenericException format:@"can't install SIGCHLD hander (%d) because %s", errno, strerror(errno)];
  47.             }
  48.             
  49.             listeningSocket = [[ONTCPSocket tcpSocket] retain];
  50.             // Master Step 1. Create an ONTCPSocket for the server.
  51.             
  52.             [listeningSocket startListeningOnLocalPort:aPort allowingAddressReuse:YES];
  53.             // Master Step 2.
  54.             // Start socket listening on the well known port.  Allowing address reuse is
  55.             // helpful while debugging (it prevents a port from getting locked up if your
  56.             // app crashes without closing the socket).
  57.             
  58.             NSLog(@"Listening for connections on host %@:%d", [ONHost localHostname], aPort);
  59.         }
  60.         NS_HANDLER {
  61.             // Handle exceptions.
  62.             NSLog(@"Can't start server: %@, %@", [localException name], localException);
  63.             [listeningSocket release];
  64.             return nil;
  65.         }
  66.         NS_ENDHANDLER
  67.     }
  68.         
  69.     return self;
  70. }
  71.  
  72.  
  73. - (void) dealloc
  74. {
  75.     [listeningSocket release];
  76.     [super dealloc];
  77. }
  78.  
  79.  
  80. - (void) run
  81. {
  82.     // This method implements a concurrent server using forking.  A new copy of the 
  83.     // program is created in a forked process.  The new copy goes on to handle the
  84.     // connection while the original continues on to processing subsequent incoming
  85.     // connections.
  86.     
  87.     ONTCPSocket *connectionSocket = nil;
  88.     Connection *someClient = nil;
  89.     unsigned short connectionCount = 0;
  90.     pid_t forkedPID;
  91.     
  92.     // 'connectionSocket' will hold the accepted connection, leaving 
  93.     // the listening socket free to continue listening.
  94.     
  95.     // 'someClient' is an object of our own custom class for handling
  96.     // connections to our server.
  97.     
  98.     // 'connectionCount' holds a count of connections accepted, just to
  99.     // provide some feedback in the spartan UI.
  100.  
  101.     NSLog(@"Forking Concurrent Server");
  102.     
  103.     do 
  104.     {
  105.         NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
  106.         // Create an autorelease pool to contain memory usage in the loop.
  107.  
  108.         NS_DURING 
  109.         {   
  110.             NSLog(@"Parent process listening for next connection");
  111.             
  112.             connectionSocket = [listeningSocket acceptConnectionOnNewSocket];
  113.             // Master Step 3. Accept the next incoming connection.  Server execution
  114.             // will block until a connection request is received.
  115.             
  116.             connectionCount += 1;
  117.             // Increment the accepted connection count
  118.  
  119.             NSLog(@"Accepted connection %d from host %@", connectionCount, 
  120.                     [[connectionSocket remoteAddressHost] hostname]);
  121.         
  122.             // Fork the process to handle the accepted connection.
  123.             
  124.             forkedPID = fork();
  125.             
  126.  
  127.             if ( forkedPID == 0 )
  128.             {
  129.                 NSLog(@"Child process forked.");
  130.                 // We're executing in the child process now.
  131.                 // fork() returns a zero value to the newly created child process. 
  132.                 // The child process gets a copy of everything the original parent
  133.                 // process had (e.g. the accepted connection socket).
  134.  
  135.                 // Continue program execution from this point on by handling
  136.                 // the accepted connection.  When finished, close the connection
  137.                 // and exit, which will terminate the child process.
  138.                 
  139.                 [listeningSocket release];
  140.                 listeningSocket = nil;
  141.                 // Close the child's copy of the listening socket.
  142.                 
  143.                 someClient = [[Connection alloc] initWithConnectedSocket:connectionSocket];
  144.                 // Slave Step 1.  Receive the accepted connection socket on initalization
  145.                 // of the slave (the Connection object plays the role of the slave here).
  146.                 
  147.                 [someClient processConnection];
  148.                 // Slave Step 2.  Process the connection in a server-specifc manner.
  149.                                 
  150.                 [someClient release];
  151.                 // Slave Step 3.  Close the connection and exit the slave.
  152.             
  153.                 NSLog(@"Child processed connection %d.", connectionCount);
  154.  
  155.                 [loopPool release];
  156.                 break;
  157.                 // Break out of the processing loop and exit this child process.
  158.  
  159.             } 
  160.             else if ( forkedPID > 0 )
  161.             {
  162.                 NSLog(@"Server spawned child process %d to handle connection", forkedPID );
  163.                 // We're still executing in the parent process.  forkedPID holds the
  164.                 // process id of the forked child process.  We need to clean up from the
  165.                 // for fork and go back to the top of the loop.
  166.                 
  167.                 [loopPool release];
  168.                 connectionSocket = nil;
  169.                 // Close the parent's copy of the accepted socket.
  170.                 
  171.                 continue;
  172.                 // Go back to the top of the loop
  173.             }
  174.             else if ( forkedPID == -1 )
  175.             {
  176.                 // fork() returned with an error condition.  Clean up and
  177.                 // exit.
  178.                 
  179.                 [loopPool release];
  180.                 break;
  181.             } 
  182.         }
  183.         
  184.         NS_HANDLER 
  185.         {
  186.             NSLog(@"Exception %@ raised in forking server loop: %@", 
  187.                     [localException name], localException);
  188.             break;
  189.             // Close done the server loop if an exception occurs.
  190.         }
  191.         NS_ENDHANDLER
  192.  
  193.  
  194.     } while (1);
  195.     // Loop forever accepting new connections.  A real server would provide some means
  196.     // of gracefully shutting the server down (e.g. implementing a SIGKILL or SIGHUP
  197.     // handler).
  198.     
  199.     if ( forkedPID == 0 )
  200.         NSLog(@"Child process is exiting");
  201.     // Some informative UI for the console.
  202.     
  203.     exit(0);
  204.  
  205. }
  206. @end
  207.  
  208. // reaper() is a cleanup routine intended to be invoked whenever the server
  209. // application receives the SIGCHLD signal.  SIGCHLD is sent to a parent process
  210. // by the OS whenever a forked child process exits.  What we do here is reap any
  211. // incompletely terminated processes ("zombies") to ensure that we don't leave any 
  212. // around to use up system resources.
  213.  
  214. void reaper()
  215. {
  216.         pid_t reaped;
  217.         int exitStatus; 
  218.         
  219.         do {
  220.             reaped = waitpid( -1, &exitStatus, WNOHANG | WUNTRACED );
  221.         } while ( reaped > 0 );
  222.         // Loop until all terminating child processes have been reaped
  223.         // by waitpid()
  224. }
  225.