You are on page 1of 54

Unix Network Programming

5. TCP Client/Server Example 컴퓨터과학과 이정호

5.1 Introduction 5.2 TCP Echo Server: main Function 5.3 TCP Echo Server: str_echo Function 5.4 TCP Echo Client: main Function 5.5 TCP Echo Client: str_cli Function 5.6 Normal Startup 5.7 Normal Termination 5.8 POSIX Signal Handling 5.9 Handling SIGCHLD Signals 5.10 wait and waitpid Functions 5.11 Connection Abort before accept Returns 5.12 Termination of Server Process 5.13 SIGPIPE Signal 5.14 Crashing of Server Host 5.15 Crashing and Rebooting of Server Host 5.16 Shutdown of Server Host 5.17 Summary of TCP Example 5.18 Data Format 5.19 Summary

5.1 Introduction
Echo Client & Server
stdin stdout fgets fputs TCP client writen readline readline writen TCP client

Figure 5.1 Simple echo client and server

The client reads a line of text from its standard input and writes the line to the server. The server reads the line from its network input and echoes the line back to the client. The client reads the echoed line and prints it on its standard output


5.1 Introduction
Implementation of an echo server
A client/server that echoes input lines is a valid, yet simple, example of a network application.

Besides running out client and server in their normal mode, we examine lots of boundary conditions for this example:
what happens:
• when the client and server are started. • when the client terminates normally. • to the client if the server process terminates before the client is done. • to the client if the server host crashes. • and so on…

5.2 TCP Echo Server: main Function
#include "unp.h" "unp.h" int main(int argc, char **argv) argc, **argv) { int listenfd, connfd; listenfd, connfd; pid_t childpid; childpid; socklen_t clilen; clilen; struct sockaddr_in cliaddr, servaddr; cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); Socket(AF_INET, bzero(&servaddr, sizeof(servaddr)); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); SERV_PORT); htons( Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Bind(listenfd, &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); Listen(listenfd, for ( ; ; ) { clilen = sizeof(cliaddr); sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); Accept(listenfd, &cliaddr, &clilen); if ( (childpid = Fork()) == 0) { /* child process */ (childpid Fork()) Close(listenfd); /* close listening socket */ ); Close(listenfd str_echo(connfd); /* process the request */ str_echo(connfd); exit(0); } Close(connfd); /* parent closes connected socket */ Close(connfd); } }

임의 주소 unp.h에 9877로 정의 5001이상~49151이하


again: while ( (n = read(sockfd. n).3 TCP Echo Server: str_echo Function #include "unp. } 6 . MAXLINE)) > 0) Writen(sockfd.h" void str_echo(int sockfd) { ssize_t n. char buf[MAXLINE]. else if (n < 0) err_sys("str_echo: read error"). if (n < 0 && errno == EINTR) goto again. buf. buf.5.

char **argv) argc.sin_addr). argv[1]. servaddr. servaddr. Connect(sockfd. servaddr. sockfd). sockfd).4 TCP Echo Client: main Function #include "unp. } /* do it all */ 7 . SOCK_STREAM.h" int main(int argc. sizeof(servaddr)). **argv) { int sockfd. exit(0).sin_port htons(SERV_PORT). bzero(&servaddr. &servaddr. err_quit("usage: IPaddress>"). servaddr. (SA *) &servaddr. sizeof(servaddr)). Socket(AF_INET. argv[1]. servaddr. sizeof(servaddr)). sockfd. bzero(&servaddr.h" "unp.sin_family AF_INET. Inet_pton(AF_INET. servaddr.sin_addr). Connect(sockfd. sockfd = Socket(AF_INET. str_cli(stdin. Inet_pton(AF_INET. struct sockaddr_in servaddr.sin_port = htons(SERV_PORT). sizeof(servaddr)). &servaddr. 0).sin_family = AF_INET.5. str_cli(stdin. if (argc != 2) (argc err_quit("usage: tcpcli <IPaddress>").

