延迟从Java到同一台计算机的多个TCP连接

时间:2009-12-09 16:40:56

标签: java http tcp

(见this question in ServerFault

我有一个Java客户端,它使用Socket打开到同一台机器的并发连接。我正在目睹一种请求完成速度非常快的现象,但其他请求的延迟时间为100-3000毫秒。使用Wireshark进行数据包检查会在离开客户端之前显示所有SYN数据包超出第一个等待很长时间。我在Windows和Linux客户端都看到了这一点。可能是什么导致了这个?当客户端是Windows 2008或Linux机箱时会发生这种情况。

附加代码:

import java.util.*;
import java.net.*;

public class Tester {
    public static void main(String[] args) throws Exception {
        if (args.length < 3) {
            usage();
            return;
        }
        final int n = Integer.parseInt(args[0]);
        final String ip = args[1];
        final int port = Integer.parseInt(args[2]);

        ExecutorService executor = Executors.newFixedThreadPool(n);

        ArrayList<Callable<Long>> tasks = new ArrayList<Callable<Long>>();
        for (int i = 0; i < n; ++i)
            tasks.add(new Callable<Long>() {
                public Long call() {
                    Date before = new Date();
                    try {
                        Socket socket = new Socket();
                        socket.connect(new InetSocketAddress(ip, port));
                    }

                    catch (Throwable e) {
                        e.printStackTrace();
                    }
                    Date after = new Date();
                    return after.getTime() - before.getTime();
                }
            });
        System.out.println("Invoking");
        List<Future<Long>> results = executor.invokeAll(tasks);
        System.out.println("Invoked");
        for (Future<Long> future : results) {
            System.out.println(future.get());
        }
        executor.shutdown();
    }

    private static void usage() {
        System.out.println("Usage: prog <threads> <url/IP Port>");
        System.out.println("Examples:");
        System.out.println("  prog tcp 10 127.0.0.1 2000");
    }
}

更新 - 如果在运行测试程序之前清除相关的ARP条目,则问题会一致地重现。我试过调整TCP retransmission timeout,但这没有帮助。此外,我们将此程序移植到.Net,但问题仍然存在。

更新2 - 3秒是从RFC 1122创建新连接的指定延迟。我仍然不完全理解为什么这里有重传,它应该由MAC层处理。另外,我们使用netcat重现了这个问题,因此它与java无关。

11 个答案:

答案 0 :(得分:3)

看起来您使用单个底层HTTP连接。因此,在close()的{​​{1}}上致电InputStream之前,无法完成其他请求。即在处理回复之前。

或者您应该使用HTTP连接池。

答案 1 :(得分:3)

您在减少问题空间的大小方面做得很对。从表面上看,这是一个不可能的问题 - 在IP堆栈,语言和机器之间移动,但不是任意可重复的(例如,我不能在Windows和Linux上使用您的代码重新编写代码)。

一些建议,从堆栈顶部到底部:

  • 代码 - 你说这发生在.Net和Java上。是否有任何语言/编译器组合不会发生?我使用您的客户端与sourceforge的SocketTest程序进行通信,并且“nc”使用相同的结果 - 没有延迟。同样,JDK 1.5和1.6对我没有任何影响。

    - 假设您调整客户端发送请求的速度,比如每500毫秒一个。问题是否重现?

  • IP堆栈 - 可能会在出路时卡在堆栈中。我看到你已经排除了Nagle,但不要忘记像防火墙/ ip表这样的愚蠢的东西。我发现很难相信Win和Linux上的TCP堆栈已经被软化了,但你永远不知道。

    - 环回接口处理可能很怪异。当你使用机器的真实IP时它会重现吗?整个网络(或者更好的是,使用x-over电缆连接到另一台机器)?

  • NIC - 如果数据包正在发送到卡上,请考虑卡的功能(TCP卸载或其他“特殊”处理)或NIC本身的怪癖。您是否与其他品牌的NIC获得了相同的结果?

答案 2 :(得分:3)

我从这次讨论中找不到真正的答案。我提出的最好的理论是:

  1. TCP层将SYN发送到MAC层。这发生在几个线程中。
  2. 第一个线程看到IP表在ARP表中没有匹配,发送ARP请求。
  3. 后续线程看到有一个待处理的ARP请求,因此他们完全丢弃该数据包。这种行为可能是在几个操作系统的内核中实现的!
  4. ARP回复返回,来自第一个线程的原始SYN请求离开计算机并建立TCP连接。
  5. TCP层等待RFC 1122中所述的3秒,然后重试并成功。
  6. 我已尝试在Windows 7中调整超时但未成功。如果有人可以重现问题并提供解决方法,我将会非常有帮助。此外,如果有人知道为什么这种现象只发生在多个线程上的详细信息,那么听起来会很有趣。

    我会尝试接受这个答案,因为我认为任何答案都没有提供真正的解释(见this discussion on meta)。

答案 3 :(得分:1)

如果其中一台机器是一个Windows框,我会看一下两者的最大并发连接数。请参阅:http://www.speedguide.net/read_articles.php?id=1497

我认为在某些情况下这是应用级限制,因此您必须按照指南提升它们。

此外,如果发生这种情况,您应该在违规机器上的系统事件日志中看到一些内容。

答案 4 :(得分:1)

  

使用HttpURLConnection打开到同一台计算机的并发连接的Java客户端。

同一台机器?客户接受什么应用程序?如果您自己编写该程序,也许您必须计算服务器接受客户端的速度。也许它只是一个糟糕(或不快速工作)的书面服务器应用程序。我认为服务器代码看起来像这样;

ServerSocket ss = ...;
while (acceptingMoreClients)
{
   Socket s = ss.accept();
   // On this moment the client is connected to the server, so start timing.
   long start = System.currentTimeMillis();
   ClientHandler handler = new ClientHandler(s);
   handler.start();

   // After "handler.start();" the handler thread is started,
   // So the next two commands will be very fast done.
   // That means the server is ready to accept a new client.
   // Stop timing.
   long stop = System.currentTimeMillis();
   System.out.println("Client accepted in " + (stop - start) + " millis");
}

如果这个结果不好,那么你知道问题出在哪里 我希望这可以帮助您更接近解决方案。


问题:

要进行测试,您是否使用从DHCP服务器或127.0.0.1收到的IP 如果来自DHCP服务器,那么一切都通过您公司的路由器/交换机/ ....这可能会减慢整个过程。

否则:

  • 在Windows中,所有TCP流量(localhost到localhost)都将重定向到系统的软件层(而不是硬件层),这就是您无法通过Wireshark查看TCP流量的原因。 Wireshark只能看到通过硬件层的流量。
  • Linux:Wireshark只能看到硬件层的流量。 Linux不会在软件层上重定向。这也是InetAddress.getLocalhost().getAddress() 127.0.0.1返回的原因。

  • 因此,当您使用Windows时,使用Wireshark看不到SYN数据包是很正常的。

马亭。

答案 5 :(得分:1)

由于问题无法重现,除非您清除关联的ARP缓存,从发出ARP请求到发出3秒延迟之后,整个数据包跟踪从定时角度看是什么样的?

如果打开与两个不同IP的连接会发生什么?两者的第一次连接是否会成功?如果是这样,那应该排除任何JVM或库问题。

在ARP响应到来之前,无法发送第一个SYN。可能OS或TCP堆栈使用超时而不是事件超过第一个尝试在关联的MAC地址未知时打开连接的线程。

想象一下以下场景:

  1. 线程#1尝试连接,但由于ARP缓存为空,因此无法发送SYN,因此它会对ARP请求进行排队。
  2. 接下来,线程#2(通过#N)尝试连接。它也无法发送SYN数据包,因为ARP缓存为空。但是,这一次,该线程不再发送另一个ARP请求,而是按照RFC中的说法进入休眠状态3秒钟。
  3. 接下来,ARP响应到来。线程#1立即唤醒并发送SYN。
  4. 线程#2没有等待ARP请求;它有一个硬编码的3秒睡眠。因此,3秒后,它会唤醒,找到所需的ARP条目,并发送SYN。

答案 6 :(得分:1)

您在多个客户端上看到这一点,具有不同的操作系统,以及不同的应用程序环境(我假设)相同的操作系统,这强烈表明它与网络或服务器有关,而不是客户端。您的意见强调了清除ARP表再现问题。

您是否在交换机上有两台具有相同MAC地址的计算机? (其中一个可能是一个欺骗MAC地址的路由器。)

或者更有可能的是,如果我正确地回想起ARP,两台机器具有相同的硬编码IP地址。当客户端发出“谁是IP 123.456.123.456”时,两人都会回答,但实际上只有一人会在听。

另一种可能性(我在公司环境中看到过这种情况)是一个流氓DHCP服务器,再次向两台机器提供相同的IP地址。

答案 7 :(得分:0)

当我获得DNS超时时,我看到过类似的行为。要对此进行测试,您可以直接使用IP地址,也可以在hosts文件中输入IP地址。

答案 8 :(得分:0)

设置socket.setTcpNoDelay( true )有帮助吗?

答案 9 :(得分:0)

您是否曾尝试使用strace运行客户端来查看系统调用。

过去,在调试一些神秘的网络问题时,对我非常有帮助。

答案 10 :(得分:0)

服务器上的监听积压是什么?它接受连接的速度有多快?如果待办事项已填满,则操作系统会忽略连接尝试。 3秒后,客户端再次尝试,并且现在已经清除积压。