您的位置:首页 > 理论基础 > 计算机网络

java socket参数详解:TcpNoDelay

2014-05-21 19:12 411 查看
原文链接:http://blog.csdn.net/huang_xw/article/details/7340241

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?

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();

}

}

接收端代码

[java] view
plaincopyprint?

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;

}

}

}

}

}

实验结果:

[plain] view
plaincopyprint?

[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算法

[plain] view
plaincopyprint?

[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算法

[plain] view
plaincopyprint?

[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算法

[plain] view
plaincopyprint?

[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算法

[plain] view
plaincopyprint?

[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机上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: