一、TCP协议

TCP协议的首部:

字段解析:

  • 源目端口号:各2字节,short类型的端口号
  • 序号:标识当前TCP数据包在数据流中的序号,即平常的SYN和SEQ信息。
  • 确认号:收到数据报后,返回的ACK序号
  • 首部长度:TCP协议的首部长度,以四字节为单位,最多60字节。默认不带选项的情况下TCP首部是4个字节。
  • TCP选项:

    • URG: 紧急指针有效,很少使用
    • ACK: 确认选项
    • PSH: 推送选项,接收方收到后应该尽快被发送到应用层,选项没有被可靠的实现或用到
    • RST: 重置连接,经常是因为错误取消
    • SYN`: 开始连接,初始化一个连接的同步序号
    • FIN`: 结束连接
  • 窗口大小:2字节,滑动窗口协议中用来控制流量大小的字段
  • 校验和:计算方式和UDP一致,也会添加一个伪首部,计算整个头部和数据部分的内容
  • 紧急指针:只有在URG被设置时才有效,很少用到。
  • 其他选项

    • MSS: 最大段大小,表示连接希望收到的报文段的最大值

二、三次握手和四次挥手

参考TCP协议中的三次握手和四次挥手

三、TCP超时和重传

在连接无法收到响应后,TCP将会尝试重传数据,重传的时间间隔称为二进制指数退避。不过和CSMA/CD协议中的不同,这里的二进制指数退避是在之前的重传间隔上加倍。

0.2 - 0.4 - 0.8 - 1.6 - 3.2 - 6.4 - 12.8 - 25 - 50 - 100 - 200 - ...

linux中可以通过配置来设置重传的次数,重传涉及两个次数:第一个是TCP重新连接时尝试重传的次数,第二个是TCP放弃当前连接的时机。

net.ipv4.tcp_retries1
net.ipv4.tcp_retries2

3.1 RTT

RTT表示一个连接的往返时间,即数据发送时刻到接收到确认的时刻的差值。

RTT的取值方式:为一个已发送但尚未确认的报文段做一次RTT采样,得到一个SampleRTT(不是为每一个发送的报文段都测量RTT),从而用这个SampleRTT来接近(代表)所有RTT。

  • 一个连接中,有且仅有一个测量定时器被使用。也就是说,如果TCP连续发出3组数据,只有一组数据会被测量。
  • TCP决不会为已被重传的报文段测量SampleRTT,仅仅为传输一次的报文段测量SampleRTT。
  • ACK数据报不会被测量,原因很简单,没有ACK的ACK回应可以供结束定时器测量。

由于链路负载的变化,很多时候RTT并不能完全代表网络中真正的状态,为了得到一个更合理更接近事实的RTT,TCP规范中通过一个低通过滤器来设置出平滑的RTT估计器:

SRTT = α(SRTT) + (1 - α)RTT

α一般设置为0.8-0.9,意思是SRTT的值的80%-90%来自前一个估计值,10%-20%来自当前的估计值。

3.2 RTO

RTO指重传超时时间,从数据发送时刻算起,超过这个时间便执行重传。

RTO的值依赖RTT,[RFC0793]中推荐的RTO计算公式为:

RTO = min(ubound, max(lbound, (RTT)β))

β是离散因子,推荐值为1.3-2.0,ubound是RTO的上边界(建议为1分钟)lbound是RTO的下边界(如1秒)。这种方法被称为经典方法,它使得RTO值为1秒或者2倍的SRTT。对于相对稳定的RTT来说,这种方法能取得不错的性能。运行在RTT波动较大的网络环境时,无法获得期望的效果。

3.3 TCP中的四个定时器

  • 重传定时器:为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。即上面说的RTT和RTO。
  • 坚持定时器:为对付零窗口通知而设立的,具体见下面
  • 保活计时器:为保持TCP的keepalive设立的,一般为2小时
  • 时间等待计时器:关闭连接时需要等待的时间,即2MSL等待期

3.4 坚持定时器

当窗口大小为0时,如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非0的窗口),而发送方在等待允许它继续发送数据的窗口更新。

为防止这种死锁情况的发生,发送方使用一个坚持定时器(persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查(window probe)。

3.4 快重传

TCP发送端在观测到多个重复的ACK后,此时重传可能丢失的数据包分组。不要等到计时器超时,此时依旧可以发送新的数据。

四、拥塞避免

4.1 慢启动

一个TCP刚启动时,不知道外部网络环境状态,为了避免发送过多数据出现拥塞,TCP会维护一个从1开始的cwnd值,每次发送cwnd个数据包。当收到一个数据报的确认后,该字段值加1。这个过程被称为慢启动。

具体的过程为:

  1. 连接建好的开始先初始化cwnd = 1,表明可以传一个SMSS大小的数据
  2. 收到一个ACK,cwnd++,呈线性上升
  3. 过了一个RTT,cwnd = cwnd*2,呈指数上升
  4. 每当产生一个RTO超时重传,cwnd = 1, ssthresh减半

慢启动有一个门限值ssthresh,超过这个值之后会启动拥塞避免算法。

4.2 拥塞避免

cwnd超过ssthresh时,开始采用拥塞避免算法。因为cwnd不可能无限增加,拥塞避免算法的目的是使得cwnd处于更缓慢增长的模式。

假设cwnd=k*SMSS,当发送端收到一个包的ACK反馈的时候,按照cwnd=cwnd+(1/k)*SMSS。这样每经过一个RTT发送k个数据包的时候,cwnd增长了大约一个SMSS,通常上我们即描述为每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1,使拥塞窗口 cwnd 按接近线性规律缓慢增长。这种增长方式叫做“加法增大”(Additive Increase)。总结如下:

  1. 收到一个ACK时,cwnd = cwnd + 1/cwnd
  2. 每过一个RTT时,cwnd = cwnd + 1

使用慢启动和拥塞避免算法产生的流量增长趋势图:

最后修改:2019 年 05 月 04 日
如果觉得我的文章对你有用,请随意赞赏