Unix Network Programming Volume 1 Intermediate

Keywords: socket network Unix Programming

Unix Network Programming Volume 1 Intermediate

Basic TCP socket programming

Ref: UNIX Network Programming Volume 1 - Notes

socket

  • Function: int socket (int family, int type, int protocol);
    • The family parameter indicates the protocol family.
    • The type parameter indicates the socket type
    • Protocol means protocol type (or set to 0)
  • Not all combinations of family and type are valid
  • The AF prefix represents the address family, and the PF prefix represents the protocol family.
  • The return value of the socket function is a non negative integer (socket descriptor, sockfd). The socket descriptor knowledge specifies the protocol family and socket type, and does not specify the local protocol or remote protocol.

connect

  • Function: int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
    • sockfd: socket descriptor
    • The second and third parameters represent a socket address structure (internal server IP+Port)
    • Error condition:
      • TCP client did not receive SYN response, such as sending SYN to a non-existent IP on the local subnet.
      • Hard error: RST received
      • There are three conditions to generate RST:
        • The destination is the SYN arrival of a port, but there is no server listening on the port;
        • TCP wants to cancel an existing connection;
        • TCP receives a segment on a connection that does not exist at all.
      • Soft error: sending SYN segment causes ICMP error of router "destination unreachable".
    #include <sys/socket.h>
    int connect(int sockfd, const sockaddr * servaddr, socklen_t addrlen);
    //0 returned successfully with - 1 error
    

bind

  • Common error "address already in use"
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
//Successfully returned 0, error returned - 1

listen

  • Listening socket maintains two queues:
    • Incomplete connection queue (syn? RCVD) and completed connection queue (ESTABLISHED).
    • The backlog requires that the sum of the two queues does not exceed it.
#include <sys/socket.h>
int listen(int sockfd, int backlog);
Successfully returned 0, error returned - 1

accept

  • accept has two value result parameters. cliaddr and addrlen can return the peer information. If they don't care, they can set NULL.
    #include <sys/socket.h>
    int accept(int sockfd, struct sockaddr * cliaddr, socklen_t *addrlen);
    //Non negative description symbol returned successfully, error returned - 1
    

close()

  • int close(sockfd);: can be used to close a socket and terminate a TCP connection
  • If you really want to terminate the connection, you can use the shutdown() function

Server: display client IP and port number

/* The server displays the ip address and port number of the client */
#include <time.h>
#include "unp.h"
 
#define MAXLINE 4096
#define LISTENQ 1024
//#define SA struct sockaddr
typedef struct sockaddr SA;
typedef int socket_t; // 2017.08.06
 
int main(int argc, char **argv)
{
	int					listenfd, connfd;
	//struct sockaddr_in	servaddr;
	struct sockaddr_in	servaddr, cliaddr; // 2017.08.06
	socket_t			len; // 2017.08.06
	char				buff[MAXLINE];
	time_t				ticks;
 
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
 
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // Host byte order to network byte order
	servaddr.sin_port        = htons(1300);	/* daytime server */
 
	bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); // Strong to general socket address structure
 
	listen(listenfd, LISTENQ); // Convert to listening socket
 
	for ( ; ; ) {
		len = sizeof (cliaddr); // 2017.08.06
		connfd = accept(listenfd, (SA *)&cliaddr, &len); // 2017.08.06
		printf("connection from %s, port %d\n",
				inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
				ntohs(cliaddr.sin_port)); // 2017.08.06
 
        ticks = time(NULL);
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        write(connfd, buff, strlen(buff));
 
		close(connfd);
	}
}

Concurrent server

/* Pseudo code */
pid_t pid;
int   listenfd, connfd;
listenfd = socket (...);
bind (listenfd, ...);
listen (listenfd, LISTENQ);
for (; ; ) {
    connfd = accept (listenfd, ...);
    if ((pid = fork()) == 0) {
        close (listenfd); /* child closes listening socket */
        /* do something */
        close (connfd);   /* done with this client */
        exit (0);
    }
    close (connfd);       /* parent closes connected socket */
}

Local and foreign protocol address functions

#include <sys/socket.h>
int getsockname (int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername (int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
  • On a TCP client without calling bind, getsockname is used to return the local IP address and local port number assigned by the kernel after the connect ion is successfully returned.
  • After calling bind with port number 0, getsockname is used to return the local port number given by the kernel.
  • getsockname can be used to get the address family of a socket.
  • When a server is executed by a process that has called accept by calling exec, the only way it can get the identity of the client is to call getpeername.
/* Code demonstration: get address family of socket */
int sockfd_to_family(int sockfd)
{
	struct sockaddr_storage ss;
	socklen_t	len;
 
	len = sizeof(ss);
	if (getsockname(sockfd, (SA *) &ss, &len) < 0)
		return(-1);
	return(ss.ss_family);
}
  • Most TCP servers are concurrent, and most UDP servers are iterative.

Sample TCP client and server programs

This chapter begins to write a complete TCP client / server program instance.
(1) read a line of text into the standard input of the customer and write it to the server
(2) the server reads this line of text from the network input and sends it back to the customer
(3) the customer reads this line of echo text from the network and displays it on the standard output.
TCP/

Client

#include "unp.h"
int main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_inservaddr;
	
	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);
}
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);
	}
}

