加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

socket(一) 通信分包通信性能测试(setTcpNoDelay)

(2013-12-11 17:45:36)
标签:

it

分类: socket

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.   
  3. import java.io.*;  
  4. import java.net.*;  
  5. import org.apache.log4j.Logger;  
  6.   
  7. public class Client  
  8.     private static Logger logger Logger.getLogger(Client.class);  
  9.     public static void main(String[] args) throws Exception  
  10.         // 是否分开写head和body  
  11.         boolean writeSplit true 
  12.         String host "localhost" 
  13.         logger.debug("WriteSplit:" writeSplit);  
  14.   
  15.         Socket socket new Socket();  
  16.         socket.setTcpNoDelay(false);  
  17.         socket.connect(new InetSocketAddress(host, 10000));  
  18.   
  19.         InputStream in socket.getInputStream();  
  20.         OutputStream out socket.getOutputStream();  
  21.         BufferedReader reader new BufferedReader(new InputStreamReader(in));  
  22.   
  23.         String head "hello " 
  24.         String body "world\r\n" 
  25.         for (int 010i++)  
  26.             long label System.currentTimeMillis();  
  27.             if (writeSplit)  
  28.                 out.write(head.getBytes());  
  29.                 out.write(body.getBytes());  
  30.             else  
  31.                 out.write((head body).getBytes());  
  32.              
  33.             String line reader.readLine();  
  34.             logger.debug("RTT:" (System.currentTimeMillis() label) ", receive: " line);  
  35.          
  36.         in.close();  
  37.         out.close();  
  38.         socket.close();  
  39.      
  40.  

接收端代码

  1. package socket.nagle;  
  2.   
  3. import java.io.*;  
  4. import java.net.*;  
  5.   
  6. import org.apache.log4j.Logger;  
  7.   
  8. public class Server  
  9.     private static Logger logger Logger.getLogger(Server.class);  
  10.   
  11.     public static void main(String[] args) throws Exception  
  12.         ServerSocket serverSocket new ServerSocket();  
  13.         serverSocket.bind(new InetSocketAddress(10000));  
  14.         logger.debug(serverSocket);  
  15.         logger.debug("Server startup at 10000");  
  16.         while (true 
  17.             Socket socket serverSocket.accept();  
  18.             InputStream in socket.getInputStream();  
  19.             OutputStream out socket.getOutputStream();  
  20.   
  21.             while (true 
  22.                 try  
  23.                     BufferedReader reader new BufferedReader(new InputStreamReader(in));  
  24.                     String line reader.readLine();  
  25.                     logger.debug(line);  
  26.                     out.write((line "\r\n").getBytes());  
  27.                 catch (Exception e)  
  28.                     break 
  29.                  
  30.              
  31.          
  32.      
  33.  

实验结果:

  1. [test5@cent4 ~]$ java socket.nagle.Server  
  2.    [main] DEBUG socket.nagle.Server ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=10000]  
  3.    [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.    [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.    [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.    [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.    [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机上。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有