【Java TCP/IP Socket编程】----深入剖析----TCP数据传输底层实现

目录

 

套接字底层数据结构

TCP数据传输底层实现

案例


--------笔记来自于书籍《Java TCP/IP Socket编程》

套接字底层数据结构

    要熟悉掌握网络编程,就需要理解套接字的具体实现所关联的数据结构和底层协议的工作细节,TCP套接字更是如此(Socket实例),需要理解套接字(socket)和Java中类Socket的概念,套接字(socket)指的是底层抽象,这种抽象由操作系统提供或者JVM自己实现(如嵌入式系统中),而Java中类Socket上的操作则转换成了这种底层抽象上的操作。需要注意的是,运行在统一主机上其他程序可能也会通过底层套接字抽象来使用网络,因此会与Java Socket实例竞争系统资源,如端口等。

    “套接字结构”指底层实现(包含了JVM和TCP/IP,通常是后者)的数据结构集,这些数据结构包含了特定Socket实例锁关联的信息。套接字结构除其他信息外还包含了:

    1.该套接字所关联的本地和远程互联网地址和端口号。

    2.一个FIFO(先进先出,First In First Out)队列用于存放接收到的等待分配的数据,以及一个用于存放等待传输的数据的队列。

    3.对于TCP套接字,还包含了与打开和关闭TCP握手相关的额外协议状态信息。

    TCP提供了一种可信赖的字节流服务,任何写入Socket的OutputStream的数据副本都必须保留,直到其连接的另一端成功接收。向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到本地缓冲区。就算在Socket的OUtputStream上调用flush()操作,也无法保证数据发送到信道中,此外字节流服务的自身属性决定了其无法保留输入流中消息的边界消息。

    对于DatagramSocket来说,数据包并没有为重传进行缓存,任何时候调用send()方法返回后,数据就已经发送给了执行传输任务的网络子系统。如果网络子系统由于某种原因无法处理这些消息,该数据包将毫无提示地被丢弃(很少发生)。

TCP数据传输底层实现

    使用TCP套接字需要注意的是:不能假设在连接的一端将数据写入输出流和另有单从输入流读取数据之间有任何一致性。

尤其是在发送端由单个输出流的write()方法传输的数据,可能会通过另一端的多个输入流read()方法来获取;而一个read()方法可能会返回多个write()方法传输的数据。我们可以认为TCP连接上发送的所以字节序列在某一瞬间被分成3个FIFO队列:

    1.SendQ:在发送端底层实现中缓存的字节,这些字节已经写入输出流,但还没有在接收端主机上成功接收。

    2.RecvQ:在接收端底层实现中缓存的字节,等待分配到接收程序,即从输入流中读取。

    3.Delivered:接收者从输入流已经读取到的字节。

发送端:调用out.write()方法将向SendQ追加字节,TCP协议负责将字节按顺序从SendQ移动到RecvQ。这个转移过程无法由用户程序控制或者直接观察到,并且在块中发生,这些块的大小在一定程序上独立与传递给write()方法的缓冲区大小。

接收端:从Socket的InputStream读取数据时,字节就从RecvQ移动到Delivered中,而转移的块的大小依赖于RecvQ中的数据量和传递给read()方法缓冲区大小。

需要说明一点的是:在UNIX(Linux)和Windows平台上,可以用netstat命令查看:SendQ和RecvQ中的字节数,本地和远程IP地址和端口,以及连接状态等

案例

byte[] buffer0 = new byte[1000];
byte[] buffer1 = new byte[2000];
byte[] buffer2 = new byte[5000];
...
Socket socket = new Socket(destAddr,destPort);
OutputStream out = socket.getOutputStream();
...//设置缓冲区的代码
out.write(buffer0);
...//设置缓冲区的代码
out.write(buffer1);
...//设置缓冲区的代码
out.write(buffer2);
..
socket.close();

说明:其中圆点代表了设置缓冲区数据的代码。以下讨论中in代表接收端Socket的InputStream,out代表了发送端socket的OutputStream.

1.上面的程序中3次调用out.write()方法后,另一端调用in.read()方法前,SendQ,RecvQ,Delivered3个队列的可能状态。不同的阴影效果分别代表了上文中3次调用write()方法传输的不同数据。即第一次写入1000字节+第二次写入中前500字节在RecvQ队列中,而第二次写入中剩余1500字节+第三次写入5000字节在SendQ队列中。

2.现在假设接收端调用read()方法时使用的缓冲区数组大小为2000字节,read()方法调用则将把等待分配队列(RecvQ)中的1500字节全部移动到数组中,返回值为1500。注意,这些数据包含了第一次和第二次调用write()方法时传输的字节。再过一段时间,当TCP连接传完更多数据后,这三部分的状态如下:

3.如果接收端现在调用read()方法时使用4000自己的缓冲区数组,将很多字节从等待分配的队列(RecvQ)转移到已分配队列(Delivered)中。这包括第二次调用write()方法时剩下的1500字节加上第三次调用write()的前2500字节。此时队列状态如下:

下次调用read()方法返回的字节数,取决于缓冲区数组的大小,以及发送方套接字/TCP实现通过网络向接收方实现传输数据的时机。数据从SendQ到RecvQ缓冲区的移动过程对应用程序协议设计有重要的指导性。

猜你喜欢

转载自blog.csdn.net/lili13897741554/article/details/83104539