Server

#include "unp.h"
int main(int argc, char **argv) {
	int listenfd, connfd;
	pid_t childpid;
	socklen_t clilen;
	struct sockaddr_incliaddr, servaddr;
	
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);
	
	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
	Listen(listenfd, LISTENQ);
	
	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 */
	}
}
void str_echo(int sockfd) {
	ssize_t n;
	char buf[MAXLINE];
	
	again:
	while ( (n = read(sockfd, buf, MAXLINE)) > 0)
	Writen(sockfd, buf, n);
	
	if (n < 0 && errno == EINTR)
	goto again;
	else if (n < 0)
	err_sys("str_echo: read error");
}

Workflow

  • The server runs in the background first.

    • Connection phase:
      • Socket creates a socket.
      • Call bind to set the port number of the service to 9877, the IP address of any network card.
      • Call listen, change socket to passive connection socket,
      • Maintain the queue, and then you can receive the customer's connect.
      • Call accept. There is no connected socket at the time of the first call. It goes to sleep.
    • Working stage:
    • To create a subprocess:
      • Put accept in an infinite loop.
      • If accept returns success, it will fork a subprocess.
      • Processing the connected task in the child process, the parent process continues to wait for the next connection.
    • Subprocess work:
      • The descriptor created by socket needs to be closed in the child process, and the descriptor returned by connect needs to be closed in the parent process.
        • Because both descriptors will be copied to the child process when fork creates the process. If it does not shut down, the FIN bytes will not be sent when the child process exits because the parent process also has the connect descriptor open, and each connection will consume a descriptor resource that will never be released.
    • In str_echo, the server reads from the socket, blocks if there is no content, and writes back to the socket directly.
  • Client

    • Link phase:
      • Create socket,
      • Set the server IP and port.
      • Call connect to initiate the connection.
        • SYN bytes will be sent when connect is called.
        • After receiving the ACK from the server,
        • connect returns to the established state.
    • Working stage
      • Read a line of text from standard input
      • Write it to the socket
      • Read a line of text from the socket
      • Write to standard output

Normal startup of client and server

  • The client is normally blocked in fgets, waiting for user input.
  • After the user enters EOF, fgets returns NULL, and str_cli exits.
  • The client program calls exit to end the program.
    • exit first closes the open socket descriptor, (client single socket close)
    • Cause FIN to be sent to the socket, enter the FIN ﹣ wait ﹣ 1 state, (client sends FIN)
    • After receiving the ACK from the server, enter the fin ﹣ wait ﹣ 2 state, (server sends reply: ACK)
    • After receiving the FIN, send the ACK and enter the time ﹣ wait state (server sends: FIN, client replies: ACK)
    • Wait for 2MSL.
  • To view socket status when a client program runs:
$ netstat -a |grep 9877
tcp        0      0 *:9877                  *:*                     LISTEN     
tcp        0      0 localhost:36368         localhost:9877          ESTABLISHED
tcp        0      0 localhost:9877          localhost:36368         ESTABLISHED
  • To view the socket status after the client program terminates:
$ netstat -a |grep 9877
tcp        0      0 *:9877                  *:*                     LISTEN     
tcp        0      0 localhost:36368         localhost:9877          TIME_WAIT

Problem analysis:

Zombie process

  • Using ps to check the status of the program and find out the existence of zombie process
$ ps -o pid,ppid,stat,args
  PID  PPID STAT COMMAND
30143 30142 Ss   -bash
34810 30143 S    ./tcpserv01
34812 34810 Z    [tcpserv01] <defunct>
34813 30143 R+   ps -o pid,ppid,stat,args
  • Avoid zombie process
  1. Let the parent process call wait or waitpid.
    • The kernel will send a SIGCHLD signal to the parent process to inform the parent process that the child process has ended.
    • At this point, if the parent process sets the signal processing function, it can call wait or waitpid. in the signal processing function.
    • If more than one child process is created,
      • Then you need to invoke waitpid in a loop, and set the WNOHANG parameter.
      • Because a wait/waitpid only processes one zombie process, and it will hang when calling wait, which is not appropriate in the signal processing function.
      • If the parent process does not set the signal processing function, then it can call wait or waitpid when the parent process exits. Usually, the parent process exits soon, or zombie process will be generated.
  2. Let the init process handle the zombie process.
    • In this case:
      • The parent process does not process SIGCHLD signals, or there is no waitpid in the signal processing function.
      • A condition that does not exist until the parent process has ended.
    • At this time, init will become the parent process of the zombie process, so we don't have to worry about it.
      • In fact, this is mostly because the parent process forgot to handle it.
    • Here we can not process SIGCHLD signal, because this signal will not cause the program to end, as long as wait / waitpid is called after close in the parent process, it is OK.

Consider when slow system calls are interrupted

  • In order to illustrate this problem, we introduce signal processing function, in fact, signal processing is equivalent to a software interrupt, which may occur at any time, so we need to consider the situation of interrupt in the process of coding.
