java socket参数详解:TcpNoDelay
16lz
2021-01-22
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?
- packagesocket.nagle;
- importjava.io.*;
- importjava.net.*;
- importorg.apache.log4j.Logger;
- publicclassClient{
- privatestaticLoggerlogger=Logger.getLogger(Client.class);
- publicstaticvoidmain(String[]args)throwsException{
- booleanwriteSplit=true;
- Stringhost="localhost";
- logger.debug("WriteSplit:"+writeSplit);
- Socketsocket=newSocket();
- socket.setTcpNoDelay(false);
- socket.connect(newInetSocketAddress(host,10000));
- InputStreamin=socket.getInputStream();
- OutputStreamout=socket.getOutputStream();
- BufferedReaderreader=newBufferedReader(newInputStreamReader(in));
- Stringhead="hello";
- Stringbody="world\r\n";
- for(inti=0;i<10;i++){
- longlabel=System.currentTimeMillis();
- if(writeSplit){
- out.write(head.getBytes());
- out.write(body.getBytes());
- }else{
- out.write((head+body).getBytes());
- }
- Stringline=reader.readLine();
- logger.debug("RTT:"+(System.currentTimeMillis()-label)+",receive:"+line);
- }
- in.close();
- out.close();
- socket.close();
- }
- }
接收端代码
[java]view plaincopyprint?
- packagesocket.nagle;
- importjava.io.*;
- importjava.net.*;
- importorg.apache.log4j.Logger;
- publicclassServer{
- privatestaticLoggerlogger=Logger.getLogger(Server.class);
- publicstaticvoidmain(String[]args)throwsException{
- ServerSocketserverSocket=newServerSocket();
- serverSocket.bind(newInetSocketAddress(10000));
- logger.debug(serverSocket);
- logger.debug("Serverstartupat10000");
- while(true){
- Socketsocket=serverSocket.accept();
- InputStreamin=socket.getInputStream();
- OutputStreamout=socket.getOutputStream();
- while(true){
- try{
- BufferedReaderreader=newBufferedReader(newInputStreamReader(in));
- Stringline=reader.readLine();
- logger.debug(line);
- out.write((line+"\r\n").getBytes());
- }catch(Exceptione){
- break;
- }
- }
- }
- }
- }
实验结果:
[plain]view plaincopyprint?
- [test5@cent4~]$javasocket.nagle.Server
- 1[main]DEBUGsocket.nagle.Server-ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=10000]
- 6[main]DEBUGsocket.nagle.Server-Serverstartupat10000
- 4012[main]DEBUGsocket.nagle.Server-helloworld
- 4062[main]DEBUGsocket.nagle.Server-helloworld
- 4105[main]DEBUGsocket.nagle.Server-helloworld
- 4146[main]DEBUGsocket.nagle.Server-helloworld
- 4187[main]DEBUGsocket.nagle.Server-helloworld
- 4228[main]DEBUGsocket.nagle.Server-helloworld
- 4269[main]DEBUGsocket.nagle.Server-helloworld
- 4310[main]DEBUGsocket.nagle.Server-helloworld
- 4350[main]DEBUGsocket.nagle.Server-helloworld
- 4390[main]DEBUGsocket.nagle.Server-helloworld
- 4392[main]DEBUGsocket.nagle.Server-
- 4392[main]DEBUGsocket.nagle.Server-
实验1:
当WriteSplit=true and TcpNoDelay=false 启用nagle算法
[plain]view plaincopyprint?
- [test5@cent4~]$javasocket.nagle.Client
- 0[main]DEBUGsocket.nagle.Client-WriteSplit:true
- 52[main]DEBUGsocket.nagle.Client-RTT:12,receive:helloworld
- 95[main]DEBUGsocket.nagle.Client-RTT:42,receive:helloworld
- 137[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
- 178[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
- 218[main]DEBUGsocket.nagle.Client-RTT:40,receive:helloworld
- 259[main]DEBUGsocket.nagle.Client-RTT:40,receive:helloworld
- 300[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
- 341[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
- 382[main]DEBUGsocket.nagle.Client-RTT:41,receive:helloworld
- 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?
- [test5@cent4~]$javasocket.nagle.Client
- 0[main]DEBUGsocket.nagle.Client-WriteSplit:false
- 27[main]DEBUGsocket.nagle.Client-RTT:4,receive:helloworld
- 31[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 34[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 38[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 42[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 44[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 47[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
- 50[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 53[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
- 54[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
实验3:
当WriteSplit=true and TcpNoDelay=true 禁用nagle算法
[plain]view plaincopyprint?
- [test5@cent4~]$javasocket.nagle.Client
- 0[main]DEBUGsocket.nagle.Client-WriteSplit:true
- 25[main]DEBUGsocket.nagle.Client-RTT:6,receive:helloworld
- 28[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 31[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 33[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
- 35[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 41[main]DEBUGsocket.nagle.Client-RTT:6,receive:helloworld
- 49[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 52[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 56[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 59[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
实验4:
当WriteSplit=false and TcpNoDelay=true 禁用nagle算法
[plain]view plaincopyprint?
- [test5@cent4~]$javasocket.nagle.Client
- 0[main]DEBUGsocket.nagle.Client-WriteSplit:false
- 21[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 23[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
- 27[main]DEBUGsocket.nagle.Client-RTT:3,receive:helloworld
- 30[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 32[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
- 35[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 38[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 41[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
- 43[main]DEBUGsocket.nagle.Client-RTT:1,receive:helloworld
- 46[main]DEBUGsocket.nagle.Client-RTT:2,receive:helloworld
实验2到4,都没有出现延时的情况。
注意:
以上实验在windows上测试下面的代码,客户端和服务器必须分在两台机器上,似乎winsock对loopback连接的处理不一样。下面的我的做法是:服务端与客户端都在一台Linux机上。
- java基础---一致性hash算法
- java-信息安全(三)-PBE加密算法
- 蓝桥杯 ALGO-53 算法训练 最小乘积(基本型)
- 算法笔记_138:稳定婚姻问题(Java)
- 排序算法之 Java简单快速排序算法
- 经典算法问题的java实现
- 《算法导论的Java实现》 10 中位数和顺序统计学
- 基于内容估计文本宽度的算法
- 算法竞赛入门经典(分数化小数)
随机推荐
-
Android简易注解View(java反射实现)
-
android解析json小例子
-
AndroidManifest.xml之 element详解
-
android讲义9之向电话本进行批处理的插入
-
cocos2d-x环境搭建 for eclipse
-
Android 原生开发、H5、React-Native开发
-
[实战示例] 带您深入探讨 Android 传感器
-
Android一个textview显示多段文本不同颜
-
Android下获取Root权限和设置目录属性
-
Android开发小知识文章目录