java socket參數詳解:TcpNoDelay降低通信延遲

TcpNoDelay=false,爲啓用nagle算法,也是默認值。 Nagle算法的立意是良好的,避免網絡中充塞小封包,提高網絡的利用率。但是當Nagle算法遇到delayed ACK悲劇就發生了。Delayed ACK的本意也是爲了提高TCP性能,跟應答數據捎帶上ACK,同時避免糊塗窗口綜合症,也可以一個ack確認多個段來節省開銷。悲劇發生在這種情況,假設一端發送數據並等待另一端應答,協議上分爲頭部和數據,發送的時候不幸地選擇了write-write,然後再read,也就是先發送頭部,再發送數據,最後等待應答。
實驗模型:
發送端(客戶端)
write(head);
write(body);
read(response);
接收端(服務端)
read(request);  
process(request);  
write(response);
這裏假設head和body都比較小,當默認啓用nagle算法,並且是第一次發送的時候,根據nagle算法,第一個段head可以立即發送,因爲沒有等待確認的段;接收端(服務端)收到head,但是包不完整,繼續等待body達到並延遲ACK;發送端(客戶端)繼續寫入body,這時候nagle算法起作用了,因爲head還沒有被ACK,所以body要延遲發送。這就造成了發送端(客戶端)和接收端(服務端)都在等待對方發送數據的現象:
發送端(客戶端)等待接收端ACK head以便繼續發送body;
接收端(服務端)在等待發送方發送body並延遲ACK,悲劇的無以言語。
這種時候只有等待一端超時併發送數據才能繼續往下走。
代碼:
發送端代碼

  1. package socket.nagle;  

  2. import java.io.*;  

  3. import java.net.*;  

  4. import org.apache.log4j.Logger;  

  5. publicclass Client {  

  6. privatestatic Logger logger = Logger.getLogger(Client.class);  

  7. publicstaticvoid main(String[] args) throws Exception {  

  8. // 是否分開寫head和body

  9. boolean writeSplit = true;  

  10.        String host = "localhost";  

  11.        logger.debug("WriteSplit:" + writeSplit);  

  12.        Socket socket = new Socket();  

  13.        socket.setTcpNoDelay(false);  

  14.        socket.connect(new InetSocketAddress(host, 10000));  

  15.        InputStream in = socket.getInputStream();  

  16.        OutputStream out = socket.getOutputStream();  

  17.        BufferedReader reader = new BufferedReader(new InputStreamReader(in));  

  18.        String head = "hello ";  

  19.        String body = "world\r\n";  

  20. for (int i = 0; i < 10; i++) {  

  21. long label = System.currentTimeMillis();  

  22. if (writeSplit) {  

  23.                out.write(head.getBytes());  

  24.                out.write(body.getBytes());  

  25.            } else {  

  26.                out.write((head + body).getBytes());  

  27.            }  

  28.            String line = reader.readLine();  

  29.            logger.debug("RTT:" + (System.currentTimeMillis() - label) + ", receive: " + line);  

  30.        }  

  31.        in.close();  

  32.        out.close();  

  33.        socket.close();  

  34.    }  

  35. }  

接收端代碼
  1. package socket.nagle;  

  2. import java.io.*;  

  3. import java.net.*;  

  4. import org.apache.log4j.Logger;  

  5. publicclass Server {  

  6. privatestatic Logger logger = Logger.getLogger(Server.class);  

  7. publicstaticvoid main(String[] args) throws Exception {  

  8.        ServerSocket serverSocket = new ServerSocket();  

  9.        serverSocket.bind(new InetSocketAddress(10000));  

  10.        logger.debug(serverSocket);  

  11.        logger.debug("Server startup at 10000");  

  12. while (true) {  

  13.            Socket socket = serverSocket.accept();  

  14.            InputStream in = socket.getInputStream();  

  15.            OutputStream out = socket.getOutputStream();  

  16. while (true) {  

  17. try {  

  18.                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));  

  19.                    String line = reader.readLine();  

  20.                    logger.debug(line);  

  21.                    out.write((line + "\r\n").getBytes());  

  22.                } catch (Exception e) {  

  23. break;  

  24.                }  

  25.            }  

  26.        }  

  27.    }  

  28. }  

