TCP的特性(包含三次握手、四次挥手问题)

TCP协议基础

在这里插入图片描述
4位首部长度:其实就是划分了header和payload之间的分界线,单位是4个字节。例如:4位首部长度值为1111=>15,实际首部长度为15*4=>60字节
在这里插入图片描述
保留位:标识了这个TCP数据报是个啥类型的数据报
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段

一、确认应答(可靠性的核心)

1.原理

发送方发数据给接收方了,接收方就回应一个应答报文,如果发送方接收到了这个应答报文,那么认为对方已经收到了。由于网络上的传输顺序是不确定的,因此不能单纯的通过收到的数据来确定逻辑,就需要对应答进行编号。确认应答的序号不一定是从1开始的。TCP的序号和确认序号是以字节为单位进行编号的。确认应答意思就是1001意思是1001以前的都收到了。
在这里插入图片描述

2.其他场景

确认应答机制也不是TCP的专属,有很多其他场景也会用到TCP,例如分布式场景
在这里插入图片描述
削峰填谷:消息队列要负责保存一些数据,但是消息队列里面的存储空间也不是无限的。像rocketmq这种,直接把数据持久化到磁盘上,存储空间还是挺大的;像rabbitmq,默认数据都在内存里,存储空间就更有限一些。存的数据,就需要定期淘汰。如果一个数据没有被消费过,那么就不能轻易的淘汰。通过确认应答的机制去判断这个数据是不是真的被消费成功过了。

二、超时重传(可靠性)

确认应答机制的传输过程中有可能会出现丢包的,一旦数据发生丢包就要进入超时重传的机制中了。
此时存在两种可能,一种是发送的消息丢了对方没看见,一种是对方回复的消息丢了。发送消息的一方把数据发送出去后,等待500ms,如果没有收到应答就认为是丢包了(Linux、Windows中,超时以500ms为一个单位进行控制)。网络是正常的情况下连续两次甚至三次都丢包的概率是非常低的。

三、连接管理(三次握手、四次挥手)可靠性

1.三次握手(如何建立连接)

A 客户端
B 服务器
在这里插入图片描述
(1)主要目的:
第一点是通过三次握手过程来确认A和B之间的传输是通畅的。尤其要确认A和B各自的发送以及接收能力是否正常。A-B B确认的A的发送正常;B-A A确认B的发送正常、接收正常;A-B B确认A的接收正常
第二点是通过三次握手让A 和B选择传输中适合的参数,例如TCP的序号从几开始。

(2)延伸问题:
如果握四次手或者两次可以吗?
四次:可以没必要,传输开销比三次大
两次:不可以

(3)TCP对应的状态转换
此图还包含对应阶段的socketAPI(Linux,C语言版本)
在这里插入图片描述
当创建好ServerSocket实例的时候就进入了LISTEN状态,SYN和ACK在连接过程中都为1
LISTEN:手机开机信号良好,随时可以打电话
ESTABLISHED:接通了电话双方可以传输信息了accpt返回了。

2.四次挥手(如何断开连接)

A 可以为客户端也可以是服务器
在这里插入图片描述
(1)对于B来说ACK和FIN触发的实际不一样:
B只要收到FIN就会立即触发ACK,这个是由内核完成的
B发送FIN的实际是由用户代码控制的,代码中出现socket.close()操作室才会触发FIN。(实际本质上是内核里面释放了对应的PCB文件描述符,若是没有调用close,但是进程结束或者是socket对象被GC回收了,PCB就会被销毁,同样也会触发FIN)

(2)TCP对应的状态转换
在这里插入图片描述
CLOSE_WAIT:服务器收到FIN以后,进入等待的状态,等待用户代码调用close,来发送FIN。
TIME_WAIT:表示客户端收到FIN以后进入了TIME_WAIT,这个状态就是为了处理最后一个ACK丢包。
若A收到FIN并返回ACK之后就销毁,而不进入TIME_WAIT状态,此时一旦最后一个ACK丢包了就无法重传ACK了(连接已经销毁了)。

(3)服务器上出现大量的CLOSE_WAIT的原因?
代码出现bug,close没有被即时调用到。

(4)服务器上出现大量的TIME_WAIT的原因?
可能是代码bug,主动发起FIN的一方会进入TIME_WAIT,需要排查服务器是否应该主动断开连接。

(5)端口绑定失败的原因?
哪方先断开连接,哪方就会进入TIME_WAIT,进程退出之后,TIME_WAIT状态仍然存在,TCP连接仍然存在。如果让服务器先退出,服务器这边就会进入到TIME_WAIT状态(原来的连接占据着端口),接下来如果服务器立即启动,新的进程又会尝试重新绑定这个端口,可能会存在端口绑定失败的情况。所以TCP服务器操作的时候,都是先结束客户端在结束服务器。
在socket api里有一个REUSE_ADDR选项,如果把这个选项加上,就能够让我们在绑定端口的时候复用TIME_WAIT 状态中的端口,Java socket里面应该是默认就设置了这个选项。

(6)四次挥手可以是三次挥手吗?
在延时应答和捎带应答中有可能会出现ACK和FIN合并的情况。

(7)四次挥手一定会执行吗?
不一定,四次挥手是一个正常断开的流程,实际上有的时候TCP也会异常断开,例如:网线断开。

四、滑动窗口(效率)

1.传输原理

