4socket缓冲区与沾包 nagle in tcp

https://blog.csdn.net/kdy527/article/details/85106657 该链接中,认为:

服务端与客户端建立连接后,服务器端先向缓冲区写入两条信息,在第一条信息写入时,缓冲区并未写满,因此在第二条信息输入时,第一条信息很可能还未发送,因此两条信息可能同时被传送到客户端。另一方面,如果在第二条信息写入时,第一条已经发送出去,那么客户端的接收操作仅会获得第一条信息,因为客户端没有继续接收的操作,因此第二条信息在缓冲区中,将不会被读取,当socket关闭时,缓冲区将被释放,未被读取的数据也就变的无效了。

 

我持不同观点,两个包大概率是分开发送的,导致另一端有时接收一条,有时2条导致沾包的原因是接收方未及时read(用户进程晚了),故本文做了实践

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;


public class Client {

    public static final int PORT = 12123;
    public static final int BUFFER_SIZE = 1024;

    public static void main(String []f) throws IOException {
        new Client().client();
    }

    //客户端代码
    public void client() throws UnknownHostException, IOException{

        final String s1 = "49.235.75.155";
        final String s2 = "localhost";
        byte[] buffer;
        Socket s = new Socket(s1,PORT);//创建socket连接
//        s.getOutputStream().write(new byte[BUFFER_SIZE]);
//        s.getOutputStream().flush();
        int i = s.getInputStream().read(buffer = new byte[BUFFER_SIZE]);
        System.out.println(new String(buffer,0,i));
    }
}

 

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static final int PORT = 12123;
    public static final int BUFFER_SIZE = 1024;

    public static void main(String [] f) throws IOException, InterruptedException {

        boolean flush = false;
        if(f != null && f.length > 0)
            flush = true;

        System.out.println("flush - " + flush);
        new Server().server(flush);
    }

    //服务端代码
    public void server(boolean flush) throws IOException, InterruptedException{
        ServerSocket ss = new ServerSocket(PORT);
        while(true) {
            Socket s = ss.accept();

            //这里向网络进行两次写入
            s.getOutputStream().write("hello ".getBytes());
            if(flush)
                s.getOutputStream().flush();
            s.getOutputStream().write("guanxinquan ".getBytes());
            s.getOutputStream().flush();
            s.close();
        }
    }
}

 

 

(1)一次flush

仅调用一次flush(尽管调用flush()方法只是将数据写入操作系统缓存中,并不保证数据会立即发送http://ifeve.com/java-socket/ )是为了验证,有没有可能 在不flush的情况下以及受flush激活的情况下,第一个包同第二个包一起出去

 

5次调用client

hello

hello

hello guanxinquan

hello guanxinquan

hello

 

显示,第3 4次出现沾包,抓包显示server5次报文都是分开发的,即使是第3 4次client接收方出现沾包,server发送端也是分开发的,只是由于接收端未及时read(用户进程晚了)导致沾包

 

tcp沾包1.pcap.zip

 

(2)2次flush

2次调用flush是为了强调在发送方分开发送(尽管调用flush()方法只是将数据写入操作系统缓存中,并不保证数据会立即发送http://ifeve.com/java-socket/ )的情况下,接收方也可能沾包 

 

hello

hello

hello guanxinquan

hello guanxinquan

hello guanxinquan

hello guanxinquan

hello

 

抓包显示,所有包均是分开发的

 

tcp沾包2.pcap.zip

 

 

总结:

1 本文通过抓包,验证了即使启用nagle的情况下,https://blog.csdn.net/kdy527/article/details/85106657中粘包的原因 为接收端没及时read(用户进程晚了),多次尝试,在本例中,发送端是分2个包发的,

当然不排除在其它情况下,发送端攒了一起发

就比如:https://blog.csdn.net/wdscq1234/article/details/52432095

Nagle算法主要是避免发送小的数据包,要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的小分组,并在确认到来时以一个分组的方式发出去。”由于nagle的存在,使得tcp在等待ack的时间区间内,足够时间攒到第2-n个包组成一个tcp包(本来第2个包直接出去了,也没机会攒)

又比如:netty(十八)《netty粘包消息定长 实践》中,沾包哪里来的?中netty client启用nagle的情况

这两个例子都有如下特点:

0)set  nagle true

1)send 1 pack

2)wait for ack of 1pack

3)now there are 2-5packs,send them in one tcp psh pack

所以第1个包一定是先出去的,无论是否启用nagle,本文没有出现发送端粘包是因为本文只有2个包,故一定是分开发的,不会上来就攒了2个包一起发,第一个包有充分理由先出去

启用nagle  pack1先出去  等ack的时候攒2-n      发送/接受都有可能导致粘包

禁用nagle  pack1先出去  不等ack一个一个发2-n    发送不会粘包,接收可能粘包

 

2 在期间有发现两个PSH包连着发,明明没有禁用nagle啊,原来是因为如果是fin包,会打破nagle,不等待对方ack而直接发送带有fin的第二个PSH包

   理论上  ----》  

 

3 Nagle算法的规则(百度百科)(可参考tcp_output.c文件里tcp_nagle_check函数注释):

(1)如果包(tcp报文-tcp报头)长度达到MSS,则允许发送;
(2)如果该包含有FIN,则允许发送;
(3)设置了TCP_NODELAY选项,则允许发送;
(4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
(5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
 
 11读缓冲区(滑动窗口)耗尽与write阻塞、拆包、延迟(一)中的情况不属于nagle范畴的允许发送,之所以不同于大多数启用nagle情况的发送端沾包是因为,缓冲区大小==包大小,在等待ack的过程中,最多只能攒1个包

posted on 2019-12-04 21:58  silyvin  阅读(303)  评论(0编辑  收藏  举报