int main(int argc, char **argv){
	int listenfd, connfd;
	pid_t childpid;
	socklen_t clilen;
	struct sockaddr_incliaddr, servaddr;
	void sig_chld(int signo);
	Sigfunc * Signal(int signo, Sigfunc *func);
	
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
	Listen(listenfd, LISTENQ);
	Signal(SIGCHLD, sig_chld);

	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 */
	}
}

void sig_chld(int signo){
	pid_t pid;
	int stat;
	printf("enter sig_chld\n");
	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
	//while( (pid = wait(NULL)) > 0)
	printf("child %d terminated\n", pid);
	printf("quit sig_chld\n");
	return;
}
  • In the above example, it is not appropriate to call printf in interrupt handling function, because printf is a non reentrant function. If the program size is large and the process is long, strange errors may occur.

  • Signal is a package function written by the author in the book. It is implemented by the signature function. In the implementation code, you can set whether to set Sa "restart, which means whether to restart automatically when the system call is interrupted.

  • Because different UNIX systems may have different implementations, some of them will restart by default, some of them will not restart by default, so we can better control our own configuration. Of course, we need not directly configure signature to wrap it up.

  • For slow system calls such as accept, read, write, select, etc., we usually hope that they can continue to return to the state before the interrupt after being interrupted, because there will be no error, while for connect, we cannot restart after the interrupt, because the connection will definitely fail after the interrupt.

  • When the server is blocked in accept, if the process suddenly crashes,

    • When the subprocess exits:
      • Send FIN bytes to the socket, and the client will respond with an ACK after receiving.
      • At the same time, the kernel sends a SIGCHLD signal to the parent process, and the parent process calls sig_chil for processing, and returns to accept after processing.
    • Then the problem comes. If the automatic restart flag is not configured,
      • The accept call will fail with errno set to EINTR.
      • The right thing to do is exit the program, but obviously we don't want this result.
  • Solution:

    • When configuring the signal processing function, set act. SA | flags | = SA |, so that when the accept is returned by the interrupt, it can continue to block.
    • Modify the judgment condition of accept: when accept returns an error, we can judge whether errno is EINTR. If it is, we will restart accept manually.
      • Code: note that we call Accept instead of Accept.
      connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
      if(connfd < 0){
      	if (errno == EINTR){
      		continue;
      	} else {
      		err_sys("accept error");
      	}
      }
      

Server process terminated unexpectedly

  • This problem can also be tested with the above scenario. We can simulate it by kill ing the server subprocesses.
  • For clients
    • When the server terminates, it will send FIN bytes (indicating that the server is not sending content), and the client will automatically respond with ACK
    • And then the server was killed.
    • But the client does not know that the server process has been killed (it only received FIN, which does not mean it has been killed). Because the client is blocked by fgets at this time, it will not send FIN bytes to the server. At this time, the client thinks that the link is not closed, so it waits for the user to input characters from the standard input.
    • If the user does not input all the time, the program never knows that the server has been hung.
    • When the user enters some characters, the server will respond to an RST, and the customer knows that the server has been hung.
    • If the customer continues to send the content, SIGPIPE signal will be triggered (this situation is likely to happen, because the content sent by the customer to the server may be sent in several times. When the content is sent for the first time, it will be recycled to RST, and a lot of content may be sent during receiving RST).
  • How to solve:
    • The root cause of this problem lies in the client. It cannot only block fgets. It should pay attention to stdin and socket at the same time. Any exit should be known in time. So you can use select to manage these two descriptors.

Restrictions on sending data format

When sending strings, there is generally no problem. As long as different hosts support the same Chinese character encoding, there are many problems if sending binary, such as different host byte order, different CPU bits, different data types taking up different space and different parity, which is actually a binary compatibility problem, so compatibility is very difficult. Big.

Server crash or network disruption

TCP has a retransmission mechanism. When the network is not available, the client will continue to retransmit the unacknowledged packets until it gives up. It may take a long time here. Of course, we hope to know about the server crash as soon as possible. We can solve this problem by using the so ﹣ keepalive socket option.

I/O reuse: select and poll

  • Five I/O models available under UNIX:
    • Blocking I/O;
    • Non blocking I/O;
    • I/O reuse;
    • Signal driven I/O;
    • Asynchronous I/O.

I/O multiplexing uses polling to process multiple descriptors. When a file is ready, the process is notified.

  • Concerns
    • Applications of I/O multiplexing
    • Client and server programs with I/O reuse
  • Applications of I/O multiplexing
    • When a client processes multiple descriptors (usually interactive input and network sockets), I/O multiplexing must be used to even inform the user about the socket
    • If a TCP server has to handle both listening and connecting sockets, I/O multiplexing is usually used
    • If you want to deal with both TCP and UDP, I/O multiplexing is usually used.
    • If a server needs to handle multiple services or protocols, such as inet daemons, I/O multiplexing is generally used

