1 / 38

TCP Sockets

TCP Sockets. Computer Network Programming. TCP Echo Server. We will write a simple echo server and client client read a line of text from standard input and wii send it to the server server will read the line from network and will write it back to client

garson
Download Presentation

TCP Sockets

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. TCP Sockets Computer Network Programming

  2. TCP Echo Server • We will write a simple echo server and client • client read a line of text from standard input and wii send it to the server • server will read the line from network and will write it back to client • the client reads the echoed line from network and prints it on the screen fgets stdin writen readline TCP client TCP server fputs stdout readline writen

  3. 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); /* 9877 */ Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); Continued on the next page

  4. 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 */ } } Blue code shows the section of the code that is executed by the child process.

  5. Wrapper Functions int Socket(int family, int type, int protocol) { int n; if ( (n = socket(family, type, protocol)) < 0) err_sys("socket error"); return(n); } You can find the wrapper functions in the source code of the examples, in file lib/wrapsocket.c For examples we are using wrapper functions so that we are not bothered to handle the error cases. void Bind(int fd, const struct sockaddr *sa, socklen_t salen) { if (bind(fd, sa, salen) < 0) err_sys("bind error"); } Similarly we have wrapper functions: Accept, Listen, Close, ….

  6. str_echo function 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); } }

  7. tcp echo server • TCP echo server is concurrent server • We are creating a new child process for every client request to server that request. • str_echo() function is used to receive and serve the request (read a line and echo it back). • child process uses exit(0) to terminate at which time all the open descriptors belonging to the child process is closed.

  8. TCP echo client #include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: tcpcli <IPaddress>"); 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 */ exit(0); }

  9. str_cli function 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); } }

  10. Running the application Run the server and clients on the same host. use the loopback address as the IP address of the host: 127.0.0.1 - localhost parent server Connection requests come to here Port# 9877 fork() client child server Echo request/reply fork() Port# 49616 Port# 9877 client Echo request/reply child server Port# 49617 Port# 9877

  11. Running the application Run server first: (server is waiting on port 9877) tcpserv01 & netstat -a | grep 9877 TCP Local Address Remote Address Swind Send-Q Rwind Recv-Q State ------------ -------------------- ----- ------ ----- ------ ------- *.9877 *.* 0 0 0 0 LISTEN Run two clients on two different windows tcpcli01 127.0.0.1 (establish connection to the same host) Execute the netstat command on a third window netstat -a | grep 9877 *.9877 *.* 0 0 0 0 LISTEN localhost.49616 localhost.9877 32768 0 32768 0 ESTABLISHED localhost.9877 localhost.49616 32768 0 32768 0 ESTABLISHED localhost.49617 localhost.9877 32768 0 32768 0 ESTABLISHED localhost.9877 localhost.49617 32768 0 32768 0 ESTABLISHED

  12. Running the application aspendos{korpe}:> ps -efl | grep tcp 8 S korpe 26623 26613 0 61 20 f63dd718 406 f5e84b8e 12:05:54 pts/17 0:00 tcpcli01 127.0.0.1 8 S korpe 26611 26372 0 61 20 f6230448 406 f62d6b1e 12:05:37 pts/13 0:00 tcpcli01 127.0.0.1 8 S korpe 26595 26360 0 54 20 f65b1780 405 f63aa0d0 11:56:50 pts/12 0:00 tcpserv01 8 S korpe 26624 26595 0 44 20 f66a2898 406 f62d631e 12:05:54 pts/12 0:00 tcpserv01 8 S korpe 26612 26595 0 45 20 f62b0710 406 f5fed4f6 12:05:37 pts/12 0:00 tcpserv01 We can see the processes using the ps command. 3 tcp server processes exists: one parent, two children 2 tcp client processes exists. All processes are sleeping (S) because they are blocking on a function call: parent server: accept() child servers: read() clients: fgets()

  13. Normally terminating the Client I typed ^D as input to one of the clients. The client terminates. aspendos{korpe}:> netstat -a | grep 9877 *.9877 *.* 0 0 0 0 LISTEN localhost.49617 localhost.9877 32768 0 32768 0 ESTABLISHED localhost.9877 localhost.49617 32768 0 32768 0 ESTABLISHED localhost.49616 localhost.9877 32768 0 32768 0 TIME_WAIT aspendos{korpe}:> aspendos{korpe}:> ps -efl | grep tcp 8 S korpe 27125 27121 0 43 20 f66a1458 406 f5fed6f6 14:10:02 pts/17 0:00 tcpserv01 8 S korpe 27121 26613 0 54 20 f65b1780 405 f63aa0d0 14:09:41 pts/17 0:00 tcpserv01 8 S korpe 27124 26360 0 41 20 f65b2bc0 406 f64df2c6 14:10:01 pts/12 0:00 tcpcli01 127.0.0.1 8 Z korpe 27123 27121 0 0 0:00 <defunct> (the output of the ps shows different pids, since I had to restart the clients and server)

  14. Normal Termination • When we type EOF character (^D) , fgets returns a NULL pointer and str_cli returns. • Clients call exit(0) • Client process terminates and open descriptors of the client is closed. Hence a FIN segment is sent to the server and an ACK is received. • When TCP server receives FİN, this causes and EOF notification to be passed to the read() and server returns with 0 from readline(). • The server child exits with exit(0). • All open descriptors of child is closed and a FIN is sent to the client and ack ACK is received. Client socket enters to TIME_WAIT state. • The child server process enters to zombie state (look ps command). A SIGCHLD signal is sent to the parent but not handled.

  15. SIGNALS • A signal is a notification to a process that an event has occurred. • They are called sometimes software interrupts. • Signalls usually occur asynchronously, meaning that the process does not know • ahead of time exactly when the signal will occur. • Signals can be sent • by a process to an other process (or to itself) • by kernel to a process signal process1 process2 signal signal 3-ways that a process can receive signals kernel

  16. Signals • Every signal has a disposition: the action associated with the signal. • Sigaction function is called to set the action for a signal. • There choices for a disposition • Provide a function that will be called whenever a specific signal occurs. This function is called signal handler and this action is called catching the signal.void handler (int signo)SIGKILL and SIGSTOP can not be caught. • Ignore the signal by setting the disposition to SIG_IGN. SIGKILL and SIGSTOP can not be ignored • Set a default disposition for a signal by setting its disposition to SIG_DFL.

  17. Signal function Sigfunc * signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signo == SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */ #endif } if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); } We write our own signal function to set a disposition for a signal. typedef void Sigfunc(int); Sigfunc *signal(int signo, Sigfunc *func);

  18. Signal Semantics • Once a signal handler installed, it remains installed • While a signal handler is executing, the signal being delivered is blocked. Additional signals that are specified with sa_mask is also blocked. • If a signal is generated one or more times while its is blocked, it is normally delivered only one time afterthe signal is unblocked. Signals are not queued.. • It is also possible to block and unblock signals using sigprocmask function. This lets us protect certain critical region of the code by preventing certain signals from being caught while that region of code is executing.

  19. Handling SIGCHLD signals • Zombie state: the child stays in this state after terminating to maintain information about the child so that the parent can fetch some later time. (process ID, termination status, resource utilization information etc). • We don’t want to leave zombies around. They take up space in the kernel (process table). • Waiting for a zombie process removes the zombie • Hence, in parent, we have to wait for terminating children so that they don’t become zombies • waiting means: waiting until the process terminates and getting the status information after the process terminates.

  20. Waiting for terminating Children • We should establish a signal handler for SIGCHLD signal: the signal that is sent to the parent when child terminates. • In the signal handler we should wait for the terminating process - reads it terminating status information. • Simply call wait or waitpid functions inside signal handler.

  21. SIGCHLD handler • Establish signal handler by callingsignal (SIGCHLD, sig_chld) • void • sig_chld(int signo) • { • pid_t pid; • int stat; • pid = wait(&stat); • printf("child %d terminated\n", pid); • return; • } Put the signal handler into the tcp server code. Run the tcp server and client again aspendos{korpe}:> tcpserv02 & [1] 28751 aspendos{korpe}:> tcpcli01 127.0.0.1 test test ^D aspendos{korpe}:> child 28753 terminated

  22. Observations • We type EOF character on the client, client TCP sends FIN to the server and receives ACK. • The receipt of the FIN delivers EOF to the readline of the child server. Hence child terminates. • The child sends a SIGCHLD signal to the parent server • The parent server is blocked in the call to accept. The accept() is interrupted and signal handler for SIGCHLD is executed (sig_chld) • The signal handler reads the termination status of the child by calling wait and the child is removed from process table - so it does not become a zombie. • The accept is interrupt, hence it return with errno=EINTR, but some systems automatically restart the accept. For example the Solaris Unix that I tested this program was restaring accept automatically without user program knows about it.

  23. Handling Interrupted system calls • Some systems does not restart the interrupted system calls automatically, so it the job of the user program to recall the system call if the error value from previous call was errno = EINTR. • We have seen calling the read and write system calls when the error code as EINTR. • Look to the readn() and writen() functions that we have seen earlier.

  24. Wait and Waitpid Wait() waits for the first terminating child and reads its termination status after the child terminates. (terminated normally, killed, …etc…) If multiple childs exits and they terminate at the same time, all SIGCHLD signals will be coming to the server at the same time. However, the signal handler will catch only one them and the rest will be lost, since the signals are not queued in UNIX. Hence wait() will be executed only once for the first terminating child, and the termination status of the other children will not be read, hence they will become zombies. pid_t wait(int *statloc); pid_t waitpid (pid_t pid, int *statloc, int options);

  25. Example SIGCHLD SIGCHLD SIGCHLD SIGCHLD client server Server child1 Server child2 Server child3 Server child4 exit(0) FIN1 FIN2 FIN3 FIN4

  26. Use of waitid void sig_chld(int signo) { pid_t pid; int stat; while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n", pid); return; } -1 argument states that we are waiting for the first terminating child WNOHANG argument states that we are not blocking on waiting if there are child processes that are still running. By use of waitpid we can clean up all the child process that are terminates without leaving any zombies behind.

  27. Abnormal Conditions • Server process that handle the request is killed - crashing of server process • Crashing of server host • Crashing and rebooting server host • Shutdown of the server host

  28. Termination of Server Process a) First, A connection is established between client and server child b) Then the following steps occurs in the order given Server parent 0- client blocks on fgets() for user input 1-We kill the child 2-FIN is sent to the client Server child client 3-ACK is sent to the server child 4- user enters input line 5 - input line is sent 7 -read() returns error and client terminates with connection reset error or some other error RST 6 - server TCP replies back with RST since there is no server process waiting on the socket - died.

  29. SIGPIPE signal • SIGPIPE signal is sent by kernel to a process who tries to write to a socket who received a RST from the peer TCP. • We can either catch signal or ignore it. If we ignore than the write operation will return with errno = EPIPE

  30. Crashing of Server Host a) First, A connection is established between client and server child b) Then the following steps occurs in the order given Server parent 1-we type a line of input and send it to the socket by calling write and the client will block on read() or readline() 0-we disconnect the server host from the network client 2-TCP sends the input line as a TCP segment Server child 5- read() will return with error ETIMEOUT (EHOSTUNREACH, ENETUNREACH) 3-TCP will not receive an ACK, hence it will timout and will retransmit the segment Server host ………….. 3-TCP will retransmit the segment couple of times (12) since it will not receive any ACK. Finally it will give up after about 9 minutes.

  31. Crashing and rebooting Server Host a) First, A connection is established between client and server child b) Then the following steps occurs in the order given Server parent 3-we type a line of input and send it to the socket by calling write and the client will block on read() or readline() 0-we disconnect the server host from the network 2-we reconnect the machine to the network client 4-TCP sends the input line as a TCP segment Server child 5-server TCP replies back with a RST 6- read() will return with error ECONNRESET 1-we shutdown the machine and then reboot All the information about the existing TCP connections are lost

  32. Shutdown of Server Host a) First, A connection is established between client and server child b) Then the following steps occurs in the order given Server parent init Process 2. Process termites when it receives the SIGKILL signal 1. Init process sends SIGTERM and SIGKILL signals to all processes 4. FIN segment is sent client Server child Rest of the operations are the same with “Termination of Server Process” 3. The sockets belonging to the terminating process are closed by the kernel 0- System Administrator shutsdown the machine by issuing shutdown command

  33. Data Formats • Usually server does some processing on the data that is received from the client. • Hence it is important how data is passed between client and server • let client pass two integers to the server and the server will add them up and will send the result back to the client. There are two ways to achieve this: • Converting the data into text strings and passing text strings between client and server • Passing binary data between directly between client and server

  34. Passing Text Strings void str_echo(int sockfd) { long arg1, arg2; ssize_t n; char line[MAXLINE]; for ( ; ; ) { if ( (n = Readline(sockfd, line, MAXLINE)) == 0) return; /* connection closed by other end */ if (sscanf(line, "%ld%ld", &arg1, &arg2) == 2) snprintf(line, sizeof(line), "%ld\n", arg1 + arg2); else snprintf(line, sizeof(line), "input error\n"); n = strlen(line); Writen(sockfd, line, n); } }

  35. Passing Binary Data - client struct args { long arg1; long arg2; }; void str_cli(FILE *fp, int sockfd) { char sendline[MAXLINE]; struct args args; struct result result; while (Fgets(sendline, MAXLINE, fp) != NULL) { if (sscanf(sendline, "%ld%ld", &args.arg1, &args.arg2) != 2) { printf("invalid input: %s", sendline); continue; } Writen(sockfd, &args, sizeof(args)); if (Readn(sockfd, &result, sizeof(result)) == 0) err_quit("str_cli: server terminated prematurely"); printf("%ld\n", result.sum); } } struct result long sum; };

  36. Passing Binary Data - server void str_echo(int sockfd) { ssize_t n; struct args args; struct result result; for ( ; ; ) { if ( (n = Readn(sockfd, &args, sizeof(args))) == 0) return; /* connection closed by other end */ result.sum = args.arg1 + args.arg2; Writen(sockfd, &result, sizeof(result)); } }

  37. Problems with passing binary data • Different systems store binary numbers is different formats: big endian, little endian. • Different systems can store the same C datatype differently: some systems use 32 bits for long but some use 64 bits. • Different systems pack structures differently • Therefore it is now wise to send binary data across a socket

  38. Two common solutions • Pass all numeric data as text strings • Explicitly define the binary formats of the supported datatypes: number of bits, little or big endian, etc and pass all data in this format. • RPC for example uses XDR (External Data Representation)

More Related