TCP不仅仅是为了保证可靠性,还要尽可能的提高传输效率。这个过程中,发送方要花很多时间来等,这个等待其实就是浪费了大量的时间。所以进行批量发送,一次等一批的ACK,把多组数据的ACK等待时间给重叠起来了,一次批量发送的数据的长度就称为“窗口大小”,如果没有批量发送数据的长度限制(窗口无限大,完全不等ACK就直接发一批)就没有可靠性可言了。
窗口越大,整体的效率越高,可靠性越低
窗口越小,整体的效率越低,可靠性越高
在这里插入图片描述

2.窗口范围

在这里插入图片描述
当前窗口范围是1001-5000,也就是发送方发送了1001-2000、2001-3000、3001-4000、4001-5000,四组数据,同时等着四组数据的ACK,假设2001先到,发送方也就知道了1001-2000这个数据已经被对方接受到了(确认序号表示之前的都收到了,如果收到了3001这个ACK代表1001-2000和2001-3000都被对方接收了),接下来就立即再发一个5001-6000,仍然保证窗口大小是4份数据。并不是等4份ACK都到了再往后发。

3.滑动窗口出现丢包的情况

只要不是全丢就行,实际上TCP为了提高效率,并不是每一条数据都有ACK,会隔几条数据才有一个ACK。如果传输文件途中有一组数据假设是1001-2000丢了,文件会继续发,只不过后面发的这些都会呆在缓冲区(2001-7000),接收端会一直给发送端发送1001的ACK,发送端就知道是1001-2000丢了,传送给接受端后会返回7001ACK。
在这里插入图片描述

五、流量控制(可靠性)

1.流量控制:

是对滑动窗口进一步的补充,是基于接受方的处理能力来限制窗口大小的。根据接收方的处理能力(接收方的缓冲区空余空间大小),来动态决定方的发送速率(控制窗口大小)
例:接收缓冲区大小是4000,1-1000数据到达的时候,缓冲区这里面的用了1000,还剩3000,返回的ACK就会把3000这个消息告诉发送方,发送方再次发送数据的时候就按照3000作为窗口大小进行发送。直到窗口大小为0,发送方停了以后,为了能够查询当前接收方的窗口大小,每隔一段时间,还会再来一个窗口探测包,通过这个包(不传输具体业务数据)触发ACK,得到窗口大小。

2.窗口实际大小

TCP中有16位窗口大小(看第一张图)
TCP首部字节选项中包含了一个窗口扩大因子,实际窗口大小是窗口字段的值左移M位。(相当于*2)

六、拥塞控制(可靠性)

站在另一个角度来限制发送方的窗口大小,站在宏观的角度来看问题,把整个链路都看成了一个整体,只看结果。先慢启动,使用一个比较小的窗口来传输数据,看看是否丢包,如果不丢包说明网络比较通畅,逐渐加大发送率;若是丢包,说明网络发生拥堵,降低发送率。通过这样的方式得到:真实的发送窗口的大小=min(流量控制的窗口,拥塞控制的窗口)。如下图:所示
在这里插入图片描述

七、延时应答(效率)

为了提高效率,让窗口大小在保证可靠的基础上,尽量大一些。对于前面的流量控制来说,窗口大小就是接收缓冲区的剩余空间大小。而延时应答来说窗口大小是接收方收到消息后又等待了一会再返回ACK时的窗口大小(有一些数据在接收方等待的时候出缓冲区了,所以窗口大小大了一些)
在这里插入图片描述

八、捎带应答(效率)

在延时应答的基础上,很多的客户端/服务器的通信模式都是一问一答的形式,应用程序要进行响应。
由于有了延时应答,返回的ACK不是立即返回而是等一会,正好和响应合二为一。
在这里插入图片描述
延时应答和捎带应答中的四次挥手是可以变为三次的但是时间不确定。

九、面向字节流(粘包问题)

在面向字节流的情况下,需要注意粘包问题(指的是应用层的数据)。不是TCP特有的问题,所有面向字节流都有这样的问题。像UDP这种面向数据报的就没有这个问题。
如何解决粘包问题:通过设计一个合理的应用层协议来解决
1.给应用层数据设定结束符、分隔符
2.给应用层数据设定长度
在这里插入图片描述

十、TCP中异常的情况(心跳机制)

1.进程终止

不管进程是如何终止的,本质上都会释放对应的PCB,也会释放对应的文件描述符,一样会触发四次挥手,"进程终止"不代表连接就终止,进程终止其实就相当于调用了socket.close()而已。

2.机器重启

机器重启的时候,其实也是先暂停进程,仍然是进行四次挥手。

3.机器掉电/网线断开(突发情况!!机器来不及进行任何动作)

(1)掉电的是接收方
此时另外一边还在发送数据.此时显然发送方不会再获取到ACK ,于是就会超时重传,重传几次之后,就会尝试重置连接、复位报文段,在然后发送方就会放弃这个连接,把连接对应的资源就回收了。
(2)掉电的是发送方
此时另外一方在尝试接收数据,此时接收不到任何数据,此时接收方采取的策略,就是“心跳包"机制(也叫做"保活"),每隔一段时间,向对方发送一个PING包,期待对方返回一个PONG包,如果PING包发过去,过了很久还没有PONG,并且重试几次也不行,此时就认为对方已经掉线了。
心跳包,这是一个应用非常广泛的机制,不仅仅是在TCP。

猜你喜欢

转载自blog.csdn.net/stitchD/article/details/123739231