你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了




重传机制

超时重传 快速重传 SACK D-SACK
超时重传
数据包丢失 确认应答丢失

超时时间应该设置为多少呢?


当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差; 当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。

快速重传

第一份 Seq1 先送到了,于是就 Ack 回 2; 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2; 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到; 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。 最后,接收到收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。
SACK 方法

Duplicate SACK

「接收方」发给「发送方」的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000 ~ 3499) 于是「接收方」发现数据是重复收到的,于是回了一个 SACK = 3000~3500,告诉「发送方」 3000~3500 的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据都已收到,所以这个 SACK 就代表着 D-SACK。 这样「发送方」就知道了,数据没有丢,是「接收方」的 ACK 确认报文丢了。

数据包(1000~1499) 被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」; 所以「接收方」回了一个 SACK=1000~1500,因为 ACK 已经到了 3000,所以这个 SACK 是 D-SACK,表示收到了重复的包。 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而是因为网络延迟了。
可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了; 可以知道是不是「发送方」的数据包被网络延迟了; 可以知道网络中是不是把「发送方」的数据包给复制了;

滑动窗口
引入窗口概念的原因


窗口大小由哪一方决定?
发送方的滑动窗口

#1 是已发送并收到 ACK确认的数据:1~31 字节 #2 是已发送但未收到 ACK确认的数据:32~45 字节 #3 是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节 #4 是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后


程序是如何表示发送方的四个部分的呢?

SND.WND:表示发送窗口的大小(大小是由接收方指定的); SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。 SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了。
接收方的滑动窗口
#1 + #2 是已成功接收并确认的数据(等待应用进程读取); #3 是未收到数据但可以接收的数据; #4 未收到数据并不可以接收的数据;

RCV.WND:表示接收窗口的大小,它会通告给发送方。 RCV.NXT:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。 指向 #4 的第一个字节是个相对指针,它需要 RCV.NXT 指针加上 RCV.WND 大小的偏移量,就可以指向 #4 的第一个字节了。
接收窗口和发送窗口的大小是相等的吗?

流量控制
客户端是接收方,服务端是发送方 假设接收窗口和发送窗口相同,都为 200 假设两个设备在整个传输过程中都保持相同的窗口大小,不受外界影响

客户端向服务端发送请求数据报文。这里要说明下,本次例子是把服务端作为发送方,所以没有画出服务端的接收窗口。 服务端收到请求报文后,发送确认报文和 80 字节的数据,于是可用窗口 Usable 减少为 120 字节,同时 SND.NXT 指针也向右偏移 80 字节后,指向 321,这意味着下次发送数据的时候,序列号是 321。 客户端收到 80 字节数据后,于是接收窗口往右移动 80 字节,RCV.NXT 也就指向 321,这意味着客户端期望的下一个报文的序列号是 321,接着发送确认报文给服务端。 服务端再次发送了 120 字节数据,于是可用窗口耗尽为 0,服务端无法在继续发送数据。 客户端收到 120 字节的数据后,于是接收窗口往右移动 120 字节,RCV.NXT 也就指向 441,接着发送确认报文给服务端。 服务端收到对 80 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 321,于是可用窗口 Usable 增大到 80。 服务端收到对 120 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 441,于是可用窗口 Usable 增大到 200。 服务端可以继续发送了,于是发送了 160 字节的数据后,SND.NXT 指向 601,于是可用窗口 ?Usable 减少到 40。 客户端收到 160 字节后,接收窗口往右移动了 160 字节,RCV.NXT 也就是指向了 601,接着发送确认报文给服务端。 服务端收到对 160 字节数据的确认报文后,发送窗口往右移动了 160 字节,于是 SND.UNA 指针偏移了 160 后指向 601,可用窗口 Usable 也就增大至了 200。
操作系统缓冲区与滑动窗口的关系
那操心系统的缓冲区,是如何影响发送窗口和接收窗口的呢?
客户端作为发送方,服务端作为接收方,发送窗口和接收窗口初始大小为 360; 服务端非常的繁忙,当收到客户端的数据时,应用层不能及时读取数据。

客户端发送 140 字节数据后,可用窗口变为 220 (360 - 140)。 服务端收到 140 字节数据,但是服务端非常繁忙,应用进程只读取了 40 个字节,还有 100 字节占用着缓冲区,于是接收窗口收缩到了 260 (360 - 100),最后发送确认信息时,将窗口大小通过给客户端。 客户端收到确认和窗口通告报文后,发送窗口减少为 260。 客户端发送 180 字节数据,此时可用窗口减少到 80。 服务端收到 180 字节数据,但是应用程序没有读取任何数据,这 180 字节直接就留在了缓冲区,于是接收窗口收缩到了 80 (260 - 180),并在发送确认信息时,通过窗口大小给客户端。 客户端收到确认和窗口通告报文后,发送窗口减少为 80。 客户端发送 80 字节数据后,可用窗口耗尽。 服务端收到 80 字节数据,但是应用程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗口收缩到了 0,并在发送确认信息时,通过窗口大小给客户端。 客户端收到确认和窗口通告报文后,发送窗口减少为 0。

