1. Overview
TCP client/server model in this Chapter
2. TCP echo server program
1). main Function
#include "unp.h"int main(int argc, char **argv){ int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket (AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl (INADDR_ANY); servaddr.sin_port = htons (SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); 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 */ }}
2) str_echo Function
#include "unp.h"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");}
3. TCP bounce client program
1). main Function
#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);}
2). str_cli Function
#include "unp.h"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); }}
4. POSIX Signal Processing
Each signal has a processing method (disposition), also known as the action associated with the signal ). We call the sigaction function to set a signal processing method.
1). A function can be provided and called immediately when a signal occurs. This function is called the signal processing function (signal handler), and this behavior is called the capture signal. There are two signals that cannot capture SIGKILL and SIGSTOP, A function is called by a single parameter of the signal value and has no return value. The function prototype is
void handler(int signo);
The signal SIGIO, SIGPOLL, and SIGURG also require that the process to capture it have other actions.
2) You can set the signal processing method to SIG_IGN to ignore it, but SIGKILL and SIGSTOP cannot be ignored.
3) You can set the signal processing method to SIG_DFL to set the default Processing Method for it.
The function prototype hierarchy of the function signal is complex.
void ( * signal (int signo, void ( * func)(int) ) )(int);
Use typedef to simplify function prototype
Typedef void Sigfunc (int); // It indicates that the signal processing program has an integer parameter and has no return value.
In this way, the signal function prototype becomes
Sigfunc * signal (int signo, Sigfunc * func); // The second parameter and return value of this function point to the pointer of the signal processing function.
5. Process SIGCHLD Signal
The purpose of setting the Zombie state is to maintain the information of the child process so that the parent process can retrieve the information at a later time. If a process is terminated and a subroutine is in the zombie state, the parent process ID of all zombie sub-processes is set to 1 (init process ), the init process will act as the stepfather of these child processes and will be responsible for clearing them (that is, the init process will wait them, thus removing zombie processes ), some Unix systems output COMMAND columns to zombie processes as <defunct> (ps COMMAND output ).
In addition:
- If fork sub-processes, wait them to prevent them from becoming zombie processes.
- Capture SIGCHLD signals and wait sub-processes in the signal processing function. We should always call waitpid instead of wait to process Sub-processes.
- We should always check whether the slow system call returns an EINTR error. And decide whether to restart these system calls. (Some systems will automatically restart interrupted system calls ).
- Connect cannot be restarted. When the connect function is interrupted by signals and cannot be restarted automatically, we must call select to wait until the connection is complete.
6. wait and waitpid Functions
You can call the following two functions to process terminated sub-processes:
# Include <sys/wait. h> pid_t wait (int * statloc); pid_t waitpid (pid_t pid, int * statloc, int options); // return value: process ID returned successfully, 0 or-1 is returned for an error;
ID of the process to be waited for by the pid parameter. -1 indicates waiting for the first child process to end, options additional options, commonly used is WNOHANG, telling the kernel not to block when the child process does not end
Both the wait and waitpid functions return two values: the return value of the function is the ID of the process that stops the child process, and the termination status of the child process (an integer) is returned through the statloc pointer.
Difference between wait and waitpid: wait waits for the first child process to end. If there is no child process to end, wait will be blocked. Waitpid through parameter settings, You Can Do not block waitpid when no sub-process ends.
7. accept returns the previous connection termination
The implementation of Berkeley processes terminated connections in the kernel. POSIX requires that an ECONNABORTED errno be returned (see UNP3 for details)
8. Service Process Termination
If a connection is initiated to a server with a service process terminated, the server returns an RST signal.
Ps rst :( Reset the connection) is used to Reset the wrong connection caused by some reason, and to reject illegal data and requests.
9. SIGPIPE Signal
- When a process performs a write operation on a socket that has received the RST, the kernel sends a SIGPIPE signal to the process,
- The default behavior of the SIGPIPE signal is to terminate the process,
10. server host crash
If a connection is established between the client and the server, the server crashes at this time (when this standard is reached, the server's network cable can be unplugged. At this time, the server cannot send FIN datagram, different from shutdown)
- If the client sends data to the server because the server does not exist, the client cannot accept the ack message from the server to the client. At this time, the client establishes a TCP connection, the returned error is ETIMEDOUT.
- If a route in the middle determines that the target host is not reachable and responds to a "destination ETINEDOUT" ICMP message, the returned error is EHOSTUNREACH or ENETUNREACH.
11. Restart After the server host crashes
When a connection has been established between the client and the server, the server crashes and the original connection information with the client is lost when it is restarted, when the client sends data to the server (the client does not know, and the server has forgotten to shake hands three times), the server sends the RST datagram and ends sending the client.
12. server host Shutdown
When a Unix system is shut down, the init process usually sends a SIGTERM signal to all processes. Wait 5-20 seconds and then send a SIGKILL signal to all processes that are still running. This aims to clear and terminate the process for a short period of time.
13. TCP program example summary
Before communication, the client/server program must specify the socket pair: local IP address, local port number, foreign IP address, and foreign port.
The local IP address and local port number of the client program are usually allocated by the kernel. The local IP address and port number of the service program are specified by the bind function.
14. Data Format
Data transmission over the network has three potential problems:
(1) Different implementations store binary numbers in different formats. The most common is the large-end and small-end bytecode.
(2) Different implementations may differ in the storage of the same C data type. For example, in a 32-bit system, long is 32-bit, and in a 64-bit system, long is 64-bit.
(3) there are differences between different implementations of packaging structures, depending on the number of digits used by various data types and the alignment restrictions of machines. Therefore, it is wise to transmit binary structures through sockets.
Two common methods to solve the above problems:
(1) transmit all numerical data as text strings, provided that the customer and the server machine have the same character set.
(2) explicitly define the binary format (digits, large-end or small-end byte order) of supported data types, and transmit all data between the customer and the server in this format.