2016-05-17 116 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