客户端发送 140 字节的数据,于是可用窗口减少到了 220。 服务端因为现在非常的繁忙,操作系统于是就把接收缓存减少了 100 字节,当收到 对 140 数据确认报文后,又因为应用程序没有读取任何数据,所以 140 字节留在了缓冲区中,于是接收窗口大小从 360 收缩成了 100,最后发送确认信息时,通告窗口大小给对方。 此时客户端因为还没有收到服务端的通告窗口报文,所以不知道此时接收窗口收缩成了 100,客户端只会看自己的可用窗口还有 220,所以客户端就发送了 180 字节数据,于是可用窗口减少到 40。 服务端收到了 180 字节数据时,发现数据大小超过了接收窗口的大小,于是就把数据包丢失了。 客户端收到第 2 步时,服务端发送的确认报文和通告窗口报文,尝试减少发送窗口到 100,把窗口的右端向左收缩了 80,此时可用窗口的大小就会出现诡异的负值。
窗口关闭
窗口关闭潜在的危险

TCP 是如何解决窗口关闭时,潜在的死锁现象呢?

如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器; 如果接收窗口不是 0,那么死锁的局面就可以被打破了。
糊涂窗口综合症
接收方每接收 3 个字节,应用程序就只能从缓冲区中读取 1 个字节的数据; 在下一个发送方的 TCP 段到达之前,应用程序
还从缓冲区中读取了 40 个额外的字节;

接收方可以通告一个小的窗口 而发送方可以发送小数据
让接收方不通告小窗口给发送方 让发送方避免发送小数据
怎么让接收方不通告小窗口呢?
怎么让发送方避免发送小数据呢?
要等到窗口大小 >= MSS 或是 数据大小 >= MSS 收到之前发送数据的 ack 回包
setsockopt(sock_fd,?IPPROTO_TCP,?TCP_NODELAY,?(char?*)&value,?sizeof(int));

拥塞控制
为什么要有拥塞控制呀,不是有流量控制了吗?
什么是拥塞窗口?和发送窗口有什么关系呢?
只要网络中没有出现拥塞,cwnd 就会增大; 但网络中出现了拥塞,cwnd 就减少;
那么怎么知道当前网络是否出现了拥塞呢?
拥塞控制有哪些控制算法?
慢启动 拥塞避免 拥塞发生 快速恢复
慢启动
连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。

那慢启动涨到什么时候是个头呢?
当 cwnd < ssthresh 时,使用慢启动算法。 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。
拥塞避免算法
当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

拥塞发生
超时重传 快速重传
发生超时重传的拥塞发生算法
ssthresh 设为 cwnd/2, cwnd 重置为 1

发生快速重传的拥塞发生算法
cwnd = cwnd/2 ,也就是设置为原来的一半; ssthresh = cwnd; 进入快速恢复算法
快速恢复
cwnd = cwnd/2 ,也就是设置为原来的一半; ssthresh = cwnd;
拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了) 重传丢失的数据包 如果再收到重复的 ACK,那么 cwnd 增加 1 如果收到新数据的 ACK 后,设置 cwnd 为 ssthresh,接着就进入了拥塞避免算法

资料:
[1] 趣谈网络协议专栏.刘超.极客时间
[2] Web协议详解与抓包实战专栏.陶辉.极客时间
[3] TCP/IP详解 卷1:协议.范建华 译.机械工业出版社
[4] 图解TCP/IP.竹下隆史.人民邮电出版社
[5] The TCP/IP Guide.Charles M. Kozierok.
[6] TCP那些事(上).陈皓.酷壳博客.
https://coolshell.cn/articles/11564.html
[7] TCP那些事(下).陈皓.酷壳博客.https://coolshell.cn/articles/11609.html


今日福利
遇见大咖
由 CSDN 全新专为技术人打造的高端对话栏目《大咖来了》来啦!
CSDN 创始人&董事长、极客帮创投创始合伙人蒋涛携手京东集团技术副总裁、IEEE Fellow、京东人工智能研究院常务副院长、深度学习及语音和语言实验室负责人何晓冬,来也科技 CTO 胡一川,共话中国 AI 应用元年来了,开发者及企业的路径及发展方向!
戳链或点击阅读原文,直达报名:https://t.csdnimg.cn/uZfQ
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
赞助链接
排名
热点
搜索指数
- 1 中央经济工作会议在北京举行 7904241
- 2 水银体温计网上已“炒疯” 7809361
- 3 日本突发列车脱轨翻覆事故 7714593
- 4 “九天”无人机成功首飞 7616121
- 5 紧急提醒:请在日中国公民进行登记 7520974
- 6 断码断货!这一款国货卖爆了 7425932
- 7 村支书卖小米被小米法务投诉下架 7329533
- 8 直击北京初雪 7237139
- 9 韩国艺人“鸟叔”被警方强制调查 7139694
- 10 寒潮来袭 “速冻”模式如何应对 7046802







CSDN
