次のネットワークトレースは、Debian Linuxを実行しているRaspberry PIに記録されました。何らかの理由でRaspberryはパケット#36995を見ていないようです。 tcp_synack_retries 制限に達するまで愚かなことに SYN,ACK を繰り返します。
何が間違っているのか知っていますか?これは、2 つのデバイス間のほとんどのデータ転送で観測されるパターンです。
カーネルを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に増えましたが、実際に問題は解決されませんでした。
わずかに異なる問題を説明する別の投稿(serverfaultの#646604、ここで評判が低すぎるためリンクできません)を見つけましたが、Linuxカーネルで責任ある部分を見つけるのに役立ちました:lxr.free-electrons com /。ソース/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;
ここでは、アプリケーションの承認キューがオーバーフローすると、2つのカウンタが同時に計算されることがわかります。したがって、制限/ボトルネックがあるかどうかを確認するためにJavaアプリケーションに焦点を当てています。
編集2: さらなる調査により、この現象を非常によく説明する次の記事が見つかりました。http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html
簡単に言うと、Linuxには、アプリケーションがaccept()呼び出しを介して新しい接続を取得する前に新しい接続を維持する2つのキューがあります。
- SYN キューの長さは net.ipv4.tcp_max_syn_backlog によって定義されます。
- Listen() 呼び出しの backlog パラメーターによって長さが決定されるキューの承認
私の場合、後者があふれていました。 Java は ServerSocket() 実装で Listen() 呼び出しをカプセル化し、バックログを固定サイズ 50 に設定します。これは次のようになりますss -l
。
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 51 50 :::44269 :::*
今の問題は、なぜJavaが承認キューを十分に早く消去できないのかということです。