select

  • int select(int maxfdp1,fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
    • timeout: tells the kernel how long it will take to wait for any of the specified descriptors to be ready
      struct timeval {
      	long tv_sec; /* seconds */
      	long tv_usec; /* microseconds ,Many UNIX up round 10ms integer times, plus scheduling delay, time is more inaccurate*/
      }
      
      • Indicates to wait forever: set null pointer and return only when descriptor is ready for I/O
      • Wait for a fixed time: specified by timeout
      • Do not wait at all: the timer value is set to 0, which is called poll ing
    • Example of using FD set variable (maxfdp1 is set to 6): notice the time value result parameter (return the position 1 that needs to be re interested in later)
      fd_set rset;
      FD_ZERO(&rset);
      FD_SET(1, &rset);
      FD_SET(4, &rset);
      FD_SET(5, &rset);
      
    • When the timer expires, 0 is returned, - 1 indicates an error
  • Conditions for descriptor ready
    • When a socket is ready to read:
      • Bytes in receive buffer > = current size of receive buffer low water mark (default 1, set by so \rcvlowat)
      • Read half closed (fin received) will not block and return 0
      • The number of connected listening socket is not 0, and accept is usually not blocked
      • There is a socket error to be handled, return - 1, error is set to specific error condition, which can be obtained and cleared by calling getsockopt through so error socket option.
    • A socket is ready to write
      • Number of bytes available in send buffer with connection socket or UDP socket > = current size of low water mark in send buffer (default 2048, so ﹐ sndlowat available)
      • Write half closed socket, write operation will generate a SIGPIPE signal
      • The socket of non blocking connect has established a connection, or connect failed
      • There is a socket error to be handled, return - 1, error is set to specific error condition, which can be obtained and cleared by calling getsockopt through so error socket option.
  • Mixing stdio and select is considered very error prone
    • There may be incomplete input lines in the readline buffer
    • There may also be one or more complete input lines

shutdown

  • int shutdown(int sockfd, int howto)
    • close() subtracts the reference count of the descriptor by 1, and shutdown directly triggers the termination of the normal connection sequence of TCP
    • shutdown tells the other party that I have nearly finished sending the data (the other party can still send it to me)
      • Shut? Rd: close the read half of the connection
        • You can set the second parameter to shut RD to prevent loopback replication
        • Turning off the so ﹐ useloback socket option also prevents loopback
      • Shut ﹣ WR: close the half of the connected write, also known as half closed
      • Shut ABCD rdwr: both read and write half of the connection are closed

TCP echo server program

  • Client program using selecet
    • Version 1: it calls Fets, Fputs, Readline and other functions with its own buffer. select can not see this, which will cause data in the buffer area to be less than consumption.
      void str_cli(FILE * fp, int sockfd){
        char sendline[MAXLINE], recvline[MAXLINE];
        int maxfdp1;
        fd_set rset;
        FD_ZERO(&rset);
        for( ; ; ){
          FD_SET(fileno(fp), &rset); // fileno converts a pointer to the socket of the file handle to a socket
          FD_SET(sockfd, &rset);
          maxfdp1 = max(fileno(fp), sockfd) + 1; // maxfdp must be MAX + 1
          Select(maxfdp1, &rset, NULL, NULL, NULL); // Detection readable
          if(FD_ISSET(sockfd, &rset)){ // Judge which one is readable
            if(Readline(sockfd, recvline, MAXLINE) == 0)
              err_quit("str_cli: server terminated prematurely");
            Fputs(recvline, stdout);
          }
          if(FD_ISSET(fileno(fp), &rset)){ // Judge which one is readable
            if(Fgets(sendline, MAXLINE, fp) == NULL)
              return ;
            Writen(sockfd, sendline, strlen(sendline));
          }
        }
      }
      
    • Version 2: use Read and Write to solve this problem, and use shutdown to close the connection instead of close. Fixed problems with batch input.
      void str_cli(FILE * fp, int sockfd){
        int maxfdp1, stdineof, n;
        fd_set rset;
        char buf[MAXLINE];
      
        stdineof = 0;
        FD_ZERO(&rset);
        for( ; ; ){
          if(stdineof == 0){ // Is a new flag initialized to 0. When the flag bit is 0, standard input detection is turned on.
            FD_SET(fileno(fp), &rset);
          }
          FD_SET(sockfd, &rset);
          maxfdp1 = max(fileno(fp), sockfd) + 1;
          Select(maxfdp1, &rset, NULL, NULL, NULL);
          if(FD_ISSET(sockfd, &rset)){
            if((n = Read(sockfd, buf, MAXLINE)) == 0){
              if(stdineof == 1)
                return ;
              else
                err_quit("str_cli: server terminated prematurely");
            }
            Write(fileno(stdout), buf, n);
          }
          if(FD_ISSET(fileno(fp), &rset)){
            if((n = Read(fileno(fp), buf, MAXLINE)) == 0){ // Read EOF flag, the file is empty
              stdineof = 1;// Close standard input 
              Shutdown(sockfd, SHUT_WR);// Turn off the read operation of socked
              FD_CLR(fileno(fp), &rset); // Delete input detection from detection bit
              continue;
            }
            Writen(sockfd, buf, n);
          }
        }
      }
      
  • Server program using select
    #include "unp.h"
    int main(int argc, char const *argv[]) {
      int i, maxi, maxfd, listenfd, connfd, sockfd;
      int nready, client[FD_SETSIZE];
      ssize_t n;
      fd_set rset, allset;
      char buf[MAXLINE];
      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_port = htons(SERV_PORT);
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
      Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
      Listen(listenfd, LISTENQ);
      maxfd = listenfd;
      maxi = -1;
      for (i = 0; i < FD_SETSIZE; i++){
        client[i] = -1;
      }
      FD_ZERO (&allset);
      FD_SET(listenfd, &allset);
      for( ; ; ){
        rset = allset;
        nready = Select(maxfd + 1, &rset, NULL, NULL, NULL);
        if(FD_ISSET(listenfd, &rset)){
          clilen = sizeof(cliaddr);
          connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
          for(i = 0; i < FD_SETSIZE; i++)
            if(client[i] < 0){
              client[i] = connfd;
              break;
            }
          if(i == FD_SETSIZE)
            err_quit("too many clients");
          FD_SET(connfd, &allset);
          if(connfd > maxfd)
            maxfd = connfd;
          if(i > maxi)
            maxi = i;
          if(--nready <= 0)
            continue;
        }
        for(i = 0; i <= maxi; i++){
          if((sockfd = client[i]) < 0)
            continue;
          if(FD_ISSET(sockfd, &rset)){
            if((n = Read(sockfd, buf, MAXLINE)) == 0){
                Close(sockfd);
                FD_CLR(sockfd, &allset);
                client[i] = -1;
            }
            else
              Writen(sockfd, buf, n);
          if(--nready <= 0)
            break;
          }
        }
      }
    }
    
    if(--nready <= 0)
        continue;
    if(--nready <= 0)
        break;
    //At the beginning, i was confused by these two pieces of code. The purpose of these two pieces of code is to improve efficiency and avoid the repeated cycle from i to maxi.
    

Using client array to store connection descriptors

  • poll
    • int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
    • The first parameter is a pointer to the first element of a structure array.
      • Each array element is a pollfd structure that specifies the conditions for testing a given descriptor fd.
      struct pollfd{
          int fd;
          short events;
          short revents;
      }
      
    • nfds: number of elements in the structure array
    • timeout: how long does it take to wait before returning
      • INFTIM: always wait

Echo server poll version:

  • Echo server poll version:
    #include "unp.h"
    #include <linux/fs.h>
    
    int main(int argc, char const *argv[]) {
      int i, maxi, listenfd, connfd, sockfd;
      int nready;
      ssize_t n;
      char buf[MAXLINE];
      socklen_t clilen;
      struct pollfd client[INR_OPEN_MAX];
      struct sockaddr_in cliaddr, servaddr;
    
      listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    
      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_port = htons(SERV_PORT);
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
      Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
    
      Listen(listenfd, LISTENQ);
      client[0].fd = listenfd;
      client[0].events = POLLRDNORM; // Ordinary readable
      for(i = 1; i < INR_OPEN_MAX; i++)
        client[i].fd = -1;
      maxi = 0;
      for( ; ; ){
        nready = Poll(client, maxi + 1, INFTIM);
        if(client[0].revents & POLLRDNORM){
          clilen = sizeof(cliaddr);
          connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);
          for(i = 1; i < INR_OPEN_MAX; i++){
            if(client[i].fd < 0){
              client[i].fd = connfd;
              break;
            }
          }
          if(i == INR_OPEN_MAX)
            err_quit("too many clients");
          client[i].events = POLLRDNORM;
          if(i > maxi)
            maxi = i;
          if(--nready <= 0)
            continue;
        }
        for(i = 1; i <= maxi; i++){
          if((sockfd = client[i].fd) < 0){
            continue;
          }
          if(client[i].revents & (POLLRDNORM | POLLERR)){
            if((n = read(sockfd, buf, MAXLINE)) < 0){
              if(errno == ECONNRESET){
                Close(sockfd);
                client[i].fd = -1;
              } else
                  err_sys("read error");
            } else if(n == 0){
                Close(sockfd);
                client[i].fd = -1;
            } else
                Writen(sockfd, buf, n);
          }
            if(--nready <= 0)
                break;
        }
      }
      return 0;
    }
    
  • Summarize the differences between poll and select:
    • poll does not need to + 1 the maximum descriptor
    • poll is faster in dealing with a large number of file descriptors, because select requires the kernel to check every bit in the fd_set corresponding to a large number of descriptors, which is time-consuming.
    • The number of file descriptors that select can monitor is fixed and relatively small (1024 or 2048). If you need to monitor larger file descriptors or less descriptors with sparse distribution, the efficiency will be very low. For poll, an array of a specific size can be created to hold the monitored descriptors, regardless of the size of the file descriptor value. Moreover, the number of file descriptors that poll can monitor is far greater than select.

