【Java网络原理】 五

本文主要介绍了TCP传输控制协议的报头字段意义以及TCP协议的十大核心特性。

一.TCP传输控制协议

1.TCP报文格式

【Java网络原理】 五_第1张图片

>端口

范围是0-65535 ,只有确定了端口号,才知道把数据报交给哪个应用程序。

>4位首部长度

TCP报头是变长的, 4bit的范围是0-15

此处的单位是4字节,因此真正的长度要乘4得到

因此tcp报头最大长度是15*4=60字节

而前20个字节是固定的,因此tcp报头的最短长度是20字节

使用首部长度,来确认报头到哪里结束,载荷数据从哪里开始;

>选项

报头变长的体现,可以有,也可以没有,选项的长度也决定了报头最后的长度

>保留(6位)

目前未使用,先占个位置,后面可能需要使用,给未来留下扩展的空间

>32位序列号

>32位确认序列号

>16位校验和

>16位窗口大小

>16位紧急指针

二.TCP十大核心特性

1.确认应答

确认应答是保证可靠性最核心的机制

TCP是有连接,可靠传输,但是由于可靠传输是内核实现的,程序员写代码的时候,并不能很好的感知。

比如:我对女神发出提问,女神对我的问题返回了一个应答

【Java网络原理】 五_第2张图片

当发送多条数据的时候,可能会出现后发先至,这会导致女神的应答乱套

解决方法是:对发送的报文进行编号,女神对我的应答也进行编号

比如:

【Java网络原理】 五_第3张图片

TCP中,面向字节流传输

1.针对字节进行编号,而不是针对条

【Java网络原理】 五_第4张图片

对于一串要传输的字节,只要知道这串字节的开始编号(报头的32位序号指明),以及数据的长度,每个字节的编号自然也就知道了

2.应答报文也是和收到的数据的序号相关联

比如:

【Java网络原理】 五_第5张图片

报头中的32位序号:报文的第一个字节的序号

32位确认序号:收到的最后的一个序号+1

确认报文 ACK:

如果ACK0 :表示这是一个普通报头,此时只有32位序号有效

如果AXK1:表示i这是一个应答报文,此时 序号 确认序号 都有效

确认应答,是TCP保证可靠性的最核心机制!

注意:在TCP报文中,是无法知道它的载荷数据是多长的,只能结合IP协议才知道,TCP载荷长度=IP载荷长度 - TCP报头长度

丢包:网络上,很可能发送一个数据,然后丢了。

往往是因为网络繁忙,后面等的数据太久了,就可能被丢弃了,网络负载越高,越繁忙,就越容易丢包

如果真的丢了,发送方就会启动超时重传

2.超时重传

超过一定时间之后,就将报文进行重传

我们把丢包分两种:

1.发的消息本身丢了

>触发超时重传

2.应答报文丢包了

>也会触发超时重传,发送方一旦重传,接收方同一消息收到了两次,就会出现问题,因此接收方需要把重复的数据丢弃,调用inputStream.read的时候,读到的不会出现重复

去重

直接使用tcp的序号作为判定依据,tcp会在内核中,给每个socket都安排一个内存空间,相当于一个队列,也称为 接收缓冲区

收到的数据,都会被放到接收缓冲区里,并且按照序号排列好顺序

收到数据,接收方的网卡,把数据放到对应的socket的接收缓冲区中,应用程序调用read,就相当于从这个接收缓冲区消费数据;

当被read走,就可以从队列中删除了

由于接收缓冲区是按照序号有序排列的,如果排列队首元素序号,已经超过新收到的这个数据的序号,表明新收到的数据对应的数据之前已经被读过

对方上述两种情况,发送方是无法区分的

注意:

1.由于接收缓冲区会对数据排列好顺序,就不必考虑数据传输的先后顺序了,因此也解决了后发先至的问题。

2.

正常情况下,丢包概率比较小,重传可以提高传输成功的可能性。

另外啊,超时时间不是固定的值,会随着轮次的增加,进一步增加

如果重传次数达到一定程度,就会放弃重传,此时就会尝试 重置TCP连接

TCP复位报文 

RSI如果为1就是复位报文

如果网络中出现严重故障,复位操作也无法成功,最终就会删除保存的对端的信息

超时重传也是TCP可靠性机制的有效补充

3.连接管理

1.建立连接  -->三次握手

握手:发一个发招呼的数据,并不会携带业务信息

A和B完成建立连接的过程,就需要三次这样的打招呼的数据交互

【Java网络原理】 五_第6张图片

注意:中间的两次可以合并成一次。

【Java网络原理】 五_第7张图片

为什么合并?

合并之后,节省了封装和分用的过程,降低成本提高了效率,而且还能起到消息协商的效果。

消息协商

