970 likes | 1.39k Views
TCP Client-Server Example. Unix Network Programming Chapter 5. fgets. stdin. TCP client. TCP server. writen. readline. stdout. readline. writen. fputs. Introduction. 1. The Client reads a line of text from its standard input and writes the line to the server
E N D
TCP Client-Server Example Unix Network Programming Chapter 5
fgets stdin TCP client TCP server writen readline stdout readline writen fputs Introduction 1. The Client reads a line of text from its standard input and writes the line to the server 2. The server reads the line from its network input and echoes the line back to the client 3. The client reads the echoed line and prints it on its standard output
Boundary Conditions • What happens when the client and server are started? • When happens when the client terminates normally? • What happens to the client if the server process terminates before the client is done? • What happens to the client if the server host crashes? • and so on
TCP Echo Server • #include <stdio.h> • •#include <stdlib.h> • •#include <unistd.h> • •#include <errno.h> • •#include <string.h> • •#include <sys/types.h> • •#include <sys/socket.h> • •#include <netinet/in.h> • •#define SERV_PORT 9876 • •#define BACKLOG 10 • •#define MAXDATASIZE 100
TCP Echo Server #include <unp.h> int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ);
TCP Echo Server for ( ; ; ) { clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); if (Childpid = Fork())== 0) /* child process */ { Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); /*parent closes connected socket*/ } }
TCP Echo Server: str_echo() void str_echo(int sockfd) { ssize_t n; char line[MAXLINE]; for ( ; ; ) { if ( (n = Readline(sockfd, line, MAXLINE)) == 0) { return; /* connection closed by other end */ } Writen(sockfd, line, n); } }
TCP Echo Client • #include <stdio.h> • •#include <stdlib.h> • •#include <unistd.h> • •#include <errno.h> • •#include <string.h> • •#include <sys/types.h> • •#include <sys/socket.h> • •#include <netinet/in.h> • •#define SERV_PORT 9876 • •#define BACKLOG 10 • •#define MAXDATASIZE 100
TCP Echo Client int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); str_cli(stdin, sockfd); /* do it all */ }
TCP Echo Client: str_cli() void str_cli(FILE *fp, int sockfd) { char sendline[MAXLINE], recvline[MAXLINE]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Writen(sockfd, sendline, strlen(sendline)); if (Readline(sockfd, recvline, MAXLINE) == 0) { err_quit("str_cli: server terminated prematurely"); } Fputs(recvline, stdout); } }
Normal Startup Only Server Running • tcpserv & • netstat –a Proto Local Address Foreign Address State tcp *.9877 *.* LISTEN Client and Server Running • tcpcli 127.0.0.1
Normal Startup • Client calls socket and connect, the latter causing TCP's three-way handshake to take place • When the three-way handshake completes, connect returns in the client and accept returns in the server
Normal Startup • The connection is established and following steps then take place: • Client calls str_cli, which will block in the call to fgets, because we have not typed a line of input yet • When accept returns in the server, it calls fork and the child server calls str_echo • This function calls Readline, which calls read, which blocks while waiting for a line to be sent from the client • The server parent, on the other hand, calls accept again, and blocks while waiting for the next client connection • There are three processes, and all three are asleep (blocked): client, server parent, and server child
Normal Startup Client and Server Running • netstat –a Proto Local Address Foreign Address State tcp localhost.9877 localhost.1052 Established tcp localhost.1052 localhost.9877 Established tcp *.9877 *.* LISTEN
Normal Startup • ps -l PID PPID WCHAN Command 19130 19129 wait -ksh 21130 19130 netcon tcpserv01 21131 19130 ttyin tcpcli01 (127.0.0.1) 21132 21130 netcon tcpserv01
Normal Termination • tcpcli 127.0.0.1 hello, world hello, world good bye good bye ^D (control-D is our terminal EOF character) • netstat -a | grep 9877 tcp 0 0 *:9877 *:* LISTEN tcp 0 0 localhost:42758 localhost:9877 TIME_WAIT
Sequence of Termination • When we type EOF character, fgets returns a null pointer and the function str_cli returns • When str_cli returns to the client main function the latter terminates by calling exit() • Kernel at the client closes the socket (all open descriptors) • A FIN is sent to the server, to which the server TCP responds with an ACK • Server socket is in the CLOSE_WAIT state and client socket is in the FIN_WAIT_1 state
Sequence of Termination • When the server TCP receives the FIN, the server child is blocked in a call to Readlineand then returns 0 • This causes the str_echo function to return to the server child main • The server child terminates by calling exit() • All open descriptors in the server child are closed • Causes the final two segments of the TCP connection termination to take place: a FIN from server to the client, an ACK from the client • At this point the connection is completely terminated • The client socket enters the TIME_WAIT state
Process Termination • Another part of the process termination is for the SIGCHLD signal to be sent to the parent when the server child terminates • This also occurs in this example but the code (at present) does not catch this signal, and the default action of this signal is to be ignored • The child enters the zombie state • ps PID STAT COMMAND 19130 Ss -ksh 21130 I tcpserv 21132 Z tcpserv (Z:zombie process) • Cleaning up of zombie processes requires dealing with Unix Signals
Signal • Signals, are notifications sent to a process in order to notify it of various "important" events • By their nature, they interrupt whatever the process is doing and force it to handle them immediately • Each signal has an integer number that represents it (1, 2 and so on), as well as a symbolic name that is usually defined in the file /usr/include/signal.h • Use the command 'kill -l' to see a list of signals supported by your system • Each signal may have a signal handler, which is a function that gets called when the process receives that signal
Signal • Signals usually occurs asynchronously • Process does not know ahead of time exactly when a signal will occur • When the signal is sent to the process, the OS stops the execution of the process, and "forces" it to call the signal handler function • When that signal handler function returns, the process continues execution from wherever it happened to be before the signal was received • Similar to interrupts • Interrupts are sent to the OS by the hardware • Where as signals are sent to the process by the OS, or by other processes
Sending Signals to Processes • Sending signals using keyboard • Ctrl-C • OS send an INT signal (SIGINT) to the running process • By default, this signal causes the process to immediately terminate • Ctrl-Z • OS send a TSTP signal (SIGTSTP) to the running process • By default, this signal causes the process to suspend execution • Ctrl-\ • OS send a ABRT signal (SIGABRT) to the running process • By default, this signal causes the process to immediately terminate
Sending Signals from Command Line • kill • Accepts two parameters: a signal name (or number), and a process ID • kill - <signal> <PID> • kill -INT 5342 • Same affect as pressing Ctrl-C • fg • Resumes execution of the process (that was suspended with Ctrl-Z), by sending it a CONT signal
Sending Signals Using System Calls • kill system call • Normal way of sending a signal from one process to another • Also used by the 'kill' command or by the 'fg' command • In the following code, a process suspend its own execution by sending itself the STOP signal #include <unistd.h> #include <sys/types.h> #include <signal.h> /* first, find my own process ID */ pid_tmy_pid = getpid(); /* now that i got my PID, send myself the STOP signal. */ kill(my_pid, SIGSTOP); • An example of a situation when this code might prove useful, is inside a signal handler that catches the TSTP signal (Ctrl-Z) in order to do various tasks before actually suspending the process
Catching Signals - Signal Handlers • Catchable And Non-Catchable Signals • Most signals may be caught by the process, but there are a few signals that the process cannot catch, and cause the process to terminate • kill -9 • One process that uses this signal is a system shutdown process • First sends a TERM signal to all processes, waits a while, and after allowing them a "grace period" to shutdown cleanly, it kills whichever are left using the kill signal
Catching Signals - Signal Handlers • STOP is also a signal that a process cannot catch, and forces the process's suspension immediately • Useful when debugging programs whose behavior depends on timing • Example • Suppose that process A needs to send some data to process B, and you want to check some system parameters after the message is sent, but before it is received and processed by process B • One way to do that would be to send a STOP signal to process B, thus causing its suspension, and then running process A and waiting until it sends its important message to process B • Now you can check whatever you want to, and later on you can use the CONT signal to continue process B's execution, which will then receive and process the message sent from process A
Catching Signals - Signal Handlers • SEGV and BUS signals are catchable • Program exiting with a message such as 'Segmentation Violation - Core Dumped', or 'Bus Error - core dumped‘ • SEGV signal is sent to program due to accessing an illegal memory address • BUS signal is sent to program, due to accessing a memory address with invalid alignment • In both cases, it is possible to catch these signals in order to do some cleanup - kill child processes, perhaps remove temporary files, etc
Catching Signals - Signal Handlers • Default Signal Handlers • If there is no signal handlers installed, the runtime environment sets up a set of default signal handlers for the program • Default signal handler for the TERM signal calls the exit() system call • Default handler for the ABRT signal calls the abort() system call, which causes the process's memory image to be dumped into a file named 'core' in the process's current directory, and then exit
Catching Signals - Signal Handlers • On some systems (such as Linux), when a signal handler is called, the system automatically resets the signal handler for that signal to the default handler • Otherwise, the next time this signal is received, the process will exit (default behavior for INT signals)
Avoiding Signal Races - Masking Signals • One of the problems that might occur when handling a signal, is the occurrence of a second signal while the signal handler function executes • Such a signal might be of a different type then the one being handled, or even of the same type • In order to avoid races between the two signals the system also contains some features that will allow us to block signals from being processed • These can be used in two 'contexts‘ • A global context - which affects all signal handlers • Per-signal type context - that only affects the signal handler for a specific signal type
Masking Signals with sigprocmask() • Posix function used to mask signals in the global context, is the sigprocmask() system call • Allows to specify a set of signals to block, and returns the list of signals that were previously blocked • Useful to restore the previous masking state once done with critical section
Masking Signals with sigprocmask() • sigprocmask() accepts three parameters • int how • Add signals to the current mask (SIG_BLOCK) • Remove them from the current mask (SIG_UNBLOCK) • Replace the current mask with the new mask (SIG_SETMASK) • const sigset_t *set • Set of signals to be blocked, or to be added to the current mask, or removed from the current mask (depending on the 'how' parameter) • sigset_t *oldset • If this parameter is not NULL, then it'll contain the previous mask • Later use this set to restore the situation back to how it was before calling sigprocmask()
Posix Signal Handling • A signal: a notification to a process that an event has occurred • Sometimes called software interrupts • Signals can be sent • by one process to another process(or itself) • by the kernel to a process • SIGCHLD signal: a signal sent by the kernel whenever a process terminates, to the parent of the terminating process
Posix Signal Handling • Every signal has a disposition • Action associated with the signal • Set the disposition of a signal by calling the sigaction function • Two signals are unable to be redefined by a signal handler, and cannot be "caught" by a signal handler • SIGKILL always stops a process • SIGSTOP always moves a process from the foreground to the background
Posix Signal Handling • Three choices for the disposition • Provide a function that is called whenever a specific signal occurs, this function is called a signal handler and this action is called catching the signal • Two signals SIGKILL and SIGSTOP can not be caught • void handler(int signo); • For most signals, calling sigaction and specifying a function to be called when the signal occurs is all that is required to catch a signal • A few signals, (SIGIO, SIGPOLL, SIGURG etc), all require additional actions on the part of the process to catch the signal
Posix Signal Handling • Ignore a signal by setting its disposition to SIG_IGN • Two signals SIGKILL and SIGSTOP can not be ignored • Set the default disposition for a signal by setting its disposition to SIG_DFL, the default is normally to terminate a process on the receipt of a signal
signal() Function • Posix establish the disposition of a signal by calling sigaction, however, this gets complicated, as one argument to the function is a structure that must be allocated and filled in • int sigaction (int signo, struct sigaction * action, struct sigaction * oldaction); • Returns 0 on success and -1 on error • struct sigaction { void (*sa_handler)(int); /*Signal handler*/ sigset_tsa_mask; /*Signals to be blocked during handler execution*/ int sa_flags; /*flags to modify the default behavior*/ }
signal() Function • Set and unset all of the flags in the given set • int sigemptyset (sigset_t *set) • int sigfillset (sigset_t *set) • Set and unset individual flags, specified by the signal number, in the given set • int sigaddset (sigset_t *set, int whichSignal) • int sigdelset (sigset_t *set, int whichSignal) • All return 0 for success and -1 for failure
signal() Function • An easier way is to call signal function, with first argument is the signal number and second argument is either a pointer to a function or one of the constants, SIG_IGN or SIG_DFL • #include <signal.h> • typedef void (*sighandler_t)(int); • signal (int signum, sighandler_t handler); • The signal() function returns the previous value of the signal handler, or SIG_ERR on error • Different implementations have different signal semantics, therefore, book has defined its own signal() function that just calls the Posix sigaction() function
signal() Function #include “unp.h” Sigfunc * signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(signo, &act, &oact) < 0) { return(SIG_ERR); } return(oact.sa_handler); }
Posix Signal Semantics • Once a signal handler is installed, it remains installed • While a signal handler is executing, the signal being delivered is blocked • Any additional signals specified in sa_mask signal are also blocked • If a signal is generated one or more times while it is blocked, it is normally delivered only one time after the signal is unblocked. • By default Unix signals are not queued • It is possible to selectively block and unblock a set of signals using the sigprocmask function
Handling SIGCHLD Signal • When a server child terminates a SIGCHILD signal is sent to the parent server process and child enters the zombie state • The purpose of the zombie state is to maintain information about the child for the parent to fetch at some later time • This information includes the • Process ID of the child • Its termination status • Information about the resource utilization of the child (CPU time, memory etc) • If a process terminates, and that process has children in the zombie state, the parent process ID of all the zombie children is set to 1 (the init process) • init inherits the children and clean them up (i.e., init will wait for them, which removes the zombie)
Handling Zombies • Zombies take up space in the memory • Whenever fork() must use the functions wait or waitpidto prevent the child process to become zombies • Establish a signal handler to catch SIGCHLDand within the handler call wait or waitpidfunctions • Establish the signal handler by adding the function call, After the call to listen() in server and before fork • Signal (SIGCHLD, sig_chld) • Then define the signal handler, the function sig_chld
Handling SIGCHLD Signals #include “unp.h” void sig_chld(int signo) { pid_tpid; int stat; pid = wait(&stat); printf("child %d terminated\n", pid); return; }
Handling SIGCHLD Signals tcpserv02 & start server in background [2] 16939 tcpcli01 127.0.0.1 then start client in foreground hi there we type this hi there and this is echoed ^D we type our EOF character child 16942 terminated output by printf in signal handler accept error: Interrupted system call main function aborts
Handling SIGCHLD Signals • Terminate the client by typing EOF • Client TCP sends a FIN to the server and the server responds with an ACK • Receipt of the FIN delivers an EOF to the child's pending readline and child terminates • Parent is blocked in its call to accept when the SIGCHLD signal is delivered • sig_chld function executes (signal handler) • wait fetches the child's PID and termination status • printf is called from the signal handler • signal handler returns
Handling Interrupted System Call • Since the signal was caught by the parent while the parent was blocked in a slow system call (accept), the kernel causes the accept to return an error of EINTR (interrupted system call) • Slow system calls – system calls that can block forever • accept() may never return if no client connects • read() may never return if client sends nothing • However, disk I/O may returns to caller assuming some hardware failure • The main rule that applies here is that • When a process is blocked in a slow system call and • The process catches a signal and • The signal handler returns, the system call can return an error EINTR
Handling Interrupted System Callrestarting the interrupted system call for ( ; ; ) { clilen = sizeof(cliaddr); connfd = accept(listenfd,(SA *)&cliaddr,&clilen); if( connfd < 0 ) { if( errno == EINTER ) continue; else err_sys (“accept error”); } } • connect() can not be restarted, will return an immediate error • When connect() is interrupted by a caught signal that is not automatically restarted, call select to wait for the connection to complete
#include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int option); both return: process ID if OK,0,or -1 on error wait & waitpid Functions • pid_t: the process ID of the terminated child • statloc : the termination status of the child (an integer) is returned through the statloc pointer • pid: specify the process ID that we want to wait for • A value of -1 tells to wait for the first child to terminate • Option: specify additional option • WNOHANG – tells the kernel not to block if there are not terminated children; it blocks only if there are children still executing • WUNTRACED – also return for children which are stopped but whose status has not been reported
wait • Suspends system execution of the current process • Until a child has exited Or • Until a signal is delivered whose action is to terminate the current process or to call a signal handling function • If the child has already exited by the time of the call (a so-called zombie process), the function returns immediately • Any system resources used by the child are freed