5-TCP Client&Server Example

来源:互联网 发布:无创dna数据辨别男女 编辑:程序博客网 时间:2024/04/18 13:49

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

  • Our echo server performs the following steps:
    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.

  • We show two arrows between the client and server, but this is really one full-duplex TCP connection.

5.2 TCP Echo Server: ‘main’ Function

Create socket, bind server’s well-known port 9–15

  • An Internet socket address structure is filled in with the wildcard address (INADDR_ANY) and the server’s well-known port(SERV_PORT). Binding the wildcard address will accept a connection destined for any local interface, in case the system is multihomed.
  • Our TCP port number should be greater than 1023(we do not need a reserved port), greater than 5000(to avoid conflict with the ephemeral ports allocated by many Berkeley-derived implementations), less than 49152(to avoid conflict with the “correct” range of ephemeral ports), and it should not conflict with any registered port.

Wait for client connection to complete 17–18

  • The server blocks in the call to accept, waiting for a client connection to complete.

Concurrent server 19–24

  • For each client, fork spawns a child, and the child handles the new client. The child closes the listening socket and the parent closes the connected socket. The child then calls str_echo to handle the client.

5.3 TCP Echo Server: ‘str_echo’ Function

  • str_echo reads data from the client and echoes it back to the client.

Read a buffer and echo the buffer 8–9

  • read reads data from the socket and the line is echoed back to the client by writen. If the client closes the connection(the normal scenario), the receipt of the client’s FIN causes the child’s read to return 0. This causes the str_echo function to return, which terminates the child.
/*void str_echo(int sockfd);void Writen(int fd, void *ptr, int nbytes);int writen(int fd, const void *vptr, int n)*/#include <stdio.h>              // printf()#include <stdlib.h>         // exit()#include <sys/types.h>#include <sys/socket.h> // socket(), bind(), listen(), accept()#include <strings.h>            // bzero()#include <arpa/inet.h>      // htonl(), htons()#include <unistd.h>         // fork(), close(), read(), write()#include <errno.h>          // errno#define     LISTENQ     32#define     MAXLINE     4096#define     SERV_PORT   7188void Exit(char *string){    printf("%s\n", string);    exit(1);}int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol)) < 0)    {        Exit("socket error");    }    return n;}void Bind(int fd, const struct sockaddr *sa, socklen_t salen){    if(bind(fd, sa, salen) < 0)    {        Exit("bind error");    }}void Listen(int fd, int backlog){    if(listen(fd, backlog) < 0)    {        Exit("listen error");    }}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr){    printf("Enter accept:\n");    int n;again:    if((n = accept(fd, sa, salenptr)) < 0)    {        // EPROTO: Protocol error; ECONNABORTED: Connection aborted        if(errno == EPROTO || errno == ECONNABORTED)        {            printf("Enter accept: errno == EPROTO || errno == ECONNABORTED\n");            goto again;        }        else        {            Exit("accept error");        }    }    return n;}pid_t Fork(){    pid_t pid;    if((pid = fork()) == -1)    {        Exit("fork error");    }    return pid;}void Close(int fd){    if(close(fd) == -1)    {        Exit("close error");    }}int writen(int fd, const void *vptr, int n){    printf("writen Enter:\n");    int nleft = n;    int nwritten;    const char *ptr = vptr;    while(nleft > 0)    {        printf("writen Enter: writing in while(nleft > 0) loop.\n");        if((nwritten = write(fd, ptr, nleft)) <= 0)        {            if(nwritten < 0 && errno == EINTR)            {                printf("writen Enter: Interrupt occurs\n");                nwritten = 0;       /* and call write() again */            }            else            {                return -1;          /* error */            }        }        nleft -= nwritten;        ptr += nwritten;    }    return n;}void Writen(int fd, void *ptr, int nbytes){    if(writen(fd, ptr, nbytes) != nbytes)    {        Exit("writen error");    }}void str_echo(int sockfd){    printf("str_echo Enter:\n");    int n;    char buf[MAXLINE];again:    while((n = read(sockfd, buf, MAXLINE)) > 0)    {        printf("str_echo Enter: Writen(sockfd, buf, n);\n");        Writen(sockfd, buf, n);    }    if(n < 0 && errno == EINTR)    {        printf("str_echo: EINTR. continue\n");        goto again;    }    else if(n < 0)    {        Exit("str_echo: read error");    }}int main(){    int listenfd, connfd;    pid_t childpid;    socklen_t clilen;    struct sockaddr_in cliaddr, servaddr;    listenfd = Socket(AF_INET, SOCK_STREAM, 0);    printf("Socket success.\n");    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, (struct sockaddr *)&servaddr, sizeof(servaddr));    printf("Bind success\n");    Listen(listenfd, LISTENQ);    printf("Listen success\n");    for(;;)    {        printf("Enter for(;;) loop:\n");        clilen = sizeof(cliaddr);        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);        printf("Accept success.\n");        if((childpid = Fork()) == 0)        {            printf("Child Enter\n");            Close(listenfd);            str_echo(connfd);            printf("Child Exit\n");            exit(0);  // Auto Close(connfd);        }        Close(connfd);    }}

5.4 TCP Echo Client: ‘main’ Function

  • Figure 5.4 shows the TCP client main function.

Create socket, fill in Internet socket address structure 9–13

  • A TCP socket is created and an Internet socket address structure is filled in with the server’s IP address and port number. We take the server’s IP address from the command-line argument and the server’s well-known port (SERV_PORT) is from our unp.h header.

Connect to server 14–15

  • connect establishes the connection with the server. The function str_cli(Figure 5.5) handles the rest of the client processing.

5.5 TCP Echo Client: ‘str_cli’ Function

  • This function, shown in Figure 5.5, handles the client processing loop: It reads a line of text from standard input, writes it to the server, reads back the server’s echo of the line, and outputs the echoed line to standard output.

Read a line, write to server 6–7

  • fgets reads a line of text and writen sends the line to the server.

Read echoed line from server, write to standard output 8–10

  • readline reads the line echoed back from the server and fputs writes it to standard output.

