TCP-DataFlow

TCP 数据流

经受时延的确认(Delayed ACK)

Delayed ACK翻译成中文,很多时候被称为经受时延的确认。这讲的是TCP连接运行中的一种性能优化机制:

TCP在接收到数据时并不立即发送ACK;相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送。 绝大多数实现采用的时延为200ms

这样的情况在TCP执行下载任务时非常常见。对于接收方来说,它只想要接收下载服务器发送来的数据,除了维持TCP数据发送的ACK报文,自己并不需要向服务器发送任何数据;因此接收端经常都是在接收到几个连续的数据包之后直接ACK最后一个数据包的序列号(相当于将这几个数据包合并ACK了)
对于具体功能的实现与操作系统有关,不同的机器上可能很可能会观测到不同的时延。
说是性能优化,很大程度上体现在以下几个方面:

  • 减小接收端和发送端之间的网络流量消耗。一个TCP报文段通过TCP/IP协议族,封装成以太网帧之后,至少都对应62个字节(以太网数据:46 + 以太网帧首部+尾部:16)。这也意味着如果接收端需要ACK每一个发送端发来的TCP报文段,每一个ACK报文至少都会占用62个字节。
    在一方只需要尽可能的发送数据流而另一方只需要尽可能的接收数据流的情况下,对网络带宽无疑不是一种极大的浪费。(对同一块数据,TCP分段越多,单纯ACK对TCP连接性能的损害越大)这时候,Delayed ACK无论是从传输效率还是流量节省方面,都是一种非常有效的性能提升手段。
  • 提高TCP协议栈程序的运行效率。类似TCP这样的全双工通信,发送接收经常会放在不同的线程中来执行。由于Delayed ACK的存在,Client减少了需要执行发送的次数,也就减少了程序在线程切换和数据同步方面的开销;类似的还有封装TCP报文段封装成以太网帧的消耗。

在报文段数很多的时候,这些看起来微小的优化都能够演变成为显著的性能提升效果。

TCP窗口(TCP Window)

TCP拥塞控制是一个重要的且规模不小的机制。不仅涵盖了TCP报文首部的窗口字段,还有拥塞算法、缓冲区、连接状态维护……。

滑动窗口

再来一个被用烂的图,发送方的窗口

关键词解释

  • 由接收方通告的窗口:对应接收方回给发送方的ACK报文中的窗口字段。
  • 窗口合拢:指窗口左边向窗口右边靠拢(图中3 - 4的窗口左沿向右边移动时),发生在接收到发送的数据对应的ACK包时
  • 窗口张开:指窗口的右边移动(图中9 - 10的窗口右沿向右边移动时),发生在接收端的TCP进程已经将收到的数据交付给应用程序(例如socket)并释放了缓冲区用以重新接收数据时。
  • 可用窗口和已发送未确认数据包:由于TCP是全双工通信,必然会出现当接收方发送ACK包通知接收方的窗口【时间点1】之后,发送方在【时间点3】接收到接收方在【时间点1】发送的ACK包更新发送方的窗口时发送方在【时间点2】发送的一些报文段还在 “途中” ,没有被接收方收到。
    这部分没有被接收到的报文段就被称为已发送未确认的数据包,而发送方拿到的 最新的接收方通告的窗口大小减去已发送未确认的数据包总量的差值,被称为发送方的可用窗口

由于对于一块将要发送的数据块,整个TCP传输过程看起来就像是一个窗口在数据块上按顺序移动,因此TCP这种窗口运作机制也被称为滑动窗口

窗口大小

很明显,从滑动窗口的例子中可以看到,接收方通告的窗口大小决定了发送方每次能够发送的TCP报文段数据量的多少,很大程度上影响着TCP连接的性能。虽然接收方的窗口大小对应的是接收方缓冲区的大小,但缓冲区并不是越大越好。一是要考虑缓冲区对于操作系统内存资源的消耗,二也要考虑缓冲区向应用程序交付字节流的吞吐量能够为应用程序所接收。

对于典型的Linux设备,可以在 /proc/sys/net/ipv4/ 路径下查看到当前操作系统对应的TCP读/写缓冲区大小以及其他TCP信息。

PSH标志

发送方可以使用该标志通知接收方将所接收到的保存在缓冲区中的数据尽快交付给应用程序;这里尽快交付的数据不仅包括在缓冲区中的数据,还包括随着PSH标志位一起发送的数据。发送端发现接收端的TCP窗口“总是”很小时,可能在发送的数据包中加上PSH标识符以“督促”接收方尽快将TCP缓冲区内的数据交付给应用层,从而清空缓冲区,打开更大的TCP拥塞窗口。

慢启动(Slow Start)

起初慢启动只是一种算法,而现在,几乎所有的操作系统TCP协议都嵌入了这一算法以便获得更好的TCP连接性能。其运作原理如下:
为发送方的TCP增加另一个窗口:拥塞窗口(Congestion Window), 记为cwnd

  • 当与另一个网络的主机建立TCP连接时,拥塞窗口将被初始化为1个报文段(即另一端通告的窗口大小)。
  • 每当发送方接收到一个ACK时,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。
  • 发送方取cwnd通告窗口最小值作为发送上限来往接收方发送报文。
  • 由于发送方每接收接收方发送的ACK拥塞窗口就+1,可以看到拥塞窗口的增加是一种指数增加的关系。1 -> 2 -> 4。
  • 通告窗口被认为是接收方采用的流量控制机制;而拥塞窗口是发送方采用的流量控制机制。

拥塞避免算法

说到TCP的拥塞窗口,绝对离不开TCP的拥塞避免算法。拥塞避免算法是一种帮助发送方处理丢失分组的方法,用以处理提高网络传输成功率。拥塞避免算法和慢启动算法是两个目的不同、相互独立的算法;但在实际中,他们两个经常一起实现。
拥塞避免一般会发生在两种情况下:

  • 发送的报文分组超时未被ACK。
  • 发送的报文分组接收到重复的ACK。

拥塞避免与慢启动的工作流程如下:

  1. 对每个连接维持两个变量,一个是拥塞窗口cwnd,另一个是慢启动门限ssthresh
  2. 在创建TCP连接时,初始化cwnd的值为1个报文段(不是1个字节),ssthresh为65535个字节(可通告的最大窗口大小)。
  3. 发送方发送的报文不超过cwnd和接收方通告的窗口大小。
  4. 当拥塞发生时(发送的报文段超时或者被重复ACK),ssthresh将被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起的拥塞,则将cwnd设置为1个报文段。
  5. 当新的ACK报文到来时就增加cwnd,但增加的方法依赖于正在进行慢启动还是拥塞避免。如果cwnd小于ssthresh,则说明正在进行慢启动(cwnd指数增加);否则则是进行拥塞避免,拥塞避免算法要求每次收到一个确认时仅将cwnd增加 1/cwnd,cwnd的增长处于一种加性增长