sendline.5 TCP Echo Client: str_cli Function #include "unp. MAXLINE) == 0) err_quit("str_cli: server terminated prematurely").h" void str_cli(FILE *fp. recvline. recvline[MAXLINE]. stdout).5. fp) != NULL) { Writen(sockfd. strlen(sendline)). } } 8 . int sockfd) { char sendline[MAXLINE]. if (Readline(sockfd. MAXLINE. Fputs(recvline. while (Fgets(sendline.

SYN A CK read tcp_data_wait D A TA ACK write ESTABLISHED ESTABLISHED 9 .서버와 클라이언트간 연결 및 데이터 전송 과정 Server Accept SY N Client Connect wait_for_connect wait_for_connect ACK.

5.6 Normal Startup (1/4) 서버 시작 10 .

5.6 Normal Startup (2/4) 서버의 LISTEN 상태 확인 11 .

6 Normal Startup (3/4) 클라이언트 시작 12 .5.

5.6 Normal Startup (4/4) 서버/클라이언트 간 연결 확인 13 .

4. 클라이언트 프로그램에서 EOF 문자(control+D)를 입력하면. 서버의 자식 프로세스가 종료될 때. 서버는 ACK를 보냄으로써 클 라이언트 프로그램이 종료된다. readline 함수는 0을 리턴하고. 서버의 자식 프로세스가 종료될 때 SIGCHLD 시그널을 부모 프 로세스에게 보낸다. 커널에서는 서버에게 FIN을 보내고. str_cli 함수가 리턴된다.5. 서버의 자식 프로세스에 있는 모든 오픈된 descriptor가 close되고. TCP 접속이 끊기면서 클라이언트 소켓은 TIME_WAIT 상태가 된다. 7. str_cli 함수는 서버 Child 프로세스의 main으로 리턴하고 exit 함수를 호출하여 서버의 자식 프로세스는 종료된다. 마지막으로. 14 . fgets가 null 포 인터를 리턴함으로써. main 프로그램에서 str_cli 함수를 리턴받으면 exit 함수를 호출하고 클라이언 트 프로그램은 종료된다. 2. 서버 소켓은 CLOSE_WAIT 상태가 되 고. 5. 오픈된 모든 descriptor들은 close되고. 클라이언트 소켓은 FIN_WAIT_1 상태이다. 본 프로그램의 예제에서는 부모 프로세스에서 SIGCHLD 시그널을 처리 하지 않기 때문에 자식 프로세스는 좀비 상태가 된다. 클라이언트 소켓은 커널에서 close한다.7 Normal Termination (1/2) Normal termination of client and server 1. 3. 서버는 FIN을 수신하면. 이 때. 이 때. 6.

5.7 Normal Termination (2/2) 자식 프로세스의 좀비 상태 확인 15 .

8 POSIX Signal Handling (1/6) POSIX (Portable Operating System Interface)란? 운영체제의 공통적인 규약을 규정하고 있는 표준이다. 상호 운용성 증대. 16 .5.

Signals are sometimes called software interrupts. Signals can be sent By one process to another process (or to itself) By the kernel to a process 17 . By this we mean that a process doesn’t know ahead of time exactly when a signal will occur.8 POSIX Signal Handling (2/6) A signal is a notification to a process that an event has occurred. Signals usually occur asynchronously.5.

8 POSIX Signal Handling (3/6) The SIGCHLD signal that we described at the end of the previous section is one that is sent by the kernel whenever a process terminates. which is also called the action is called the action associated with the signal. Every signal has a disposition. 18 . to the parent of the terminating process.5.

8 POSIX Signal Handling (4/6) We set the disposition of a signal by calling the sigaction function and we have three choices for the disposition: We can provide a function that is called whenever a specific signal occurs. We can ignore a signal by setting its disposition to SIG_IGN. This function is called a signal handler and this action is called catching a signal. 19 . The two signals SIGKILL and SIGSTOP cannot be caught. • Its function prototype is therefore void handler(int signo). The default is normally to terminate a process on receipt of a signal.5. with certain signals also generating a core image of the process in its current working directory. The two signals SIGKILL and SIGSTOP cannot be ignored. • Out function is called with a signal integer argument that is the signal number and the function returns nothing. We can set the default disposition for a signal by setting its disposition to SIG_DFL.

