/************************************************************ Program: Simple Server Example File: server.cpp Author: Ryan Junee - ryan@cs.usyd.edu.au Version: 1.0 Date: 02/09/2001 Revisions: 1.0 02/09/2001 First Version This program creates a server that listens on the specified port. Each time a client connects, a new server process is forked. The server simply echoes whatever the client sends. Note that I have included very little error handling in this example, and lots of things can go wrong in network programs. If you are serious about writing a network app, read Stevens - Unix Network Programming. **************************************************************/ #include "sockio.h" /* Function prototypes */ int establish(int); // Establishes a new socket, listening on the given port int getConnection(int); // Gets an incoming client connection. void handleClient(int); // Does the actual work once a client has connected void zombieAssassin(int); // Kills zombie processes. /** * Main: Called with one argument, the port number that the server should * use. Simply establishes a socket listening on that port number, and * waits for clients to connect. Each time a client connects a new server * process is forked to handle it. */ int main(int argc, char **argv) { int s; // Will hold the socket once established. // Note that 'sockets' are simply integers, they reference // a socket resource that has been created by the OS. /* First handle command line args */ if (argc != 2) { std::cout << "Usage: " << argv[0] << " " << endl; std::cout << " where is the port on which the server should listen." << endl; return 0; } /* Establish a listening socket on the given port */ s = establish(atoi(argv[1])); if (s < 0) { std::cerr << "Couldn't establish listening socket." << endl; return 1; } /* This line of code would take a long time to explain, but basically when * a client disconnects, and the server process which is handling it exits, * it becomes a zombie process. We don't want lots of zombie processes * lying around, so we have to catch the signal (software interrupt) that * tells us a child process has finished. We then clean up the zombie. * For more information see Stevens - Unix Network Programming. */ signal(SIGCHLD, zombieAssassin); /* This is the main server loop. Basically loops forever waiting for * clients to connect. When they connect, spawns a new process that * will call the handleClient() function */ while (true) { int t; // This will be the connected socket when a client connects /* Get a client connection. This call will block. */ if ((t = getConnection(s)) < 0) { /* If there was an error, check if it was an interrupted system * call. If so, it was probably the zombieAssasin() routine running, * so all is well. Continue waiting for a connection */ if (errno == EINTR) continue; /* Otherwise it was some other error. Report it then quit. */ else { perror("accept"); exit(1); } } std::cout << "A client has connected." << endl; /* Now fork a new process to handle the client connection */ switch(fork()) { case -1: // Something went wrong with forking, quit. perror("fork"); close(s); close(t); exit(1); case 0: // We are the child process, handle the client connection close(s); // Close our copy of the listening socket handleClient(t); // Handle the client std::cout << "A client has disconnected." << endl; exit(0); // And exit when we're finished default: // We are the parent process, continue waiting for other connections close(t); // Close our copy of the connected client continue; } } return 0; } /** * This function establishes a listening socket on the specified port. * @param portnum The port number on which to listen * @return a listening socket (or -1 if an error) */ int establish(int portnum) { struct sockaddr_in sa; // Will store the listening socket address struct hostent *hp; // A host entry for this machine char myname[MAX_HOST_NAME + 1]; // Will store the hostname of this server int s; // Will hold the listening socket memset(&sa, 0, sizeof(struct sockaddr_in)); // Clear the socket address // Set up the address that we listening socket will be bound to. gethostname(myname, MAX_HOST_NAME); // Find out the name of this machine hp = gethostbyname(myname); // Then look up its IP address etc. if (hp == NULL) { // Can't find the IP address, give up. std::cerr << "Couldn't determine the local IP address." << endl; return -1; } // Put the address and port number into the address structure sa.sin_family = hp->h_addrtype; sa.sin_port = htons(portnum); // Create the socket (Creating a TCP/IP streaming socket using Internet addressing) if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { // Error creating the socket, give up. std::cerr << "OS was unable to create the socket" << endl; return -1; } // Bind the socket to the address we created earlier if (bind(s, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)) < 0) { // Error binding the socket. Close it and give up. close(s); std::cerr << "Couldn'd bind the socket to the local address and port number." << endl; return -1; } /* If we got to here then the socket is working fine. * Set the listen queue to 5. (Means we can have a queue of 5 clients * waiting to connect. Should be fine since we fork connected clients anyway.*/ listen(s,5); std::cout << "Server is now listening on " << myname << " port " << portnum << ". Press ctrl-c to exit." << endl; return s; } /** * This function gets the next waiting client connection from the specified socket. * If there is no client waiting, this acall will block until a client connects. * @param s The listening socket * @return The connected socket (or -1 if an error) */ int getConnection(int s) { int t; // Will hold the connected socket if ((t = accept(s, NULL, NULL)) < 0) // Accept the connection. Blocks if there is none. return -1; return t; } /** * This function handles the client once it has connected. This is where you * will stick your application specific code. This server is simply an * echo server, that reads in data sent by the client, and echos it back again * @param t The connected socket through which we can talk to the client */ void handleClient(int t) { ssize_t n; // Will store the number of chars read char line[MAXLINE]; // Will store the line of data read from the client while (true) { if ((n = readline(t, line, MAXLINE)) == 0) return; // Connection was closed by client. writen(t, line, n); } } /** * This function kills zombie processes * @param sig The signal that occurred */ void zombieAssassin(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0) ; }