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,悲剧的无以言语。
这种时候只有等待一端超时并发送数据才能继续往下走。
代码:
发送端代码
[java]view plaincopyprint?
  1. packagesocket.nagle;
  2. importjava.io.*;
  3. importjava.net.*;
  4. importorg.apache.log4j.Logger;
  5. publicclassClient{
  6. privatestaticLoggerlogger=Logger.getLogger(Client.class);
  7. publicstaticvoidmain(String[]args)throwsException{
  8. //是否分开写head和body
  9. booleanwriteSplit=true;
  10. Stringhost="localhost";
  11. logger.debug("WriteSplit:"+writeSplit);
  12. Socketsocket=newSocket();
  13. socket.setTcpNoDelay(false);
  14. socket.connect(newInetSocketAddress(host,10000));
  15. InputStreamin=socket.getInputStream();
  16. OutputStreamout=socket.getOutputStream();
  17. BufferedReaderreader=newBufferedReader(newInputStreamReader(in));
  18. Stringhead="hello";
  19. Stringbody="world\r\n";
  20. for(inti=0;i<10;i++){
  21. longlabel=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. Stringline=reader.readLine();
  29. logger.debug("RTT:"+(System.currentTimeMillis()-label)+",receive:"+line);
  30. }
  31. in.close();
  32. out.close();
  33. socket.close();
  34. }
  35. }
接收端代码
[java]view plaincopyprint?
  1. packagesocket.nagle;
  2. importjava.io.*;
  3. importjava.net.*;
  4. importorg.apache.log4j.Logger;
  5. publicclassServer{
  6. privatestaticLoggerlogger=Logger.getLogger(Server.class);
  7. publicstaticvoidmain(String[]args)throwsException{
  8. ServerSocketserverSocket=newServerSocket();
  9. serverSocket.bind(newInetSocketAddress(10000));
  10. logger.debug(serverSocket);
  11. logger.debug("Serverstartupat10000");
  12. while(true){
  13. Socketsocket=serverSocket.accept();
  14. InputStreamin=socket.getInputStream();
  15. OutputStreamout=socket.getOutputStream();
  16. while(true){
  17. try{
  18. BufferedReaderreader=newBufferedReader(newInputStreamReader(in));
  19. Stringline=reader.readLine();
  20. logger.debug(line);
  21. out.write((line+"\r\n").getBytes());
  22. }catch(Exceptione){
  23. break;
  24. }
  25. }
  26. }
  27. }
  28. }
实验结果:
[plain]view plaincopyprint?
  1. [test5@cent4~]$javasocket.nagle.Server
  2. 1[main]DEBUGsocket.nagle.Server-ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=10000]
  3. 6[main]DEBUGsocket.nagle.Server-Serverstartupat10000
  4. 4012[main]DEBUGsocket.nagle.Server-helloworld
  5. 4062[main]DEBUGsocket.nagle.Server-helloworld
  6. 4105[main]DEBUGsocket.nagle.Server-helloworld
  7. 4146[main]DEBUGsocket.nagle.Server-helloworld
  8. 4187[main]DEBUGsocket.nagle.Server-helloworld
  9. 4228[main]DEBUGsocket.nagle.Server-helloworld
  10. 4269[main]DEBUGsocket.nagle.Server-helloworld
  11. 4310[main]DEBUGsocket.nagle.Server-helloworld
  12. 4350[main]DEBUGsocket.nagle.Server-helloworld
  13. 4390[main]DEBUGsocket.nagle.Server-helloworld
  14. 4392[main]DEBUGsocket.nagle.Server-
  15. 4392[main]DEBUGsocket.nagle.Server-
实验1: 当WriteSplit=true and TcpNoDelay=false 启用nagle算法
[plain]view plaincopyprint?
  1. [test5@cent4~]$javasocket.nagle.Client
  2. 0[main]DEBUGsocket.nagle.Client-WriteSplit:true
  3. 52[main]DEBUGsocket.nagle.Client-RTT:12,receive:helloworld
  4. 95[main]DEBUGsocket.nagle.Client-RTT:42,receive:helloworld
  5. 137[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
  6. 178[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
  7. 218[main]DEBUGsocket.nagle.Client-RTT:40,receive:helloworld
  8. 259[main]DEBUGsocket.nagle.Client-RTT:40,receive:helloworld
  9. 300[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
  10. 341[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
  11. 382[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
  12. 422[main]DEBUGsocket.nagle.Client-RTT:40,receive:helloworld
可以看到,每次请求到应答的时间间隔都在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算法
[plain]view plaincopyprint?
  1. [test5@cent4~]$javasocket.nagle.Client
  2. 0[main]DEBUGsocket.nagle.Client-WriteSplit:false
  3. 27[main]DEBUGsocket.nagle.Client-RTT:4,receive:helloworld
  4. 31[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  5. 34[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  6. 38[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  7. 42[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  8. 44[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  9. 47[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
  10. 50[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  11. 53[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
  12. 54[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
实验3: 当WriteSplit=true and TcpNoDelay=true 禁用nagle算法
[plain]view plaincopyprint?
  1. [test5@cent4~]$javasocket.nagle.Client
  2. 0[main]DEBUGsocket.nagle.Client-WriteSplit:true
  3. 25[main]DEBUGsocket.nagle.Client-RTT:6,receive:helloworld
  4. 28[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  5. 31[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  6. 33[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
  7. 35[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  8. 41[main]DEBUGsocket.nagle.Client-RTT:6,receive:helloworld
  9. 49[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  10. 52[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  11. 56[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  12. 59[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
实验4: 当WriteSplit=false and TcpNoDelay=true 禁用nagle算法
[plain]view plaincopyprint?
  1. [test5@cent4~]$javasocket.nagle.Client
  2. 0[main]DEBUGsocket.nagle.Client-WriteSplit:false
  3. 21[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  4. 23[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
  5. 27[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
  6. 30[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  7. 32[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
  8. 35[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  9. 38[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  10. 41[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
  11. 43[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
  12. 46[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
实验2到4,都没有出现延时的情况。
注意: 以上实验在windows上测试下面的代码,客户端和服务器必须分在两台机器上,似乎winsock对loopback连接的处理不一样。下面的我的做法是:服务端与客户端都在一台Linux机上。

更多相关文章

  1. java基础---一致性hash算法
  2. java-信息安全(三)-PBE加密算法
  3. 蓝桥杯 ALGO-53 算法训练 最小乘积(基本型)
  4. 算法笔记_138:稳定婚姻问题(Java)
  5. 排序算法之 Java简单快速排序算法
  6. 经典算法问题的java实现
  7. 《算法导论的Java实现》 10 中位数和顺序统计学
  8. 基于内容估计文本宽度的算法
  9. 算法竞赛入门经典(分数化小数)

随机推荐

  1. Android简易注解View(java反射实现)
  2. android解析json小例子
  3. AndroidManifest.xml之 element详解
  4. android讲义9之向电话本进行批处理的插入
  5. cocos2d-x环境搭建 for eclipse
  6. Android 原生开发、H5、React-Native开发
  7. [实战示例] 带您深入探讨 Android 传感器
  8. Android一个textview显示多段文本不同颜
  9. Android下获取Root权限和设置目录属性
  10. Android开发小知识文章目录