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(用户进程晚了)导致沾包
(2)2次flush
2次调用flush是为了强调在发送方分开发送(尽管调用flush()方法只是将数据写入操作系统缓存中,并不保证数据会立即发送http://ifeve.com/java-socket/ )的情况下,接收方也可能沾包
hello
hello
hello guanxinquan
hello guanxinquan
hello guanxinquan
hello guanxinquan
hello
抓包显示,所有包均是分开发的
总结:
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函数注释):