1关于tcp delayedack实践(一)tcp

根据网络编程中Nagle算法和Delayed ACK的测试

以及一次简单的 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   

 tcpackdelayed.zip

 

一个重要的前提:在连接初始建立的时候,按照上面描述会进入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算法》

 

posted on 2019-12-02 14:15  silyvin  阅读(690)  评论(0编辑  收藏  举报