文章目录
- 1. 慢启动
- 2. 拥塞避免
- 3. 快速重传和快速恢复
初识tcp报文
我们先来简单认识一下报文的格式,具体理解需要后面详细介绍
- 源端口和目的端口:顾名思义就是标识传输双方的信息
- 首部长度:指的是TCP报头的长度,换句话来说,我们需要用一个属性来描述报头的长度,就说明TCP的报头是变长的
- 选项:实际上这里翻译为"可选项"更加准确,这是TCP为了适应复杂的网络环境和更好的服务应用层而进行设计的,TCP选项部分最长可以达到40byte ,再加上TCP选项外的固定的20byte字节部分, TCP的最长头部可达60byte
- 序列号:在一个TCP连接中传输的字节流中的每一个字节都按顺序编号,该字段表示本报文所发送的数据的第一个字节的序号
- 确认号:表示期待收到对方下一个报文段的第一个数据字节的字段,例如当确认号为N,表示序列号N-1之前的所有数据都已全部接收到
- 窗口:通知发送端,接收端可接受的空间大小
- 校验和:发送端计算发送的TCP报文段的校验和,接收端对接收到的TCP报文段,验证其校验和,如果不一致,说明数据包在发送端和接受端之间传输的时候发生了变动,此时该TCP报文会被直接丢弃
- 保留位: TCP报头里,提前申请好的一块空间,这个空间暂时用不上,但是说不定以后就能够用的上(万一以后TCP要扩展一些新的功能,就可以用这个保留位来表示了)
- 6位标志位:他们中的一位或者多位可以同时设置为1,用于说明该报文性质,其中下面三个字段需要格外注意:
- ACK,仅当ACK为1的时候,确认号字段才有效,TCP规定,当连接建立之后,所有传输的报文都必须将ACK设置为1
- SYN,仅在三次握手建立TCP连接时有效,当SYN=1,ACK=0时,表明这是一个连接请求报文,若对方同意连接,则在响应报文中设置SYN=1 和 ACK = 1
- FIN,用来释放一个连接,当FIN=1时,表示此报文的发送方的数据已经发送完毕,并要求释放连接
三次握手建立连接
为了确保通信双方都能**确认对方的存在和可用性**,并且**数据能够被正确的传输和接收**,TCP要求通信双方建立一条可靠的通信连接三次握手是TCP协议中用于建立可靠连接的过程:
- A向B发送一个SYN报文,请求建立连接
- B收到A的SYN报文后回复一个SYN + ACK的报文
- A收到B的SYN报文之后,回复一个ACK报文
- B接受到ACK报文之后,表示一个TCP连接就建立好了
如果对上面的建立连接过程不懂,可以看下面的例子:
那么此时就很容易发现,为什么一定要三次握手??两次行不行??
答案是否定的,因为只有经过最后一次交互,B才能知道对方的接受能力和自己的发送能力是没问题的
分段机制
TCP经过三次握手之后就能发送数据了,但是一般网络不能连续的传输任意长的数据,因此,发送方A将需要传输的消息分割成小快,按照顺序分别发送,这些小块就是数据包B接受到数据报之后,经过校验和校验无误之后,开始将收到的数据包进行重新组装,还原成原始消息
但是可想而知,数据包组装需要安装分割前的顺序进行拼接,但是我们确不能保证B接受数据包的顺序和我们传输的顺序是一致的,因为在网络传输过程中会经过很多交换机和路由器的传输,而不同的设备传输速度,负载情况,网络拥堵等因素都会影响数据包的传输顺序
为了解决这个问题,TCP在数据包的头部定义了一个序列号的字段,用于对在一个TCP连接中传输的字节流中的每一个字节按顺序进行编号,就可以解决乱序的问题了
确认应答+超时重传机制
为了保证所有的数据包都能够正确接收,TCP引入了确认应答机制B在接收到数据之后,会回复一个携带确认号的确认包,也就是ACK包,A接收到ACK报之后,再发送下一个数据包
如果结果一段等待时间之后,发送包无法知道数据包的情况,则可能出现以下的情况:
解决这个问题的一般方法是超时重传,A在发送数据包之后,不会一直等待,而是启动一个重传定时器,定时到期之后未收到数据包,就会认为数据包丢失了,从而重传数据包
滑动窗口机制
在上面确认应答机制的情况下,A只有当接收到B回复的ACK包之后,才能继续发送下一个数据包在这种场景下,发送方大多数时间都是在等待接收方的ACK中,极大的影响了通信的效率
一种解决问题的方法就是滑动窗口,这种机制允许发送方连续发送多个数据包而无需等待
TCP使用一个称为发送窗口的变量,表示无需等待确认即可发送的数据包的数量
那么发送窗口的大小设置为多少才是合理的呢?
如果太小,那就和我们上面简单的确认应答机制没什么区别了
如果太大,接收方可能会来不及处理而丢失数据包,得不偿失
TCP使用滑动窗口的机制,要求接收方维护一个可处理的接收窗口,发送方根据接收窗口来调整发送窗口的大小,从而根据接收方的接受能力来动态控制流量
通信双方每次在发送数据时,都会在TCP报头中携带窗口的大小.表示自己当前的接收能力
前面我们说过为了保证所有数据包都被收到,要求每个数据包接收到之后都要返回一个ACK包
延时应答 +累计确认
在滑动窗口的场景下,如果每个数据包都要回复一个ACK包的话,过多的确认包会消耗网络资源,降低网络利用率对于累计确认来说,就是如果接收方返回确认号为N的ACK包,则表明N之前但不包括N的数据包已经全部接收到了
而延迟确认指的是接收方在接收到数据包之后不立即回复,而是延迟等待一段时间,这样在延迟的时间内收到多个连续的包可以累计成一个ACK确认
通过这两种方式,减少了ACK包的数量,提高了网络利用率
快速重传机制
前面说过,接收方等待一定时间之后,如果未接收到ACK包,就会触发超时重传但是还是需要等待一定时间,降低了传输效率
TCP引入了快速重传机制:当接收方连续收到三个相同的确认号的时候,就会触发快速重传,立即重传数据包,而不必等待超时时间
拥塞控制
滑动窗口只考了接收方的处理能力,发送端根据接收窗口的大小发送数据,而忽略了网络的负载情况随着网络连接数量的增加,必然会造成网络拥堵,就可能引起大量的超时重传,形成恶性循环,造成整个网络不可用
因此要进行拥塞控制来避免这种情况的发生
拥塞控制的主要思路是防止过多数据注入网络,以避免网络出现负载过大,也就是要根据网络拥塞情况,对发送数据包的数据进行控制
TCP定义了一个拥塞窗口的变量,如果网络拥堵就减少拥塞窗口,畅通就增加拥塞窗口
我们前面说过,发送窗口的大小等于接收窗口的大小
现在引入拥塞窗口之后,发送窗口的大小就等于拥塞窗口和接收窗口中的最小值,这样就能有效控制网络的发送效率
其中Reno算法是当前应用最广泛的拥塞控制算法
主要包含慢启动、拥塞避免、快速重传、快速回复
1. 慢启动
刚接入网络的时候先发送少量数据包,探测网络情况,然后再一点点逐步提高发送包的数量
2. 拥塞避免
慢启动的指数级别增长不可能一直持续下去,当发生网络拥堵的时候,慢启动增长就会结束
同时TCP定义了一个慢启动阈值的变量,当拥塞窗口超过这个阈值,就会进入拥塞避免模式,结束指数级增长
假设我们这个阈值是8,当超过8之后,就会以1的数量级增加,以线性增长,这种线性增长的方式相对于指数增长的方式更加稳定,可以避免网络拥塞的发生
但是也不能一直增长,和慢启动一样,当发生网络拥堵的时候,拥塞窗口的增长就会结束
那么网络拥堵是怎么感知到的?? 同时拥塞窗口又怎么调整呢?
TCP是通过丢包来感知网络拥堵状态的
丢包可以分为两种
- 超时丢包
也就是经过一段等待时间之后,还没收到ACK包,这种情况说明网络拥堵比较严重,TCP会进入慢启动模式,并将慢启动阈值设置为出现丢包时候的拥塞窗口的一般,重新开始慢启动,后拥塞避免…
- 第二种是收到三次重复的ACK
此时就会认为之前发送的数据包丢失,这种情况下认为当前网络发生了轻微拥堵,此时拥塞窗口减小的幅度不会像超时丢包那么大,会执行快速重传和快速恢复算法
3. 快速重传和快速恢复
- 将慢启动阈值设置为当前拥塞窗口的一半,并重传丢失的数据包
- 将拥塞窗口设置为当前慢启动阈值加上3,这是因为,发送方收到了3个相同的ACK说明当前已经有三个数据包到达了接收端,网络的"在途数据量"减少了三个,此时将拥塞窗口加上3,就是适当将拥塞窗口尽可能的变大些
- 再次收到重复的ACK,将拥塞窗口增加1
- 当收到新的数据包的ACK的时候,将拥塞窗口设置为第一步设置的阈值的值,此时快速恢复结束,再次进入拥塞避免阶段
实际上第一次学我也是云里雾里的,这里有必要通过一个例子来解释一下我的理解:
cwnd(拥塞窗口)=9
,此时发送9个数据包(序号1-9)- 假设5数据包丢失,但是报文6~9正常到达接收端
- 接收端每次接受到6~9的数据包,都会回复一个ACK=5,发送端连续收到了3个ACK
- 触发快速重传,立即重传丢失的报文5,同时更新阈值
(ssthresh= cwnd / 2 = 4.5 = 4)
- 进入快速恢复阶段,设置
cwnd = ssthresh+3 = 7
,加3是因为,由于已经收到了3个重复的ACK,说明(6,7,8)报文已经成功到达了接收端,可以将窗口向上提3个大小,不会导致下降太多 - 在等待ACK=5的报文阶段,允许发送新的报文(10~13),此时又会收到连续的重复的ACK=5,此时每收到一个就将cwnd+1,原因和5一样
- 当收到新的ACK=6,说明网络恢复,此时重置窗口cwnd=4,退出快速回复,进入拥塞避免阶段,为什么要重新降低?? 我觉得有以下原因
- 快速恢复阶段通过临时增大窗口(
**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">cwnd = ssthresh + 3</font>**
)来维持数据传输,但此时网络可能仍处于敏感状态,若继续保持这个值,可能会导致拥塞循环 - 重置窗口后,TCP进入拥塞避免阶段,确保窗口增长更平滑,避免突然的流量激增。
- 快速恢复阶段通过临时增大窗口(
四次挥手结束通信
TCP是全双工的,每个方向可以同时传递数据,因此每个方向需要单独的关闭- A方发送
FIN
来终止这个方向上的传输,待对方确认(返回ACK)后进入半关闭状态(也就是说A此时还能接受B发送来的数据 ,因为只是A认为自己结束了,B可能还在发送) - B发现自己数据发送完毕之后,发送给A
FIN
来终止传输,待A确定后(返回ACK)终断连接