Socket options

getsockopt and setsockopt functions

  • Socket only
  • Definition:
    #include <sys/socket.h>
    int getsockopt(int sockfd, int level, int optname, void *optname, void *optval, socklen_t *optlen);
    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    // All returned: success 0, error - 1
    
  • Parameters:
    • sockfd: an open socket descriptor
    • Level: (level) specifies the code of interpretation options in the system, or the general socket code, or the code specific to a protocol (IPV4, IPV6, TCP, SCTP)
    • optval: pointer to a variable,
      • setsockopt uses it to get new values.
      • getsockopt stores the current value of the acquired option in * optval
    • optlen: the length of optval
  • Socket options are roughly divided into two basic types:
    • Flag options: enable or disable binary options for a property
      • getsockopt:optval of 0 means forbidden, otherwise it means enabled
      • setsockopt:optval is 0 to disable
    • Value options: gets and returns specific value options that we can set or check
      • The value of the data type referred to is passed between the user process and the system

Socket state

  • accept does not return to the server's connected socket until the three handshakes are completed. To ensure that one of these socket options is set for the connected socket when the three handshakes are completed, you must first set the listening socket.

General socket options

  • SO_BROADCAST:

    • This option enables or disables the process to send broadcast (only supported by datagram socket, and on networks supporting broadcast messages, such as Ethernet and token ring network), which can prevent sending broadcast data when it is not set to broadcast: for example, the destination address of UDP sending is a broadcast address, but if this option is not set, EACCES error will be returned.
  • SO_DEBUG

    • Only supported by TCP, when the option is turned on, the kernel will reserve details for TCP to send and receive all packets on the socket, which can be viewed with trpt.
  • SO_DONTROUTE

    • Specifies that outgoing packets will bypass the normal routing mechanism of the underlying protocol, which is used to bypass the routing table to force packets to be sent out from a specific interface
  • So? Error

    • When an error occurs in a socket, set the so error variable of the socket to one of the Unix Exxx values, also known as pending error. You can notify the process immediately in one of the following two ways
      • When blocking in select, return to set one or two conditions in RW
      • Signal driven IO model:
        • SIGIO signals are generated to inform the process or process group.
          • The process gets the value of so error by accessing so error
        • The whole value returned by getsockopt() is the pending error.
        • Reset from kernel to 0 after processing
      • When the process calls read and no data is returned, if so error is not 0 (it should be blocked, but it is found that there is an error, it will return) - 1,error is set to the value of so error, and so error is set to 0.
      • When calling write, so error is not 0, return - 1,error is set to the value of so error, so error is set to 0
  • SO_KEEPALIVE

    • After setting the keep alive option, two hours later (no data during the period), TCP will automatically send the keep alive probe, which will lead to three situations.
      • Process is not notified with expected ACK response
      • In response to RST, the peer of the table has crashed and restarted, and the pending error of the socket is set to econreset.
      • There is no response. Eight detection segments are sent every 75s. After 11m15s, it is abandoned and the error with error is set to ETIMEOUT. If ICMP error is received, the corresponding error will be returned.
    • This is a good way to clean up half open connections to inaccessible customers
  • SO_LINGER

    • This option specifies how the close() function operates on the face-to-face connection protocol. It returns immediately by default. If there is data residue, it will try to send the data to the opposite end.
    • It is required to transfer to the kernel as follows:
    struct linger {
         int l_onoff;    /*0=off,l_linger Ignored, > nonzero = on*/
         int l_linger;   /*linger time*/
    }
    
    • linger=0: discard any data in the buffer and send RST to the opposite end. There is no quartered termination sequence, which can avoid the time ﹣ wait state of TCP.
      • Possible error: another avatar is created in 2MSL, and the old segment on the newly terminated connection is not correctly passed to the new avatar.
    • linger!=0, the kernel delays for some time when the socket is closed: the process will sleep until all data has been sent and confirmed or the delay time is up to
    • Socket is non blocking. EWOULDBLOCK error is returned when data is not sent before the delay time.
    • The successful return of close() can only indicate that the sent data and FIN have been confirmed by the opposite end, but it does not mean that the process has read. So it is better to use shutdown instead. Of course, it can also use application level ACK.
  • SO_OOBINLINE

    • The out of band data will be stored in the normal input queue (i.e. online storage). At this time, the MSG ﹣ OOB flag of the receiving function cannot be used to read out of band data.
  • So? Cvbuf and so? Sndbuf

    • The size of the free space in the socket receive buffer limits the size of the TCP notification peer window
    • Note the order: the window size option is obtained by SYN segmentation when establishing the connection, so the customer needs to be before connect, and the serv needs to be before listen.
    • According to the fast recovery algorithm, the buffer size should be at least four times the MSS value, preferably an even multiple
  • So ﹣ rcvlowat and so ﹣ sndlowat (low water mark)

    • Receive low water mark: select returns the amount of data needed to receive the buffer when it is readable. The default value of TCP/UDP and SCTP is 1.
    • Send buffer: select() returns the free space needed to send the buffer when it is writable. tcp is 2048 by default, and the number of bytes available in UDP's send buffer never changes (no copies are kept)
  • So? Rcvtimeo and so? Sndtimeo

    • Set the timeout value, which is 0 by default, that is, prohibit timeout
  • So? Reuseaddr and so? Reuseport (reuse address port)

    • When the listening server is terminated, the subprocess continues to process the connection. When the listening server is restarted, there will be an error, but when the so \reuseaddr is turned on, there will be no error.
    • So ﹣ reuseaddr allows multiple server instances of the same port (as long as different local IP addresses are available), and general configuration address bundling is generally put at the end.
    • So? Reuseaddr allows the same port to bundle different sockets of the same process
    • So? Reuseaddr allows UDP to be completely rebranded (in general) for multicast
  • SO_TYPE

    • This option returns the socket type. The return value is a value such as sock? Stream or sock? Dgram, which is usually used by processes that inherit the socket at startup.
  • SO_USELOOPBACK

    • Only used for AF? Route socket, which is on by default. When on, the corresponding socket receives a copy of any datagram sent on it.

