socket(一) 通信分包通信性能测试(setTcpNoDelay)
标签:
it |
分类: socket |
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,悲剧的无以言语。
这种时候只有等待一端超时并发送数据才能继续往下走。
代码:
发送端代码
-
package
socket.nagle; -
-
import
java.io.*; -
import
java.net.*; -
import
org.apache.log4j.Logger; -
-
public
class Client { -
private static Logger class);logger = Logger.getLogger(Client. -
public static void main(String[] throwsargs) 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( newInputStreamReader(in)); -
-
String head = "hello " ; -
String body = "world\r\n"; -
for ( inti 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 class);logger = Logger.getLogger(Server. -
-
public static void main(String[] throwsargs) 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( newInputStreamReader(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机上。

加载中…