實驗結果:
  1. [test5@cent4 ~]$ java socket.nagle.Server  

  2. 1    [main] DEBUG socket.nagle.Server - ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=10000]  

  3. 6    [main] DEBUG socket.nagle.Server - Server startup at 10000  

  4. 4012 [main] DEBUG socket.nagle.Server - hello world  

  5. 4062 [main] DEBUG socket.nagle.Server - hello world  

  6. 4105 [main] DEBUG socket.nagle.Server - hello world  

  7. 4146 [main] DEBUG socket.nagle.Server - hello world  

  8. 4187 [main] DEBUG socket.nagle.Server - hello world  

  9. 4228 [main] DEBUG socket.nagle.Server - hello world  

  10. 4269 [main] DEBUG socket.nagle.Server - hello world  

  11. 4310 [main] DEBUG socket.nagle.Server - hello world  

  12. 4350 [main] DEBUG socket.nagle.Server - hello world  

  13. 4390 [main] DEBUG socket.nagle.Server - hello world  

  14. 4392 [main] DEBUG socket.nagle.Server -  

  15. 4392 [main] DEBUG socket.nagle.Server -  

實驗1:當WriteSplit=true and TcpNoDelay=false 啓用nagle算法
  1. [test5@cent4 ~]$ java socket.nagle.Client  

  2. 0    [main] DEBUG socket.nagle.Client - WriteSplit:true  

  3. 52   [main] DEBUG socket.nagle.Client - RTT:12, receive: hello world  

  4. 95   [main] DEBUG socket.nagle.Client - RTT:42, receive: hello world  

  5. 137  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world  

  6. 178  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world  

  7. 218  [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world  

  8. 259  [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world  

  9. 300  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world  

  10. 341  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world  

  11. 382  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world  

  12. 422  [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world  

可以看到,每次請求到應答的時間間隔都在40ms,除了第一次。linux的delayed ack是40ms,而不是原來以爲的200ms。第一次立即ACK,似乎跟linux的quickack mode有關,這裏我不是特別清楚,
其實問題不是出在nagle算法身上的,問題是出在write-write-read這種應用編程上。禁用nagle算法可以暫時解決問題,但是禁用 nagle算法也帶來很大壞處,網絡中充塞着小封包,網絡的利用率上不去,在極端情況下,大量小封包導致網絡擁塞甚至崩潰。在這種情況下,其實你只要避免write-write-read形式的調用就可以避免延遲現象,如下面這種情況發送的數據不要再分割成兩部分。
實驗2:當WriteSplit=false and TcpNoDelay=false 啓用nagle算法
  1. [test5@cent4 ~]$ java socket.nagle.Client  

  2. 0    [main] DEBUG socket.nagle.Client - WriteSplit:false  

  3. 27   [main] DEBUG socket.nagle.Client - RTT:4, receive: hello world  

  4. 31   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  5. 34   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  6. 38   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  7. 42   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  8. 44   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  9. 47   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

  10. 50   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  11. 53   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

  12. 54   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

實驗3:當WriteSplit=true and TcpNoDelay=true 禁用nagle算法
  1. [test5@cent4 ~]$ java socket.nagle.Client  

  2. 0    [main] DEBUG socket.nagle.Client - WriteSplit:true  

  3. 25   [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world  

  4. 28   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  5. 31   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  6. 33   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

  7. 35   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  8. 41   [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world  

  9. 49   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  10. 52   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  11. 56   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  12. 59   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

實驗4:當WriteSplit=false and TcpNoDelay=true 禁用nagle算法
  1. [test5@cent4 ~]$ java socket.nagle.Client  

  2. 0    [main] DEBUG socket.nagle.Client - WriteSplit:false  

  3. 21   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  4. 23   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

  5. 27   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world  

  6. 30   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  7. 32   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

  8. 35   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  9. 38   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  10. 41   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

  11. 43   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world  

  12. 46   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world  

實驗2到4,都沒有出現延時的情況。
注意:以上實驗在windows上測試下面的代碼,客戶端和服務器必須分在兩臺機器上,似乎winsock對loopback連接的處理不一樣。下面的我的做法是:服務端與客戶端都在一臺Linux機上。

轉載自:http://blog.csdn.net/huang_xw/article/details/7340241

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章