IPv4 socket options

  • IP_HDRINCL
    • If it is set for the original IP socket, the header must be constructed by itself, with the following exceptions:
    • See Page168
  • IP_OPTIONS
    • Allow IP options to be set in IPv4 header
  • IP_RECVDSTADDR
    • When enabled, the destination address of the received UDP datagram is returned by recvmsg as an auxiliary function.
  • IP_RECVIF
    • Turn on the receive interface index that causes the received UDP datagram to be returned by the recvmsg function as an auxiliary function
  • IP_TOS
    • This socket option allows us to set the service type field in the IP header for TCP, UDP and SCTP.
  • IP_TTL
    • Used to set or get the default TTL value the system uses for unicast packets sent from a given socket

fcntl function

  • The fcntl() function (file control) can perform various descriptor control operations
  • int fcntl(int fd, int cmd, .../* int arg */);
  • Each type of descriptor has a set of file flags obtained by the f ﹣ getfl command or set by the f ﹣ setfl command. There are two types of descriptors that affect socket descriptors:
    • O? Nonblock
    • O'async (signal driven IO)
  • Set the writing method of non blocking IO correctly:
    int flag;
    /* Set a socket as nonblocking */
    if((flag=fcntl(fd, F_GETFL, 0)) < 0){    //You must get other file flags first
        err_sys("F_GETFL, error");
    }
    flag |=O_NONBLOCK;                       //Or operation, open non blocking flag
    if(fcntl(fd, F_SETFL, flags) <0 ){
        err_sys("F_SETFL error");
    }
    flag &=~O_NONBLOCK;                      //And operation, turn off non blocking flag
    if(fcntl(fd, F_SETFL, flags) <0 ){
        err_sys("F_SETFL error");
    }
    
  • If the parameter of F ﹣ setown is positive, it indicates the process ID of the received signal; if it is negative, it indicates the group ID of the signal.
  • F'getown is similar to the above
  • The socket created using the socket() function does not belong to a group. If a new socket is created from a listening socket, the Homegroup will inherit it.

