Solution to invisible TCP packet loss on Linux
Author: miles (http://blog.csdn.net/yanghehong)
Some netizens encountered the following problems when working on a Linux client:
In Linux, we develop a network client program (the server cannot be modified) and constantly send small packages (usually dozens of bytes) to the server from time to time. This is now the case: most packets are sent normally, but when the wireless modem is disconnected, if there is a packet at this time, it may be lost, but it is displayed in the program that the packet has been sent successfully, resulting in packet loss.
The reason is as follows:
1: The main program creates socket, TCP/IP mode, and uses stream mode
2: The main program calls write, writes the packet to the system's socket buffer, and returns a successful write. because the number of bytes is small, it generally returns a successful write immediately!
3: the Linux TCP/IP protocol stack sends Socket buffer data to the server.
If the wireless modem is disconnected in step 3 after Step 2 is completed, the main program may think that the message has been sent successfully, but the server cannot receive the message.
We searched for a lot of information on the network, but did not find the Final Solution. Generally, we wanted to modify the Protocol and add ack to the packet, but we could not control the server here.
We also tried to control keepalive and nodelay, but it still didn't work:
// Set keepalive and nodelay for sock_cli
Len = sizeof (unsigned INT );
Setsockopt (sock_cli, sol_socket, so_keepalive, & optval, Len); // use keepalive
Setsockopt (sock_cli, ipproto_tcp, tcp_nodelay, & optval, Len); // disable the Nagle Algorithm
How can this problem be solved?
For Windows, you can set the kernel sending buffer to 0, that is, the socket so_sndbuf option. Then, the client will return success only when the server TCP receives the data and ack it.
However, this so_sndbuf method does not work in Linux. Linux does not allow sending buffer to be set to 0.
The code in the Linux kernel is as follows:
Socket. c
Int sock_setsockopt (struct socket * sock, int level, int optname, <br/> char _ User * optval, int optlen) <br/>{< br/> case so_sndbuf: <br/>/* Don't error on this BSD doesn't and if you think <br/> about it this is right. otherwise apps have to <br/> play 'Guess the biggest size 'games. rcvbuf/sndbuf <br/> are treated in BSD as hints */</P> <p> If (Val> sysctl_wmem_max) <br/> val = sysctl_wmem_max; <br/> set_sndbuf: <br/> SK-> sk_userlocks | = sock_sndbuf_lock; <br/> If (Val * 2) <sock_min_sndbuf) <br/> SK-> sk_sndbuf = sock_min_sndbuf; <br/> else <br/> SK-> sk_sndbuf = Val * 2; </P> <p>
As you can see, the socket sending buffer sk_sndbuf won't let you set too large or too small. The value you provided is too small (including 0). sk_sndbuf will be set to the default value sock_min_sndbuf, that is, 2048.
The above methods (including methods already tried by netizens) are all trying to send data immediately. If you want to complete sending when the network is disconnected, all the results will fail. If many methods of the same type do not work, you can try another method.
This is another way of saying that when the wireless modem is disconnected, the application misjudges that the data cannot be sent successfully.
Since sending failed (some failed to be sent) is inevitable, acknowledge this failure. In this case, the client needs to do one more step to find out how much remains.
Use the Linux API
IOCTL (tcp_socket, siocoutq, & value );
You can obtain the number of data that has not been sent in the TCP socket sending queue.
If the value is not 0, it indicates that the last message was not sent successfully, even though the buffer was written successfully. In this case, the client program shows failure.