Socket programming practices in Linux (4) TCP server optimization and common functions
Concurrent zombie process Processing
When only one process is connected, we can use the following two methods to handle zombie processes:
1) Avoid botnets by ignoring SIGCHLD Signals
Add
Signal (SIGCHLD, SIG_IGN );
2) Solve botnets through the wait/waitpid Method
signal(SIGCHLD,onSignalCatch); void onSignalCatch(int signalNumber) { wait(NULL); } What if multiple clients in the multi-process state are closed at the same time?
We can test it using the following client code:
/** Test code implemented by the client **/int main () {int sockfd [50]; for (int I = 0; I <50; ++ I) {if (sockfd [I] = socket (AF_INET, SOCK_STREAM, 0) =-1) err_exit ("socket error"); struct sockaddr_in serverAddr; serverAddr. sin_family = AF_INET; serverAddr. sin_port = htons (8001); serverAddr. sin_addr.s_addr = inet_addr ("127.0.0.1"); if (connect (sockfd [I], (const struct sockaddr *) & serverAddr, sizeof (serverAddr) =-1) err_exit ("connect error");} sleep (20 );}At this time, because the signal arrives at the same time and the SIGCHLD is an unreliable signal, queuing is not supported and a considerable number of zombie processes will be left behind.
Solution:
The cyclic waitpid function can be used to process all zombie processes left by sub-processes.
Void sigHandler (int signo) {while (waitpid (-1, NULL, WNOHANG)> 0) ;}// pid =-1 wait for any sub-process, equivalent to wait (). // WNOHANG if the child process specified by the pid is not completed, the waitpid () function returns 0 and does not wait. If the process ends, the ID of the sub-process is returned.
Address query API
# Include
Int getsockname (int sockfd, struct sockaddr * addr, socklen_t * addrlen); // get the local addr structure int getpeername (int sockfd, struct sockaddr * addr, socklen_t * addrlen ); // get the addr structure int gethostname (char * name, size_t len); int sethostname (const char * name, size_t len); # include
Extern int h_errno; struct hostent * gethostbyname (const char * name); # include
/* For AF_INET */struct hostent * gethostbyaddr (const void * addr, socklen_t len, int type); struct hostent * gethostent (void );
// Hostent struct hostent {char * h_name;/* official name of host */char ** h_aliases;/* alias list */int h_addrtype; /* host address type */int h_length;/* length of address */char ** h_addr_list; /* list of addresses */} # define h_addr h_addr_list [0]/* for backward compatibility */
The call time of these two functions is very important; otherwise, the correct address and port cannot be obtained:
TCP
For the server, you can call getsockname after bind to obtain the local address and port, although this does not make much sense. Getpeername is called only after the link is established. Otherwise, the address and port of the Peer cannot be obtained correctly. Therefore, the parameter description is generally a link description rather than a listener interface description.
For the client, the kernel does not allocate IP addresses and ports when the socket is called. Calling getsockname does not obtain the correct port and address (of course, if the link is not established, it is impossible to call getpeername ), of course, if bind is called, you can use getsockname. To access the peer address correctly (this function is generally not required by the client), the client address and port must be specified after the link is established and after the same link is established, this is the time to call getsockname.
UDP
UDP is divided into two types: link and no link (UDP and connect can find relevant content)
A udp without link cannot call getpeername, but can call getsockname. Like TCP, its address and port are not specified when the socket is called, but after the sendto function is called for the first time.
The connected UDP functions can be used after connect is called (similarly, getpeername does not make much sense. If you do not know the address and port of the other party, you cannot call connect ).
Obtain all IP addresses of the local machine:
/** Get local IP list **/int gethostip (char * ip) {struct hostent * hp = gethostent (); if (hp = NULL) return-1; strcpy (ip, inet_ntoa (* (struct in_addr *) hp-> h_addr); return 0 ;}int main () {char host [128] = {0 }; if (gethostname (host, sizeof (host) =-1) err_exit ("gethostname error"); cout <"host-name:"
TCP 11 statuses
1. When the client and server are connected, both parties are in the ESTABLISHED state.
2. For TIME_WAIT status, see http://www.mamicode.com/info-detail-190400.html
3. 11th statuses of TCP/IP protocol: the figure contains only 10 statuses, and there is also a CLOSING status.
Reasons for CLOSING status:
When both the Server and Client close (Both ends call close at the same time and send a FIN packet to the peer end at the same time), the closing status is generated and both ends enter the TIME_WAIT status. (Because either party will switch to the TIME_WAIT status, and both parties will also switch to the TIME_WAIT status)
SIGPIPE Signal
Writing to a received FIN is allowed. Receiving FIN only means that the other party does not send data. However, if writing continues after receiving the RST segment, when you call write, the SIGPIPE signal is generated. We usually ignore the processing of this signal.
Signal (SIGPIPE, SIG_IGN );
SIGPIPE, although I have received FIN, but I can also send data to the other party; if the other party does not exist, TCP will reset and the TCP protocol stack will send the RST segment. After receiving the RST, write will generate the SIGPIPE signal.
In fact, it is easy to understand: TCP can be regarded as a full-duplex pipeline, and the Reading end signal does not exist. If you write the pipeline again, the SIGIPPE signal will be generated, this signal can be ignored during processing. In fact, it is based on the pipeline rules.
During the test, each message sent by the Client is sent twice. When the Server is closed, the Server sends a FIN Shard to the Client. After the first message is sent, the Server sends an RST segment to the Client. When the second message is sent (calling write), The SIGPIPE signal is generated.
Differences between close and shutdown Functions
#include
int close(int fd); #include
int shutdown(int sockfd, int how);
How parameter of shutdown |
SHUT_RD |
Close the read end |
SHUT_WR |
Disable the write end |
SHUT_RDWR |
Read/write disabled |
Close terminates two data transmission directions. shutdown can terminate data transmission in one direction or terminate data transmission in another direction.
Shutdowm how = 1 ensures that the Peer receives an EOF character, regardless of whether other processes have enabled the socket.
However, close cannot be guaranteed until the socket reference count is reduced to 0. That is to say, all processes have closed the socket.
Calling the close function may result in data loss when the full-duplex pipeline has not been shot back to the client. For example
FIN D C B
A B C D ---- Lost, closed close
The exact meaning of Close is that the FIN is sent only when the socket reference count is reduced to 0.
Int conn; conn = accept (sock, NULL, NULL); pid_t pid = fork (); if (pid =-1) ERR_EXIT ("fork "); if (pid = 0) {close (sock); // communication close (conn ); // at this time, the FIN segment will be sent to the other party (because the conn reference count is reduced to 0)} else if (pid> 0) close (conn ); // The FIN segment is not sent to the client, but the reference count of the socket is reduced by 1.