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