TCP SYN、ACK 重传

TCP SYN、ACK 重传

以下网络跟踪记录在运行 Debian Linux 的 Raspberry PI 上。由于某种原因,Raspberry 似乎没有看到数据包#36995;它只是愚蠢地重复他的 SYN,ACK 直到达到 tcp_synack_retries 限制。

您知道会出现什么问题吗?这是我们在这两个设备之间大多数数据传输中观察到的模式。

我们已经尝试将内核从 3.18.11 更新到 4.1.20+。端口 44269 后面的服务是一个运行在 Oracle JRE 1.8.0-b132 上的 Java 程序(摘录如下)。

在 CloudShark 上查看:https://www.cloudshark.org/captures/9a562c79855a

No.     Time           Source                Destination           Protocol Length Info
  36988 0.000000000    192.168.1.150         192.168.1.200         TCP      66     62935 > 44269 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=2 SACK_PERM=1
  36989 0.000302000    192.168.1.200         192.168.1.150         TCP      66     44269 > 62935 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1 WS=64
  36991 0.001051000    192.168.1.150         192.168.1.200         TCP      60     62935 > 44269 [ACK] Seq=1 Ack=1 Win=65700 Len=0
  36995 0.046655000    192.168.1.150         192.168.1.200         TCP      425    62935 > 44269 [PSH, ACK] Seq=1 Ack=1 Win=65700 Len=371
  37051 0.942187000    192.168.1.200         192.168.1.150         TCP      66     44269 > 62935 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1 WS=64
  37052 0.001155000    192.168.1.150         192.168.1.200         TCP      60     [TCP Dup ACK 36995#1] 62935 > 44269 [ACK] Seq=372 Ack=1 Win=65700 Len=0
  37183 1.998841000    192.168.1.200         192.168.1.150         TCP      66     44269 > 62935 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1 WS=64
  37184 0.001005000    192.168.1.150         192.168.1.200         TCP      60     [TCP Dup ACK 36995#2] 62935 > 44269 [ACK] Seq=372 Ack=1 Win=65700 Len=0
  37188 0.054728000    192.168.1.150         192.168.1.200         TCP      425    [TCP Retransmission] 62935 > 44269 [PSH, ACK] Seq=1 Ack=1 Win=65700 Len=371
  37299 1.756498000    192.168.1.150         192.168.1.200         TCP      60     62935 > 44269 [FIN, ACK] Seq=372 Ack=1 Win=65700 Len=0
  37429 2.187771000    192.168.1.200         192.168.1.150         TCP      66     44269 > 62935 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1 WS=64
  37430 0.001090000    192.168.1.150         192.168.1.200         TCP      60     [TCP Dup ACK 37299#1] 62935 > 44269 [ACK] Seq=373 Ack=1 Win=65700 Len=0
  37579 2.062723000    192.168.1.150         192.168.1.200         TCP      425    [TCP Retransmission] 62935 > 44269 [FIN, PSH, ACK] Seq=1 Ack=1 Win=65700 Len=371
  37964 5.936190000    192.168.1.200         192.168.1.150         TCP      66     44269 > 62935 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1 WS=64
  37965 0.001178000    192.168.1.150         192.168.1.200         TCP      60     [TCP Dup ACK 37579#1] 62935 > 44269 [ACK] Seq=373 Ack=1 Win=65700 Len=0
  38357 6.184544000    192.168.1.150         192.168.1.200         TCP      425    [TCP Retransmission] 62935 > 44269 [FIN, PSH, ACK] Seq=1 Ack=1 Win=65700 Len=371
  39002 9.814283000    192.168.1.200         192.168.1.150         TCP      66     44269 > 62935 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1 WS=64
  39003 0.001056000    192.168.1.150         192.168.1.200         TCP      60     [TCP Dup ACK 38357#1] 62935 > 44269 [ACK] Seq=373 Ack=1 Win=65700 Len=0
  39935 14.424503000   192.168.1.150         192.168.1.200         TCP      425    [TCP Retransmission] 62935 > 44269 [FIN, PSH, ACK] Seq=1 Ack=1 Win=65700 Len=371
  43097 48.376598000   192.168.1.150         192.168.1.200         TCP      425    [TCP Retransmission] 62935 > 44269 [FIN, PSH, ACK] Seq=1 Ack=1 Win=65700 Len=371
  43098 0.000295000    192.168.1.200         192.168.1.150         TCP      54     44269 > 62935 [RST] Seq=1 Win=0 Len=0

Java服务器:

@Override
public void run()
{
    LOG.info("Opening listen socket on port " + port);
    try (ServerSocket serverSocket = new ServerSocket(port))
    {
        while (true)
        {
            Socket socket;
            try
            {
                LOG.debug("Listening on port {} for a client to connect...", port);
                socket = serverSocket.accept();
                LOG.debug("Client connected! Creating worker-thread for " + socket.getInetAddress().getHostName() + ":"
                        + socket.getPort());
                new WorkerThread(socket).start();
            }
            catch (IOException e)
            {
                LOG.error("Failed to listen for a connection", e);
                continue;
            }
        }
    }
    catch (IOException e)
    {
        LOG.error("Failed to open listen socket", e);
        LOG.info("----------SOFTWARE TERMINATED----------");
        System.exit(1);
    }
}

编辑1: 我注意到,netstat -s监听队列的溢出数量非常多:

netstat -s | grep -i list
    226094 times the listen queue of a socket overflowed
    226094 SYNs to LISTEN sockets dropped

这让我检查了积压的大小,这是128( cat /proc/sys/net/ipv4/tcp_max_syn_backlog)。我将其增加到2048,但这并没有真正解决问题。

我发现另一篇文章(#646604 on serverfault,由于这里的声誉太低,无法链接它)描述了一个稍微不同的问题,但它帮助我找到了 Linux 内核中的负责部分:lxr.free-electrons.com /source/net/ipv4/tcp_ipv4.c#L1274

1274         if (sk_acceptq_is_full(sk))
1275                 goto exit_overflow;

1346 exit_overflow:
1347         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
1348 exit_nonewsk:
1349         dst_release(dst);
1350 exit:
1351         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
1352         return NULL;

我在这里看到,当应用程序的接受队列溢出时,两个计数器将同时计数。因此,我现在将重点转向 Java 应用程序,看看是否存在限制/瓶颈。

编辑2: 进一步的调查让我看到这篇文章很好地解释了这种现象:http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

简而言之,Linux 有两个队列,在应用程序通过 accept() 调用获取新连接之前保存它们:

  • SYN队列,其长度由net.ipv4.tcp_max_syn_backlog定义
  • 接受队列,其长度由listen()调用中的backlog参数决定

后者在我的情况下已经溢出了。 Java 将listen() 调用封装在其ServerSocket() 实现中,并将backlog 设置为固定大小50。这可以通过以下方式查看ss -l

State      Recv-Q Send-Q      Local Address:Port          Peer Address:Port
LISTEN     51     50                     :::44269                   :::*

现在的问题是,为什么 Java 清空接受队列的速度不够快?

相关内容