TCP 连接挂在 SYN_SENT 上

问题描述 投票:0回答:1

考虑以下客户端和服务器组件:

import java.io.InputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class client {
    public static void main(String[] args) throws IOException {
        while (true) {
            URL url = new URL("http://localhost:8000");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            int statusCode = connection.getResponseCode();
            System.out.println("Status Code: " + statusCode);
            connection.disconnect();
        }
    }
}
import java.io.OutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000);
        while (true) {
            Socket clientSocket = serverSocket.accept();
            OutputStream outputStream = clientSocket.getOutputStream();
            outputStream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes());
            outputStream.flush();
            clientSocket.close();
        }
    }
}

在服务器运行时运行客户端时,您很快就会看到客户端开始在 TCP 级别上挂起

SYN_SENT
(总共大约 30 秒):

$ watch -n 0.1 "ss -on state syn-sent '( dport = :8000 )'"
Every 0.1s: ss -on state syn-sent '( dport = :8000 )'               myhost: Tue Jul 16 04:08:52 2024

Netid Recv-Q Send-Q      Local Address:Port        Peer Address:Port           Process
tcp   0      1           [::ffff:127.0.0.1]:60418  [::ffff:127.0.0.1]:8000     timer:(on,3.731ms,2)
$ pkill -3 java
# Stack trace of client's main thread while hanging outputted in Java terminal...
"main" #1 prio=5 os_prio=0 cpu=2429.68ms elapsed=40.96s tid=0x000079e6c40266c0 nid=0x18a1c6 runnable  [0x000079e6cb9fd000]
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.Net.connect0([email protected]/Native Method)
    at sun.nio.ch.Net.connect([email protected]/Net.java:579)
    at sun.nio.ch.Net.connect([email protected]/Net.java:568)
    at sun.nio.ch.NioSocketImpl.connect([email protected]/NioSocketImpl.java:593)
    at java.net.Socket.connect([email protected]/Socket.java:633)
    at java.net.Socket.connect([email protected]/Socket.java:583)
    at sun.net.NetworkClient.doConnect([email protected]/NetworkClient.java:183)
    at sun.net.www.http.HttpClient.openServer([email protected]/HttpClient.java:533)
    at sun.net.www.http.HttpClient.openServer([email protected]/HttpClient.java:638)
    at sun.net.www.http.HttpClient.<init>([email protected]/HttpClient.java:281)
    at sun.net.www.http.HttpClient.New([email protected]/HttpClient.java:386)
    at sun.net.www.http.HttpClient.New([email protected]/HttpClient.java:422)
    at sun.net.www.protocol.http.HttpURLConnection.setNewClient([email protected]/HttpURLConnection.java:831)
    at sun.net.www.protocol.http.HttpURLConnection.setNewClient([email protected]/HttpURLConnection.java:819)
    at sun.net.www.protocol.http.HttpURLConnection.writeRequests([email protected]/HttpURLConnection.java:759)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0([email protected]/HttpURLConnection.java:1708)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream([email protected]/HttpURLConnection.java:1611)
    at java.net.HttpURLConnection.getResponseCode([email protected]/HttpURLConnection.java:529)
    at client.main(client.java:13)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0([email protected]/Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke([email protected]/NativeMethodAccessorImpl.java:77)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke([email protected]/DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke([email protected]/Method.java:568)
    at com.sun.tools.javac.launcher.Main.execute([email protected]/Main.java:419)
    at com.sun.tools.javac.launcher.Main.run([email protected]/Main.java:192)
    at com.sun.tools.javac.launcher.Main.main([email protected]/Main.java:132)

我正在构建一个Java应用程序(当我遇到这个问题时,我尝试了10毫秒的轮询间隔 - 30毫秒似乎对我有用),我需要在一段时间内像这样快速发送HTTP请求(我无法使用网络套接字)。所以,我的问题是为什么会发生这种挂断以及如何修复它?

到目前为止,我修复此问题的最佳尝试是增加双方可用文件描述符的数量(

uname -n unlimitied
),但无济于事。

现在测试更多...我也可以用 Python 重现同样的事情:

import requests

while True:
    response = requests.get("http://localhost:8000")
    print(f"Status Code: {response.status_code}")

然后

python -m http.server
对于服务器,您将获得
SYN_SENT
挂起。因此,看起来这个问题可能比我最初预期的要更深,但我很好奇并愿意听到任何潜在的补救措施。

我期望的行为是不要发生这个

SYN_SENT
悬挂问题。我希望能够将 HTTP 请求轮询间隔调整为非常低(甚至本地网络上的请求之间为 1 毫秒;只要没有资源泄漏并且所有请求都连续发生,我不明白为什么会这样)无法实现),同时仍然让我的 Java 或其他应用程序以完美稳健的方式工作。不过,我也想抓住这个问题。 Wireshark 的视图让我感到困惑,因为它显示服务器已返回其 HTTP 200 OK 响应,但由于某种原因,客户端在读取它时挂起(我尝试包含重现我所看到的内容所需的所有内容,因为我一直在调试)这几个小时)。谢谢您的宝贵时间。

http sockets networking tcp freeze
1个回答
0
投票

我很高兴地说我已经找到了这个问题的根本原因!我注意到每当我遇到这些

SYN_SENT
挂起(如
ss
所示)时,我也会将其登录到我的
dmesg
:

nf_conntrack: nf_conntrack: table full, dropping packet

连接跟踪表已满!我发出的每个 HTTP 1.1 请求都在其自己的 TCP 流上运行。现在一切都变得非常有意义了。

我正在运行一个相当独特的 Linux 发行版,所以我不确定我的连接跟踪表是否小于平均水平。我的应用程序中还存在一些套接字/资源泄漏问题,甚至在我的系统上的单独程序之间也加剧了该问题。我还没有考虑增加这个表的大小。如果您遇到此问题,请记住问题也可能是网络上其他设备(例如路由器、交换机或防火墙)的小连接表。

我让我的应用程序以非常低的轮询间隔运行一夜,看看会发生什么,当我醒来时,Java 编译器失败了,因为 systemd 似乎已经用这些

/tmp
日志填充了
nf_conntrack
。就在那时我想检查一下
dmesg
,瞧!然后我重新启动以清除
tmpfs

© www.soinside.com 2019 - 2024. All rights reserved.