java socket參數詳解:TcpNoDelay

來源:互聯網
上載者:User

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,悲劇的無以言語。
這種時候只有等待一端逾時並發送資料才能繼續往下走。
代碼:
發送端代碼

package socket.nagle;import java.io.*;import java.net.*;import org.apache.log4j.Logger;public class Client {private static Logger logger = Logger.getLogger(Client.class);public static void main(String[] args) throws Exception {// 是否分開寫head和bodyboolean writeSplit = true;String host = "localhost";logger.debug("WriteSplit:" + writeSplit);Socket socket = new Socket();socket.setTcpNoDelay(false);socket.connect(new InetSocketAddress(host, 10000));InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));String head = "hello ";String body = "world\r\n";for (int i = 0; i < 10; i++) {long label = System.currentTimeMillis();if (writeSplit) {out.write(head.getBytes());out.write(body.getBytes());} else {out.write((head + body).getBytes());}String line = reader.readLine();logger.debug("RTT:" + (System.currentTimeMillis() - label) + ", receive: " + line);}in.close();out.close();socket.close();}}

接收端代碼

package socket.nagle;import java.io.*;import java.net.*;import org.apache.log4j.Logger;public class Server {private static Logger logger = Logger.getLogger(Server.class);public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket();serverSocket.bind(new InetSocketAddress(10000));logger.debug(serverSocket);logger.debug("Server startup at 10000");while (true) {Socket socket = serverSocket.accept();InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();while (true) {try {BufferedReader reader = new BufferedReader(new InputStreamReader(in));String line = reader.readLine();logger.debug(line);out.write((line + "\r\n").getBytes());} catch (Exception e) {break;}}}}}

實驗結果:

[test5@cent4 ~]$ java socket.nagle.Server1    [main] DEBUG socket.nagle.Server - ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=10000]6    [main] DEBUG socket.nagle.Server - Server startup at 100004012 [main] DEBUG socket.nagle.Server - hello world4062 [main] DEBUG socket.nagle.Server - hello world4105 [main] DEBUG socket.nagle.Server - hello world4146 [main] DEBUG socket.nagle.Server - hello world4187 [main] DEBUG socket.nagle.Server - hello world4228 [main] DEBUG socket.nagle.Server - hello world4269 [main] DEBUG socket.nagle.Server - hello world4310 [main] DEBUG socket.nagle.Server - hello world4350 [main] DEBUG socket.nagle.Server - hello world4390 [main] DEBUG socket.nagle.Server - hello world4392 [main] DEBUG socket.nagle.Server -4392 [main] DEBUG socket.nagle.Server - 

實驗1:當WriteSplit=true and TcpNoDelay=false 啟用nagle演算法

[test5@cent4 ~]$ java socket.nagle.Client0    [main] DEBUG socket.nagle.Client - WriteSplit:true52   [main] DEBUG socket.nagle.Client - RTT:12, receive: hello world95   [main] DEBUG socket.nagle.Client - RTT:42, receive: hello world137  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world178  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world218  [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world259  [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world300  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world341  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world382  [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world422  [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演算法

[test5@cent4 ~]$ java socket.nagle.Client0    [main] DEBUG socket.nagle.Client - WriteSplit:false27   [main] DEBUG socket.nagle.Client - RTT:4, receive: hello world31   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world34   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world38   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world42   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world44   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world47   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world50   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world53   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world54   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world

實驗3:當WriteSplit=true and TcpNoDelay=true 禁用nagle演算法

[test5@cent4 ~]$ java socket.nagle.Client0    [main] DEBUG socket.nagle.Client - WriteSplit:true25   [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world28   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world31   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world33   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world35   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world41   [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world49   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world52   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world56   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world59   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world

實驗4:當WriteSplit=false and TcpNoDelay=true 禁用nagle演算法

[test5@cent4 ~]$ java socket.nagle.Client0    [main] DEBUG socket.nagle.Client - WriteSplit:false21   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world23   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world27   [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world30   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world32   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world35   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world38   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world41   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world43   [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world46   [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.