Below we use the simplest one-to-one client server model to reproduce some of the problems encountered in programming:
When writing a socket program, you may encounter many inexplicable problems. For example, the Common Errors returned by the BIND function are:Eaddrinuse
Use the following program to reproduce the status:
Client:
Int main (INT argc, const char * argv []) {struct sockaddr_in serveradd; bzero (& serveradd, sizeof (serveradd); serveradd. sin_family = af_inet; serveradd. sin_addr.s_addr = inet_addr (serv_addr); serveradd. sin_port = htons (serv_port); int connfd = socket (af_inet, sock_stream, 0); int connresult = connect (connfd, (struct sockaddr *) & serveradd, sizeof (serveradd )); if (connresult <0) {printf ("Connection Failed \ n"); close (connfd); Return-1 ;}ssize_t writelen; ssize_t readlen; char recvmsg [65535] = {0}; char sendmsg [20] = "I am client"; writelen = write (connfd, sendmsg, sizeof (sendmsg )); if (writelen <0) {printf ("failed to send \ n"); close (connfd); Return-1 ;}else {printf ("sent successfully \ n ");} while (1) {// sleep (1); readlen = read (connfd, recvmsg, sizeof (recvmsg); If (readlen <0) {printf ("read failed \ n"); close (connfd); Return-1;} If (readlen = 0) {printf ("Server Disabled \ n "); close (connfd); Return-1;} printf ("server said: % s \ n", recvmsg);} Close (connfd); Return 0 ;}
Server:
Int main (INT argc, const char * argv []) {struct sockaddr_in serveradd; struct sockaddr_in clientadd; bzero (& serveradd, sizeof (serveradd); serveradd. sin_family = af_inet; serveradd. sin_addr.s_addr = htonl (inaddr_any); serveradd. sin_port = htons (serv_port); socklen_t clientaddrlen; int listenfd = socket (af_inet, sock_stream, 0); If (listenfd <0) {printf ("failed to create socket \ n "); close (listenfd); Return-1;} int bindresult = BIND (listenfd, (struct sockaddr *) & serveradd, sizeof (serveradd); If (bindresult <0) {close (listenfd); printf ("failed to bind port, errno = % d \ n", errno); Return-1 ;} else {printf ("port bound successfully \ n");} Listen (listenfd, 20); int connfd; unsigned char recvmsg [65535]; char replymsg [20] = "I am server"; clientaddrlen = sizeof (clientadd); connfd = accept (listenfd, (struct sockaddr *) & clientadd, & clientaddrlen ); if (connfd <0) {close (listenfd); printf ("Connection Failed \ n"); Return-1 ;}else {printf ("connection successful \ n ");} ssize_t readlen = read (connfd, recvmsg, sizeof (recvmsg); printf ("readlen: % LD \ n", readlen); If (readlen <0) {printf ("read failed \ n"); Return-1;} else if (readlen = 0) {printf ("read completed \ n"); close (listenfd ); return 0;} replymsg [readlen] = '\ 0'; printf ("client said: % s \ n", replymsg); While (1) {write (connfd, replymsg, sizeof (replymsg);} Close (connfd); Return 0 ;}
First run the server program, then run the client, and then shut down the server, and then immediately open the server, the following information will be printed: 48 corresponding to the eaddrinuse error code
Failed to bind port,Errno=48
Here is a question:
When a Unix process ends voluntarily (calling exit or returning from the main function) or involuntarily (receiving a signal to terminate the process), all opened descriptors are disabled, this will also cause a fin to be sent on any TCP connection that is still open.
Obviously, the server has been closed. Why does port binding fail? The following are the four sub-sections for TCP connection termination:
In some cases, the fin of the first Shard is sent along with the data. In addition, the second and third shards may be merged into one shard;
Here, our server is the end of the active shutdown. When the active sending of the fin Shard is waiting for confirmation, the status changes to fin_wait_1. After receiving the confirmation, the status changes to fin_wait_2, after receiving the fin segments from the client, the status changes to time_wait,HereTime_wait status changes to the closed status only after 2msl. Therefore, when we immediately start the server, the previous connection is not in the closed status, therefore, the binding fails;Later I will explain why the time_wait status exists;
Here, the client is the end that is passively closed. After receiving the fin from the server, the status changes to close_wait. At this time, the read method returns 0, and then sends a confirmation to the first shard, at this time, the client calls the close method to send the fin sub-section to the server to enter the last_ack status. Wait for the confirmation to arrive. After receiving the confirmation, the connection status changes to closed;
Time_wait status: the duration of staying in this status is twice that of maximum segment life time (MSL. MSL is the maximum time that any IP datagram can survive on the Internet. The maximum value is 255. This is a hop count limit, not a real time limit;
Time_wait status reasons:
Reliable termination of TCP full-duplex connection: the ACK may have to be re-transmitted. The time_wait is closed. If the 2msl of time_wait is not directly closed, if the last Ack is lost, the server will not resend the ACK. If the server cannot receive the ACK, it will resend the final fin. when the client is already closed, it will respond to an rst, this rst will be interpreted as an error by the server.
Allowed repeated segments disappear in the Network: each time a TCP connection is established successfully, the old repeated groups from the previous embodiment of the connection have disappeared in the network, this will not be misunderstood as a new connected group.
There is another situation: Open the sleep in the while client (or block the code below the client), then run the server program, and then run the client, after the server is closed, the server is immediately restarted and the binding fails. The status is a little different from the previous one.
If (readlen = 0) {printf ("server closed \ n"); close (connfd); Return-1 ;}
We printed the following information from the terminal. At this time, the server is in the fin_wait_2 state. As mentioned above, because the client has not closed the connection, the third fin Shard is not sent, at this time, the client is in the close_wait status because it has received the fin segments from the server;
Wanglijuntekimac-mini :~ Wanglijun $ netstat-an | grep 8000
Tcp4 0 0 192.168.1.103.8000 192.168.1.103.49632 fin_wait_2
Tcp4 290960 0 192.168.1.103.49632 192.168.1.103.8000 close_wait
Solution:
Here is a so_reuseaddr socket option, which can solve the above problem after it is enabled. we add the following setting code before band:
<span style="font-size:12px;">int yes = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));</span>
When the server restarts the listener, attempts to bind the port on the existing connection will fail (in another case, the child process derived from earlier may still process the connection) if the so_reuseaddr socket option is set, the BIND succeeds. All TCP servers should specify the so_reuseaddr socket option to allow the server to be restarted in this case; so_reuseaddr allows multiple instances of the same server to be started on the same port, as long as each instance is bound with a different local IP address;
For TCP, it is absolutely impossible to start multiple servers bound with the same IP address and the same port number.
Refer:
UNIX Network programmingvolume 1, third edition: thesockets networking API
Network Programming socket: Explanation of TCP time_wait status