通过学习视频加博客的组合形式,整理了一些关于TCP协议的知识。
*图源:@临界~的csdn博客。
一、TCP建立连接
TCP的建立连接,大致可以分为面向连接、TCP报文结构、TCP的三次握手、TCP的建立状态、SYN泛洪攻击。
1.1、面向连接
面向连接 --- 发送数据的时候需要先建立一条点到点的连接,TCP采用这种连接方式,UDP被认为是一种“无连接”的协议。
点到点的连接说明,在TCP的通讯中,只有通讯双方,而不存在第三方。而UDP可以一个主机同时向多个主机发送消息。
这里注意:连接其实是一个参数比对的过程
源IP地址、目标IP地址、源端口、目标端口 --- ”TCP的四元组“,TCP四元组可以唯一的区分和标识一条TCP的连接。
1.2、TCP的报文结构
TCP作为传输层协议,源端口和目标端口相当于它的一个地址,这是两端口存在的意义,因为传输层需要实现端到端的传输。
源端口号、目标端口号 --- 端口号主要是传输层定义的一种地址,其目的是区分上层应用。
序号(序列号) --- TCP是一个基于“字节流”的协议,这个序列号就是字节流的编号,每一个字节都会有自己的序列号。
比方说现在有两个主机A和B,他们通过TCP进行连接通信,假设现在A想要发送1000个字节的数据,而由于MTU(最大传输单元)的限制,数据会进行一个分段,然后根据rwnd(接收窗口)一组一组的进行发送,而TCP的确认机制是累积确认,但是怎么累积确认呢,通过发送的ACK包上的Ack Num来进行确认。所以说,当A第一次发送的是0~999序号的字节流后,B发送的确认序列号是1000,即表明了B想要的下一次数据的序列号,同时还告诉A自己之前收到了0~999的序号的字节流。
TCP的确认序列号 --- 接收方期望收到发送方发送的下一个字节的序号 --- 累积确认
首部长度 --- TCP报文头部的长度 --- TCP报文存在选项字段,称之为可变长头部,TCP头部最短20字节(通常,选项字段为空)
保留 --- 用 000000 进行填充
标记位 --- 保留位后面的六组字母,每组占一位,当为1时表示激活,为0时未激活
URG --- 紧急标记位 --- TCP的数据报文中有紧急处理的数据(应用层上层来定义),把需要紧急处理的数据放到数据报文中的最前面,当URG置1时,紧急指针被激活,紧急指针位于需要紧急处理数据长度的位置
ACK --- 确认标记位 --- 当ACK置1时,确认序列号被激活
主机A给主机B发送数据段的时候,主机B会开启一个缓存空间用来存储数据段
PSH --- 当该位置1的时候,把当前缓存空间的数据段推送到进程中(在滑动窗口中的可以知道所以然),可能当前的数据段需要优先处理等需求
RST --- 当该位置1,强制断开TCP的连接
SYN --- 希望建立TCP连接的时候,该位置1,同时会随机给序列号设置一个初始值
FIN --- 当该位置1,表示正常TCP协商断开
窗口大小 --- 实现流量控制的一个重要参数
流量控制也是保障TCP传输可靠的一个举措
校验和 --- 数据完整性检验,TCP会有伪头部校验
伪头部校验,TCP校验的时候会带上IP头部进行校验,当校验完成后发给IP协议进行完整检验
IP头部(12字节):32位源IP、32位目标IP、8位保留、8位协议、16位报文长度
这里数据包只是到了TCP,明明还没到网络层进行封装,怎么会有IP头部呢?
这里用到了预封装,而预封装的IP头部就称为伪头部
字节流的序号应该是连续的,TCP的会话建立是全双工模式。
第二次的确认包中不止可以带有确认序列号,还可以携带数据,因为此时客户端对于服务器的会话已经建立。
1.3、TCP的三次握手
TCP的连接过程是由参与TCP通讯的双方中的其中一方发起的,我们一般认定发送方为客户端,而另一方为服务器。同时TCP的会话是一个双向的会话。
三次握手的第一个报文:
SYN报文:其是没有携带数据的,做请求连接这件事情,这个时候接收方会给TCP分配一部分的缓存空间,定义一些初始变量。
三次握手的第二个报文:
Ack报文:由于这个时候没有数据,所以是初始化的seq+1,这个确认包除了能表示我收到了你的请求SYN包,还能表示我同意和你进行一个会话的建立,此时发送方对于接收方的会话已经建立(发送方对接收方的TCP已经建立)。
SYN报文(与第一个SYN无关):表示接收方也想和发送方进行一个TCP的建立,这时发送方也应该分配一部分缓存空间给TCP,定义一些初始变量。
三次握手的第三个报文:
ACK报文: 此时发送方对于接收方的TCP会话已经建立是可以发送数据的,所以发送的数据序列号seq应该是初始化的seq+1(即client_isn + 1)
问:为什么每次的SYN报文后给定的seq是一个随机值,而不是一个固定值?
这样的设计可以防止历史报文,如果seq是一个固定值,当两个报文的四元组相同时,但是发送的时间不同,由于链路堵塞等问题导致第一次的TCP报文还残留在链路中,当第二次TCP报文发送到对端的时候两个报文都来到对端,对端无法判断哪一个是历史报文,哪一个是新的TCP报文。
随机序列号的作用:
隔离序列号空间:新连接的序列号范围与旧连接的序列号范围完全无关,旧报文中的序列号几乎不可能落在新连接的接收窗口内。
窗口校验:TCP接收端会检查报文的序列号是否在当前的接收窗口内。即使旧报文到达,其序列号大概率不在新窗口的有效范围内,会被直接丢弃。
1.4、TCP的状态变化
客户端:
1、关闭状态 --- 在发送SYN报文请求建立连接后,进入到下一个状态。
2、SYN_SENT --- 客户端等待服务器返回SYN_ACK报文。
3、建立完成 --- 收到服务器返回的SYN_ACK报文,因为此时客户端向服务器的会话已经建立完成,所以,客户端发送给服务器的最后一个ACK报文,是允许携带数据的。
服务器:
1、关闭状态,当服务器的应用程序创建一个监听的套接字后,将进入到下一个状态。
2、Listen(侦听状态):当接收到客户端发送的SYN报文后,为TCP连接分配缓存空间,同时,发送SYN_ACK报文段,然后进入到下一个状态。
3、SYN_RCVD,等待客户端回复ACK,收到之后,将进入到下一个状态,超时时间是1min。
4、建立完成 --- TCP双向会话均建立完成。
TCP异常连接:
当客户端发送TCP连接报文的时候,服务器没有进入到Listen状态,无法处理发过来的TCP报文,所以当接收到客户端的建立报文后,会回复一个RST(重置)的TCP报文,表示要中断本次连接。
1.5、TCP的SYN泛洪攻击
Dos攻击 --- 拒绝服务,发送大量数据包,导致网络拥塞无法正常处理业务,一般泛洪攻击都是Dos攻击
SYN泛洪攻击(本质上是Dos攻击):发送SYN报文后,服务器会分配一个缓存空间然后发送SYN_ACK报文,但是此时攻击者不发送第三次握手的ACK报文,导致缓存空间的不断占用。
应对方法:
1、防火墙代理 --- 采用防火墙充当中间人,由原来的三次握手变成六次握手,但是防火墙被攻陷也相当于服务器被攻陷,所以防火墙也有自我保护机制,每目标IP代理阈值(直接放通,直到达到阈值(根据业务流量决定))和每目标IP的丢弃阈值(访问数量太大,达到了丢弃阈值,则直接丢弃),丢弃阈值会比代理阈值大。
2、SYN Cookie --- 源IP、目标IP、源端口号、目标端口(四元组)再加上一个随机数通过hash算法得到一个摘要值,这个摘要值就是SYN Cookie。 当客户端发送SYN报文请求连接后,服务器不分配缓存空间,而是计算出SYN Cookie,当作服务器SYN报文中的Seq Num,然后等待客户端发送的ACK报文,再通过hash算法计算出客户端发送ACK报文的摘要值,进行加1匹配,成功后才分配缓存空间。
1.6、TCP三次的原因
问:TCP建立连接为什么必须是三次握手,而不是两次或四次?
为什么不是四次?
TCP会话的建立是双向的,SYN_ACK报文包可以一起进行一个发送就没必要分开,这样就浪费了资源。
为什么不能是两次?
两次的过程只能是在客户端发送SYN报文后,服务器就分配缓存空间,同时进入到ES状态,然后发送SYN_ACK报文,客户端接收后同样分配缓存空间并进入到ES状态。
三次握手可以防止旧重复连接造成混乱。
客户端发送了一个SYN报文请求建立连接后突然down了,但是发送的SYN报文由于网络堵塞在链路中停留,后面客户端恢复连接,发送了一个新的SYN报文,但是旧的SYN报文比新的SYN报文先到,然后服务器发送SYN_ACK报文,但是此时的ACK不是客户端想要的值,则客户端就会发送一个RST报文中断连接,一段时间后,新的SYN报文到达然后按照正常流程建立TCP双向连接。
当只有两次握手就可以建立TCP连接时,客户端发送了一个SYN报文请求建立连接后突然down了,但是发送的SYN报文由于网络堵塞在链路中停留,后面客户端恢复连接,发送了一个新的SYN报文,但是旧的SYN报文比新的SYN报文先到,此时由于是两次握手,导致服务器会直接建立连接,然后在SYN_ACK报文中携带数据,后面流程跟三次握手差不多。只是在发送SYN_ACK报文这一过程中发送了不必要的数据,浪费了资源。
二、TCP断开连接
TCP的”四次挥手“,由于在发送FIN报文的时候会携带数据,所以就不会像连接一样需要序列号,而且TCP的断开是谁先发送完数据谁发送断开请求,不像建立连接一样是客户端先向服务器发送SYN请求报文。
2.1、TCP的“四次挥手”
FIN报文是可以携带数据的,一般是携带数据的最后一小段字节流。
谁先发送断开请求,谁就是下面的状态流程:
建立完成状态 --- 发送完所以字节流后,携带最后一组字节流的数据段同时将FIN标记位置1,之后进入到下一个状态。
1、FIN_WAIT_1 --- 等待服务器回复ACK,收到ACK应答后,将进入到下一个状态。
2、FIN_WAIT_2 --- 等待服务器发送FIN断开请求,将回复ACK进行确认,进入下一个状态。
3、TIME_WAIT --- 等待2MSL时间后进行下一个状态。
4、CLOSE --- 关闭状态,断开TCP连接,释放掉所有TCP连接占用的资源。
后面发送断开请求的状态流程:
1、建立完成状态 --- 收到客户端发送的FIN断开请求后,服务器将回复一个ACK确认报文,之后,进入到下一个状态。
2、CLOSED_WAIT --- 等待服务器自身字节流的发送,当自身所有字节流传递完毕后,将发送一个FIN断开请求,之后进入到下一个状态。
3、LAST_ACK --- 等待客户端进行最后的ACK应答,当收到客户端发送的ACK确认报文之后,进入到下一个状态。
4、CLOSE --- 关闭状态,断开TCP连接,释放掉所有TCP连接占用的资源。
2.2、WIME_WAIT存在的原因
问:为什么要有TIME_WAIT状态?
保证TCP会话可以正常关闭,在正常的TCP断开连接的流程中,如果TIME_WAIT时间过短或没有,会导致在客户端发送给服务器ACK确认报文丢失的之后,服务器重传的FIN报文,客户端无法识别,然后发送RST,导致这次会话异常断开;如果TIME_WAIT的时间合适,就可以接收到重传的FIN报文就可以正常断开连接。
TIME_WAIT时间过短或没有导致异常终止的后果:
1、服务器资源泄露:
服务器在发送FIN后处于LAST_ACK状态,若未收到客户端的ACK,会反复重传FIN(默认重试次数由tcp_orphan_retries
控制)。如果客户端已销毁连接,服务器将永远无法关闭连接,导致,占用端口、内存等资源。
2、新数据连接混乱(旧报文干扰):
客户端若立即复用相同四元组(源IP、源端口、目标IP、目标端口)建立新连接:
旧连接的延迟报文可能被新连接接收(序列号匹配时)。
服务器未关闭的旧连接可能对新连接发送的SYN返回RST(认为非法报文)。
3、破坏协议可靠性:TIME_WAIT的核心作用:
确保最后一个ACK可靠到达:等待2MSL(足够时间让ACK丢失后重传FIN并响应)。
4、隔离新旧连接:通过2MSL等待,确保网络中旧连接的报文全部过期。
5、若跳过TIME_WAIT:TCP的可靠关闭机制被破坏,可能引发不可预知的传输错误。
2.3、TIME_WAIT时间的设定
问:为什么TIME_WAIT状态的时间是2MSL?
MSL --- 报文最大生存时间
防止旧的数据包 --- 在TIME_WAIT时间过短或没有的情况下,某次服务器发送了一组字节流因为网络延迟,停留在链路中,然后双方继续正常交流直至断开连接,可是在双方都是CLOSE状态后,之前的那组字节流被发送客户端,而客户端想要的下一个序列号刚好跟发过来的旧的SEQ对应上了,所以客户端就会接收旧的数据,而报文在链路中最大生存时间为MSL,在定义了TIME_WAIT为2MSL就可以保证旧的数据报文在链路中被丢弃,而不会被误收。
确保重传FIN报文 --- 能保证在服务器在发送了FIN报文后没有收到ACK确认报文,在第一个MSL中丢弃掉之前的FIN报文,然后在第二个MSL期间,服务器达到重传时间,重传FIN报文被客户端接收到。
时间定义的太长,会导致占用资源过大
2.3、TCP断开连接的次数
问:TCP的断开连接为什么是四次而不是三次或两次?
保证数据传输的完整 --- 在客户端发送完数据后就会发送FIN报文,而此时服务器未必发完数据了,所以服务器只会回复一个ACK确认报文,等到自己的数据发送完成后,才会发送FIN报文,然后客户端发送ACK确认报文,最终双方断开连接。而实际上是有可能是三次的,客户端发送FIN报文的时候,服务器刚好也发送完了数据包。两次的话就是有一方出现了故障。
三、TCP可靠性传输
确认、重传、排序、流控(后面单起一节)
3.1、排序
分段 --- TCP是一款基于字节流的协议
MSS --- 最大段长度 --- SYN报文中会携带需要协商的参数,当双方发送的MSS不一致时,最后建立连接后会按照较小的MSS来进行传输 --- MSS最大典型值为1460字节(如果在应用层进行分段后还需要在网络层进行分片,这就有点浪费资源,最好就是一步到位,而MSS最大即在到达数据链路层的其他层最小,所以就是网络层的最小头部长度和传输层的最小头部长度都是20字节,1500减去40,就是MSS的典型值1460字节)
MTU --- 最大传输单元 --- 数据链路层所能携带的最大字节数,默认是1500字节,在层层封装之后由网络层发送给数据链路层的时候需要保证传输的数据段最大长度是1500字节
分片 --- 网络层的IP协议执行
可以通过指定发送的数据大小来实现分片的效果,只要指定的数据大于MTU
可以通过这种方式在虚拟设备上进行一个环境复现
ping -s 指定的数据大小<20-9600> 目标IP
这里可以发现数据大小是1480,这是因为在IP协议分片的的分片包中,只有第一个数据包上会携带IPv4头部内容、ICMP协议内容,在后面的数据包中是只有IPV4头部内容加上数据,所以是1480,这样的话容易出现在传输过程中,但凡有一片数据丢失,到达主机后无法合成完成数据报文,但是由于数据没有标识,目标主机要求重传的时候只能是整个数据全部重传,这样效率很低,这个时候就需要依靠上层协议来进行标识,TCP协议中有一个序列号来进行标识。
序列号 --- 序列号是基于字节流的传输协议,会给每一个字节进行标识
3.2、确认机制
TCP为了保证对方能够收到本端发送的数据段的方法,是让对方回复一个确认报文段,这就是确认机制的做法。
确认序列号 --- 接收方期望收到发送方发送的下一个字节的序号 --- 累积确认
ACK标记位置1,然后开启Ack Num,该值为发送的seq的值+1,代表着下一个字节的序列号
3.3、重传机制
超时重传 --- 本端在一定时间内,没有收到对端反馈的确认报文触发的重传机制。
系统会定义一个最大重传次数,要是一直没有接收到反馈报文,达到次数后就关闭TCP连接(不同系统不同的次数)。
RTO --- 超时重传时间,RTO的值在计算时,需要略大于RTT,因为RTT是一个可变化的值,所以,RTO也是一个动态变化的值。
RTT --- 往返时间 --- 指的是发出端将数据发出后,到ta接收到对端反馈的确认报文之后的这一段时间(由于网络链路情况不定,该值也是波动的)。
如果RTO过长 --- 则会导致丢包之后,重传的效率降低,无法及时做出响应
如果RTO过短 --- 则会导致不必要的重传
超时间隔加倍
一般是在网络拥塞的情况下,出现了seq或ack报文没有发出或收到的情况下,如果发送端还是无脑的不停重传,无疑会加重网络中的拥塞,所以采用这种超时间隔加倍的机制,使得不去加重网络拥塞的情况。
3.4、重传机制的优化
快速重传机制
失序报文 --- 接收方收到了一个数据段中的序列号大于自己期望的序列号,这样的报文就是一个失序报文。在客户端发送数据段的时候不是一发一等,这样效率有点低,一般是一组一等,所以才会出现失序报文。最大数据段数 = 发送窗口 / MSS
若 rwnd=65535 字节,cwnd=10000 字节,MSS=1460 字节,则发送窗口为 10000 字节,最多可发送 6 个数据段(10000 / 1460 ≈ 6.85,向下取整)
若网络拥塞导致 cwnd 降至 5000 字节,则发送窗口变为 5000 字节,最多可发送 3 个数据段。
rwnd:全称 Receive Window(接收窗口),由接收方告知发送方的可用接收缓冲区大小(单位:字节),表示接收方当前还能接收多少数据。
cwnd:全称 Congestion Window(拥塞窗口),由发送方动态调整的窗口大小(单位:字节),表示发送方根据网络拥塞情况认为当前可安全发送的数据量。
MSS:全称 Maximum Segment Size(最大段大小),指 TCP 数据段中能携带的最大有效载荷(不包含 TCP 和 IP 头部),通常由链路 MTU(最大传输单元)减去头部开销得出(如 MTU=1500 时,MSS≈1460 字节)。
冗余ACK --- Duplicate ACK
发送端接收到了连续三次冗余ACK后,会触发一个快速重传fast retransmission
3.5、SACK
这种设计基于实践经验:三次冗余 ACK 能有效区分数据包丢失与网络延迟导致的乱序。若仅收到一两次冗余 ACK,更可能是短暂的网络延迟,而非丢包,此时触发重传会增加不必要的流量。
由于接收端连续发送了三个冗余ACK,而疏忽了对于后续数据段的一个确认,导致后面发送端快速重传的时候,会从缺失数据段开始直到最后的数据,全部重新发送,这浪费了资源,除了缺失数据段后面的数据段可能是无用功,所以就衍生出了选择确认机制。
选择确认机制 --- SACK --- 在快速重传机制中,接收方在回复冗余ACK时,里面将携带自身已经接收到的数据信息,避免重复传输,资源浪费。SACK会在冗余ACK中存在,ta会把后续接收到的数据段标识出来
SACK的开启需要在三次握手中进行一个协商
3.6、TCP中三次握手的可靠性
第一次握手中的SYN报文丢失:
丢失后,会按照RTO进行一个超时重传,但是RTO是根据RTT来进行计算的,而此时是第一次建立连接,不知道RTT需要多久的时候,所以,这个时候的RTO其实就是一个定义值(不同系统不一样,一般推荐时间是1s)
第二次握手中的SYN_ACK报文丢失:
ACK报文丢失,发送端会觉得是自己的问题会触发超时重传,而这个数据包中还有接收端的SYN报文,所以,与此同时,接收端也会触发超时重传
第三次握手中的ACK报文丢失:
此时客户端对服务器的TCP会话已经建立,而服务器没有收到客户端的ACK报文后,会认为是自己的SYN报文出现问题,触发超时重传
3.7、TCP中四次挥手的可靠性
第一个FIN报文丢失:
客户端发送完第一个FIN报文之后,将进入到FIN_WAIT1的状态,在这个状态中,如果能够正常收到服务器发送的ACK报文,则可以进入到FIN_WAIT2的状态。
当然,如果这个数据包丢失的话,他自然也无法收到服务器反馈的ACK。则将触发超时重传。重传之后收到ACK则将继续向下切换状态。但是,如果这个数据包持续丢失,则重传时间将间隔翻倍,最终,达到最大重传次数后,进入CLOSE(关闭)状态。
第二个ACK报文段::
服务器收到FIN之后,将回复ACK报文作为应答。回复完成将进入到CLOSED_WAIT状态。如果这个ACK报文对方没有收到,注意,ACK报文段是不会重传的,则客户端将超时重传FIN报文段。
如果,这个ACK报文段一直丢失,则客户端重传FIN达到最大次数后,也将进入CLOSE(关闭)状态。
第三个FIN报文段:
服务器发送的这个FIN报文如果客户端没有收到,服务器也就无法收到客户端最后回复的ACK应答,则服务器将触发超时重传。
如果,这个FIN报文段一直丢失,则服务器重传FIN达到最大次数后,将进入CLOSE(关闭)状态。
第四个ACK报文段:
其实最后一个ACK报文段的情况也是差不多的,我们专门设计了主动段开放再回复最后一个ACK之后,需要等待2MSL的机制。所以,客户端最后一个ACK如果服务器没有收到,则将触发服务器的超时重传,重传FIN报文。
客户端在收到这个FIN报文后,会重新反馈ACK报文,并且重置2MSL的计时器时间。
如果,这个ACK报文段一直丢失,则服务器重传FIN达到最大次数后,将进入CLOSE(关闭)状态。而客户端再没有收到新的FIN后,等待2MSL时间到达后也将进入CLOSE(关闭)状态。
四、TCP流量控制
4.1、滑动窗口
滑动窗口 --- 窗口大小不断的更新去适应环境,这样的窗口流控机制,就称为滑动窗口机制。
窗口 --- 窗口大小指的就是无需等待确认应答,而可以连续发送的数据包的最大值,窗口大小跟建立TCP连接中分配的缓存空间有联系。
接收窗口 --- rwnd --- 反映的是接收方此时缓存空间可用的大小 --- 因为缓存空间中存放的数据的空间是随时间变化,所以,rwnd也是随时间变化的,最开始时,rwnd = RcvBuffer。
缓存空间和空闲空间共同组成RcvBuffer,当成功传输了一组数据段后就会放到缓存空间中,导致接收空间变小,所以rwnd是一个变化值。
通告rwnd的时机:注意这里是双向会话,两边都有滑动窗口机制
首次通告:三次握手阶段(SYN-ACK 报文段)
在 TCP 三次握手的第二个报文段(接收方发送的SYN-ACK 段)中,接收方会首次向发送方通告自己的初始接收窗口大小(rwnd)。发送方根据该 rwnd 值(结合自身的拥塞窗口 cwnd)确定初始发送窗口,开始数据传输。
常规通告:数据确认时(ACK 报文段携带)
当接收方收到发送方的数据段后,在发送ACK 确认报文段时,会同时携带当前的接收窗口大小(rwnd)。
立即确认:若接收方启用 “立即 ACK”(不延迟确认),则收到数据后立即发送 ACK + rwnd。
延迟确认:若启用 “延迟 ACK”(如等待 ACK 累积或等待发送数据捎带确认),则在延迟后的 ACK 中更新 rwnd(通常延迟时间不超过 200ms)。
更新逻辑:rwnd 的值反映接收方当前可用的接收缓冲区空间(即总缓冲区大小 - 已接收但未被上层应用读取的数据量)。每次数据被接收或上层应用读取后,rwnd 可能变化,并通过 ACK 实时通告给发送方。
特殊场景:窗口更新(Window Update 报文段)
当接收方的可用缓冲区空间(rwnd)从 0 变为非 0 时(即之前因缓冲区满而通告 rwnd=0,后续缓冲区释放),会主动发送一个仅包含 rwnd 字段的 ACK 报文段(称为 “窗口更新”),通知发送方恢复数据发送。
注意:窗口更新报文段不携带数据确认(即 ACK 号不变化),仅用于告知发送方可用窗口已更新,避免发送方因等待超时才重新尝试发送。
核心规则:rwnd 始终通过 ACK 报文段携带
无论何种场景,rwnd 的值永远包含在 TCP 的 ACK 报文段中(包括 SYN-ACK、数据 ACK、窗口更新 ACK)。发送方通过解析 ACK 中的窗口字段获取当前 rwnd,并据此调整发送窗口(取 rwnd 和 cwnd 的较小值)。
4.2、窗口关闭
窗口关闭 --- 接收方发送一个窗口值为0的数据报文段,代表此时接收方所有缓存空间均已占满,无法再进行数据接收,则收到后,发送方将不再发送数据
此时会出现一个问题,当服务器发送了一个win=0的报文表示目前的缓存空间已经占满,客户端就会关闭窗口,不再发送数据,但是之后服务器处理完数据,再次在ACK报文中发送自己能接收的窗口大小丢失(ACK报文丢失是不会触发超时重传的,之前的超时重传是因为发送了SYN报文而太久没收到ACK报文而触发的超时重传)。
应对举措:客户端周期性的发送一个窗口探测报文(只包含一个字节),要是服务器的窗口大小还是为0,就继续周期性等待,要是服务器发送过来的rwnd大于0,则客户端就开始继续发送数据段。
当接收方通告小窗口比如说10字节,而TCP/IP的头部都有40字节,这是一种资源的浪费,里面都无法携带数据,达不到发送数据要求的窗口就没有意义,称之为经济性很差。
4.3、小窗口的处理
接收方 --- 设定一个通告窗口的最小值,一般是MSS和1/2缓存空间中的较小值,如果通告的窗口值小于设定值,则会通告一个窗口值为0的数据报文来关闭窗口,直到窗口大小突破最小值之后,再打开窗口。
发送方 --- 一般采用延时处理,只有满足以下两个条件之一,才发送数据,否则将囤积数据,直到满足条件为止
1、要等到窗口大小 >= MSS 并且数据大小 >= MSS
2、收到之前发送数据的ACK回报
五、TCP拥塞控制
TCP的拥塞控制 --- TCP也会观察网络的堵塞情况,如果网络堵塞严重,则降低发送量,以缓解网络的拥塞情况。
1、TCP该如何判断此时网络环境拥塞
2、TCP该如何控制数据的发送量
5.1、TCP的拥塞判断
TCP将连接中的丢包行为,视为拥塞的表现:
1、数据包确认超时(超时重传)
2、收到接收方发送的3个冗余ACK(快速重传)
5.2、TCP的拥塞控制
拥塞窗口 --- cwnd
接收窗口 --- rwnd
发送方,发出未收到确认的字节数必须小于或等于rwnd或cwnd中的较小值
TCP拥塞控制算法(自计时的协议) --- 慢启动、拥塞避免、快速恢复
5.3、慢启动
慢启动 --- slow - start,在TCP建立连接的时候的算法,最开始的时候设置为1MSS,每收到一个正常ACK(不包含重传ACK),cwnd就会增加一个MSS大小
慢启动增长的速率:指数增长,这样的增长速率对于网络链路是不友好的。
慢启动门限 --- ssthresh --- cwnd < ssthresh --- 慢启动算法
cwnd > ssthresh --- 拥塞避免算法
cwnd = ssthresh --- 慢启动算法/拥塞避免算法
5.4、拥塞避免
拥塞避免算法就是在一个RTT(往返时间)时间内,cwnd只增长一个MSS,尽可能降低网络拥塞,而不是直接避免网络拥塞。
5.5、快速恢复
数据包确认超时:
发送超时重传,首先先将ssthresh(慢启动门限)设置为当前cwnd值的一半,之后,将cwnd设置为一个MSS,按照新的门限值,执行慢启动算法,达到门限后,重新进入到拥塞避免算法
收到接收方发送的3个冗余ACK:
实施快速恢复。首先将慢启动门限值设置为当前拥塞窗口(发送拥塞事件时候的拥塞窗口)的一半,之后,直接基于慢启动门限执行拥塞避免算法