![TCP SYN, ACK 재전송](https://linux55.com/image/86537/TCP%20SYN%2C%20ACK%20%EC%9E%AC%EC%A0%84%EC%86%A1.png)
다음 네트워크 추적은 Debian Linux를 실행하는 Raspberry PI에 기록되었습니다. 어떤 이유로 Raspberry는 패킷 #36995를 보지 못하는 것 같습니다. tcp_synack_retries 제한에 도달할 때까지 어리석게도 SYN,ACK를 반복합니다.
무엇이 잘못될 수 있는지 아시나요? 이는 두 장치 간의 대부분의 데이터 전송에서 관찰되는 패턴입니다.
커널을 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
자바 서버:
@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;
여기에서는 애플리케이션의 승인 대기열이 오버플로되면 두 카운터가 동시에 계산된다는 것을 알 수 있습니다. 따라서 이제 제한 사항/병목 현상이 있는지 확인하기 위해 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는 ServerSocket() 구현에서 Listen() 호출을 캡슐화하고 백로그를 고정 크기 50으로 설정합니다. 이는 다음을 통해 볼 수 있습니다 ss -l
.
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 51 50 :::44269 :::*
이제 문제는 왜 Java가 승인 대기열을 충분히 빨리 지우지 못하는가입니다.