This provides a simple interface with the desired POSIX semantics. The solution is to define our own function named signal that just calls the POSIX sigaction function.8 POSIX Signal Handling (5/6) signal Function The POSIX way to establish the disposition of a signal is to call the sigaction function. 20 . signal is an historical function that predates POSIX. But. An easier way to set the disposition of a signal is to call the signal function.5.

sa_handler). act.sa_handler).5.sa_flags #endif } if (sigaction(signo.sa_flags if (signo == SIGALRM) { (signo SIGALRM) #ifdef SA_INTERRUPT act. act.x */ act. sigemptyset(&act. oact. return(SIG_ERR). &oact) return(SIG_ERR).sa_handler = func. 44BSD */ act.h" "unp. Sigfunc *func). return(oact.8 POSIX Signal Handling (6/6) signal function that calls the POSIX sigaction function #include "unp. Sigfunc *func) signo. void (*func)(int)))(int).sa_handler func. 21 . sigemptyset(&act. &act. &oact) < 0) (sigaction(signo.sa_flags = 0.sa_flags |= SA_RESTART.sa_flags |= SA_INTERRUPT. return(oact. func) { struct sigaction act. oact.h" Sigfunc * signal(int signo. Sigfunc 함수 원형 Sigfunc *signal(int signo. act. /* SunOS 4.sa_flags #endif } else { #ifdef SA_RESTART act. /* SVR4.sa_mask). act. typedef void Sigfunc(int). } void (*signal(int signo.sa_mask).

