home *** CD-ROM | disk | FTP | other *** search
- //
- // ForkingServer.m
- // SchmoozingExamples
- //
- // Created by garrison on Fri Apr 20 2001.
- // Copyright (c) 2001 Standard Orbit Software, LLC. All rights reserved.
- //
- // Permission is granted to use this code for any purpose, at your own risk.
- // No warranties are expressed or implied.
- //
-
- /* An Algorithm for a Concurrent Server [Comer93]
-
- Master 1) Create a socket for the server and bind it to the well known port
- for the service being offered.
- Master 2) Establishing a listener on the socket.
- Master 3) Repeatedly accept incoming requests and create new slaves to handle the response.
- Slave 1) Receive a connection request/socket upon creation.
- Slave 2) Interact with the client using the socket, reading requests and sending back replies.
- Slave 3) Close the connection and exit. The slave exits after handling all requests
- from one client.
- */
-
- #import <OmniNetworking/OmniNetworking.h>
- #import <Connection.h>
- #import "ForkingServer.h"
- #include <unistd.h> // for fork()
- #include <sys/wait.h> // for waitpid()
-
- static void reaper();
-
- @implementation ForkingServer
-
- - (id) initWithLocalPort:(unsigned short) aPort
- {
- self = [super init];
-
- if ( self )
- {
- NS_DURING {
-
- // Establish a handler for the SIGCHLD signal so that we can
- // do cleanup on forked processes.
- if ( signal( SIGCHLD, reaper ) == SIG_ERR )
- {
- [NSException raise:NSGenericException format:@"can't install SIGCHLD hander (%d) because %s", errno, strerror(errno)];
- }
-
- listeningSocket = [[ONTCPSocket tcpSocket] retain];
- // Master Step 1. Create an ONTCPSocket for the server.
-
- [listeningSocket startListeningOnLocalPort:aPort allowingAddressReuse:YES];
- // Master Step 2.
- // Start socket listening on the well known port. Allowing address reuse is
- // helpful while debugging (it prevents a port from getting locked up if your
- // app crashes without closing the socket).
-
- NSLog(@"Listening for connections on host %@:%d", [ONHost localHostname], aPort);
- }
- NS_HANDLER {
- // Handle exceptions.
- NSLog(@"Can't start server: %@, %@", [localException name], localException);
- [listeningSocket release];
- return nil;
- }
- NS_ENDHANDLER
- }
-
- return self;
- }
-
-
- - (void) dealloc
- {
- [listeningSocket release];
- [super dealloc];
- }
-
-
- - (void) run
- {
- // This method implements a concurrent server using forking. A new copy of the
- // program is created in a forked process. The new copy goes on to handle the
- // connection while the original continues on to processing subsequent incoming
- // connections.
-
- ONTCPSocket *connectionSocket = nil;
- Connection *someClient = nil;
- unsigned short connectionCount = 0;
- pid_t forkedPID;
-
- // 'connectionSocket' will hold the accepted connection, leaving
- // the listening socket free to continue listening.
-
- // 'someClient' is an object of our own custom class for handling
- // connections to our server.
-
- // 'connectionCount' holds a count of connections accepted, just to
- // provide some feedback in the spartan UI.
-
- NSLog(@"Forking Concurrent Server");
-
- do
- {
- NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
- // Create an autorelease pool to contain memory usage in the loop.
-
- NS_DURING
- {
- NSLog(@"Parent process listening for next connection");
-
- connectionSocket = [listeningSocket acceptConnectionOnNewSocket];
- // Master Step 3. Accept the next incoming connection. Server execution
- // will block until a connection request is received.
-
- connectionCount += 1;
- // Increment the accepted connection count
-
- NSLog(@"Accepted connection %d from host %@", connectionCount,
- [[connectionSocket remoteAddressHost] hostname]);
-
- // Fork the process to handle the accepted connection.
-
- forkedPID = fork();
-
-
- if ( forkedPID == 0 )
- {
- NSLog(@"Child process forked.");
- // We're executing in the child process now.
- // fork() returns a zero value to the newly created child process.
- // The child process gets a copy of everything the original parent
- // process had (e.g. the accepted connection socket).
-
- // Continue program execution from this point on by handling
- // the accepted connection. When finished, close the connection
- // and exit, which will terminate the child process.
-
- [listeningSocket release];
- listeningSocket = nil;
- // Close the child's copy of the listening socket.
-
- someClient = [[Connection alloc] initWithConnectedSocket:connectionSocket];
- // Slave Step 1. Receive the accepted connection socket on initalization
- // of the slave (the Connection object plays the role of the slave here).
-
- [someClient processConnection];
- // Slave Step 2. Process the connection in a server-specifc manner.
-
- [someClient release];
- // Slave Step 3. Close the connection and exit the slave.
-
- NSLog(@"Child processed connection %d.", connectionCount);
-
- [loopPool release];
- break;
- // Break out of the processing loop and exit this child process.
-
- }
- else if ( forkedPID > 0 )
- {
- NSLog(@"Server spawned child process %d to handle connection", forkedPID );
- // We're still executing in the parent process. forkedPID holds the
- // process id of the forked child process. We need to clean up from the
- // for fork and go back to the top of the loop.
-
- [loopPool release];
- connectionSocket = nil;
- // Close the parent's copy of the accepted socket.
-
- continue;
- // Go back to the top of the loop
- }
- else if ( forkedPID == -1 )
- {
- // fork() returned with an error condition. Clean up and
- // exit.
-
- [loopPool release];
- break;
- }
- }
-
- NS_HANDLER
- {
- NSLog(@"Exception %@ raised in forking server loop: %@",
- [localException name], localException);
- break;
- // Close done the server loop if an exception occurs.
- }
- NS_ENDHANDLER
-
-
- } while (1);
- // Loop forever accepting new connections. A real server would provide some means
- // of gracefully shutting the server down (e.g. implementing a SIGKILL or SIGHUP
- // handler).
-
- if ( forkedPID == 0 )
- NSLog(@"Child process is exiting");
- // Some informative UI for the console.
-
- exit(0);
-
- }
- @end
-
- // reaper() is a cleanup routine intended to be invoked whenever the server
- // application receives the SIGCHLD signal. SIGCHLD is sent to a parent process
- // by the OS whenever a forked child process exits. What we do here is reap any
- // incompletely terminated processes ("zombies") to ensure that we don't leave any
- // around to use up system resources.
-
- void reaper()
- {
- pid_t reaped;
- int exitStatus;
-
- do {
- reaped = waitpid( -1, &exitStatus, WNOHANG | WUNTRACED );
- } while ( reaped > 0 );
- // Loop until all terminating child processes have been reaped
- // by waitpid()
- }
-