通信的时候涉及到一些参数,需要双方保持一致,通过协商,来确定参数。

比如双方的序号从几开始(一般不从0/1),这样做主要是保证两次连接,消息的序号能够有较大的差异,从而去判定出某个消息是否属于这个连接的

比如:某个消息知道直到连接断开才到达对端,此时的连接已经不是彼时的连接了,这个时候,就可以通过序号,明显判别出这个是上一个连接的消息,就可以直接丢弃。

总结:

三次握手的初心有两方面

1.投石问路,验证通信路径是否畅通,双方的发送/接收能力是否正常

2.协商必要的参数,使客户端和服务器使用相同的参数进行消息传输

ACK是应答报文

SYN申请建立连接的请求 同步报文段

三次握手的过程可以化简成如下:

【Java网络原理】 五_第8张图片

2.断开连接  -->四次挥手

连接,通信双方在各自的内存中保存了对端的信息;如果不需要连接了,就得即使释放上述的空间

FIN 为1,就是结束报文段

【Java网络原理】 五_第9张图片

经过上述四个步骤之后,连接就彻底不能使用了,双方就可以把各自保存的对端信息的空间释放了


四次挥手:

为什么是四次?能不能是合并?

FIN的触发,是应用程序代码控制的,调用socket.close()或者进程结束,就会触发FIN;ACK则是内核控制的收到FIN立即就会返回ACK

因此,如果close执行的快,也有可能立即触发FIN,导致ACK和FIN的合并,形成三次挥手。


问题:

1.如果服务器始终不close会怎样,客户端的连接就始终不关闭吗?

【Java网络原理】 五_第10张图片

此时,服务器的TCP状态,就处于CLOSE_WAIT状态

站在服务器的角度,虽然这里的连接没有关闭,但是这个连接其实已经不能正常使用了

针对当前socket进行读操作,如果数据还没读完,能正常读到缓冲区的数据;如果已经读完了,此时就会读到EOF

针对当前socket进行写操作,直接就会触发异常;

无论无何,这个连接,已经没用了,关闭是唯一的选择。更极端情况,忘记写close了,等不到关闭,就会单方面放弃连接。

2.如果通信中,出现丢包了,怎么办?

【Java网络原理】 五_第11张图片

尽可能重传,如果重传仍然失败,连续多次,此时仍然会单方面释放连接

丢包分两种情况:

1.第一组丢了

客户端直接重传FIN即可

2.第二组丢了

>FIN丢了  重传

>ACK丢失 

如果最后一个ACK丢失,B就会重传一个FIN;如果此时A已经释放连接了,重传的FIN就无人进行ACK了;

因此,需要让A在发出去最后一个ACK之后让连接等一会;如果等了一会之后,对方没有重传FIN,就认为ACK已经被收到了

此时A才能正确释放连接。

A等待的这个时候叫做:MSL等待时间是网络上任意两点之间传输数据的最大空间*2

4.滑动窗口

提高传输效率,更准确的说是让TCP的可靠传输的前提下,效率不要太拉跨

如果滑动窗口发一次数据等待一个应答,就会导致消耗大量的时间等待ack,使用滑动窗口,就是为了缩短上述等待时间:

【Java网络原理】 五_第12张图片

有滑动窗口:

【Java网络原理】 五_第13张图片

批量发送:一次性发一组数据,发这一组数据的过程中,不需要等待ack,此时就相当于用一份等待时间等四个ack

窗口:一次性发多少数据,不用等ack这样的大小

窗口越大,批量发送的数据就越多,效率就越高;当然不能无限大,接收方能否处理过来,中间的网络设备能否承受住,都是未知数。

滑动窗口是一个形象的比喻,实质上就是批量发送数据。


问题:丢包

>数据丢了

>重传

比如:当1001-2000丢失,即使2001-3000数据到达,B返回的ACK确认序号仍然是1001

也就是索要1001数据,当A连续多次收到来自B的索要1001数据,A就会重传1001-2000.

>ACK丢了

不用做任何处理!

确认序号:该序号之前的数据都收到了!

比如,就算A没有收到1001确认序号,但是收到了2001序号,就足以表明1001已经收到了。

除非所有ACK都丢了,否则丢一部分,都是没有影响的!

注意:

滑动窗口不是使用TCP一定会涉及的,如果通信双方大规模传输数据,会有滑动窗口;如果数据规模小,那么就不会使用滑动窗口;

5.流量控制

作为滑动窗口的补充,给滑动窗口踩刹车

滑动窗口,窗口越大,传输效率越高

但是传输的窗口也不能无限大,如果太大,接收方/中间链路就可能处理不过来,这样一旦丢包,就得重传,窗口大反而影响了效率。

接收缓冲区

发送方发过来的数据会先放到接收方的接收缓冲区;如果这个缓冲区满了,继续发数据,就会丢包。

流量控制就是根据接收方的处理能力,来限制发送方的发送速度(窗口)

如何衡量接收方的处理速度

此处使用接收缓冲区剩余空间大小来作为衡量指标。

报头中的16位窗口大小:只对ack报文才有意义

这个数字返回给发送方,就可以作为下一轮发送的参考数据。

对于大小:

选项中有一个选项是窗口大小扩展因子

实际的窗口大小是16位窗口<<扩展因子(此时就很大了)

当接收缓冲区满的时候,发送方就会等待,周期性的发送窗口探测包(不会携带具体数据),只是为了触发ACK,查询当前接收缓冲区的情况,当不是0了,就可以继续发送了。


6.拥塞控制

总的传输效率,是一个木桶效应,取决于最短板

如果中间某个环节,转发能力比较差,此时发送方的发送速度就不应该超过这里的阈值

具体怎么去衡量中间设备的转发能力呢

采用实验的方式动态调整

1.使用一个较小的窗口传输,如果传输通畅,就调大窗口

2.使用一个较大的窗口,如果出现丢包/堵塞,就调小窗口

拥塞控制:

1.慢启动

刚开始通信的时候,先使用一个非常小的窗口

2.指数增长(*2)

3.线性增加(+n)

4.拥塞窗口回归小窗口

当窗口增长过程中,如果传输出现丢包,就把窗口调整为最初的小窗口,继续回到指数增长,线性增长,并根据丢包的窗口大小,调整阈值(丢包窗口大小/2)

【Java网络原理】 五_第14张图片

 这样的调整就可以很好的适应多变的网络环境。

实际发送的窗口=min(拥塞窗口,流量控制窗口)

拥塞控制和流量控制,共同限制了滑动窗口机制,可以使滑动窗口,能够在可靠性的前提下,提高传输效率。


7.延迟应答

提高传输效率的机制

在返回ack的时候,拖延一点时间,就可以给应用程序腾出更多的消费数据,接收缓冲区的剩余空间就更大了。

此处,到底通过延迟应答能提高多少速度,还是取决于接收方的实际出列能力

8.捎带应答

在延时应答的基础上,引入进一步提高效率的方式

延时应答是让ack传输的时机更慢

捎带应答是基于延时应答,让数据进行合并

比如:

由于ack会延时应答,响应就可以直接捎带上ack一起发送

【Java网络原理】 五_第15张图片

数据报合并成一个,效率会有明显的提升

能合并的原因:

1.时机上是可以同时的

2.ack 数据本身不携带载荷,和正常的数据也不冲突,完全可以让这一个数据报,既可以携带在载荷数据又带有ack的信息


9.面向字节流

在面向字节流的情况下,会产生粘包问题(粘的是应用层数据报)

通过tcp read/write的数据,都是tcp报文的载荷,也就是应用层数据

 发送方一次性是可以发送多个应用层数据报的,但是接收的时候,怎么区分一个完整的数据报

解决方法是,设计合理的应用层协议

1.应用层协议中,引入分隔符,区分包之间的边界

2.应用层协议中,引入包长度,也能区分包之间的边界


10.TCP异常情况处理

网络本身就会存在一些变数,导致tcp连接不能继续正常工作

(1)进程崩溃

进程没来-->PCB没了 --> 文件描述符表也就没了  -->相当于调用socket.close()

崩溃的这一方就会发出FIN,进一步触发四次挥手,此时连接就正常释放了

此时tcp的处理和进程正常退出没啥区别

(2)主机关机(正常关机)

正常关机,就会先强制终止所有的进程,就和上面的情况是一样的

主机关机会有一定的时间,在这个时间之内,四次挥手可能没挥完,但是也没关系

如果有包没送达,自然会单方面释放。

(3)主机掉电

瞬间掉电,没有任何可以操作的空间,此时无法发任何FIN

两种情况

a.如果A给B正在给B发消息,B掉电,A等不到ack就会单方面释放连接

b.如果A给B发消息,A掉电,B在等A的消息,B会阻塞等待

心跳包

B会周期性给对方发送一个不携带任何业务数据的tcp数据报,目的就是为了触发ack,确认一下A是否正常工作/确认网络是否畅通

和前面说的流量控制的窗口探测报文,是一样的。

(4)网线断开

和主机掉电一样~

最后再回顾一下TCP十大核心特性(当然TCP不止这十个特性!!)

【Java网络原理】 五_第16张图片

关于TCP和UDP的对比

TCP优势在于可靠性,适用于绝大部分场景

UDP优势在于效率,适用于机房内部主机之间的通信

你可能感兴趣的:(Java网络原理,Java,EE初阶,网络)