Basic UDP socket programming

recvfrom() and sendto()

  • Definition:
    • ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flag, struct sockaddr *from, socklen_t *addrlen);
    • ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flag, const struct sockaddr *to, socklen_t addrlen);
  • Parameters:
    • The first three parameters are equivalent to read and write: descriptors, pointers to read / write buffers, and read / Write Bytes
    • buff: received content, can be 0
    • False: will be discussed later
    • from: refers to the set of cut-off address structure of the protocol address of the sender of the datagram filled in when the function returns. The return bytes are in addrlen.
    • To: parameter points to a socket address structure (including packet receiver protocol address: IP and Port). The length is specified by addrlen.
    • recvfrom is like the combination of accept and read
    • But there is an additional flag parameter in the middle, which will be introduced in Chapter 14 when discussing recv, send and recvmsg functions. This chapter is set to 0.

UDP echo server

  • UDP server is a typical iterator model. Most TCP servers are concurrent. For this socket, the UDP layer implicitly contains queued sending. Each UDP socket has a receive buffer, which implements FIFO mechanism.
  • Code
#include "unp.h"
void dg_echo(int , SA *, socklen_t);
int main(int argc, char const *argv[]) {
  int sockfd;
  struct sockaddr_in servaddr, cliaddr;

  sockfd = Socket(AF_INET, SOCK_DGRAM, 0); //First use datagram type
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(SERV_PORT);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  Bind(sockfd, (SA*)&servaddr, sizeof(servaddr));

  dg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr));
}

void dg_echo(int sockfd, SA * pcliaddr, socklen_t clilen){
  int n;
  socklen_t len;
  char msg[MAXLINE];
  for( ; ; ){
    len = clilen;
    n = Recvfrom(sockfd, msg, MAXLINE, 0, pcliaddr, &len); // Message received
    Sendto(sockfd, msg, n, 0, pcliaddr, len); // Send it back to him
  }
}

UDP echo client

  • Code
#include "unp.h"

void dg_cli(FILE *, int, const SA *, socklen_t);

int main(int argc, char const *argv[]) {
  if(argc != 2)
    err_quit("usage: ./udpcli01 <IPaddress>");

  int sockfd;
  struct sockaddr_in servaddr;

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(SERV_PORT);
  Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

  dg_cli(stdin, sockfd, (SA *)&servaddr, sizeof(servaddr));
}

void dg_cli(FILE * fp,int sockfd, const SA * servaddr, socklen_t servlen){
  char buf[MAXLINE];
  int n;
  socklen_t len = servlen;
  for( ; ; ){
    if(Fgets(buf, MAXLINE, fp) == NULL)
      return ;
    else
      Sendto(sockfd, buf, strlen(buf), 0, servaddr, servlen);
    if((n = Recvfrom(sockfd, buf, n, 0, NULL, NULL)) == 0){
      err_quit("connection close");
    }
    else if(n < 0)
      err_quit("Recvfrom error");
    buf[n] = 0;
    Fputs(buf, stdout);
  }
}