Return to main 11–12

  • The loop terminates when fgets returns a null pointer, which occurs when it encounters either an end-of-file (EOF) or an error. Our Fgets wrapper function checks for an error and aborts if one occurs, so Fgets returns a null pointer only when an end-of-file is encountered.
/*void Inet_pton(int family, const char *strptr, void *addrptr);void Connect(int fd, const struct sockaddr *sa, socklen_t salen);void str_cli(FILE *fp, int sockfd);char* Fgets(char *ptr, int n, FILE *fp);void Fputs(const char *ptr, FILE *fp);static int my_read(int fd, char *ptr);int readline(int fd, void *vptr, int maxlen);int Readline(int fd, void *ptr, int maxlen);*/#include <stdio.h>              // printf(), fgets(), ferror(), fputs()#include <stdlib.h>         // exit()#include <sys/types.h>#include <sys/socket.h> // socket(), connect()#include <strings.h>            // bzero()#include <string.h>         // strlen()#include <arpa/inet.h>      // htons(), inet_pton()#include <unistd.h>         // read(), write()#include <errno.h>          // errno#define MAXLINE     4096#define SERV_PORT   7188void Exit(char *string){    printf("%s\n", string);    exit(1);}int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol)) < 0)    {        Exit("socket error");    }    return n;}void Inet_pton(int family, const char *strptr, void *addrptr){    if(inet_pton(family, strptr, addrptr) <= 0)    {        Exit("inet_pton error");    }}void Connect(int fd, const struct sockaddr *sa, socklen_t salen){    if(connect(fd, sa, salen) < 0)    {        Exit("connect error");    }}char* Fgets(char *ptr, int n, FILE *fp){    char *rptr;    if((rptr = fgets(ptr, n, fp)) == NULL && ferror(fp))    {        Exit("fgets error");    }    return rptr;}int writen(int fd, const void *vptr, int n){    int nleft = n;    int nwritten;    const char *ptr = vptr;    while(nleft > 0)    {        if((nwritten = write(fd, ptr, nleft)) <= 0)        {            if(nwritten < 0 && errno == EINTR)            {                nwritten = 0;       /* and call write() again */            }            else            {                return -1;          /* error */            }        }        nleft -= nwritten;        ptr += nwritten;    }    return n;}void Writen(int fd, void *ptr, int nbytes){    if(writen(fd, ptr, nbytes) != nbytes)    {        Exit("writen error");    }}static int  read_cnt = 0;static char *read_ptr = NULL;static char read_buf[MAXLINE];static int my_read(int fd, char *ptr){    if(read_cnt <= 0)    {again:        if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)        {            if (errno == EINTR)            {                goto again;            }            return -1;        }        else if(read_cnt == 0)        {            return 0;        }        read_ptr = read_buf;    }    read_cnt--;    *ptr = *read_ptr++;    return 1;}int readline(int fd, void *vptr, int maxlen){    int n, rc;    char c, *ptr = vptr;    for(n = 1; n < maxlen; n++)    {        if((rc = my_read(fd, &c)) == 1)        {            *ptr++ = c;            if (c == '\n')            {                break;  // newline is stored, like fgets()            }        }        else if (rc == 0)        {            *ptr = 0;            return n - 1;  // EOF, n - 1 bytes were read        }        else        {            return -1;  // error, errno set by read()        }    }    *ptr = 0;  // null terminate like fgets()    return n;}int Readline(int fd, void *ptr, int maxlen){    int n;    if((n = readline(fd, ptr, maxlen)) < 0)    {        Exit("readline error");    }    return n;}void Fputs(const char *ptr, FILE *fp){    if(fputs(ptr, fp) == EOF)    {        Exit("fputs error");    }}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)        {            Exit("str_cli: server terminated prematurely");        }        Fputs(recvline, stdout);    }}int main(int argc, char **argv){    int sockfd;    struct sockaddr_in servaddr;    if(argc != 2)    {        Exit("usage: tcpcli <IPaddress>");    }    sockfd = Socket(AF_INET, SOCK_STREAM, 0);    bzero(&servaddr, sizeof(servaddr));    servaddr.sin_family = AF_INET;    Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);    servaddr.sin_port = htons(SERV_PORT);    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));    str_cli(stdin, sockfd);    exit(0);}

5.6 Normal Startup

  • We first start the server in the background on the host linux.
$ ./5-2-tcpserv01 &[1] 10538
  • When the server starts, it calls socket, bind, listen, and accept, blocking in the call to accept. Before starting the client, we run the netstat to verify the state of the server’s listening socket.

  • A socket is in the LISTEN state with a wildcard for the local IP address and a local port of 7188. netstat prints a * for an IP address of 0(INADDR_ANY) or for a port of 0.
  • We then start the client on the same host, specifying the server’s IP address of 127.0.0.1(the loopback address).
    $ ./5-4-tcpli01 127.0.0.1
  • The 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. The connection is established. The following steps then take place:
    1. The client calls str_cli, which will block in the call to fgets.
    2. When accept returns in the server, it calls fork and the child calls str_echo. This function calls read, which blocks while waiting for a line to be sent from the client.
    3. The server parent calls accept again, and blocks while waiting for the next client connection.
  • We have three processes, and all three are asleep(blocked): client, server parent, and server child.
  • When the three-way handshake completes, we list the client step first, and then the server steps. The reason can be seen in Figure 2.5: connect returns when the second segment of the handshake is received by the client, but accept does not return until the third segment of the handshake is received by the server, one-half of the RTT after connect returns.

  • The first ESTABLISHED line corresponds to the server child’s socket, the second is the client’s socket. If we were running the client and server on different hosts, the client host would display only the client’s socket, and the server host would display only the two server sockets.
  • We can also use the ps command to check the status and relationship of these processes.

  • The PID and PPID columns show the parent and child relationships. We can tell that the first tcpserv01 line is the parent and the second tcpserv01 line is the child since the PPID of the child is the parent’s PID. Also, the PPID of the parent is the shell (bash).
  • The STAT column for all three of our network processes is “S”, meaning the process is sleeping(waiting for something). When a process is asleep, the WCHAN column specifies the condition. Linux prints wait_for_connect when a process is blocked in either accept or connect, tcp_data_wait when a process is blocked on socket input or output, or read_chan when a process is blocked on terminal I/O. The WCHAN values for our three network processes therefore make sense.