which removes the zombie). etc..). and that process has children in the zombie state.5. the parent process ID of all the zombie children is set to 1(the init process). termination status.e. which will inherit the children and clean them up(i. memory. init will wait for them. information on the resource utilization of the child(CPU time. 22 . If a process terminates.9 Handling SIGCHLD Signals (1/4) The purpose of the zombie state is to maintain information about the child for the parent to fetch at some later time. process ID of the child.

Whenever we fork children. To do this. we call wait. we must wait for them to prevent them from becoming zombies.9 Handling SIGCHLD Signals (2/4) Handling zombies Obviously we do not want to leave zombies around. we establish a signal handler to catch SIGCHLD. They take up space in the kernel and eventually we can run out of process. 23 .5. and within the handler.

5. printf("child %d terminated\n". } 24 . int stat. pid = wait(&stat). pid).9 Handling SIGCHLD Signals (3/4) Version of SIGCHLD signal handler that calls wait #include "unp.h" void sig_chld(int signo) { pid_t pid. return.

9 Handling SIGCHLD Signals (4/4) zombie 상태 처리 결과 25 .5.

int *statloc. we called the wait function to handle the terminated child. int options).5.7.10. wait and waitpid Functions (1/6) In Figure 5. 26 . Difference between wait and waitpid include <sys/wait.h> pid_t wait(int *statloc). pid_t waitpid(pid_t pid.

wait and waitpid Functions (2/6) wait Function If there are no terminated children for the process calling wait.10.5. • First. The most common option is WNOHANG. but the process has one or more children that are still executing. waitpid Function waitpid gives us more control over which process to wait for and whether or not to block. A value of -1 says to wait for the first of our children to terminate. then wait blocks until the first of the existing children terminates. 27 . • The options argument lets us specify additional options. the pid argument lets us specify the process ID that we want to wait to wait for.

wait and waitpid Functions (3/6) 28 .5.10.

5. 29 . wait and waitpid Functions (4/6) It is this delivery of multiple occurrences of the same signal that causes that problem we about to see.10.

pid). } 30 . &stat.h" void sig_chld(int signo) { pid_t pid.10. while ( (pid = waitpid(-1.5. int stat. #include "unp. WNOHANG)) > 0) printf("child %d terminated\n". return. wait and waitpid Functions (5/6) Final (correct) version of sig_chld function that calls waitpid.

We must catch the SIGCHLD signal when forking child processes. wait and waitpid Functions (6/6) The purpose of this section has been to demonstrate three scenarios that we can encounter with network programming. 2.10.5. We must handle interrupted system calls when we catch signals. 3. 31 . A SIGCHLD handler must be coded correctly using waitpid to prevent any zombies from being left around. 1.

11 Connection Abort before accept Returns (1/2) Receiving an RST for an ESTABLISHED connection before accept is called. 32 .5.

11 Connection Abort before accept Returns (2/2) 커넥션이 취소된 경우 어떤 일이 발생하는 가는 구현에 따라 다르다. SVR4 implementations: EPROTO(“protocol error”)를 가진 errno를 리턴. 33 . POSIX: ECONNABORTED 에러로 규정. Berkeley-derived implementations: 커널 내부에 서 모두 처리.5. 서버는 모름.

34 .5.12 Termination of Server Process (1/10) We will now start our client/server and kill the server child process. This simulates the crashing of the server process. so we can see what happens to the client.

c) 35 .5.c) Client (tcpcli04. Server (tcpserv04.12 Termination of Server Process (2/10) Step 1 Start the server and client and type one line to the client to verify that all is okay.

5.12 Termination of Server Process (3/10) Step 2 Find the process ID of the server child and kill it. 36 .

12 Termination of Server Process (4/10) Step 3 The SIGCHLD signal is sent to the server parent and handled correctly.5. 37 .

The client TCP receives the FIN from the server TCP and responds with an ACK. 38 .12 Termination of Server Process (5/10) Step 4 Nothing happens at the client.5. but the problem is that the client process is blocked in the call to fgets waiting for a line from the terminal.

5.c) 39 .12 Termination of Server Process (6/10) Step 5 Running netstat at this point shows the stat of the sockets. Client (tcpcli04.c) Server (tcpserv04.

12 Termination of Server Process (7/10) Step 6 (1/2) We can still type a line of input to the client. Here is what happens at the client starting from Step 1: 40 .5.

This is allowed by TCP because the receipt of the FIN by the client TCP only indicates that the server process has closed its end of the connection and will not be sending any more data. The receipt of the FIN does not tell the client TCP that the server process has terminated. When the server TCP receives the data from the client. str_cli calls writen and the client TCP sends the data to the server. 41 .12 Termination of Server Process (8/10) Step 6 (2/2) When we type “hi”. it response with an RST since the process that had that socket open has terminated.5.

Our client is not expecting to receive an EOF at this point so it quits with the error message “server terminated prematurely.5.12 Termination of Server Process (9/10) Step 7 The client process will not see the RST because it calls readline immediately the FIN that was received in Step 2.” 42 .

it should block on input from either source.12 Termination of Server Process (10/10) Step 8 When the client terminates. ※The problem in this example is that the client is blocked in the call to fgets when the FIN arrives on the socket.5. 43 . all its open descriptors are closed. ※The client is really working with two descriptors-the socket and the user input-and instead of blocking on input from only one of the two sources.

The rule that applies is: When a process writes to a socket that has received an RST. So the process must catch the signal to avoid being involuntarily terminated. if the client needs to perform two writes to the server before reading anything back.13 SIGPIPE Signal (1/4) What happens if the client ignores the error return from readline and writes more data to the server? For example.5. the SIGPIPE signal is sent to the process. The default action of this signal is to terminate the process. with the first write eliciting the RST. 44 .

strlen(sendline)if (Readline(sockfd.13 SIGPIPE Signal (2/4) To see what happens with SIGPIPE. sleep(1). Fputs(recvline. int sockfd) fp. err_quit("str_cli: Fputs(recvline. stdout). fp) != NULL) { (Fgets(sendline. } } 45 . sendline[MAXLINE]. sockfd) { char sendline[MAXLINE]. MAXLINE) == 0) (Readline(sockfd. sendline. #include "unp.h" "unp. sendline. while (Fgets(sendline. recvline. 1). Writen(sockfd.5. fp) Writen(sockfd. Writen(sockfd. stdout). MAXLINE. recvline[MAXLINE]. strlen(sendline)-1). Writen(sockfd.h" void str_cli(FILE *fp. recvline. err_quit("str_cli: server terminated prematurely"). we modify our client as shown in this code. recvline[MAXLINE]. sendline+1.

0.0.1 hi there hi there bye Broken pipe 46 .5. we get: linux % tcpcli11 127.13 SIGPIPE Signal (3/4) If we run the client on our Linux host.

5.13 SIGPIPE Signal (4/4) 47 .

2. 4.5. and is sent by the client TCP as a data segment. When the client TCP fanally gives up.14 Crashing of Server Host This scenario will test to see what happens when the server host crashes. an error is returned to the client process. 1. We type a line of input to the client. nothing is sent out on the existing network connections. 48 . We will see the client TCP continually retransmitting the data segment. The client then blocks in the call to readline. When the server host crashes. 3. waiting for the echoed reply. it is writen by writen. trying to receive an ACK from the server.

1. causing readline to return the error ECONNRESET. When the server host reboots after crashing. Start the server and then the client. 49 . Therefore. the server TCP responds to the received data segment from the client with an RST. which is sent as a TCP data segment to the server host. 3. 5. its TCP loses all information about connections that existed before the crash. 4.5.15 Crashing and Rebooting of Server Host In this scenario. Type a line of input to the client. The server host crashes and reboots. we will establish a connection between the client and server and then assume the server host crashes and reboots. Client is blocked in the call to readline when the RST is received. 2.

16 Shutdown of Server Host 호스트에서 서버 프로세스가 동작 중일 때 서버 호스트를 운영자가 끄면 어떻게 되는가 유닉스 시스템이 종료할 때는 보통 init 프로세스가 SIGTERM 시그널을 모든 프로세스에게 보내고. 만약 서버가 SIGTERM을 캐치하여 종료하지 못하면 SIGKILL 시그널에 의해 종료된다. 50 . 일정 시간(5~20초) 동안 기다린다. 클라이언트에서 서버 프로세스의 종료를 곧바로 감지 하기 위해서는 select나 poll 함수를 사용해야 한다.5. 일정 시간 후에도 여전히 실행 중인 모든 프로세스로 SIGKILL 시그널을 보낸다.

5.17 Summary of TCP Example 클라이언트 관점 51 .

17 Summary of TCP Example 서버 관점 52 .5.

snprintf(line. "%ld\ else snprintf(line. . return. MAXLINE)) == 0) Readline(sockfd. ssize_t n. "%ld%ld". arg1 + arg2). n). /* connection closed by other end */ if (sscanf(line. Writen(sockfd. sizeof(line). line. line[MAXLINE]. char line[MAXLINE]. strlen(line).5. &arg1. Writen(sockfd. line. for ( . error\ n = strlen(line).h" void str_echo(int sockfd) sockfd) { long arg1. } } 53 . sizeof(line). arg2. sizeof(line). "input error\n").18 Data Format 데이터 스트링 송·수신 #include "unp. snprintf(line. "%ld\n". &arg2) == 2) (sscanf(line. sizeof(line). "%ld%ld".h" "unp. snprintf(line. ) { if ( (n = Readline(sockfd.

"%ld%ld". if (Readn(sockfd.sum).arg1. sizeof(result)) err_quit("str_cli: server terminated prematurely"). sizeof(result)) == 0) (Readn(sockfd.h" void str_cli(FILE *fp. &args. struct args args. sockfd) { char sendline[MAXLINE].18 Data Format 이진 데이터 송·수신 #include "unp.arg2) != 2) { (sscanf(sendline. result. } Writen(sockfd. sizeof(args)). sendline[MAXLINE]. } } 54 .sum). sizeof(args)). &result. continue. "%ld%ld". while (Fgets(sendline. printf("invalid input: %s". int sockfd) fp.5. result. fp) if (sscanf(sendline. struct result result. MAXLINE. &args. args.h" "sum. &args. sendline). &args. sendline). fp) != NULL) { (Fgets(sendline.h" "unp. err_quit("str_cli: printf("%ld\n". printf("%ld\ result. Writen(sockfd.h" #include "sum.