java socket参数详解:TcpNoDelay
2012-03-10 18:10
471 查看
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,悲剧的无以言语。
这种时候只有等待一端超时并发送数据才能继续往下走。
代码:
发送端代码
其实问题不是出在nagle算法身上的,问题是出在write-write-read这种应用编程上。禁用nagle算法可以暂时解决问题,但是禁用 nagle算法也带来很大坏处,网络中充塞着小封包,网络的利用率上不去,在极端情况下,大量小封包导致网络拥塞甚至崩溃。在这种情况下,其实你只要避免write-write-read形式的调用就可以避免延迟现象,如下面这种情况发送的数据不要再分割成两部分。
实验2:当WriteSplit=false and TcpNoDelay=false 启用nagle算法
注意:以上实验在windows上测试下面的代码,客户端和服务器必须分在两台机器上,似乎winsock对loopback连接的处理不一样。下面的我的做法是:服务端与客户端都在一台Linux机上。
实验模型:
发送端(客户端)
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和body boolean 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.Server 1 [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 10000 4012 [main] DEBUG socket.nagle.Server - hello world 4062 [main] DEBUG socket.nagle.Server - hello world 4105 [main] DEBUG socket.nagle.Server - hello world 4146 [main] DEBUG socket.nagle.Server - hello world 4187 [main] DEBUG socket.nagle.Server - hello world 4228 [main] DEBUG socket.nagle.Server - hello world 4269 [main] DEBUG socket.nagle.Server - hello world 4310 [main] DEBUG socket.nagle.Server - hello world 4350 [main] DEBUG socket.nagle.Server - hello world 4390 [main] DEBUG socket.nagle.Server - hello world 4392 [main] DEBUG socket.nagle.Server - 4392 [main] DEBUG socket.nagle.Server -实验1:当WriteSplit=true and TcpNoDelay=false 启用nagle算法
[test5@cent4 ~]$ java socket.nagle.Client 0 [main] DEBUG socket.nagle.Client - WriteSplit:true 52 [main] DEBUG socket.nagle.Client - RTT:12, receive: hello world 95 [main] DEBUG socket.nagle.Client - RTT:42, receive: hello world 137 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world 178 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world 218 [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world 259 [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world 300 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world 341 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world 382 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world 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算法
[test5@cent4 ~]$ java socket.nagle.Client 0 [main] DEBUG socket.nagle.Client - WriteSplit:false 27 [main] DEBUG socket.nagle.Client - RTT:4, receive: hello world 31 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 34 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 38 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 42 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 44 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 47 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world 50 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 53 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world 54 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world实验3:当WriteSplit=true and TcpNoDelay=true 禁用nagle算法
[test5@cent4 ~]$ java socket.nagle.Client 0 [main] DEBUG socket.nagle.Client - WriteSplit:true 25 [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world 28 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 31 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 33 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world 35 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 41 [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world 49 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 52 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 56 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 59 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world实验4:当WriteSplit=false and TcpNoDelay=true 禁用nagle算法
[test5@cent4 ~]$ java socket.nagle.Client 0 [main] DEBUG socket.nagle.Client - WriteSplit:false 21 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 23 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world 27 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world 30 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 32 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world 35 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 38 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 41 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world 43 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world 46 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world实验2到4,都没有出现延时的情况。
注意:以上实验在windows上测试下面的代码,客户端和服务器必须分在两台机器上,似乎winsock对loopback连接的处理不一样。下面的我的做法是:服务端与客户端都在一台Linux机上。
相关文章推荐
- java socket参数详解:TcpNoDelay
- java socket参数详解:TcpNoDelay
- socket参数详解:TcpNoDelay
- java socket参数详解:SO_REUSEADDR
- Java Socket参数详解
- java socket参数详解:SoTimeout
- java socket参数详解:SoTimeout
- java socket参数详解:KeepAlive
- Java学习总结12——网络1(Java Socket参数详解)
- java socket参数详解:SoLinger
- java socket参数详解:SendBufferSize和ReceiveBufferSize
- java socket参数详解:BackLog
- Java套接字Socket编程--TCP参数
- java socket参数详解:OOBInline和UrgentData (可用于简体网络是否畅通)
- java socket参数详解:OOBInline和UrgentData
- ChannelOption.SO_BACKLOG, 1024-> java socket参数详解:BackLog
- java socket参数详解:OOBInline和UrgentData
- java socket参数详解:BackLog
- Java套接字Socket编程--TCP参数
- Java套接字Socket编程--TCP参数