TCP/IP协议学习( 五 ) ----UDP/TCP 协议 (下)
UDP/TCP协议
一 . TCP为什么靠谱?
为了保证顺序性,每一个包都有一个 ID。在建立连接的时候,会商定起始的 ID 是什么,然后按照 ID 一个个发送。为了保证不丢包,对于发送的包都要进行应答,但是这个应答也不是一个一个来的,而是会应答某个之前的 ID,表示都收到了,这种模式称为累计确认或者累计应答(cumulative acknowledgment)。
1.1 顺序问题与丢包问题
每发送一个包,seq就会增大对应这个包数据大小的字节, 参考
1. TCP包的seq和ack号计算方法
2.tcp***回绕与解决
PS:
1.Ack的对应是本机接收端 代表之前的包都收到,期望收到哪个包(累计应答).
2. seq对应的是本机发送端,发送了多少数据.
1.1.1 发送端的超时重传(自适应重传算法(Adaptive Retransmission Algorithm))
一种方法就是超时重试,也即对每一个发送了,但是没有 ACK 的包,都有设一个定时器,超过了一定的时间,就重新尝试。
Q1 :这个定时器的时间如何确认呢?
估计往返时间,需要== TCP 通过采样 RTT 的时间,然后进行加权平均==,算出一个值,而且这个值还是要不断变化的,因为网络状况不断地变化。除了采样 RTT,还要采样 RTT 的波动范围,计算出一个估计的超时时间。由于重传时间是不断变化的,我们称为自适应重传算法(Adaptive Retransmission Algorithm)。
Q2 超时后重传的包再次超时怎么办?
TCP 的策略是超时间隔加倍。每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
1.1.2 接收端 快速重传的机制
超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?
有一个可以快速重传的机制,当接收方收到一个序号大于下一个所期望的报文段时,就会检测到数据流中的一个间隔,于是它就会发送冗余的 ACK,仍然 ACK 的是期望接收的报文段。而当客户端收到三个冗余的 ACK 后,就会在定时器过期之前,重传丢失的报文段。
1.1.3 SACK( Selective Acknowledgment )
还有一种方式称为 Selective Acknowledgment (SACK)。这种方式需要在 TCP 头里加一个 SACK 的东西,可以将缓存的地图发送给发送方。例如可以发送 ACK6、SACK8、SACK9,有了地图,发送方一下子就能看出来是 7 丢了。
1.2 流量控制
1.2.1 接收端和发送端数据结构
为了记录所有发送的包和接收的包,TCP 也需要发送端和接收端分别都有缓存来保存这些记录。发送端的缓存里是按照包的 ID 一个个排列,根据处理的情况分成四个部分。
这里 里面有个重要的概念:滑动窗口
Advertised window = 发送为确认 + 未发送可发送的部分
所以,滑动窗口包含已发送未反馈,准备发送但未发送的数据,这个窗口是接收端发送过来的,用于确认接收端还能接收多少数据.
对于接收端:
对于接收端的三部分,接收已确认部分为 tcp已经接收放入缓存,但是应用层还没读取该buffer .
流量控制中的,传给接收端的滑动窗口大小就是这里控制的
滑动窗口大小 = MaxRcvBuffer-((NextByteExpected-1)-LastByteRead)。
1.2.2 流量控制问题
发送端发送的每一个数据包,服务端都要给一个确认包(ACK).确认它收到了。 服务端给发送端发送的确认包(ACK包)中,同时会携带一个窗口的大小。 这个窗口的大小就代表目前服务器端的处理能力。(接收端最大缓存量-接收已确认但还未被应用层读取的部分)。
所以,这个窗口的大小也是时时刻刻在变化的(接收已确认但还未被应用层读取的部分会变化)
我们来看一下,下面的例子:
当包4 的ack 携带的窗口大小为9 ,发送端队列会出现下面情况.
假设接收端一直没有回复ack , (其他发送的ack没回来), 此时会继续发送 包 10 / 11 /12 /13 ,
- 情况1 如果包5的ack回来,携带的滑动窗口仍然是9的话,可以发送的部分整体往右移,此时 包14可以发送
- 情况2 如果接收端实在满了 ,应用端无法处理这么多数据,收到 包5的 ack 携带的滑动窗口就是8,
因为
滑动窗口 = MaxRcvBuffer-((NextByteExpected-1)-LastByteRead)。
上述的情况 MaxRcvBuffer 不变 , LastByteRead不变,
NextByteExpected = NextByteExpected+1 ,
滑动窗口= 滑动窗口 -1
上述情况2 极端的情况下,则随着确认的包越来越多,窗口越来越小,直到为 0。
这样的话
发送方会定时发送窗口探测数据包,看是否有机会调整窗口的大小。当接收方比较慢的时候,要防止低能窗口综合征,别空出一个字节来就赶快告诉发送方,然后马上又填满了,可以当窗口太小的时候,不更新窗口,直到达到一定大小,或者缓冲区一半为空,才更新窗口。
参考博客:
1.Linux网络编程——浅谈 TCP 三次握手和四次挥手
2.趣谈网络协议-------极客时间 刘超
3.TCP:WireShark分析,***Seq和确认号Ack