共计 10595 个字符,预计需要花费 27 分钟才能阅读完成。
起源 | 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 也就是两个小时检测一次,工夫太长
- 工夫再短一些,也难以体现其设计的初衷, 即检测长时间的死连贯
因而是一个比拟难堪的设计。