java socket参数详解:TcpNoDelay

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 plaincopyprint?

    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 plaincopyprint?

    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 plaincopyprint?

    [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 plaincopyprint?

    [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 plaincopyprint?

    [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 plaincopyprint?

    [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 plaincopyprint?

    [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机上。

猜你喜欢

转载自san-yun.iteye.com/blog/1748221