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

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