前言
前面几章中详细介绍了TCP套接字的各种用法,现在我们详细介绍一个TCP套接字中的缓冲。
TCP中的缓冲
作为程序员,在使用TCP套接字时需要记住的最重要的一点是:
——“不能假设在连接的一端,将数据写入输出流和在另一端从输入流读取数据之间有任何的一致性” (也就说 在连接的一端与另一端的连接之间, 写入数据与读取数据
不存在一致性 )
场景分析
“TCP发送端与接收端中,写入数据与读取数据之间不一致”,多发生在 发送端由单个输出流的write( )方法传输的数据,可能会通过另一端的多个输入流的read( )
方法来获取; 而一个read( )方法可能会返回多个write( )方法传输的数据。
1.为了实现以上情况,示例代码如下:
byte[] buffer0 = new byte[1000];
byte[] buffer1 = new byte[2000];
byte[] buffer2 = new byte[5000];
Socket s = new Socket(inetAddress, port);
OutputStream out = s.getOutputStream();
...
out.write(buffer0);
...
out.write(buffer1);
...
out.write(buffer2);
...
s.close();
其中,圆点表示设置缓冲区数据的代码,但不包括对out.write( )的调用, " in "代表接收端Socket的InputStream , "out" 代表发送端Socket的OutputStream。
2. 实现原理:
该TCP连接向接收端传输8000个字节。在连接的接收端,这8000字节的发送方式屈取决于连接两端out.write( )方法和in.read( )方法的调用时间差,以及提供给in.read( )方法
的缓冲区大小。我们可以认为在TCP连接上,发送的所有字节序列在某一个瞬间被分成了3个FIFO队列(先入先出队列):
(1)SendQ:在发送端底层实现缓存的字节,这些字节已经写入到输出流,但是还没有在接收端上成功接收;
(2)RecvQ:在接收端底层实现缓存的字节,等待分配到接收程序——即从输入流中读取;
(3)Delivered:接收端从输入流已经读取到的字节;
调用out.write( )方法可以向SendQ中追加字节,TCP协议负责将字节按顺序从SendQ移动到RecvQ中(注意:这个移动过程用户无法由用户程序控制或直接观察到),并且在
块中发生,块的大小在一定程度上取决于传递给write( )方法的缓冲大小;
接收端从InputStream流中读取数据时,字节从RecvQ中移动到Delivered中,而移动的块大小取决于RecvQ中的数据量和传递给read( )方法的缓冲区的大小;
3. 实现分析:
图6-2展示了发送端3次调用out.write( )后, 接收端调用in.read( )方法前,三个队列的信息如下:( 不同阴影效果分别代表了发送端3次调用out.write( )方法所传输的不同数据 ):
假设接收端调用read( )方法时使用的缓冲数组大小为2000字节,in.read( )方法的调用则把等待分配队列(RecvQ)中的1500个字节全部转移到缓冲数组中(注意:这些数据
包括了发送端第一次发送的数据和第二次发送的数据),再过一段时间,当发送端发送完更多
的数据后(这个时候发送端发送了6000个字节,而接收端则接收了6000个字节),三个队列的信息如下(图6-3):
如果接收端在接收数据调用in.read( )方法时,使用4000个字节的缓冲数组,将会有4000个字节从等待分配队列(RecvQ)转移到已分配队列(Delivered)中,此时,三个队
列的信息如下:
注意:下次接收端在调用in.read( )方法接收数据,取决于缓冲数组的大小以及发送端发送数据的时机;
结论总结
通过上边的分析证明——“在TCP套接字的发送端与接收端中,写入数据与读取数据并不是一致的”。