5.7 Normal Termination

  • At this point, the connection is established and whatever we type to the client is echoed back.
$ ./5-4-tcpli01 127.0.0.1Socket success.Connect success.str_cli Enter:Fgets Enter:hello, worldwriten Enter:writen Enter: writing in while(nleft > 0) loop.readline Enter:readline Enter: my_read needs read.readline Enter: Encounter newline '\n' hello, worldFputs Enter:Fgets Enter:good byewriten Enter:writen Enter: writing in while(nleft > 0) loop.readline Enter:readline Enter: my_read needs read.readline Enter: Encounter newline '\n' good byeFputs Enter:Fgets Enter:^D
  • If we immediately execute netstat, we have

  • The client’s side of the connection enters the TIME_WAIT state(Section 2.7), and the listening server is still waiting for another client connection.
  • We can follow through the steps involved in the normal termination of our client and server:
    1. When type EOF, fgets returns a null pointer and str_cli returns.
    2. When str_cli returns to the client main function, client terminates by calling exit.
    3. Part of process termination is the closing of all open descriptors, so the client socket is closed by the kernel. This sends a FIN to the server, to which the server TCP responds with an ACK. This is the first half of the TCP connection termination sequence. At this point, the server socket is in the CLOSE_WAIT state and the client socket is in the FIN_WAIT_2 state(Figures 2.4 and 2.5).
    4. When the server TCP receives the FIN, the server child is blocked in a call to read, and read then returns 0. This causes str_echo return to the server child main.
    5. The server child terminates by calling exit.
    6. All open descriptors in the server child are closed. The closing of the connected socket by the child causes the final two segments of the TCP connection termination to take place: a FIN from the server to the client, and an ACK from the client. At this point, the connection is completely terminated. The client socket enters the TIME_WAIT state.
    7. Finally, the SIGCHLD signal is sent to the parent when the server child terminates. The default action of the signal is to be ignored. Thus, the child enters the zombie state. We can verify this with the ps command.

  • The STAT of the child is now Z(for zombie).

5.8 POSIX Signal Handling

  • A signal is a notification to a process that an event has occurred. Signals are sometimes called software interrupts.
  • Signals can be sent
    1. By one process to another process(or to itself)
    2. By the kernel to a process(SIGCHLD: Whenever a process terminates, kernel sends to the parent of the terminating process.)
  • Every signal has a disposition(i.e., the action associated with the signal). We set the disposition of a signal by calling sigaction() and we have 3 choices for disposition:
    1. Provide a function that is called whenever a specific signal occurs. This function is called a signal handler and this action is called catching a signal. The two signals SIGKILL and SIGSTOP cannot be caught. Our function is called with a single integer argument that is the signal number and the function returns nothing. Its function prototype is void handler (int signo);
    2. Ignore a signal by setting its disposition to SIG_IGN. The two signals SIGKILL and SIGSTOP cannot be ignored.
    3. Set the default disposition for a signal by setting its disposition to SIG_DFL. The default is normally to terminate a process on receipt of a signal, with certain signals also generating a core image of the process in its current working directory. Signals whose default disposition is to be ignored: SIGCHLD and SIGURG(sent on the arrival of out-of-band data).

signal Function

#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • An easier way to set the disposition of a signal is to call the signal function. The first argument is the signal name and the second argument is either a pointer to a function or one of the constants SIG_IGN or SIG_DFL.
  • Define our own function named signal that calls the POSIX sigaction function. This provides a simple interface with the desired POSIX semantics. This function is shown in Figure 5.6.

Simplify function prototype using typedef 2–3

  • Normal function prototype for signal:
    void (* signal(int signo, void (*func)(int) ) )(int);
    Define Sigfunc as
    typedef void Sigfunc(int);.
    Signal handlers are functions with an integer argument and the function returns nothing. The function prototype then becomes
    Sigfunc *signal (int signo, Sigfunc *func);

Set handler 6

  • The sa_handler member of the sigaction structure is set to the func argument.

Set signal mask for handler 7

  • POSIX allow us to specify a set of signals that will be blocked when our signal handler is called. Any signal that is blocked cannot be delivered to a process. We set sa_mask to the empty set, which means that no additional signals will be blocked while our signal handler is running. POSIX guarantees that the signal being caught is always blocked while its handler is executing.

Set SA_RESTART flag 8–17

  • SA_RESTART is an optional flag. When the flag is set, a system call interrupted by this signal will be automatically restarted by the kernel. If the signal being caught is not SIGALRM, we specify the SA_RESTART flag.
  • The reason for making a special case for SIGALRM is that the purpose of generating this signal is to place a timeout on an I/O operation(Section 14.2), so we want the blocked system call to be interrupted by the signal.

Call sigaction 18–20

  • We call sigaction and then return the old action for the signal as the return value of the signal function.

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 that were specified in the sa_mask signal set passed to sigaction when the handler was installed are also blocked. We set sa_mask to the empty set, meaning no additional signals are blocked other than the signal being caught.
  • 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.
  • We can selectively block and unblock a set of signals using the sigprocmask function. This lets us protect a critical region of code by preventing certain signals from being caught while that region of code is executing.

5.9 Handling ‘SIGCHLD’ Signals

  • 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, and information on the resource utilization of the child.
  • 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), which will inherit the children and clean them up(i.e., init will wait for them, which removes the zombie). Some Unix systems show the COMMAND column for a zombie process as .

