前言
TCP作为面向连接的可靠传输协议,被广泛应用于各种场景,也是在面试时经常被提及的内容。学习了解TCP协议也是学习网络必不可少的一步,作者从个人的角度对TCP的一些知识进行了梳理,也是对学习的一个记录,有不对的地方也欢迎大家进行交流。
1.协议格式
1.1 TCP协议格式
TCP协议格式
TCP协议特点:面向连接的,发送数据需要建立连接;可靠的,保证数据从发送方到达接收方;
- 16位源端口号:数据发送方的端口号,表示数据从哪里来
- 16位目标端口号:数据接收方的端口号,表示数据要到哪里去
- 32位序号:每一次通信TCP报文的编号
- 32确认序号:用于对发送方发送的报文的确认,为接收到的报文序号+1
- 4位首部长度:表示TCP报文头部有多少个4字节,因为4位最大表示15,所以TCP报文头最大长度为15*4=60
- 6位标志位:
- URG: 紧急指针是否有效
- ACK: 确认应答
- PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
- RST: 表示要求对方重新建立连接
- SYN: 请求建立连接,我们把携带SYN标识的称为同步报文段
- FIN: 通知对方本端要关闭了,我们把携带FIN标识的为结束报文段
- 16位窗口大小:TCP流量控制的一个手段,这里指接收窗口,用于告知发送端本端的TCP缓冲区还能容纳多少字节的数据,这样发送方就可以控制发送的数据量
- 16位校验和:由发送方填充,接收端对TCP报文段执行CRC校验以检查数据是否损坏,这个校验不仅包含TCP头部,也包含数据部分
- 16位紧急指针:一个正的偏移量,它与序号字段相加表示最后一个紧急数据的下一个序号,所以这个字段是紧急指针相对当前序号的偏移,用于发送方向接收方发送紧急数据
- 0-40字节选项数据:存储一些可能需要的额外信息
TCP/IP协议关系
1.2 UDP协议格式
UDP协议
UDP协议特点:无连接,只需要知道对方地址就可以发送数据;不可靠,不保证数据有序安全到达对端,没有丢包检测机制;面向数据报的,有最大长度限制,并且是整条交付。
- 16位源端口号:数据发送方的端口号,表示数据从哪里来
- 16位目的端口号:数据接收方的端口号,表示数据要到哪里去
- 16位UDP长度:报文长度,UDP报文长度须小于64k - 8
- 16位UPD校验和:用于校验接收的数据是否与发送的数据一致,如果校验出错,就会直接丢弃。使用二进制反码求和算法:即将报文的每个字节取反相加得到的和
- 变长数据:应用数据,小于64k-8
2.TCP 和 UDP的区别
TCP | UDP |
---|---|
面向连接的,发送数据前需要建立连接 | 1.无连接,只需要知道对方地址就可以发送数据; 2.面向数据报的,有最大长度限制,并且是整条交付 |
可靠的,保证数据从发送方到达接收方 | 不可靠,UDP尽最大努力交付,不保证数据有序安全到达对端,没有丢包检测机制 |
适用于网页,邮件等 | 传输效率更高,适用于高速传输和实时性要求高的通信,适用于视频,语音广播等 |
3.TCP的三次握手
TCP三次握手
开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态
- 第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SEND 状态
- 第二次握手(SYN=1, ACK=1, seq=y, ack=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态
- 第三次握手(ACK=1,ack=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输。
3.1为什么是三次握手?不是两次或四次?
因为TCP发送端和接收端的消息都需要被确认
为什么不是两次?
- 如果客户端发往服务端的SYN消息滞留在网络中,客户端发起重传SYN并建立连接,如果等到连接关闭时,此时之前滞留在网络中的SYN消息到达服务端,服务端回复,就默认建立连接,但是此时客户端已经断开连接了,造成连接资源的浪费
- 如果只有两次,发送端发往服务端的SYN消息,服务端收到SYN消息,如果服务端回复的消息丢失的话,服务端认为建立连接了,但其实客户端并没有建立连接
为什么不是四次?
三次握手足够确认双方的发送和接收能力,再多的消息就多余了
4. TCP的四次挥手过程
TCP四次挥手
- 第一次挥手(FIN=1, seq=u),发送完毕后,客户端进入FIN_WAIT_1 状态
- 第二次挥手(ACK=1, seq =v, ack=u+1),发送完毕后,服务器端进入CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态
- 第三次挥手(FIN=1, ACK=1, seq=w, ack=u+1),发送完毕后,服务器端进入LAST_ACK 状态,等待来自客户端的最后一个ACK
- 第四次挥手(ACK=1, seq=u+1, ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
4.1 TCP挥手为什么需要四次?
因为当客户端发起关闭后,服务端收到并回复确认,但此时服务端可能还有发往客户端的数据没有发完,所以等待服务端发往后再发起关闭连接,客户端此时回复确认,此时整个连接才可以关闭。
4.2 TIME-WAIT 状态为什么需要等待2MSL?
TIME-WAIT状态等待2MSL的原因
2MSL,2 Maximum Segment Lifetime,即两个最大段生命周期.
- 1 个 MSL 确保四次挥手中客户端最后的 ACK 报文最终能达到服务端
- 1个 MSL 保证服务端没有收到 ACK 那么进行重传的 FIN 报文能够到达
5. TCP是如何保证可靠性的
- 连接管理:三次握手四次挥手。
- 校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
- 序列号:TCP在传输时将数据进行了编号,每个连接都会选择一个初始序列号,初始序列号(视为一个 32 位计数器),会随时间而改变(每 4 微秒加 1),序列号可以用于应答,保证数据的有序性,同时也可以用于去除重复数据。
- 确认应答:TCP 传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答,也就是发送 ACK 报文。
- 超时重传:超时重传机制,简单理解就是发送方在发送完数据后等待一个时间,时间到达没有接收到 ACK 报文,那么对刚才发送的数据进行重新发送。
- 流量控制:当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。
- 拥塞控制:拥塞控制是 TCP 在传输时尽可能快的将数据传输,并且避免拥塞造成的一系列问题。是可靠性的保证,同时也是维护了传输的高效性。
6. TCP重传机制
6.1 超时重传
TCP 为了实现可靠传输,实现了重传机制。最基本的重传机制,就是超时重传,这个超时时间,一般设置为多少呢?我们先来看下什么叫RTT(Round-Trip Time,往返时间)。
RTT
RTT就是,一个数据包从发出去到回来的时间,即数据包的一次往返时间。超时重传时间,就是Retransmission Timeout ,简称RTO。
RTO设置多久呢?
一般RTO要略大于RTT,效果是最好的,那有没有什么计算公式来计算RTO呢?当然有!
经典方法:
经典方法引入了一个新的概念——SRTT(Smoothed round trip time,即平滑往返时间)
1. 先计算SRTT(计算平滑的RTT)
SRTT = (α * SRTT) + ((1 - α) * RTT) //加权平均
2. 计算RTO
RTO = min(ubound, max(lbound, β * SRTT))
α 是平滑因子,建议值是0.8
,β 是加权因子,一般为1.3 ~ 2.0
, lbound 是下界,ubound 是上界。
局限性:因为平滑因子 α 的范围是0.8 ~ 0.9
, RTT 对于 RTO 的影响太小。
Jacobson / Karels 算法:
也是计算RTO的标准方法,解决经典方法对于 RTT 变化不敏感的问题
1. 先计算SRTT(计算平滑的RTT)
SRTT = (1 - α) * SRTT + α * RTT //求 SRTT 的加权平均
2. 再计算RTTVAR (round-trip time variation)
RTTVAR = (1 - β) * RTTVAR + β * (|RTT - SRTT|) //计算 SRTT 与真实值的差距
3. 最终的RTO
RTO = µ * SRTT + ∂ * RTTVAR = SRTT + 4·RTTVAR
其中,α = 0.125,β = 0.25, μ = 1,∂ = 4
,这些参数都是大量结果得出的最优参数。
★超时重传的缺点
- 当一个报文段丢失时,会等待一定的超时周期然后才重传分组,增加了端到端的时延。
- 当一个报文段丢失时,在其等待超时的过程中,可能会出现这种情况:其后的报文段已经被接收端接收但却迟迟得不到确认,发送端会认为也丢失了,从而引起不必要的重传,既浪费资源也浪费时间。
6.2 快速重传
TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。
快速重传
发送端发送了 1,2,3,4,5份数据:
- 第一份 Seq 1 先送到了,于是就 Ack 回 2
- 第二份 Seq 2 由于网络等其他原因,没送到
- 第三份 Seq 3 到达了,但是由于 Seq 2 没到,于是还是 Ack 回 2
- 后面 Seq 4 和 Seq 5 都到了,由于 Seq 2 没到,于是还是 Ack 回 2
- 发送端收到了三个 Ack = 2 的确认,知道了 Seq 2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2
- 最后,收到了 Seq 2,此时因为 Seq 3,Seq 4,Seq 5 都收到了,于是 Ack 回 6
但快速重传还可能会有个问题:ACK只向发送端告知最大的有序报文段,到底是哪个报文丢失了呢?并不确定!那到底该重传多少个包呢?
比如上面在进行重传时,是只重传Seq 2呢,还是 3,4,5 都需要重传?
6.3 带选择确认的重传(SACK)
为了解决不知道重传哪些TCP报文,应该重传多少个包?于是就有了SACK(Selective Acknowledgment)带选择确认的重传
这种方式需要在 TCP 头部「选项」字段里加一个 SACK
的东西,接收端ack最近收到的报文段序号范围,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
上图中,发送端收到三次相同的ack=20的确认报文,预计触发快速重传机制,通过SACK信息发现只有20~29需要重传,于是重传20~29的报文段。
6.4 Duplicate SACK
D-SACK,在SACK的基础上做了一些扩展,,主要用来告诉发送方,有哪些数据包自己重复接收了。DSACK的目的是帮助发送方判断,是否发生了包失序、ACK丢失、包重复或伪重传。让TCP可以更好的做网络流控。
在接收方回复的ack连续丢失,引起发送方超时重传后,接收方回复SACK=10~20告诉发送方10~19已经收过了,因为此时ACK已经到30了,同时SACK=40~50告诉发送方30~39报文丢失了。
7. TCP的滑动窗口
TCP 发送一个数据,需要收到确认应答,才会发送下一个数据。这样有个缺点,就是效率会比较低。
这就像我和你面对面聊天,你说一句,我应答一句,你再说下一句。但这种方式的缺点是效率比较低的。如果你说完一句,我没有及时应答你,你就迟迟不说下一句,这显然很不现实。
为了解决这个问题,TCP引入了窗口的概念,它是操作系统开辟的一个缓存空间,窗口大小表示发送方在等待确认应答返回之前可以发送的数据最大值。
TCP头部的win字段,即16位的窗口大小,它告诉对端当前接收缓冲区剩余空间大小,这样发送方就可以控制发送的数据量。
★ 通俗点讲,就是接受方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的缓存区还有多少空余空间,缓冲区的空剩余空间,我们就称之为接收窗口大小。这就是win。
TCP的滑动窗口有发送窗口和接收窗口,它们分别由以下几部分组成。
发送窗口:
- 已发送且已收到ACK确认
- 已发送但未收到ACK确认
- 未发送但可以发送
- 未发送且不可发送
- 虚线矩形框就是发送窗口
- SND.WND 表示发送窗口的大小,本例中发送窗口大小为12
- SND.UNA 表示
unacknowledged
即未确认的,它指向的是已发送但未确认的第一个字节的序列号 - SND.NXT 表示下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号
接收窗口rwnd:
- 已接收并确认
- 未接收,但可以接收
- 未接收,且不可接收
接收窗口
- 虚线矩形框就是发送窗口
- RCV.WND 表示接收窗口大小,本例中为5
- RCV.NXT 表示下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号
8. TCP的流量控制
TCP经历三次握手,发送端和接收端进入到ESTABLISHED状态,它们即可以愉快地传输数据啦
但是发送端不能疯狂地向接收端发送数据,因为接收端接收不过来的话,接收方只能把处理不过来的数据存在缓存区里。如果缓存区都满了,发送方还在疯狂发送数据的话,接收方只能把收到的数据包丢掉,这就浪费了网络资源啦。
★TCP 提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量,这就是 流量控制
TCP通过滑动窗口实现流量控制
首先双方三次握手,初始化各自的窗口大小,均为 400 个字节。
- 第一次发送方发送了200字节,发送方的
SND.NXT
会右移200个字节,也就是说当前的可用窗口减少了200 个字节 - 接受方收到后,放到缓冲队列里面,RCV.WND=400-200=200字节,所以win=200字节返回给发送方。接收方会在 ACK 的报文首部带上可接收窗口大小200字节
- 发送方又发送200字节过来,200字节到达,继续放到缓冲队列。不过这时候,由于大量负载的原因,接受方处理不了这么多字节,只能处理100字节,剩余的100字节继续放到缓冲队列。这时候,RCV.WND = 400-200-100=100字节,即win=100返回发送方
- 发送方继续发送100字节过来,这时候,接受窗口win变为0。
- 发送方停止发送,开启一个定时任务,每隔一段时间,就去询问接受方,直到win大于0,才继续开始发送。
9. TCP的拥塞控制
流量控制是作用与发送端和接收端的,根据接收端接收能力控制发送速度,并没有考虑网络环境的影响,如果因为网络差大量丢包,发送端不加控制的发送的话就可能会引入大量的无效重传,或进一步加剧网络拥塞, 所以引入拥塞控制来解决这一问题。
拥塞控制是作用于网络的,防止过多的数据注入到网络中,避免出现网络负载过大的情况。主要目标是最大话利用网络上瓶颈链路的带宽。
我们可以把网络链路比喻成一根水管,如果我们想最大化利用网络来传输数据,那就是尽快让水管达到最佳充满状态。发送方维护一个拥塞窗口cwnd(congestion window) 的变量,用来估算在一段时间内这条链路(水管)可以承载和运输的数据(水)的数量。它大小代表着网络的拥塞程度,并且是动态变化的,但是为了达到最大的传输效率,我们该如何知道这条水管的运送效率是多少呢?
一种简单的方法是不断增加传输的数据量,知道水管快要爆裂为止,对应于网络上就是发生丢包时。
只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去,但只要网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入到网络中的数据包数。
常用的拥塞控制算法有慢启动,拥塞避免,拥塞发生,快速恢复,这几个状态的转换关系如下图所示:
9.1 慢启动
刚开始进入传输数据的时候,你是不知道现在的网路到底是稳定还是拥堵的,如果做的太激进,发包太急,那么疯狂丢包,造成雪崩式的网络灾难。慢启动就是指刚开始时慢慢发,探测一下网络状态,如果没有出现丢包,则每收到一个ACK,拥塞窗口cwnd大小就加1(单位是MSS)。每轮次发送窗口增加一倍,呈指数增长,如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。
发送窗口=min(rwnd,cwnd)
- TCP连接完成,初始化cwnd = 1,表明可以传一个MSS单位大小的数据。
- 每当收到一个ACK,cwnd就加一;
- 每当过了一个RTT,cwnd就增加一倍; 呈指数让升
“ 那慢启动涨到什么时候是个头呢?
为了防止网络拥塞,还需要一个慢启动阀值ssthresh(slow start threshold)状态变量。当cwnd
到达该阀值后,就好像水管被关小了水龙头一样,减少拥塞状态。即当cwnd >ssthresh时,进入了拥塞避免算法。
9.2 拥塞避免
当拥塞窗口 cwnd
到达慢启动阈值后,就会进入拥塞避免算法。
- 每收到一个ACK时,cwnd = cwnd + 1/cwnd
- 当每过一个RTT时,cwnd = cwnd + 1
显然拥塞避免时的 cwnd 变成了线性增长。
进入拥塞避免后,cwnd还在慢慢增长,网络慢慢就会进入拥塞状况,紧接着就会出现丢包,就需要进行丢包重传
9.3 拥塞发生
拥塞发生丢包时,会有两种情况
- RTO超时重传
- 快速重传
如果是发生了RTO超时重传,就会使用拥塞发生算法
- 慢启动阀值sshthresh = cwnd /2
- cwnd 重置为 1
- 进入新的慢启动过程
这真的是辛辛苦苦几十年,一朝回到解放前。其实还有更好的处理方式,就是快速重传。发送方收到3个连续重复的ACK时,就会快速地重传。忘记的小伙伴可以返回到6.2节再看下。
快速重传时,慢启动阀值ssthresh 和 cwnd 变化如下:
- 拥塞窗口大小 cwnd = cwnd/2
- 慢启动阀值 ssthresh = cwnd
- 进入快速恢复算法
9.4 快速恢复
快速重传和快速恢复算法一般同时使用。快速恢复算法认为,还有3个重复ACK收到,说明网络也没那么糟糕,所以没有必要像RTO超时那么强烈。
正如前面所说,进入快速恢复之前,cwnd 和 sshthresh已被更新:
- cwnd = cwnd /2
- sshthresh = cwnd
然后,进入快速恢复算法如下:
- cwnd = sshthresh + 3
- 重传重复的那几个ACK(即丢失的那几个数据包)
- 如果再收到重复的 ACK,那么 cwnd = cwnd +1
- 如果收到新数据的 ACK 后, cwnd = sshthresh。因为收到新数据的 ACK,表明恢复过程已经结束,可以再次进入了拥塞避免的算法了。
10. TCP的粘包和拆包
TCP是面向字节流的,没有界限的一串数据,TCP底层并不清楚上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行分包,所以对业务层来说,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
为什么会产生粘包和拆包呢?
- 要发送的数据小于TCP发送缓冲区大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包
- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包
- 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包
- 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。
粘包解决措施
- 发送端将每个数据包封装为固定长度,这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
- 设置消息边界,在消息尾部设置特殊的分隔符,服务端可根据分割符分离出消息内容。
- 将消息分为两部分,一部分是头部,一部分是内容。其中头部结构大小固定,且有一个字段声明内容的大小
11. TCP的Keep-alive机制
如果两端的TCP连接一直没有数据交互,达到了触发TCP保活机制条件,TCP协议栈就会发送探测报文,所以通过探测报文来确认TCP连接是否存活
- 如果对端正常工作,就会响应探测报文 ,这样TCP的保活时间就会被重置,等待下一个TCP保活时间的到来
- 如果对端不回复探测报文,则发送端在重试发送探测报文达到阈值后,就会关闭此TCP连接
TCP 保活相关参数
- SO_KEEPALIVE:是否开启保活
- TCP_KEEPIDLE: Start keeplives after this period
- TCP_KEEPINTVL:Interval between keepalives
- TCP_KEEPCNT:Number of keepalives before death
12. TCP报文中时间戳的作用
TCP Timestamps Option 最初是在 RFC 1323 中引入的,由四部分构成:类别(kind)、长度(Length)、发送方时间戳(TS value)、回显时间戳(TS Echo Reply)。时间戳选项类别(kind)的值等于 8,用来与其它类型的选项区分。长度(length)等于 10。两个时间戳相关的选项都是 4 字节。
- 发送方发送数据时将自己的时间放入TSval中
- 接收方收到数据包后,将收到的数据包中的时间戳放入TSecr字段中,同时把自己的时间戳放入TSval字段中
- 自己的时间戳放在TSval中,对方的时间戳放到TSecr中
时间戳的作用:
- 两端往返延迟测量(RTTM)
- PWAS:防止序列号回绕,在高速网络中,序列号在很短的时间内就会被重复使用,如果网络中的脏数据序号恰巧和下一次要传输的包序号对上,就会收错包,如果有Timestamps ,内核会为每个连接维护一个ts_recent,记录最后一次通信的timestamps值,由于脏数据的timestamps < ts_recent,所以会被丢掉。
13. 半连接队列和SYN Flood攻击
TCP进入三次握手前,服务端会从 CLOSED状态变为 LISTEN状态,同时在内部创建了两个队列:半连接队列(SYN队列)和全连接队列(ACCEPT队列)。
什么是半连接队列?
TCP三次握手时,客户端发送SYN到服务端,服务端收到之后,便回复ACK和SYN,状态由LISTEN变为SYN_RCVD,此时这个连接就被推入了SYN队列,即半连接队列。
什么是全连接队列?
当客户端回复ACK, 服务端接收后,三次握手就完成了。这时连接会等待被具体的应用取走,在被取走之前,它被推入ACCEPT队列,即全连接队列。
什么是SYN Flood攻击?
SYN Flood 属于典型的 DoS/DDoS (Denial of Service,拒绝服务)攻击。就是用客户端在短时间内伪造大量不存在的 IP 地址,并向服务端疯狂发送SYN
。当服务器回复SYN+ACK报文后,不会收到ACK回应报文,会产生两个危险的后果:
- 服务器上建立大量的半连接,导致半连接队列满了,无法处理正常的TCP请求
- 服务端长时间收不到客户端的ACK,会导致服务端不断重发数据,直到耗尽服务端的资源
如何应对SYN Flood攻击?
- syn cookie:在收到SYN包后,服务器根据一定的方法,以数据包的源地址、端口等信息为参数计算出一个cookie值作为自己的SYNACK包的序列号,回复SYN+ACK后,服务器并不立即分配资源进行处理,等收到发送方的ACK包后,重新根据数据包的源地址、端口计算该包中的确认序列号是否正确,如果正确则建立连接,否则丢弃该包。
- SYN Proxy防火墙:服务器防火墙会对收到的每一个SYN报文进行代理和回应,并保持半连接。等发送方将ACK包返回后,再重新构造SYN包发到服务器,建立真正的TCP连接。
14. Nagle 算法与延迟确认
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。 Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是:任意时刻,最多只能有一个未被确认的小段。所谓“小段”指的是小于MSS的数据块,“没有被确认”指的是一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
Nagle算法的实现规则:
- 如果包长度达到MSS,则允许发送
- 如果该包含有FIN,则允许发送
- 设置了TCP_NODELAY选项,则允许发送
- 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送
- 上述条件都未满足,但发生了超时,则立即发送
延迟确认:
延迟确认顾名思义,就是在收到数据时晚一会再ack,如果在延迟阶段有新数据到来,则合并ack
接收方收到数据包以后如果暂时没有数据要发给对端,它可以等一段时再确认(Linux上默认是40ms)。如果这段时间刚好有数据要传给对端,Ack就随着数据传输,而不需要单独发送一次Ack。如果超过时间还没有数据要发送,也发送Ack,避免对端以为丢包
不过需要主要的是,有一些场景是不能延迟确认的,比如发现了乱序包、接收到了大于一个 frame 的报文,且需要调整窗口大小等。
一般情况下,Nagle算法和延迟确认不能一起使用,Nagle算法意味着延迟发,延迟确认意味着延迟接收,这样就会造成更大的延迟,会产生性能问题。