java套接字保持 Activity 很慢,重新打开套接字更快

标签 java performance sockets keep-alive

我试图提出一个简单的 HTTP 客户端的 java 实现,该客户端保持套接字打开并重用它来查询同一主机上的其他(或相同)URL。

我有一个使用 java.net.Socket 的简单实现,但不知何故,当我保持套接字打开时的性能比我不断创建新套接字时的性能要差。

结果第一,完整的可执行代码如下:

使用 KeepAlive:从迭代 #2 开始速度较慢

> java -server -Xms100M -Xmx100M -cp . KeepAlive 10 true
--- Warm up ---
18
61
60
60
78
62
59
60
59
60
Total exec time: 626
--- Run ---
26
59
60
61
60
59
60
60
62
58
Total exec time: 576

每次重新创建套接字都会得到更好的结果:

> java -server -Xms100M -Xmx100M -cp . KeepAlive 10 false
--- Warm up ---
188
34
39
33
33
33
33
33
34
33
Total exec time: 494
--- Run ---
33
35
33
34
44
34
33
34
32
34
Total exec time: 346

KeepAlive.java(独立,无依赖项)

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;

public class KeepAlive {

    private static final String NL = "\r\n";
    private static final int READ_SIZE = 1000;
    private Socket socket;
    private DataOutputStream writer;
    private BufferedReader reader;

    public static void main(String[] args) throws Exception {
        if (args.length == 2) {
            KeepAlive ka = new KeepAlive();
            System.out.println("--- Warm up ---");
            ka.query(Integer.parseInt(args[0]), args[1].equals("true"));
            System.out.println("--- Run ---");
            ka.query(Integer.parseInt(args[0]), args[1].equals("true"));
        } else {
            System.out.println("Usage: keepAlive <n queries> <reuse socket>");
        }
    }

    private void query(int n, boolean reuseConnection) throws Exception {
        long t0 = System.currentTimeMillis();
        if (reuseConnection) {
            open();
            for (int i = 0; i < n; i++) {
                long tq0 = System.currentTimeMillis();
                query();
                System.out.println(System.currentTimeMillis() - tq0);
            }
            close();
        } else {
            for (int i = 0; i < n; i++) {
                long tq0 = System.currentTimeMillis();
                open();
                query();
                close();
                System.out.println(System.currentTimeMillis() - tq0);
            }
        }
        System.out.println("Total exec time: " + (System.currentTimeMillis() - t0));
    }

    private void open() throws Exception {
        socket = new Socket();
        socket.setKeepAlive(false);
        socket.connect(new InetSocketAddress("example.org", 80));
        writer = new DataOutputStream(socket.getOutputStream());
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }

    private void query() throws Exception {
        StringBuilder req = new StringBuilder();
        req.append("GET / HTTP/1.1").append(NL);
        req.append("Host: example.org").append(NL);
        req.append("Connection: Keep-Alive").append(NL);
        req.append(NL);
        String reqStr = req.toString();

        long t0 = System.currentTimeMillis();
        writer.writeBytes(reqStr);
        writer.flush();

        String line;
        int contentLength = 0;
        while ((line = reader.readLine()) != null) {
            if (line.startsWith("Content-Length: ")) {
                contentLength = Integer.parseInt(line.substring(16));
            }
            if (line.equals("")) {
                char[] buf = new char[contentLength];
                int offset = 0;
                while (offset < contentLength) {
                  int len = contentLength - offset;
                  if (len > READ_SIZE) {
                    len = READ_SIZE;
                  }
                  int ret = reader.read(buf, offset, len);
                  if (ret == -1) {
                    System.out.println("End of stream. Exiting");
                    System.exit(1);
                  }
                  offset += ret;
                }

                break;
            }
        }
    }

    private void close() throws Exception {
        writer.close();
        reader.close();
        socket.close();
    }
}

现在,我很确定:

  1. Web 服务器无法快速处理新请求 ( HTTP Keep Alive and TCP keep alive )

  2. 我使用缓冲阅读器的方式有问题,因为这就是浪费所有时间的地方,但是查看其他可用的方法(我尝试了一些),我找不到我需要做的事情解决这个问题...

知道如何才能让这项工作更快吗?也许要在服务器本身上更改配置?...

<小时/>

解决方案

正如下面 apangin 所解释的,性能较慢是由默认启用的 Nagle 算法引起的。 使用 setTcpNoDelay(true),我得到了更新的以下性能:

没有保持 Activity 状态:

java -server -Xms100M -Xmx100M -cp . KeepAlive 10 false
--- Warm up ---
49
22
25
23
23
22
23
23
28
28
Total exec time: 267
--- Run ---
31
23
23
24
25
22
23
25
33
23
Total exec time: 252

保持 Activity 状态:

java -server -Xms100M -Xmx100M -cp . KeepAlive 10 true
--- Warm up ---
13
12
12
14
11
12
13
12
11
12
Total exec time: 168
--- Run ---
14
12
11
12
11
12
13
11
21
28
Total exec time: 158

因此,在这里,我们可以看到每次迭代以及比较总执行时间时,保持 Activity 版本的性能远远优于非保持 Activity 版本。 :)

最佳答案

这就是 Nagle's algorithm 的效果。 它会延迟发送 TCP 数据包,以应对更多传出数据。

Nagle 的算法与 TCP delayed acknowledgment 的交互效果很差在写-写-读场景中。 这正是您的情况,因为 writer.writeBytes(reqStr) 逐字节发送字符串。

现在您有两个选项来修复该行为:

  1. 使用socket.setTcpNoDelay(true)禁用Nagle算法;
  2. 通过一次操作发送完整的请求:writer.write(reqStr.getBytes());

在这两种情况下,重用的连接预计会运行得更快。

关于java套接字保持 Activity 很慢,重新打开套接字更快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37265825/

相关文章:

java - new Double(String) 能否返回空值?

python - wxPython 渲染问题,很慢而且崩溃,不知道为什么

perl - 绑定(bind)到 perl 中的接口(interface)

javascript - 使用 JQuery 创建或删除数十个 div 时如何优化性能?

c# - 在 task.Factory.FromAsync 中包装 WCF 异步调用不返回任何结果

c++ - 有什么方法可以比套接字更快地将数据从 C++ 传输到 Flex/Air?

java - 客户端通过套接字从服务器接收消息时出现一些问题

java - 覆盖 application.properties 以在 spring-boot 应用程序中进行集成测试

java - 如何使用 Mailgun 避免 Outlook/Hotmail 中的垃圾邮件?

java - 在 Project Reactor 中处理来自 Mono 的可选值