简体   繁体   English

Java套接字保持活动很慢,重新打开套接字更快

[英]java socket keep alive is slow, reopening a socket is faster

I am trying to come up with a java implementation of a simple HTTP client that keeps a socket open and reuses it to query other (or same) URLs on the same host. 我试图提出一个简单的HTTP客户端的Java实现,该实现将套接字保持打开状态并重用它来查询同一主机上的其他(或相同)URL。

I have a simple implementation that uses java.net.Socket but somehow the performance when I keep the socket open is worse than when I keep creating a new one. 我有一个使用java.net.Socket的简单实现,但是以某种方式保持套接字打开的性能比保持创建一个新套接字的性能差。

Results first, full executable code below: 结果优先,完整的可执行代码如下:

With KeepAlive: slower starting at iteration #2 使用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

Recreating the socket every time gives better results: 每次重新创建套接字都会产生更好的结果:

> 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 (standalone, no dependencies) 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();
    }
}

Now, I'm pretty sure that either: 现在,我很确定:

  1. the web server sucks at handling the new requests fast ( HTTP Keep Alive and TCP keep alive ) Web服务器无法快速处理新请求( HTTP Keep Alive和TCP keep alive

  2. something is wrong with the way I use the buffered reader because that's where all the time is lost but looking at the other methods available (and I tried a few), I can't find what I need to do to fix this... 我使用缓冲读取器的方式有些问题,因为那会浪费所有时间,但查看其他可用的方法(我尝试了一些),我找不到解决该问题所需的工作...

Any idea how I could make this work faster? 知道如何使这项工作更快吗? Maybe a config to change on the server itself?... 也许要在服务器本身上进行更改的配置?...


Solution

As explained by apangin below, the slower perf is caused by Nagle's algorithm, which is enabled by default. 如下面的apangin所述, 性能下降较慢是由Nagle的算法引起的,该算法默认启用。 Using setTcpNoDelay(true), I get the updated following perfs: 使用setTcpNoDelay(true),得到更新的以下性能:

Without keep-alive: 没有保持活动状态:

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

With keep-alive: 使用保持活动:

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

So here, we can see the keep-alive version performing far better than the non keep-alive one for each iteration and also if comparing total execution times. 因此,在这里,对于每次迭代以及比较总执行时间,我们都可以看到保持活动版本的性能要比非保持活动版本好得多。 :) :)

That's the effect of Nagle's algorithm . 那就是Nagle算法的效果。 It delays sending TCP packets in anticipation of more outgoing data. 由于预期会有更多传出数据,它会延迟发送TCP数据包。

Nagle's algorithm interacts badly with TCP delayed acknowledgment in write-write-read scenarios. Nagle的算法在写-写-读方案中与TCP延迟确认交互不良。 This is exactly your case, because writer.writeBytes(reqStr) sends a string byte-by-byte. 这正是您的情况,因为writer.writeBytes(reqStr)逐字节发送字符串。

Now you have two options to fix the behavior: 现在,您有两个选项可以解决此问题:

  1. use socket.setTcpNoDelay(true) to disable Nagle's algorithm; 使用socket.setTcpNoDelay(true)禁用Nagle的算法;
  2. send the complete request in one operation: writer.write(reqStr.getBytes()); 通过一个操作发送完整的请求: writer.write(reqStr.getBytes());

In both cases the reused connection will expectedly work faster. 在这两种情况下,重用的连接均有望更快地工作。

reader.read(buf);

Your test is invalid. 您的测试无效。 You aren't necessarily reading the entire response. 您不一定要阅读整个回复。 You need to change this to a loop that counts the data returned. 您需要将其更改为对返回的数据进行计数的循环。 And if you don't read the entire response, you will be getting out of sync in the keep-alive case. 而且,如果您没有阅读完整的回复,则在保持连接状态下您将不同步。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM