2016-05-17 70 views
2

我想要拿出一个简单的HTTP客户端的Java实现,它保持套接字打开并重用它来查询同一主机上的其他(或相同)URL 。Java套接字保持活着很慢,重新打开套接字更快

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

结果第一,低于全可执行代码:

成KeepAlive:

> 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 

保持活动:在迭代#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(独立,无依赖)

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. 东西是错误的我用的是缓冲读者,因为路这就是所有的时间都失去了,但看着其他可用的方法(和我尝试了一些),我无法找到我需要做什么来解决这个问题......我

任何想法,怎么可能做这个w ork更快?也许一个配置更改服务器本身?上...


解决方案

由于apangin下面解释由越慢PERF是Nagle算法,它是默认启用造成的。 使用setTcpNoDelay(真),我得到了更新以下perfs:

不保活:

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 

随着保活:

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 

所以在这里,我们可以看到保对于每次迭代,如果比较总执行时间,动态版本的执行效率会比非保持活动版本要好得多。 :)

+0

你并不是真的像这里一样测试。在一个你正在尽可能努力地锤击服务器。另一方面,你会在每个查询之间暂停一下。您是否尝试过每次运行测试客户端的总时间? – BevynQ

+0

是的,这就是测试的重点:看看两种不同行为中哪一种表现最好。问题在于:“我能以多快的速度提出请求并获得响应,无论是否保持活力”。我为你添加了总执行时间。 – fabien

回答

8

这就是Nagle's algorithm的效果。 它延迟发送TCP数据包的预期更多传出数据。

Nagle的算法与TCP delayed acknowledgment严重交互写入读取方案。 这正是你的情况,因为writer.writeBytes(reqStr)发送一个字节的字节的字节。

现在你有两个选择来解决该问题:

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

在这两种情况下,重复使用的连接将果然工作得更快。

+0

为什么不适用于这两种连接类型? – EJP

+0

@EJP显然,'socket.close()'强制立即发送所有未完成的数据。这对于一个保持打开的套接字不会发生。 – apangin

+0

这既不明显也不正确,并且该请求仍然必须在不关闭套接字的情况下发送。就数据而言,close()所做的就是在待处理数据之后发送FIN。它不会关闭[Nagle算法](https://tools.ietf.org/html/rfc896)。 – EJP

-2
reader.read(buf); 

您的测试无效。您不一定阅读整个回复。您需要将其更改为对返回的数据进行计数的循环。如果你没有阅读整个回复,你会在保持活跃的情况下失去同步。

+0

你没有错。这里测试的有效载荷非常小(约1Kb),这并不重要。我在上面的代码中添加了循环并更新了结果。和预期的一样。 – fabien