Disadvantages and improvement of UDP

  • Datagram lost:
    • The loss of datagram causes the client to block the recvfrom call, which can be solved by setting the timeout mechanism, which is discussed in section 14.2.
  • Verify the acknowledgement of the received response:
    • From a sockaddr * variable in the new malloc, obtain its value in Recvfrom, and compare it with the servaddr sent previously. The same is the required variable.
  • Server process is not running
    • The server host responds to the "port unreachable" ICMP message, but the process at this time cannot recognize the asynchronous error, which is always blocked by the recvfrom call.
    • We need to use connect to get the ICMP message.

Summary of UDP program examples

connect function of UDP

  • Here, connect() is different from TCP. Only when checking, there is an immediately known error. Record the peer IP address and port number, and then return it to the process. There are three main changes after connection:
    • Do not specify the destination address, that is, instead of SendTo (or null pointer for the sixth parameter), use write() or send().
    • Instead of using recvfrom(), read(),recv(),recvmsg(), which means that a UDP socket only exchanges data with an IP address (multicast, broadcast address).
    • Asynchronous errors will be returned to the process
  • Call connect() multiple times for a UDP socket to disconnect the socket or specify a new IP address and port number
    -The most convenient way to migrate is to reset an address structure and set its address cluster member to AF? Unspec.
  • If you want to send multiple datagrams to the same destination address, it's better to explicitly connect
  • Code
    • When connecting to a program without a running udp server, the connection will not make an error, but when sending data, it will return an ICMP error with an unreachable destination port, which is mapped to econnreused by the kernel, and will not be returned by the UnixWare kernel (page200).
    void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
    {
    	int		n;
    	char	sendline[MAXLINE], recvline[MAXLINE + 1];
    
    	Connect(sockfd, (SA *) pservaddr, servlen);
    
    	while (Fgets(sendline, MAXLINE, fp) != NULL) {
    
    		Write(sockfd, sendline, strlen(sendline));
    
    		n = Read(sockfd, recvline, MAXLINE);
    
    		recvline[n] = 0;	/* null terminate */
    		Fputs(recvline, stdout);
    	}
    }
    

Determination of UDP out of office interface

  • A side effect of the connect() function is that it can be used to determine the out of office interface for a specific destination (the local IP is obtained by searching the routing table for the destination address)
int main(int argc, char **argv){
	int					sockfd;
	socklen_t			len;
	struct sockaddr_in	cliaddr, servaddr;

	if (argc != 2)
		err_quit("usage: udpcli <IPaddress>");

	sockfd = Socket(AF_INET, SOCK_DGRAM, 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));

	len = sizeof(cliaddr);
	Getsockname(sockfd, (SA *) &cliaddr, &len);
	printf("local address %s\n", Sock_ntop((SA *) &cliaddr, len));

	exit(0);
}

TCP and UDP echo server programs using select()

  • Code
    #include	"unp.h"
    int main(int argc, char **argv){
    	int					listenfd, connfd, udpfd, nready, maxfdp1;
    	char				mesg[MAXLINE];
    	pid_t				childpid;
    	fd_set				rset;
    	ssize_t				n;
    	socklen_t			len;
    	const int			on = 1;
    	struct sockaddr_in	cliaddr, servaddr;
    	void				sig_chld(int);
    
    	/* 4create listening TCP socket */
    	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);
    
    	Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));  //Prevent existing connections on this port
    	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
    	Listen(listenfd, LISTENQ);
    
    	/* 4create UDP socket */
    	udpfd = Socket(AF_INET, SOCK_DGRAM, 0);
    
    	bzero(&servaddr, sizeof(servaddr));
    	servaddr.sin_family      = AF_INET;
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	servaddr.sin_port        = htons(SERV_PORT);
    
    	Bind(udpfd, (SA *) &servaddr, sizeof(servaddr));
    	/* end udpservselect01 */
    	/* include udpservselect02 */
    	Signal(SIGCHLD, sig_chld);	/* must call waitpid(), Because TCP connections are handled by subprocesses  */
    
    	FD_ZERO(&rset);
    	maxfdp1 = max(listenfd, udpfd) + 1;
    	for ( ; ; ) {
    		FD_SET(listenfd, &rset);
    		FD_SET(udpfd, &rset);
    		if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
    			if (errno == EINTR)
    				continue;		/* back to for() */
    			else
    				err_sys("select error");
    		}
    
    		if (FD_ISSET(listenfd, &rset)) {
    			len = sizeof(cliaddr);
    			connfd = Accept(listenfd, (SA *) &cliaddr, &len);
    	
    			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 */
    		}
    
    		if (FD_ISSET(udpfd, &rset)) {
    			len = sizeof(cliaddr);
    			n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len);
    
    			Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len);
    		}
    	}
    }
    /* end udpservselect02 */
    

Posted by TheKiller on Wed, 23 Oct 2019 23:08:31 -0700