1关于tcp delayedack实践(一)tcp
以及一次简单的 HTTP 调用,为什么时延这么大?抓个包分析下
本文做下测试,以网络编程中Nagle算法和Delayed ACK的测试中代码:
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8000));
System.out.println("Server startup at 8000");
for (;;) {
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();
out.write((line + "\r\n").getBytes());
}
catch (Exception e) {
break;
}
}
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws Exception {
/**
* null tcp telay and no flush
* 1 tcp no delay and noflush
* 2 tcp delay and flush
* 3 一个包
*/
String flag = null;
if (args != null && args.length > 0) {
flag = args[0];
}
Socket socket = new Socket();
if ("1".equals(flag))
socket.setTcpNoDelay(true);
String host = "49.235.75.155";
socket.connect(new InetSocketAddress(host, 8000));
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 (!"3".equals(flag)) {
out.write(head.getBytes());
out.write(body.getBytes());
if ("2".equals(flag))
out.flush();
} else {
out.write((head + body).getBytes());
}
String line = reader.readLine();
System.out.println("RTT:" + (System.currentTimeMillis() - label) + " ,receive:" + line);
此处可以sleep,看客户端的delayed ack多久
}
in.close();
out.close();
socket.close();
}
}
Null
RTT:62 ,receive:hello world
RTT:107 ,receive:hello world
RTT:100 ,receive:hello world
RTT:109 ,receive:hello world
RTT:104 ,receive:hello world
RTT:104 ,receive:hello world
RTT:100 ,receive:hello world
RTT:100 ,receive:hello world
RTT:100 ,receive:hello world
RTT:100 ,receive:hello world

结论:
1)如11,与文中相同,除第一次外,服务端收到hello,但继续等待world\r\n,未等到,触发linuxack超时,返还ack,这个过程均有40ms的delayed ack,客户端收到第一个ack后,发送第二个包(world\r\n)
2)如15,客户端收到服务端的helloworld\r\n后返回ack间隔30ms(程序执行循环时间),该ack跟着下一个write(hello)发送到服务端,最后一条跟着fin包

那么mac客户端的delayed ack多少呢?我们可以在每次循环结束后sleep 1s

16是客户端循环开始write(hello),可以看到客户单收到返回ack 也是40ms,在ack后,sleep 1s进行下一轮发送
1
RTT:44 ,receive:hello world
RTT:47 ,receive:hello world
RTT:45 ,receive:hello world
RTT:37 ,receive:hello world
RTT:48 ,receive:hello world
RTT:50 ,receive:hello world
RTT:49 ,receive:hello world
RTT:41 ,receive:hello world
RTT:49 ,receive:hello world
RTT:50 ,receive:hello world

可以看到,客户端关闭 nagle后,连发了hello和world\r\n两个包,中间未等服务端的ack,服务端读到\r\n后,返回hello world,同时捎带了ack
2
RTT:68 ,receive:hello world
RTT:111 ,receive:hello world
RTT:98 ,receive:hello world
RTT:101 ,receive:hello world
RTT:97 ,receive:hello world
RTT:103 ,receive:hello world
RTT:107 ,receive:hello world
RTT:100 ,receive:hello world
RTT:100 ,receive:hello world
RTT:100 ,receive:hello world

可以看到flush是没用的,并不能打破nagle
(译者注:调用flush()方法只是将数据写入操作系统缓存中,并不保证数据会立即发送)http://ifeve.com/java-socket/
3
RTT:38 ,receive:hello world
RTT:36 ,receive:hello world
RTT:45 ,receive:hello world
RTT:37 ,receive:hello world
RTT:46 ,receive:hello world
RTT:31 ,receive:hello world
RTT:42 ,receive:hello world
RTT:36 ,receive:hello world
RTT:41 ,receive:hello world
RTT:44 ,receive:hello world

同样的,使用sleep在每次循环末尾后,同样发现mac客户端delayedack 40ms

附件:
tcpdump在服务端8000端口 tcpdump使用与ping
tcpdump -i eth0 tcp and port 8000 -s 0 -w traffic.pcap
一个重要的前提:在连接初始建立的时候,按照上面描述会进入quick ACK模式
https://www.cnblogs.com/lshs/p/6038635.html 详细世间了quick ACK触发和退出机制《TCP系列28—窗口管理&流控—2、延迟ACK(Delayed Acknowledgments)》
https://www.cnblogs.com/purpleraintear/p/6013725.html 从源码分析了什么时候quick ACK 《Linux下TCP延迟确认(Delayed Ack)机制导致的时延问题分析》
所以http短链接不会deleyed ack,但请注意,微服务中的长连接就有这样的情况了,请看关于tcp delayedack实践(三)服务间调用
总结:
1 本文通过抓包,验证了nagle+delayed ack的情况,观察到linux和mac的默认delayed ack时间为40ms
2 客户端关闭nagle后,前后两PSH包发送间不再等待对方的ack,直接连着发送2个PSH包,间隔不再相差40ms,而是15ms,整体响应时间从100ms降到45ms
3 验证了flush不能打破nagle,调用flush()方法只是将数据写入操作系统缓存中,并不保证数据会立即发送,在tcp层面,由操作系统控制发送
4 使用一次写入,整体响应时间从100ms降到40ms
5 在连接初始建立的时候,按照上面描述会进入quick ACK模式,所以http短链接不会deleyed ack,但请注意,微服务中的长连接就有这样的情况了,请看关于tcp delayedack实践(三)服务间调用
6 故应避免 写写读 这种代码
1.write(header)
2.write(body)
3.read(response)
服务器read写法如下
1. read(request)
2. process(request)
3. write(response)
https://blog.csdn.net/wdscq1234/article/details/52432095 Hello 5个包分开与nagle 《TCP-IP详解:Nagle算法》
浙公网安备 33010602011771号