socket参数详解:TcpNoDelay
2015-06-17 10:59
706 查看
jedis中socket的各种设置如下
socket = new Socket();
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
SendBufferSize和ReceiveBufferSize
TCP发送缓存区和接收缓存区,默认是8192,一般情况下足够了,而且就算你增加了发送缓存区,对方没有增加它对应的接收缓冲,那么在TCP三握手时,最后确定的最大发送窗口还是双方最小的那个缓冲区,就算你无视,发了更多的数据,那么多出来的数据也会被丢弃。除非双方都协商好。
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 plain
copy
print
?
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 plain
copy
print
?
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 plain
copy
print
?
[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 plain
copy
print
?
[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 plain
copy
print
?
[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 plain
copy
print
?
[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 plain
copy
print
?
[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机上。
socket = new Socket();
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
SendBufferSize和ReceiveBufferSize
TCP发送缓存区和接收缓存区,默认是8192,一般情况下足够了,而且就算你增加了发送缓存区,对方没有增加它对应的接收缓冲,那么在TCP三握手时,最后确定的最大发送窗口还是双方最小的那个缓冲区,就算你无视,发了更多的数据,那么多出来的数据也会被丢弃。除非双方都协商好。
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 plain
copy
?
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 plain
copy
?
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 plain
copy
?
[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 plain
copy
?
[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 plain
copy
?
[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 plain
copy
?
[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 plain
copy
?
[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机上。
相关文章推荐
- CvValArray和CvCARTNode
- 在Ubuntu系统上安装Ghost博客平台的教程
- 为什么我要用 Node.js? 案例逐一介绍
- NodeJS框架express的途径映射(路由)功能及控制
- [LeetCode][JavaScript]Populating Next Right Pointers in Each Node
- Nodejs正则表达式函数之match、test、exec、search、split、replace使用详解
- nodejs socket.io初探
- leetcode Count Complete Tree Nodes
- Count Complete Tree Nodes
- leetcode--CountCompleteTreeNodes
- 使用async解决nodejs异步问题
- Swap Nodes in Pairs
- [LeetCode] Count Complete Tree Nodes
- HDFS中的NameNode和DataNode
- Node.js Express 使用 在Mac电脑
- node js 安装 以及 nvm 使用 在mac 电脑
- Count Complete Tree Nodes
- 配置haoop,datanode启动不了解决方式
- C实现 LeetCode->RemoveNthNodeFromEndofList
- LeetCode OJ 之 Count Complete Tree Nodes(计算完全二叉树的结点数目)