Handling Zombies

  • We do not want to leave zombies around. They take up space in the kernel and eventually we can run out of processes. Whenever we fork children, we must wait for them to prevent them from becoming zombies.
  • We establish a signal handler by adding the function call
    Signal (SIGCHLD, sig_chld);
    after the call to listen, to catch SIGCHLD, and within the handler, we call wait. Signal must be done before forking the first child and needs to be done only once.

  • Calling standard I/O functions such as printf in a signal handler is not recommended (Section 11.18).

  • The sequence of steps is as follows:
    1. We terminate the client by typing EOF. The client TCP sends a FIN to the server and the server responds with an ACK.
    2. The receipt of the FIN delivers an EOF to the child’s pending readline. The child terminates.(Right???)
    3. The parent is blocked in its call to accept when the SIGCHLD signal is delivered. The sig_chld function executes, wait fetches the child’s PID and termination status, and printf is called from the signal handler. The signal handler returns.
    4. 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). The parent does not handle this error, so it aborts.
  • When writing network programs that catch signals, we must be aware of interrupted system calls and handle them.

Handling Interrupted System Calls

  • We use term “slow system call” for any system call that can block forever. That is, there is no guarantee that the system call will ever return.
  • 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 of EINTR. Some kernels automatically restart some interrupted system calls, some not.
  • To handle an interrupted accept, we change the call to accept in Figure 5.2, the beginning of the for loop, to the following:
for ( ; ; ){    clilen = sizeof (cliaddr);    if((connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)    {        if (errno == EINTR)        {            continue;  // back to for ()        }        else        {            err_sys("accept error");        }    }}
  • We restart the interrupted system call. This is fine for accept, read, write, select, and open. But we cannot restart connect: If connect returns EINTR, we cannot call it again, as doing so will return an immediate error. When connect is interrupted by a caught signal and is not automatically restarted, we must call select to wait for the connection to complete(Section 16.3).

5.10 ‘wait’ and ‘waitpid’ Functions

#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *statloc);pid_t waitpid(pid_t pid, int *statloc, int options);Both return: process ID if OK, 0 or–1 on error
  • wait and waitpid both return two values: the return value of the function is the process ID of the terminated child, and the termination status of the child(an integer) is returned through the statloc pointer.
  • There are three macros that we can call that examine the termination status and tell us if the child terminated normally, was killed by a signal, or was stopped by job control. Additional macros let us fetch the exit status of the child, or the value of the signal that killed the child, or the value of the job-control signal that stopped the child.
  • If there are no terminated children for the process calling wait, but the process has one or more children that are still executing, then wait blocks until the first of the existing children terminates.
  • waitpid: pid lets us specify the process ID that we want to wait for. A value of -1 says to wait for the first of our children to terminate. options lets us specify additional options, such as WNOHANG(tell the kernel not to block if there are no terminated children).

Difference between wait and waitpid

  • Modify TCP client as shown in Figure 5.9. The client establishes five connections with the server and then uses only the first one(sockfd[0]) in the call to str_cli.

  • When the client terminates, all open descriptors are closed automatically by the kernel, and all five connections are terminated at about the same time. This causes five FINs to be sent, one on each connection, which in turn causes all five server children to terminate at about the same time. This causes five SIGCHLD signals to be delivered to the parent at about the same time, which we show in Figure 5.10.

  • We first run the server in the background and then our new client. Our server is Figure 5.2, modified to call signal to establish Figure 5.7 as a signal handler for SIGCHLD.

  • Only one printf is output, when we expect all five children to have terminated. If we execute ps, we see that the other four children still exist as zombies.

  • Establishing a signal handler and calling wait from that handler are insufficient for preventing zombies. The problem is that all five signals are generated before the signal handler is executed, and the signal handler is executed only one time because Unix signals are normally not queued.
  • With the client and server on the same host, the signal handler is executed once, leaving four zombies. But if we run the client and server on different hosts, the signal handler is normally executed two times: once as a result of the first signal being generated, and since the other four signals occur while the signal handler is executing, the handler is called only one more time. This leaves three zombies. But sometimes, probably dependent on the timing of the FINs arriving at the server host, the signal handler is executed three or even four times.
  • The correct solution is to call waitpid instead of wait.

  • This version works because we call waitpid within a loop, fetching the status of any of our children that have terminated. We must specify the WNOHANG option: This tells waitpid not to block if there are running children that have not yet terminated. In Figure 5.7, we cannot call wait in a loop, because there is no way to prevent wait from blocking if there are running children that have not yet terminated.

  • Figure 5.12 correctly handles a return of EINTR from accept and it establishes a signal handler(Figure 5.11) that calls waitpid for all terminated children.
  • This section demonstrates 3 scenarios that we can encounter with network programming:
    1. We must catch the SIGCHLD signal when forking child processes.
    2. We must handle interrupted system calls when we catch signals.
    3. A SIGCHLD handler must be coded correctly using waitpid to prevent any zombies from being left around.
/*void str_echo(int sockfd);void Writen(int fd, void *ptr, int nbytes);int writen(int fd, const void *vptr, int n)*/#include <stdio.h>              // printf()#include <stdlib.h>         // exit()#include <sys/types.h>#include <sys/socket.h> // socket(), bind(), listen(), accept()#include <strings.h>            // bzero()#include <arpa/inet.h>      // htonl(), htons()#include <unistd.h>         // fork(), close(), read(), write()#include <errno.h>          // errno#include <signal.h>         // signal(), sigemptyset(), sigaction()#include <bits/sigaction.h>#include <sys/wait.h>       // wait()#define     LISTENQ     32#define     MAXLINE     4096#define     SERV_PORT   7188void Exit(char *string){    printf("%s\n", string);    exit(1);}int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol)) < 0)    {        Exit("socket error");    }    return n;}void Bind(int fd, const struct sockaddr *sa, socklen_t salen){    if(bind(fd, sa, salen) < 0)    {        Exit("bind error");    }}void Listen(int fd, int backlog){    if(listen(fd, backlog) < 0)    {        Exit("listen error");    }}typedef void Sigfunc(int);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;}void Signal(int signo, Sigfunc *func)   /* for our signal() function */{    Sigfunc *sigfunc;    if((sigfunc = signal(signo, func)) == SIG_ERR)    {        Exit("signal error");    }}void sig_chld(int signo){    pid_t pid;    int stat;    while((pid = waitpid(-1, &stat, WNOHANG)) > 0)    {        printf("child %d terminated\n", pid);    }}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr){    printf("Enter accept:\n");    int n;again:    if((n = accept(fd, sa, salenptr)) < 0)    {        // EPROTO: Protocol error; ECONNABORTED: Connection aborted        if(errno == EPROTO || errno == ECONNABORTED)        {            printf("Enter accept: errno == EPROTO || errno == ECONNABORTED\n");            goto again;        }        else if(errno == EINTR)  // EINTR: Interrupted function call        {            printf("Enter accept: errno == EINTR\n");            goto again;        }        else        {            Exit("accept error");        }    }    return n;}pid_t Fork(){    pid_t pid;    if((pid = fork()) == -1)    {        Exit("fork error");    }    return pid;}void Close(int fd){    if(close(fd) == -1)    {        Exit("close error");    }}int writen(int fd, const void *vptr, int n){    printf("writen Enter:\n");    int nleft = n;    int nwritten;    const char *ptr = vptr;    while(nleft > 0)    {        printf("writen Enter: writing in while(nleft > 0) loop.\n");        if((nwritten = write(fd, ptr, nleft)) <= 0)        {            if(nwritten < 0 && errno == EINTR)            {                printf("writen Enter: Interrupt occurs\n");                nwritten = 0;       /* and call write() again */            }            else            {                return -1;          /* error */            }        }        nleft -= nwritten;        ptr += nwritten;    }    return n;}void Writen(int fd, void *ptr, int nbytes){    if(writen(fd, ptr, nbytes) != nbytes)    {        Exit("writen error");    }}void str_echo(int sockfd){    printf("str_echo Enter:\n");    int n;    char buf[MAXLINE];again:    while((n = read(sockfd, buf, MAXLINE)) > 0)    {        printf("str_echo Enter: Writen(sockfd, buf, n);\n");        Writen(sockfd, buf, n);    }    if(n < 0 && errno == EINTR)    {        printf("str_echo: EINTR. continue\n");        goto again;    }    else if(n < 0)    {        Exit("str_echo: read error");    }}int main(){    int listenfd, connfd;    pid_t childpid;    socklen_t clilen;    struct sockaddr_in cliaddr, servaddr;    listenfd = Socket(AF_INET, SOCK_STREAM, 0);    printf("Socket success.\n");    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, (struct sockaddr *)&servaddr, sizeof(servaddr));    printf("Bind success\n");    Listen(listenfd, LISTENQ);    printf("Listen success\n");    Signal(SIGCHLD, sig_chld);    printf("Signal success\n");    for(;;)    {        printf("Enter for(;;) loop:\n");        clilen = sizeof(cliaddr);        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);        printf("Accept success.\n");        if((childpid = Fork()) == 0)        {            printf("Child Enter\n");            Close(listenfd);            str_echo(connfd);            printf("Child Exit\n");            exit(0);  // Auto Close(connfd);        }        Close(connfd);    }}
/*void Inet_pton(int family, const char *strptr, void *addrptr);void Connect(int fd, const struct sockaddr *sa, socklen_t salen);void str_cli(FILE *fp, int sockfd);char* Fgets(char *ptr, int n, FILE *fp);void Fputs(const char *ptr, FILE *fp);static int my_read(int fd, char *ptr);int readline(int fd, void *vptr, int maxlen);int Readline(int fd, void *ptr, int maxlen);*/#include <stdio.h>              // printf(), fgets(), ferror(), fputs()#include <stdlib.h>         // exit()#include <sys/types.h>#include <sys/socket.h> // socket(), connect()#include <strings.h>            // bzero()#include <string.h>         // strlen()#include <arpa/inet.h>      // htons(), inet_pton()#include <unistd.h>         // read(), write()#include <errno.h>          // errno#define MAXLINE     4096#define SERV_PORT   7188void Exit(char *string){    printf("%s\n", string);    exit(1);}int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol)) < 0)    {        Exit("socket error");    }    return n;}void Inet_pton(int family, const char *strptr, void *addrptr){    if(inet_pton(family, strptr, addrptr) <= 0)    {        Exit("inet_pton error");    }}void Connect(int fd, const struct sockaddr *sa, socklen_t salen){    if(connect(fd, sa, salen) < 0)    {        Exit("connect error");    }}char* Fgets(char *ptr, int n, FILE *fp){    printf("Fgets Enter:\n");    char *rptr;    if((rptr = fgets(ptr, n, fp)) == NULL)    {        if(ferror(fp))        {            Exit("fgets error");        }        printf("Fgets Enter: read EOF\n");    }    return rptr;}int writen(int fd, const void *vptr, int n){    printf("writen Enter:\n");    int nleft = n;    int nwritten;    const char *ptr = vptr;    while(nleft > 0)    {        printf("writen Enter: writing in while(nleft > 0) loop.\n");        if((nwritten = write(fd, ptr, nleft)) <= 0)        {            if(nwritten < 0 && errno == EINTR)            {                printf("writen Enter: Interrupt occurs\n");                nwritten = 0;       /* and call write() again */            }            else            {                return -1;          /* error */            }        }        nleft -= nwritten;        ptr += nwritten;    }    return n;}void Writen(int fd, void *ptr, int nbytes){    if(writen(fd, ptr, nbytes) != nbytes)    {        Exit("writen error");    }}static int  read_cnt = 0;static char *read_ptr = NULL;static char read_buf[MAXLINE];static int my_read(int fd, char *ptr){    if(read_cnt <= 0)    {        printf("readline Enter: my_read needs read.\n");again:        if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)        {            if (errno == EINTR)            {                printf("readline Enter: my_read INTR\n");                goto again;            }            return -1;        }        else if(read_cnt == 0)        {            printf("readline Enter: my_read read NOthing, return.\n");            return 0;        }        read_ptr = read_buf;    }    read_cnt--;    *ptr = *read_ptr++;    return 1;}int readline(int fd, void *vptr, int maxlen){    printf("readline Enter:\n");    int n, rc;    char c, *ptr = vptr;    for(n = 1; n < maxlen; n++)    {        if((rc = my_read(fd, &c)) == 1)        {            *ptr++ = c;            if (c == '\n')            {                printf("readline Enter: Encounter newline '\\n' \n");                break;  // newline is stored, like fgets()            }        }        else if (rc == 0)        {            printf("readline Enter: Encounter EOF '\n' \n");            *ptr = 0;            return n - 1;  // EOF, n - 1 bytes were read        }        else        {            printf("readline Enter: my_read error.\n");            return -1;  // error, errno set by read()        }    }    *ptr = 0;  // null terminate like fgets()    return n;}int Readline(int fd, void *ptr, int maxlen){    int n;    if((n = readline(fd, ptr, maxlen)) < 0)    {        Exit("readline error");    }    return n;}void Fputs(const char *ptr, FILE *fp){    if(fputs(ptr, fp) == EOF)    {        Exit("fputs error");    }    printf("Fputs Enter:\n");}void str_cli(FILE *fp, int sockfd){    printf("str_cli Enter:\n");    char sendline[MAXLINE], recvline[MAXLINE];    while(Fgets(sendline, MAXLINE, fp) != NULL)    {        Writen(sockfd, sendline, strlen(sendline));        if(Readline(sockfd, recvline, MAXLINE) == 0)        {            Exit("str_cli: server terminated prematurely");        }        Fputs(recvline, stdout);    }}int main(int argc, char **argv){    int sockfd[5];    struct sockaddr_in servaddr;    if(argc != 2)    {        Exit("usage: tcpcli <IPaddress>");    }    for(int index = 0; index < 5; ++index)    {        sockfd[index] = Socket(AF_INET, SOCK_STREAM, 0);        bzero(&servaddr, sizeof(servaddr));        servaddr.sin_family = AF_INET;        Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);        servaddr.sin_port = htons(SERV_PORT);        Connect(sockfd[index], (struct sockaddr *)&servaddr, sizeof(servaddr));    }    printf("Five clients done.\n");    str_cli(stdin, sockfd[0]);    exit(0);}

5.11 Connection Abort before ‘accept’ Returns

  • There is another condition similar to the interrupted system call example in the previous section that can cause accept to return a nonfatal error, in which case we should call accept again. The sequence of packets shown in Figure 5.13 has been seen on busy servers (typically busy Web servers).

  • The three-way handshake completes, the connection is established, and then the client TCP sends an RST(reset). On the server side, the connection is queued by its TCP, waiting for the server process to call accept when the RST arrives. Sometime later, the server process calls accept.
  • One way to simulate this scenario is to start the server, have it call socket, bind, and listen, and then go to sleep for a short period of time before calling accept. While the server process is asleep, start the client and have it call socket and connect. As soon as connect returns, set the SO_LINGER socket option to generate the RST(Section 7.5) and terminate.
  • What happens to the aborted connection is implementation-dependent. Berkeley-derived implementations handle the aborted connection completely within the kernel, and the server process never sees it. Most SVR4 implementations return an error to the process as the return from accept, and the error depends on the implementation. These SVR4 implementations return an errno of EPROTO (“protocol error”), but POSIX specifies that the return must be ECONNABORTED (“software caused connection abort”) instead. The reason for the POSIX change is that EPROTO is also returned when some fatal protocol-related events occur on the streams subsystem. Returning the same error for the nonfatal abort of an established connection by the client makes it impossible for the server to know whether to call accept again or not. In the case of the ECONNABORTED error, the server can ignore the error and just call accept again.
  • We return to aborted connections in Section 16.6 and see how they can present a problem when combined with select and a listening socket in the normal blocking mode.

5.12 Termination of Server Process

  • We find the process ID of the server child and kill it. All open descriptors in the child are closed. This causes a FIN to be sent to the client, and the client TCP responds with an ACK. This is the first half of the TCP connection termination.
  • The SIGCHLD signal is sent to the server parent and handled correctly(Figure 5.12).
  • Nothing happens at the client. The client TCP receives the FIN from the server TCP and responds with an ACK, but the problem is that the client process is blocked in the call to fgets waiting for a line from the terminal.
  • Running netstat at this point shows the state of the sockets: server in “FIN_WAIT_2” state, client in “CLOSE_WAIT” state.

  • We can still type a line of input to the client. Here is what happens at the client starting from Step 1:

  • When we type “another line,” str_cli calls writen and the client TCP sends the data to the server. 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 send any more data. The receipt of the FIN does not tell the client TCP that the server process has terminated(which in this case, it has).
  • When the server TCP receives the data from the client, it responds with an RST since the process that had that socket open has terminated.
  • The client process will not see the RST because it calls readline immediately after the call to writen and readline returns 0(EOF) immediately because of the FIN that was received. Our client is not expecting to receive an EOF at this point(Figure 5.5) so it quits with the error message “server terminated prematurely.”
  • When the client terminates, all its open descriptors are closed.
  • What we have described also depends on the timing of the example. The client’s call to readline may happen before the server’s RST is received by the client, or after. If the readline happens before the RST is received(as in our example), the result is an unexpected EOF in the client. But if the RST arrives first, the result is an ECONNRESET(“Connection reset by peer”) error return from readline.
  • The problem is that the client is blocked in the call to fgets when the FIN arrives on the socket. 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, it should block on input from either source. This is one purpose of the select and poll functions.

5.13 ‘SIGPIPE’ Signal

  • What happens if the client ignores the error return from readline and writes more data to the server? This can happen if the client needs to perform two writes to the server before reading anything back, with the first write eliciting the RST.
  • The rule that applies is: When a process writes to a socket that has received an RST, the SIGPIPE signal is sent to the process. The default action of this signal is to terminate the process, so the process must catch the signal to avoid being involuntarily terminated.
  • If the process either catches the signal and returns from the signal handler, or ignores the signal, the write operation returns EPIPE.
  • We can’t obtain this signal on the first write, not the second. The first write elicits the RST and the second write elicits the signal. It is okay to write to a socket that has received a FIN, but it is an error to write to a socket that has received an RST.

  • We call writen two times: the first time the first byte of data is written to the socket, followed by a pause of one second, followed by the remainder of the line. The intent is for the first writen to elicit the RST and then for the second writen to generate SIGPIPE.
  • If we run the client on our Linux host, we get:

  • After terminating the server child, we type another line (“bye”) and the shell tells us the process died with a SIGPIPE signal(some shells do not print anything when a process dies without dumping core).
  • The way to handle SIGPIPE depends on what the application wants to do when this occurs. If there is nothing special to do, then setting the signal disposition to SIG_IGN, assuming that subsequent output operations will catch the error of EPIPE and terminate. If special actions are needed when the signal occurs, then the signal should be caught and any desired actions can be performed in the signal handler.
  • If multiple sockets are in use, the delivery of the signal will not tell us which socket encountered the error. If we need to know which write caused the error, then we must either ignore the signal or return from the signal handler and handle EPIPE from the write.

5.14 Crashing of Server Host

  • We must run the client and server on different hosts. Then start the server, start the client, type in a line to the client to verify that the connection is up, disconnect the server host from the network, and type in another line at the client.
  • This also covers the scenario of the server host being unreachable when the client sends data(i.e., some intermediate router goes down after the connection has been established).
  • The following steps take place:
    1. When the server host crashes, nothing is sent out on the existing network connections. Assume the host crashes and is not shut down by an operator(Section 5.16).
    2. We type a line of input to the client, it is written by writen, and is sent by the client TCP as a data segment. The client then blocks in the call to readline, waiting for the echoed reply.
    3. If we watch the network with tcpdump, we will see the client TCP continually retransmitting the data segment, trying to receive an ACK from the server. Berkeley-derived implementations retransmit the data segment 12 times, waiting for around 9 minutes before giving up. When the client TCP finally gives up(assuming the host was still unreachable), an error is returned to the client process. Since the client is blocked in the call to readline, it returns an error. Assuming the server host crashed and there were no responses at all to the client’s data segments, the error is ETIMEDOUT. If some intermediate router determined that the server host was unreachable and responded with an ICMP “destination unreachable’ message, the error is either EHOSTUNREACH or ENETUNREACH.
  • Although our client eventually discovers that the peer is down or unreachable, there are times when we want to detect this quicker. Solution is to place a timeout on the call to readline(Section 14.2).
  • The scenario just discussed detects that the server host has crashed only when we send data to that host. If we want to detect the crashing of the server host even if we are not actively sending it data, we use the SO_KEEPALIVE socket option(Section 7.5).

5.15 Crashing and Rebooting of Server Host

  • We establish a connection between the client and server and then assume the server host crashes and reboots before sending it data. If the client is not actively sending data to the server when the server host crashes, the client is not aware that the server host has crashed.(Assumes not use SO_KEEPALIVE socket option.)
  • The following steps take place:
    1. We start the server and then the client. We type a line to verify that the connection is established.
    2. The server host crashes and reboots.
    3. We type a line of input to the client, which is sent as a TCP data segment to the server host.
    4. When the server host reboots after crashing, its TCP loses all information about connections that existed before the crash. Therefore, the server TCP responds to the received data segment from the client with an RST.
    5. Our client is blocked in the call to readline when the RST is received, causing readline to return the error ECONNRESET.

5.16 Shutdown of Server Host

  • Consider what happens if the server host is shut down by an operator while our server process is running on that host.
  • When Unix is shut down, the init process sends the SIGTERM signal(can catch) to all processes, waits some fixed amount of time(often between 5 and 20 seconds), and then sends the SIGKILL signal(cannot catch) to any processes still running. This gives all running processes a short amount of time to clean up and terminate.
  • If we do not catch SIGTERM and terminate, our server will be terminated by the SIGKILL signal. When the process terminates, all open descriptors are closed, and follow the same sequence of steps discussed in Section 5.12(Termination of Server Process). We must use the select or poll function in our client to have the client detect the termination of the server process as soon as it occurs.

5.17 Summary of TCP Example

5.18 Data Format

Example: Passing Text Strings between Client and Server

  • Modify server to reads a line containing two integers separated by white space, and the server returns the sum of those two integers.

  • We call sscanf to convert the two arguments from text strings to long integers, and then snprintf is called to convert the result into a text string.
  • This new client and server work fine, regardless of the byte ordering of the client and server hosts.

Example: Passing Binary Structures between Client and Server

  • We now modify our client and server to pass binary values across the socket. This does not work when the client and server are run on hosts with different byte orders, or on hosts that do not agree on the size of a long integer(Figure 1.17).
  • We define one structure for the two arguments, another structure for the result, and place both definitions in our sum.h header, shown in Figure 5.18. Figure 5.19 shows the str_cli function.

  • sscanf converts the two arguments from text strings to binary, and we call writen to send the structure to the server.
  • We call readn to read the reply, and print the result using printf.

  • We read the arguments by calling readn, calculate and store the sum, and call writen to send back the result structure.
  • If we run the client and server on two machines of the same architecture, everything works fine.

  • When the client and server are on two machines of different architectures, it does not work.

  • Problem is that the byte order are different. There are 3 potential problems with this example:
    1. Different implementations store binary numbers in different formats. The most common formats are big-endian and little-endian.
    2. Different implementations can store the same C datatype differently. For example, 32-bit systems use 32 bits for a long but 64-bit systems typically use 64 bits for the same datatype(Figure 1.17). There is no guarantee that a short, int, or long is of any certain size.
    3. Different implementations pack structures differently, depending on the number of bits used for the various datatypes and the alignment restrictions of the machine. It is never wise to send binary structures across a socket.
  • There are two common solutions to this data format problem:
    1. Pass all numeric data as text strings. This assumes that both hosts have the same character set.
    2. Explicitly define the binary formats of the supported datatypes(number of bits, big- or little-endian) and pass all data between the client and server in this format. RPC packages normally use this technique.

5.19 Summary

Exercises 5.1

Build the TCP server from Figures 5.2 and 5.3 and the TCP client from Figures 5.4 and 5.5. Start the server and then start the client. Type in a few lines to verify that the client and server work. Terminate the client by typing your EOF character and note the time. Use netstat on the client host to verify that the client’s end of the connection goes through the TIME_WAIT state. Execute netstat every five seconds or so to see when the TIME_WAIT state ends. What is the MSL for this implementation?

  • The duration of the TIME_WAIT state should be between 1 and 4 minutes, giving an MSL between 30 seconds and 2 minutes.

Exercises 5.2

What happens with our echo client/server if we run the client and redirect standard input to a binary file?

  • Our client/server programs do not work with a binary file.
  • Assume the first 3 bytes in the file are binary 1, binary 0, and a newline. The call to fgets reads up to MAXLINE-1 characters, or until a newline is encountered, or up through the EOF. In this example, it will read the first three characters and then terminate the string with a null byte. But, our call to strlen returns 1, since it stops at the first null byte. One byte is sent to the server, but the server blocks in its call to readline, waiting for a newline character. The client blocks waiting for the server’s reply. This is called a deadlock: Both processes are blocked waiting for something that will never arrive from the other one.
  • Problem is that fgets signifies the end of the data that it returns with a null byte, so the data that it reads cannot contain any null bytes.

Exercises 5.3

What is the difference between our echo client/server and using the Telnet client to communicate with our echo server?

  • Telnet converts the input lines into NVT ASCII(Section 26.4 of TCPv1), which terminates every line with the two-character sequence of a CR(carriage return) followed by an LF(linefeed). Our client adds only a newline, which is a linefeed character.
  • We can use the Telnet client to communicate with our server as our server echoes back every character, including the CR that precedes each newline.

Exercises 5.4

In our example in Section 5.12, we verified that the first two segments of the connection termination are sent(the FIN from the server that is then ACKed by the client) by looking at the socket states using netstat. Are the final two segments exchanged(a FIN from the client that is ACKed by the server)? If so, when, and if not, why?

  • No, the final two segments of the connection termination sequence are not sent. When the client sends the data to the server after we kill the server child, the server TCP responds with an RST. The RST aborts the connection and also prevents the server end of the connection(the end that did the active close) from passing through the TIME_WAIT state.

Exercises 5.5

What happens in the example outlined in Section 5.14 if between Steps 2 and 3 we restart our server application on the server host?

  • Nothing changes because the server process that is started on the server host creates a listening socket and is waiting for new connection requests to arrive. What we send in Step 3 is a data segment destined for an ESTABLISHED TCP connection. Our server with the listening socket never sees this data segment, and the server TCP still responds to it with an RST.

Exercises 5.6

To verify what we claimed happens with SIGPIPE in Section 5.13, modify Figure 5.4 as follows: Write a signal handler for SIGPIPE that just prints a message and returns. Establish this signal handler before calling connect.
Change the server’s port number to 13, the daytime server. When the connection is established, sleep for two seconds, write a few bytes to the socket, sleep for another two seconds, and write a few more bytes to the socket. Run the program. What happens?

#include <stdio.h>              // printf()#include <stdlib.h>         // exit()#include <sys/types.h>#include <sys/socket.h> // socket(), connect()#include <strings.h>            // bzero()#include <string.h>         // strlen()#include <arpa/inet.h>      // htons(), inet_pton()#include <unistd.h>         // read(), write(), sleep()#include <errno.h>          // errno#include <signal.h>         // signal(), sigemptyset(), sigaction()#include <bits/sigaction.h>#define MAXLINE     4096#define SERV_PORT   13void Exit(char *string){    printf("%s\n", string);    exit(1);}int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol)) < 0)    {        Exit("socket error");    }    return n;}void Inet_pton(int family, const char *strptr, void *addrptr){    if(inet_pton(family, strptr, addrptr) <= 0)    {        Exit("inet_pton error");    }}void Connect(int fd, const struct sockaddr *sa, socklen_t salen){    if(connect(fd, sa, salen) < 0)    {        Exit("connect error");    }}typedef void Sigfunc(int);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;}void Signal(int signo, Sigfunc *func)   /* for our signal() function */{    Sigfunc *sigfunc;    if((sigfunc = signal(signo, func)) == SIG_ERR)    {        Exit("signal error");    }}void sig_pipe(int signo){    printf("sig_pipe: Catch SIGPIPE\n");}void Write(int fd, void *ptr, size_t nbytes){    if(write(fd, ptr, nbytes) != nbytes)    {        Exit("write error");    }}int main(int argc, char **argv){    int sockfd;    struct sockaddr_in servaddr;    if(argc != 2)    {        Exit("usage: tcpcli <IPaddress>");    }    sockfd = Socket(AF_INET, SOCK_STREAM, 0);    printf("Socket success.\n");    bzero(&servaddr, sizeof(servaddr));    servaddr.sin_family = AF_INET;    Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);    servaddr.sin_port = htons(SERV_PORT);    Signal(SIGPIPE, sig_pipe);    printf("Signal success.\n");    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));    printf("Connect success.\n");    sleep(2);    Write(sockfd, "hello", 5);    sleep(2);    Write(sockfd, "world", 5);    exit(0);}$ ./ex5-6cli 127.0.0.1Socket success.Signal success.Connect success.sig_pipe: Catch SIGPIPEwrite error
  • The first sleep let the daytime server send its reply and close its end of the connection. Our first write sends a data segment to the server, which responds with an RST since the daytime server has completely closed its socket. TCP allows us to write to a socket that has received a FIN. The second sleep lets the server’s RST be received, and our second write generates SIGPIPE. Since our signal handler returns, write returns an error of EPIPE.

Exercises 5.7

What happens in Figure 5.15 if the IP address of the server host that is specified by the client in its call to connect is the IP address associated with the rightmost datalink on the server, instead of the IP address associated with the leftmost datalink on the server?

  • Assuming the server host supports the weak end system model(Section 8.8), everything works. That is, the server host will accept an incoming IP datagram (which contains a TCP segment in this case) arriving on the leftmost datalink, even though the destination IP address is the address of the rightmost datalink. We can test this by running our server on our host linux(Figure 1.16) and then starting the client on our host solaris, but specifying the other IP address of the server(206.168.112.96) to the client. After the connection is established, if we run netstat on the server, we see that the local IP address is the destination IP address from the client’s SYN, not the IP address of the datalink on which the SYN arrived(as we mentioned in Section 4.4).

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

0 0