/* * COSC 6377 Sample Code #3 - a clear channel echo TCP server * * This server merely copies all input received via the network back to * the network. * * Compilation instructions: * * HPUX: gcc sample2-sig.c -o sample2-sig-hpux -DHAS_HERROR * Solaris: gcc sample2-sig.c -o sample2-sig-solaris -lsocket -lnsl * SunOS: gcc sample2-sig.c -o sample2-sig-sunos * FreeBSD: gcc sample2-sig.c -o sample2-sig-fbsd -DHAS_HERROR * Linux: gcc sample2-sig.c -o sample2-sig-linux -DHAS_HERROR * IRIX: gcc sample2-sig.c -o sample2-sig-sgi * * Solaris and SunOS can't use herror() - they're kind of braindead. * Solaris needs to be linked against both the socket and ns libraries, * SunOS against the NS library, and HPUX is the smartest of them all. * * 02/24/97 - Changed double fork() to SIGCHLD handler (U.P.) * 02/11/97 - Initial revision - D.A. * */ #include /* HPUX requires for getopt */ #include /* Everyone else requires for getopt :) */ #include /* fprintf, stderr, etc */ #include /* Required for socket types and such */ #include #include /* Internet networking headers */ #include #include #include /* Used for the ioctl stuff */ #include /* (does not relate) */ #include #include /* Needed for SIGCHLD handling. */ #include #include /* * herror() is a very cool function introduced in 4.3BSD Tahoe * - unfortunatly, Sun decided to leave it out. We replace * - it with a less-informative printf for Sun boxes. */ #ifndef HAS_HERROR #define herror(A) printf("%s\n", A) #endif void usage() { printf("syntax: sample2-sig \n\n"); } void handle_io(int netinout) { int nread; char buffer[BUFSIZ]; fprintf(stderr, "Hey - I've got a connection!\n"); /* * A simple copy loop -- anything we get on the socket connection * we hand back immediately. The stdio stuff has been omitted. */ while ((nread = read(netinout, buffer, sizeof(buffer))) > 0) { fprintf(stderr, "%d: %.*s", netinout, nread, buffer); if (write(netinout, buffer, nread) < 0) { perror("write"); break; } /* * Extra feature -- terminate the parent if the line * starts with the word "QUIT". */ if (strncmp(buffer, "QUIT", 4) == 0) { fprintf(stderr, "%d: Killing the parent and exiting.\n", netinout); kill(getppid(), SIGTERM); exit(0); } } if (nread < 0) { perror("read"); } } int do_server(int port) { int newsockfd, sockfd; /* Our socket descriptor */ struct protoent *proto; struct sockaddr_in mysock, theirsock; int childpid, clilen; /* * First, we use getprotobyname to get the protocol number for * what we're trying to do. This isn't amazinglyl necessary - * many implementations just specify 0, the generic IP protocol * number, but we're trying to be anal here. */ if ( ( proto = getprotobyname("tcp")) == NULL) { perror("Could not get protocol number for TCP"); return -1; } if ( (sockfd = socket(AF_INET, /* Internet socket */ SOCK_STREAM, /* Streaming (TCP), not dgram (UDP) */ proto->p_proto)) < 0) { perror("Could not obtain a socket"); return -1; } bzero( (char *)&mysock, sizeof(mysock)); mysock.sin_family = AF_INET; mysock.sin_addr.s_addr = htonl(INADDR_ANY); /* Accept connections on all addresses */ mysock.sin_port = htons(port); if (bind(sockfd, (struct sockaddr *)&mysock, sizeof(mysock)) < 0) { perror("Could not bind local socket\n"); return -1; } listen(sockfd, 5); while (1) { clilen = sizeof(theirsock); newsockfd = accept(sockfd, (struct sockaddr *) &theirsock, &clilen); /* This blocks until we get a connection request. */ if (newsockfd < 0) { if (errno == EINTR) { /* * This might happen as a result of the SIGCHLD. * No harm done, however -- just retry the accept() call. */ fprintf(stderr, "Accept was interrupted -- continuing...\n"); continue; } else { /* * A true error -- barf and exit. */ perror("Accept error"); return -1; } } fprintf(stderr, "Accept returned sockfd %d\n", newsockfd); if ( (childpid = fork()) < 0) { perror("Fork failed"); return -1; } /* * We're just forking once -- when the child exits the parent gets * a SIGCHLD signal. Function reaper() gets called automagically * by the kernel and that's when we know it's safe to do wait(). * Doing a wait() reclaims the zombie process slot and everybody * is happy. */ if (childpid == 0) /* We're the child */ { close(sockfd); handle_io(newsockfd); fprintf(stderr, "Child with sockfd %d closing\n", newsockfd); exit(0); } close(newsockfd); fprintf(stderr, "Forked off child %d\n", childpid); } } /* A note about handle_io - we're cheating here, because the * point of this sample is the connection establishment code, * not how to handle a select() loop, which you may or may not * need for your assignment. * * Select lets you deal with data as it comes in on file descriptors * and when you can write out to those FDs. * * We're not blocking on reading here, but we're assuming we can always * write - an assumption we should not depend upon. */ /* * The grim reaper -- a function that gets invoked whenever a child * terminates. We need to have this function in order to reclaim child's * process slot -- when a process exits it's moved to a `zombie' state. * It stays there until a wait() call is performed by the parent. * All this is needed to keep the kernel proc table from overflowing. */ void reaper(void) { pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { /* * Return value indicates that we reaped yet another child. * The exit status is available in `status' variable. * Signal numbers are available in */ fprintf(stderr, "Child %d terminated: ", pid); if (WIFEXITED(status)) { fprintf(stderr, "exit status %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { fprintf(stderr, "killed by signal %d\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { /* This cannot happen unless you're the debugger. */ fprintf(stderr, "stopped by signal %d\n", WSTOPSIG(status)); } } } /* * The terminator -- gets invoked on SIGTERM, when a child requests * that the parent terminate and exit. This function is not really * neccessary here because we have nothing to do before the exit. * If you had something to do (such as flushing a log file or something) * you could do it here and then terminate gracefully. * Also, if you simply let SIGTERM kill the parent your shell will * print the `Terminated' status -- we get rid of this by exit()ing * with a zero exit status. */ void terminator(void) { fprintf(stderr, "Server got a terminate request.\n"); exit(0); } /* * A skeleton main - it just calls a few functions and then trusts that * they'll handle everything. * * It sets up SIGCHLD and SIGTERM signal handlers as well. */ int main(int argc, char **argv) { struct sockaddr_in sock; struct sigaction sact; int port; int netfd = -1; /* Our network file descriptor - remote end */ if (argc != 2) { usage(); exit(-1); } port = atoi(argv[1]); /* Ordinarily, do more checking here.. */ /* This will segfault if not a number. */ fprintf(stderr, "Opening server on port %d\n", port); /* * Set up signal handling for SIGCHLD and SIGTERM. */ sact.sa_flags = 0; /* No special flags needed here. */ sact.sa_handler = reaper; /* This is our handler function. */ sigemptyset(&sact.sa_mask); /* No other signals have to be blocked. */ if (sigaction(SIGCHLD, &sact, NULL) < 0) { perror("sigaction/SIGCHLD"); exit(-1); } sact.sa_handler = terminator; if (sigaction(SIGTERM, &sact, NULL) < 0) { perror("sigaction/SIGTERM"); exit(-1); } do_server(port); exit(0); }