标签 tcpip 下的文章

一、关于滑动窗口协议

在TCP协议中,所有的SEQ包发送出去都必须要受到对方的ACK才认为是发送成功,如果长时间没有收到ACK回复确认,发送方需要重新发送该包。

而如果发送方每次都是发送一个包,然后等到接收方回复ACK了再发送下一个包,那么数据包的传输效率就相当低了。滑动窗口协议的作用就是为了解决这个问题。

在滑动窗口协议中,发送方可以同时发送多个数据包并可以不用等待接收方确认,这样就大大增加了网络的推图两。唯一的限制是:接收方未确认的数据包不得超过双方约定的窗口个数。

TCP包头中有一个两字节的字段表示滑动窗口的大小,因此最大的滑动窗口大小为65535,这个大小是由接收方提出的,以接收方的大小为准。

最大窗口65536并不是绝对的,TCP选项中有提供一个窗口放大的选项可以对这个数值缩放。

例如以下数据,将发送的字节从1至11进行标号,接收方提出的窗口大小为6,此时已发送的1/2/3字节数据被接收方确认,当前窗口覆盖了从第4字节到第9字节的区域。4/5/6字节是已经发送但是还未收到确认,此时发送方还可以发送的数据域只剩下三个,即7/8/9三个数据。当7/8/9发送完成后,发送方无法再继续发送数据。只有接收方再确认序号4的数据后,发送方才能继续发送数据10。此时可用的窗口将会向右延伸到10,同时左边已发送并确认的数据也会向右延伸到4。

这个过程看起来就像是一个窗口不停向右滑动,因此该协议被称为滑动窗口协议。

822287-20180227212818806-1798395637.png

滑动窗口涉及到以下几个概念:

  1. 窗口合拢:当窗口左边界向右靠近时,这种现象发生在数据被发送方确认时。
  2. 窗口张开:窗口的右边界向右移动的时候,这种现象发生在接收端处理的数据的时候。
  3. 窗口收缩:窗口右边界向左移动时,这种现象不常发生。
注意:窗口大小是由接收方通告的,通过采取慢启动和拥塞避免算法等机制来使带宽和性能取得最佳。

二、坚持定时器

2.1 坚持定时器的引入

思考以下场景:接收方的接收缓冲区已满,因此接收方发送了一个窗口大小为0的数据到发送方,此时发送方停止发送数据。经过一段时间后,接收方有空闲的缓冲区可以接收数据,此时给发送方发了一个窗口大小不为0的包,但是恰巧这个包在路上出现了意外没有到达发送方。此时发送方还在一直等待接收方发送不为0的窗口过来,但是接收方又在等待发送方继续发送数据。两者相当于发生了死锁。

2.2 工作原理

为了解决上面这个问题,TCP引入了坚持定时器,该定时器功能为:TCP连接的一方收到对方窗口为0的通知时,启动该定时器,若定时器持续时间到达后还没有收到对方窗口大小不为0的通知,主动发送一个零窗口探测包(仅携带1字节数据),对方则需要在这个包中回复当前窗口的大小。

三、关于window scale选项

window scale选项是提供给滑动窗口协议用于对窗口大小放大使用,在SYN包或者SYN,ACK包中作为选项字段,表示滑动窗口的实际大小要根据这个值来进行缩放(SYN包本身的窗口不会缩放)。

发送方和接收方的滑动窗口缩放因子大小可以不一样,两者互不干扰,但实际上窗口大小还是以接收方为准。使用wireshark抓包很容易就能看到这个缩放选项,以下是一个数据包示例。

这个包是客户端发起的三次握手数据包:

image.png

客户端设置了窗口缩放大小为256,因此后面实际计算的时候窗口大小要乘以这个缩放大小1025(使用wireshark很容日就能看到),乘以256后得到实际的窗口大小为262400

image0855a2b1457c0f0d.png

Calculated window size: wireshark根据缩放因子计算出来的实际窗口大小。

Window size scaling factor: 窗口缩放因子。

这两个参数都是wireshark自动生成的,并不是携带在TCP包中的字段。

一、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

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

一、UDP协议

UDP协议是一种简单的面向数据报的传输层协议,它不提供差错纠正、队列管理、重复消除、流量控制以及拥塞控制等功能。它只提供最小的差错检测功能,要想保证数据被可靠的投递或正确排序,应用程序必须自己实现这些功能。

因为它是无连接协议,所以它比其他传输层协议使用更少的开销。

UDP的首部如下:

UDP首部共包含8个字节,内容很简单,仅仅包含了源目端口号、报文长度以及校验和这四个元素。其中,长度字段表示的是数据长度和首部长度

二、UDP校验和

UDP校验和要用到一个伪首部,用到来自IP数据报中的部分字段,伪首部的结构如下:

伪头部的目的是让UDP层验证数据是否已经到达正确目的地(即,该IP没有接受地址错误的数据报,也没有给UDP一个其他传输协议的数据报),它不会被发送出去,只用作校验功能。

注意

  1. UDP数据报长度可以是奇数个字节,而校验算法只相加16位(2字节),因此如果UDP长度是奇数时,计算校验和会在末尾补零(但不会被发送出去)。
  2. UDP中的校验和是可选的(强烈建议使用)。因此对于使用UDP的应用层协议,在收到数据时应当先校验校验和。

三、UDP/IP分片

UDP数据报文最大可达2^16=65535个字节,除去首部还剩下65527个字节。但是受限于网络中的MTU等设置,过长的UDP数据报文段将会在IP数据报中分片。例如在一个MTU为1500的网络环境中发送一个长度为3000的UDP报文将会以如下形式发送出去:

注意:

  1. 偏移的单位是8个字节,例如第二片的偏移是185,实际上是第185*8=1480个字节。

TCP与UDP基本区别:

  • TCP提供的是面向连接,类似于打电话,会有握手流程。UDP无连接,类似广播。
  • TCP要求系统资源较多,UDP程序结构较简单,要求的系统资源也较少。
  • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。UDP尽最大努力交付,即不保证可靠交付。
  • 流模式(TCP)与数据报模式(UDP),TCP面向字节流,UDP面向报文。
  • TCP保证数据正确性,UDP可能丢包。
  • TCP保证数据顺序,UDP不保证。
  • TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的交互通信。

为什么UDP有时比TCP更有优势:

UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。

  1. 网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
  2. TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。