起源 | urlify.cn/rqumIn
先亮出这篇文章的思维导图:
TCP 作为传输层的协定,是一个IT工程师素养的体现,也是面试中常常被问到的知识点。在此,我将 TCP 外围的一些问题梳理了一下,心愿能帮到各位。
001. 能不能说一说 TCP 和 UDP 的区别?
首先概括一下根本的区别:
TCP是一个面向连贯的、牢靠的、基于字节流的传输层协定。
而UDP是一个面向无连贯的传输层协定。(就这么简略,其它TCP的个性也就没有了)。
具体来剖析,和 UDP
相比,TCP
有三大外围个性:
- 面向连贯。所谓的连贯,指的是客户端和服务器的连贯,在单方相互通信之前,TCP 须要三次握手建设连贯,而 UDP 没有相应建设连贯的过程。
- 可靠性。TCP 花了十分多的功夫保障连贯的牢靠,这个可靠性体现在哪些方面呢?一个是有状态,另一个是可管制。
TCP 会精准记录哪些数据发送了,哪些数据被对方接管了,哪些没有被接管到,而且保障数据包按序达到,不容许半点过错。这是有状态。
当意识到丢包了或者网络环境不佳,TCP 会依据具体情况调整本人的行为,管制本人的发送速度或者重发。这是可管制。
相应的,UDP 就是无状态
, 不可控
的。
- 面向字节流。UDP 的数据传输是基于数据报的,这是因为仅仅只是继承了 IP 层的个性,而 TCP 为了保护状态,将一个个 IP 包变成了字节流。
002: 说说 TCP 三次握手的过程?为什么是三次而不是两次、四次?
恋爱模仿
以谈恋爱为例,两个人可能在一起最重要的事件是首先确认各自爱和被爱的能力。接下来咱们以此来模仿三次握手的过程。
第一次:
男: 我爱你。
女方收到。
由此证明男方领有爱
的能力。
第二次:
女: 我收到了你的爱,我也爱你。
男方收到。
OK,当初的状况阐明,女方领有爱
和被爱
的能力。
第三次:
男: 我收到了你的爱。
女方收到。
当初可能保障男方具备被爱
的能力。
由此残缺地确认了单方爱
和被爱
的能力,两人开始一段苦涩的恋情。
实在握手
当然刚刚那段属于扯淡,不代表自己价值观,目标是让大家了解整个握手过程的意义,因为两个过程十分类似。对应到 TCP 的三次握手,也是须要确认单方的两样能力: 发送的能力
和接管的能力
。于是便会有上面的三次握手的过程:
从最开始单方都处于CLOSED
状态。而后服务端开始监听某个端口,进入了LISTEN
状态。
而后客户端被动发动连贯,发送 SYN , 本人变成了SYN-SENT
状态。
服务端接管到,返回SYN
和ACK
(对应客户端发来的SYN),本人变成了SYN-REVD
。
之后客户端再发送ACK
给服务端,本人变成了ESTABLISHED
状态;服务端收到ACK
之后,也变成了ESTABLISHED
状态。
另外须要揭示你留神的是,从图中能够看出,SYN 是须要耗费一个序列号的,下次发送对应的 ACK 序列号要加1,为什么呢?只须要记住一个规定:
但凡须要对端确认的,肯定耗费TCP报文的序列号。
SYN 须要对端的确认, 而 ACK 并不需要,因而 SYN 耗费一个序列号而 ACK 不须要。
为什么不是两次?
根本原因: 无奈确认客户端的接管能力。
剖析如下:
如果是两次,你当初发了 SYN 报文想握手,然而这个包滞留在了以后的网络中迟迟没有达到,TCP 认为这是丢了包,于是重传,两次握手建设好了连贯。
看似没有问题,然而连贯敞开后,如果这个滞留在网路中的包达到了服务端呢?这时候因为是两次握手,服务端只有接管到而后发送相应的数据包,就默认建设连贯,然而当初客户端曾经断开了。
看到问题的吧,这就带来了连贯资源的节约。
为什么不是四次?
三次握手的目标是确认单方发送
和接管
的能力,那四次握手能够嘛?
当然能够,100 次都能够。但为了解决问题,三次就足够了,再多用处就不大了。
三次握手过程中能够携带数据么?
第三次握手的时候,能够携带。前两次握手不能携带数据。
如果前两次握手可能携带数据,那么一旦有人想攻打服务器,那么他只须要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会耗费更多的工夫和内存空间去解决这些数据,增大了服务器被攻打的危险。
第三次握手的时候,客户端曾经处于ESTABLISHED
状态,并且曾经可能确认服务器的接管、发送能力失常,这个时候绝对平安了,能够携带数据。
同时关上会怎么?
如果单方同时发 SYN
报文,状态变动会是怎么的呢?
这是一个可能会产生的状况。
状态变迁如下:
在发送方给接管方发SYN
报文的同时,接管方也给发送方发SYN
报文,两个人刚上了!
发完SYN
,两者的状态都变为SYN-SENT
。
在各自收到对方的SYN
后,两者状态都变为SYN-REVD
。
接着会回复对应的ACK + SYN
,这个报文在对方接管之后,两者状态一起变为ESTABLISHED
。
这就是同时关上状况下的状态变迁。
003: 说说 TCP 四次挥手的过程
过程拆解
刚开始单方处于ESTABLISHED
状态。
客户端要断开了,向服务器发送 FIN
报文,在 TCP 报文中的地位如下图:
发送后客户端变成了FIN-WAIT-1
状态。留神, 这时候客户端同时也变成了half-close(半敞开)
状态,即无奈向服务端发送报文,只能接管。
服务端接管后向客户端确认,变成了CLOSED-WAIT
状态。
客户端接管到了服务端的确认,变成了FIN-WAIT2
状态。
随后,服务端向客户端发送FIN
,本人进入LAST-ACK
状态,
客户端收到服务端发来的FIN
后,本人变成了TIME-WAIT
状态,而后发送 ACK 给服务端。
留神了,这个时候,客户端须要期待足够长的工夫,具体来说,是 2 个 MSL
(Maximum Segment Lifetime,报文最大生存工夫
), 在这段时间内如果客户端没有收到服务端的重发申请,那么示意 ACK 胜利达到,挥手完结,否则客户端重发 ACK。
期待2MSL的意义
如果不期待会怎么?
如果不期待,客户端间接跑路,当服务端还有很多数据包要给客户端发,且还在路上的时候,若客户端的端口此时刚好被新的利用占用,那么就接管到了无用数据包,造成数据包凌乱。所以,最保险的做法是等服务器发来的数据包都死翘翘再启动新的利用。
那,照这样说一个 MSL 不就不够了吗,为什么要期待 2 MSL?
- 1 个 MSL 确保四次挥手中被动敞开方最初的 ACK 报文最终能达到对端
- 1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文能够达到
这就是期待 2MSL 的意义。
为什么是四次挥手而不是三次?
因为服务端在接管到FIN
, 往往不会立刻返回FIN
, 必须等到服务端所有的报文都发送结束了,能力发FIN
。因而先发一个ACK
示意曾经收到客户端的FIN
,提早一段时间才发FIN
。这就造成了四次挥手。
如果是三次挥手会有什么问题?
等于说服务端将ACK
和FIN
的发送合并为一次挥手,这个时候长时间的提早可能会导致客户端误以为FIN
没有达到客户端,从而让客户端一直的重发FIN
。
同时敞开会怎么?
如果客户端和服务端同时发送 FIN ,状态会如何变动?如图所示:
004: 说说半连贯队列和 SYN Flood 攻打的关系
三次握手前,服务端的状态从CLOSED
变为LISTEN
, 同时在外部创立了两个队列:半连贯队列和全连贯队列,即SYN队列和ACCEPT队列。
半连贯队列
当客户端发送SYN
到服务端,服务端收到当前回复ACK
和SYN
,状态由LISTEN
变为SYN_RCVD
,此时这个连贯就被推入了SYN队列,也就是半连贯队列。
全连贯队列
当客户端返回ACK
, 服务端接管后,三次握手实现。这个时候连贯期待被具体的利用取走,在被取走之前,它会被推入另外一个 TCP 保护的队列,也就是全连贯队列(Accept Queue)。
SYN Flood 攻打原理
SYN Flood 属于典型的 DoS/DDoS 攻打。其攻打的原理很简略,就是用客户端在短时间内伪造大量不存在的 IP 地址,并向服务端疯狂发送SYN
。对于服务端而言,会产生两个危险的结果:
- 解决大量的
SYN
包并返回对应ACK
, 势必有大量连贯处于SYN_RCVD
状态,从而占满整个半连贯队列,无奈解决失常的申请。 - 因为是不存在的 IP,服务端长时间收不到客户端的
ACK
,会导致服务端一直重发数据,直到耗尽服务端的资源。
如何应答 SYN Flood 攻打?
- 减少 SYN 连贯,也就是减少半连贯队列的容量。
- 缩小 SYN + ACK 重试次数,防止大量的超时重发。
- 利用 SYN Cookie 技术,在服务端接管到
SYN
后不立刻调配连贯资源,而是依据这个SYN
计算出一个Cookie,连同第二次握手回复给客户端,在客户端回复ACK
的时候带上这个Cookie
值,服务端验证 Cookie 非法之后才调配连贯资源。
005: 介绍一下 TCP 报文头部的字段
报文头部构造如下(单位为字节):
请大家牢记这张图!
源端口、指标端口
如何标识惟一标识一个连贯?答案是 TCP 连贯的四元组
——源 IP、源端口、指标 IP 和指标端口。
那 TCP 报文怎么没有源 IP 和指标 IP 呢?这是因为在 IP 层就曾经解决了 IP 。TCP 只须要记录两者的端口即可。
序列号
即Sequence number
, 指的是本报文段第一个字节的序列号。
从图中能够看出,序列号是一个长为 4 个字节,也就是 32 位的无符号整数,示意范畴为 0 ~ 2^32 - 1。如果达到最大值了后就循环到0。
序列号在 TCP 通信的过程中有两个作用:
- 在 SYN 报文中替换彼此的初始序列号。
- 保障数据包按正确的程序组装。
ISN
即Initial Sequence Number(初始序列号)
,在三次握手的过程当中,单方会用过SYN
报文来替换彼此的 ISN
。
ISN 并不是一个固定的值,而是每 4 ms 加一,溢出则回到 0,这个算法使得猜想 ISN 变得很艰难。那为什么要这么做?
如果 ISN 被攻击者预测到,要晓得源 IP 和源端口号都是很容易伪造的,当攻击者猜想 ISN 之后,间接伪造一个 RST 后,就能够强制连贯敞开的,这是十分危险的。
而动静增长的 ISN 大大提高了猜想 ISN 的难度。
确认号
即ACK(Acknowledgment number)
。用来告知对方下一个冀望接管的序列号,小于ACK的所有字节曾经全副收到。
标记位
常见的标记位有SYN
,ACK
,FIN
,RST
,PSH
。
SYN 和 ACK 曾经在上文说过,后三个解释如下: FIN
:即 Finish,示意发送方筹备断开连接。
RST
:即 Reset,用来强制断开连接。
PSH
:即 Push, 告知对方这些数据包收到后应该马上交给下层的利用,不能缓存。
窗口大小
占用两个字节,也就是 16 位,但实际上是不够用的。因而 TCP 引入了窗口缩放的选项,作为窗口缩放的比例因子,这个比例因子的范畴在 0 ~ 14,比例因子能够将窗口的值扩充为原来的 2 ^ n 次方。
校验和
占用两个字节,避免传输过程中数据包有损坏,如果遇到校验和有过错的报文,TCP 间接抛弃之,期待重传。
可选项
可选项的格局如下:
罕用的可选项有以下几个:
- TimeStamp: TCP 工夫戳,前面具体介绍。
- MSS: 指的是 TCP 容许的从对方接管的最大报文段。
- SACK: 抉择确认选项。
- Window Scale:窗口缩放选项。
006: 说说 TCP 疾速关上的原理(TFO)
第一节讲了 TCP 三次握手,可能有人会说,每次都三次握手好麻烦呀!能不能优化一点?
能够啊。明天来说说这个优化后的 TCP 握手流程,也就是 TCP 疾速关上(TCP Fast Open, 即TFO)的原理。
优化的过程是这样的,还记得咱们说 SYN Flood 攻打时提到的 SYN Cookie 吗?这个 Cookie 可不是浏览器的Cookie
, 用它同样能够实现 TFO。
TFO 流程
首轮三次握手
首先客户端发送SYN
给服务端,服务端接管到。
留神哦!当初服务端不是立即回复 SYN + ACK,而是通过计算失去一个SYN Cookie
, 将这个Cookie
放到 TCP 报文的 Fast Open
选项中,而后才给客户端返回。
客户端拿到这个 Cookie 的值缓存下来。前面失常实现三次握手。
首轮三次握手就是这样的流程。而前面的三次握手就不一样啦!
前面的三次握手
在前面的三次握手中,客户端会将之前缓存的 Cookie
、SYN
和HTTP申请
(是的,你没看错)发送给服务端,服务端验证了 Cookie 的合法性,如果不非法间接抛弃;如果是非法的,那么就失常返回SYN + ACK
。
重点来了,当初服务端能向客户端发 HTTP 响应了!这是最显著的扭转,三次握手还没建设,仅仅验证了 Cookie 的合法性,就能够返回 HTTP 响应了。
当然,客户端的ACK
还得失常传过来,不然怎么叫三次握手嘛。
流程如下:
留神: 客户端最初握手的 ACK 不肯定要等到服务端的 HTTP 响应达到才发送,两个过程没有任何关系。
TFO 的劣势
TFO 的劣势并不在与首轮三次握手,而在于前面的握手,在拿到客户端的 Cookie 并验证通过当前,能够间接返回 HTTP 响应,充分利用了1 个RTT(Round-Trip Time,往返时延)的工夫提前进行数据传输,积攒起来还是一个比拟大的劣势。
007: 能不能说说TCP报文中工夫戳的作用?
timestamp
是 TCP 报文首部的一个可选项,一共占 10 个字节,格局如下:
kind(1 字节) + length(1 字节) + info(8 个字节)
其中 kind = 8, length = 10, info 有两局部形成: timestamp和timestamp echo,各占 4 个字节。
那么这些字段都是干嘛的呢?它们用来解决那些问题?
接下来咱们就来一一梳理,TCP 的工夫戳次要解决两大问题:
- 计算往返时延 RTT(Round-Trip Time)
- 避免序列号的回绕问题
计算往返时延 RTT
在没有工夫戳的时候,计算 RTT 会遇到的问题如下图所示:
如果以第一次发包为开始工夫的话,就会呈现左图的问题,RTT 显著偏大,开始工夫应该采纳第二次的;
如果以第二次发包为开始工夫的话,就会导致右图的问题,RTT 显著偏小,开始工夫应该采纳第一次发包的。
实际上无论开始工夫以第一次发包还是第二次发包为准,都是不精确的。
那这个时候引入工夫戳就很好的解决了这个问题。
比方当初 a 向 b 发送一个报文 s1,b 向 a 回复一个含 ACK 的报文 s2 那么:
- step 1: a 向 b 发送的时候,
timestamp
中寄存的内容就是 a 主机发送时的内核时刻ta1
。 - step 2: b 向 a 回复 s2 报文的时候,
timestamp
中寄存的是 b 主机的时刻tb
,timestamp echo
字段为从 s1 报文中解析进去的 ta1。 - step 3: a 收到 b 的 s2 报文之后,此时 a 主机的内核时刻是 ta2, 而在 s2 报文中的 timestamp echo 选项中能够失去
ta1
, 也就是 s2 对应的报文最后的发送时刻。而后间接采纳 ta2 - ta1 就失去了 RTT 的值。
避免序列号回绕问题
当初咱们来模仿一下这个问题。
序列号的范畴其实是在0 ~ 2 ^ 32 - 1, 为了不便演示,咱们放大一下这个区间,假如范畴是 0 ~ 4,那么达到 4 的时候会回到 0。
假如在第 6 次的时候,之前还滞留在网路中的包回来了,那么就有两个序列号为1 ~ 2
的数据包了,怎么辨别谁是谁呢?这个时候就产生了序列号回绕的问题。
那么用 timestamp 就能很好地解决这个问题,因为每次发包的时候都是将发包机器过后的内核工夫记录在报文中,那么两次发包序列号即便雷同,工夫戳也不可能雷同,这样就可能辨别开两个数据包了。
008: TCP 的超时重传工夫是如何计算的?
TCP 具备超时重传机制,即距离一段时间没有等到数据包的回复时,重传这个数据包。
那么这个重传距离是如何来计算的呢?
明天咱们就来讨论一下这个问题。
这个重传距离也叫做超时重传工夫(Retransmission TimeOut, 简称RTO),它的计算跟上一节提到的 RTT 密切相关。这里咱们将介绍两种次要的办法,一个是经典办法,一个是规范办法。
经典办法
经典办法引入了一个新的概念——SRTT(Smoothed round trip time,即平滑往返工夫),没产生一次新的 RTT. 就依据肯定的算法对 SRTT 进行更新,具体而言,计算形式如下(SRTT 初始值为0):
SRTT = ( * SRTT) + ((1 - ) * RTT)
其中, 是平滑因子,倡议值是0.8
,范畴是0.8 ~ 0.9
。
拿到 SRTT,咱们就能够计算 RTO 的值了:
RTO = min(ubound, max(lbound, * SRTT))
是加权因子,个别为1.3 ~ 2.0
, lbound 是下界,ubound 是上界。
其实这个算法过程还是很简略的,然而也存在肯定的局限,就是在 RTT 稳固的中央体现还能够,而在 RTT 变动较大的中央就不行了,因为平滑因子 的范畴是0.8 ~ 0.9
, RTT 对于 RTO 的影响太小。
规范办法
为了解决经典办法对于 RTT 变动不敏感的问题,前面又引出了规范办法,也叫Jacobson / Karels 算法
。
一共有三步。
第一步: 计算SRTT
,公式如下:
SRTT = (1 - ) * SRTT + * RTT
留神这个时候的 跟经典办法中的
取值不一样了,倡议值是
1/8
,也就是0.125
。
第二步: 计算RTTVAR
(round-trip time variation)这个两头变量。
RTTVAR = (1 - ) * RTTVAR + * (|RTT - SRTT|)
倡议值为 0.25。这个值是这个算法中出彩的中央,也就是说,它记录了最新的 RTT 与以后 SRTT 之间的差值,给咱们在后续感知到 RTT 的变动提供了抓手。
第三步: 计算最终的RTO
:
RTO = µ * SRTT + ∂ * RTTVAR
µ
倡议值取1
, ∂
倡议值取4
。
这个公式在 SRTT 的根底上加上了最新 RTT 与它的偏移,从而很好的感知了 RTT 的变动,这种算法下,RTO 与 RTT 变动的差值关系更加亲密。
009: 能不能说一说 TCP 的流量管制?
对于发送端和接收端而言,TCP 须要把发送的数据放到发送缓存区, 将接管的数据放到接管缓存区。
而流量管制索要做的事件,就是在通过接管缓存区的大小,管制发送端的发送。如果对方的接管缓存区满了,就不能再持续发送了。
要具体了解流量管制,首先须要理解滑动窗口
的概念。
TCP 滑动窗口
TCP 滑动窗口分为两种: 发送窗口和接管窗口。
发送窗口
发送端的滑动窗口构造如下:
其中蕴含四大局部:
- 已发送且已确认
- 已发送但未确认
- 未发送但能够发送
- 未发送也不能够发送
其中有一些重要的概念,我标注在图中:
发送窗口就是图中被框住的范畴。SND 即send
, WND 即window
, UNA 即unacknowledged
, 示意未被确认,NXT 即next
, 示意下一个发送的地位。
接管窗口
接收端的窗口构造如下:
REV 即 receive
,NXT 示意下一个接管的地位,WND 示意接管窗口大小。
流量管制过程
这里咱们不必太简单的例子,以一个最简略的来回来模仿一下流量管制的过程,不便大家了解。
首先单方三次握手,初始化各自的窗口大小,均为 200 个字节。
如果以后发送端给接收端发送 100 个字节,那么此时对于发送端而言,SND.NXT 当然要右移 100 个字节,也就是说以后的可用窗口
缩小了 100 个字节,这很好了解。
当初这 100 个达到了接收端,被放到接收端的缓冲队列中。不过此时因为大量负载的起因,接收端解决不了这么多字节,只能解决 40 个字节,剩下的 60
个字节被留在了缓冲队列中。
留神了,此时接收端的状况是解决能力不够用啦,你发送端给我少发点,所以此时接收端的接管窗口应该放大,具体来说,放大 60 个字节,由 200 个字节变成了 140 字节,因为缓冲队列还有 60 个字节没被利用拿走。
因而,接收端会在 ACK 的报文首部带上放大后的滑动窗口 140 字节,发送端对应地调整发送窗口的大小为 140 个字节。
此时对于发送端而言,曾经发送且确认的局部减少 40 字节,也就是 SND.UNA 右移 40 个字节,同时发送窗口放大为 140 个字节。
这也就是流量管制的过程。只管回合再多,整个管制的过程和原理是一样的。
010: 能不能说说 TCP 的拥塞管制?
上一节所说的流量管制产生在发送端跟接收端之间,并没有思考到整个网络环境的影响,如果说以后网络特地差,特地容易丢包,那么发送端就应该留神一些了。而这,也正是拥塞管制
须要解决的问题。
对于拥塞管制来说,TCP 每条连贯都须要保护两个外围状态:
- 拥塞窗口(Congestion Window,cwnd)
- 慢启动阈值(Slow Start Threshold,ssthresh)
波及到的算法有这几个:
- 慢启动
- 拥塞防止
- 疾速重传和疾速复原
接下来,咱们就来一一拆解这些状态和算法。首先,从拥塞窗口说起。
拥塞窗口
拥塞窗口(Congestion Window,cwnd)是指目前本人还能传输的数据量大小。
那么之前介绍了接管窗口的概念,两者有什么区别呢?
- 接管窗口(rwnd)是
接收端
给的限度 - 拥塞窗口(cwnd)是
发送端
的限度
限度谁呢?
限度的是发送窗口
的大小。
有了这两个窗口,如何来计算发送窗口
?
发送窗口大小 = min(rwnd, cwnd)
取两者的较小值。而拥塞管制,就是来管制cwnd
的变动。
慢启动
刚开始进入传输数据的时候,你是不晓得当初的网路到底是稳固还是拥挤的,如果做的太激进,发包太急,那么疯狂丢包,造成雪崩式的网络劫难。
因而,拥塞管制首先就是要采纳一种激进的算法来缓缓地适应整个网路,这种算法叫慢启动
。运作过程如下:
- 首先,三次握手,单方宣告本人的接管窗口大小
- 单方初始化本人的拥塞窗口(cwnd)大小
- 在开始传输的一段时间,发送端每收到一个 ACK,拥塞窗口大小加 1,也就是说,每通过一个 RTT,cwnd 翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,顺次类推。
难道就这么无止境地翻倍上来?当然不可能。它的阈值叫做慢启动阈值,当 cwnd 达到这个阈值之后,好比踩了下刹车,别涨了那么快了,老铁,先 hold 住!
在达到阈值后,如何来管制 cwnd 的大小呢?
这就是拥塞防止做的事件了。
拥塞防止
原来每收到一个 ACK,cwnd 加1,当初达到阈值了,cwnd 只能加这么一点: 1 / cwnd。那你认真算算,一轮 RTT 下来,收到 cwnd 个 ACK, 那最初拥塞窗口的大小 cwnd 总共才减少 1。
也就是说,以前一个 RTT 下来,cwnd
翻倍,当初cwnd
只是减少 1 而已。
当然,慢启动和拥塞防止是一起作用的,是一体的。
疾速重传和疾速复原
疾速重传
在 TCP 传输的过程中,如果产生了丢包,即接收端发现数据段不是按序达到的时候,接收端的解决是反复发送之前的 ACK。
比方第 5 个包丢了,即便第 6、7 个包达到的接收端,接收端也一律返回第 4 个包的 ACK。当发送端收到 3 个反复的 ACK 时,意识到丢包了,于是马上进行重传,不必等到一个 RTO 的工夫到了才重传。
这就是疾速重传,它解决的是是否须要重传的问题。
选择性重传
那你可能会问了,既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢?
当然第 6、7 个都曾经达到了,TCP 的设计者也不傻,曾经传过来干嘛还要传?罗唆记录一下哪些包到了,哪些没到,针对性地重传。
在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就能够加上SACK
这个属性,通过left edge
和right edge
告知发送端曾经收到了哪些区间的数据报。因而,即便第 5 个包丢包了,当收到第 6、7 个包之后,接收端仍然会通知发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment),它解决的是如何重传的问题。
疾速复原
当然,发送端收到三次反复 ACK 之后,发现丢包,感觉当初的网络曾经有些拥塞了,本人会进入疾速复原阶段。
在这个阶段,发送端如下扭转:
- 拥塞阈值升高为 cwnd 的一半
- cwnd 的大小变为拥塞阈值
- cwnd 线性减少
以上就是 TCP 拥塞管制的经典算法: 慢启动、拥塞防止、疾速重传和疾速复原。
011: 能不能说说 Nagle 算法和提早确认?
Nagle 算法
试想一个场景,发送端不停地给接收端发很小的包,一次只发 1 个字节,那么发 1 千个字节须要发 1000 次。这种频繁的发送是存在问题的,不光是传输的时延耗费,发送和确认自身也是须要耗时的,频繁的发送接管带来了微小的时延。
而防止小包的频繁发送,这就是 Nagle 算法要做的事件。
具体来说,Nagle 算法的规定如下:
- 当第一次发送数据时不必期待,就算是 1byte 的小包也立刻发送
- 前面发送满足上面条件之一就能够发了:
- 数据包大小达到最大段大小(Max Segment Size, 即 MSS)
- 之前所有包的 ACK 都已接管到
提早确认
试想这样一个场景,当我收到了发送端的一个包,而后在极短的工夫内又接管到了第二个包,那我是一个个地回复,还是略微等一下,把两个包的 ACK 合并后一起回复呢?
提早确认(delayed ack)所做的事件,就是后者,稍稍提早,而后合并 ACK,最初才回复给发送端。TCP 要求这个提早的时延必须小于500ms,个别操作系统实现都不会超过200ms。
不过须要次要的是,有一些场景是不能提早确认的,收到了就要马上回复:
- 接管到了大于一个 frame 的报文,且须要调整窗口大小
- TCP 处于 quickack 模式(通过
tcp_in_quickack_mode
设置) - 发现了乱序包
两者一起应用会怎么?
前者意味着提早发,后者意味着提早接管,会造成更大的提早,产生性能问题。
012. 如何了解 TCP 的 keep-alive?
大家都据说过 http 的keep-alive
, 不过 TCP 层面也是有keep-alive
机制,而且跟应用层不太一样。
试想一个场景,当有一方因为网络故障或者宕机导致连贯生效,因为 TCP 并不是一个轮询的协定,在下一个数据包达到之前,对端对连贯生效的状况是无所不知的。
这个时候就呈现了 keep-alive, 它的作用就是探测对端的连贯有没有生效。
在 Linux 下,能够这样查看相干的配置:
sudo sysctl -a | grep keepalive// 每隔 7200 s 检测一次net.ipv4.tcp_keepalive_time = 7200// 一次最多重传 9 个包net.ipv4.tcp_keepalive_probes = 9// 每个包的距离重传距离 75 snet.ipv4.tcp_keepalive_intvl = 75
不过,现状是大部分的利用并没有默认开启 TCP 的keep-alive
选项,为什么?
站在利用的角度:
- 7200s 也就是两个小时检测一次,工夫太长
- 工夫再短一些,也难以体现其设计的初衷, 即检测长时间的死连贯
因而是一个比拟难堪的设计。