摘自:《深入理解计算机网络》 王达著 机械工业出版社
相关知识链接
1. IPV4数据报头部格式
2. IPv6数据报头部格式
3. IPv4数据报的封装与解封装
4. IPv4数据报的分段与重组
5. ARP协议报文格式及ARP表
6. ARP地址解析原理
7. ICMP协议及报文格式
8. IPv6协议族的其它协议
9. TCP的主要特性
10. TCP的套接字
11. TCP端口
12. TCP连接状态转移
13. TCP传输的建立
14. TCP 传输链接的释放
15. TCP 数据段格式
TCP是一个可以提供可靠数据传输的传输层协议,那么它到底是如何来保障可靠传输的呢?下面进行具体的分析。
TCP可靠传输方面,主要采用以下4中机制:
以上所说的“字节编号机制”比较好理解,因为是按字节进行编号,所以接收端根据所接收到的数据段中的序号就可以知道前面是否还有数据没有接收到,数据可以按顺序向应用进程提交,在对经过了数据段的数据进行重组时也可以根据这个序号进行正确重组。下面我们将会重点介绍 2、3、4 机制的运行原理。
若想详细了解TCP数据段,请点击TCP数据段格式
数据段指 TCP 对从应用层接受的数据进行分割所得到的数据块(也可以是没有经过分割的整个报文,只要它的大小在 MSS 之内),通常包括千个以上的字节,而且必须是整数倍字节数。正因如此,TCP 发送的是字节流,而不是通常所说的报文流,因为在 TCP 数据段中没有报文边界。
TCP 发送的数据段中“数据”部分(不包括数据段头部),每个字节都有一个序号,每个数据段中的“序号”字段是以该数据段中第一个字节的序号进行填充的。
窗口大小是本端要告诉对端当前可以接受的数据量,也暗示着对端此时可以一次性发送的数据大小,以字节为单位,但这里仅是针对数据段中的“数据”部分,不包括 TCP 数据段头部,因为数据段到了对端的应用层后仅提交“数据”部分。TCP 可以一次性连续发送窗口大小的数据(但实际上发送数据的大小还会受到当前可用的“发送窗口大小”影响),其中包括一个或多个 TCP 数据段。但窗口大小必须小于对应网络中的 MTU (最大传输单元)值大小,否则到了数据链路层还是要进行数据分割。同时要注意的是,“窗口大小”字段是随着接收端可用“接受窗口大小”变化而变化的,不是固定的。
注意:无论是发送端,还是接收端,都分别有“发送窗口”和“接收窗口”这两个窗口,其大小分别用于发送数据和接受数据的缓存大小。这两个缓存的物理大小对于具体的主机来说是固定不变的,除非人为扩展物理内存,否则不会再扩大,只能沿着缩小的方向进行变化。
确认号指发送包含这个“确认号”的数据段的一段期望接收另一端的下一个数据段的起始序号。同时也暗示了在此序号前的所有字节数据均已正确接收。
ACK 是一个表明“确认号”字段是否有效的标志位。只有 ACK 字段的值为1,数据段中的“确认号”才有意义,否则数据段中的“确认号”没有意义,即不具有上面所说得“确认号”含义,因为 TCP 通常不会针对单个数据段进行确认,而是一次性对多个连续数据段进行确认。只需要最近收到的那个数据段进行确认,即表明前面所有数据段均已正确接收。
TCP 不需要等待接受对方发送的确认数据段(“ACK”字段值为1的数据段)就可以一次性连续发送多个数据段,这样可大大提高数据发送效率。但一次性可发送多少个数据段是受对方返回的“窗口大小”字段值和当前可用“发送窗口”大小双重限制的。因为发送端对还没有收到确认的数据段要进行缓存,这需要占用一定的“发送窗口”大小。
假设发送端的物理“发送窗口”和接收端的物理“接受窗口”大小均为2000字节,设每个数据段大小为100字节,而接收端返回的数据段中显示“窗口大小”字段值也为2000,同时发送端此时的“发送窗口”还有两个数据段(200字节)还没有收到确认,则此时发送端可发送的数据段数量为18(2000/100 - 2),即1800字节,不能发送全部的2000字节数据。
如果接收端返回的数据段中显示的“窗口大小”值为1000,则此时发送端可以发送1000字节数据,因为所发送的1000字节数据,在加上原来还没有收到确认的200字节数据,小于发送端的物理“发送窗口”大小——2000。
返回的确认数据段中的“确认号”字段值仅代表对端已正确接收的连续数据段(最高字节序号+1),而不一定是已正确接收数据段中的“最高序号 + 1”,因为中间可能还有数据段因为网络延迟而暂时未收到,或出现了传输错误而丢失。
假设每个数据段的长度大小均 100 字节,接收端接收到了序号为 1、101、201、401 四个数据段。其中序号为 301 的数据段暂时还没有收到,此时接收端返回的确认数据段中的“确认号”只能是301,而不会是501,(尽管401~501的数据已经正确接受)也就是只对前三个数据段进行确认,不会对后面的401数据段进行确认,因为中间的301数据段还没有收到。当后面收到了301数据段后,可能会返回一个“确认号”为501的数据段,这时就代表301和401数据段均以正确接收了。
当主机接收到的数据段序号不连续时,不连续部分向应用层的应用进程进行提交,而是先缓存在“接收窗口”中,等待接收到中断的序号的数据段后再一起提交。这时,尽管接收端已正确接受了某些数据,但仍不能及时向应用层提交,需要占用“接收窗口”空间。对于没有按先后次序正确接受的数据,在向应用层提交时会重新按数据段序号重新组合,然后再提交给应用层。
如某主机先后接收到了对端发来的序号分别为 1、101、201、301、601、501、801 的数据段(假设数据段大小均为100字节),则该主机首先把 1、101、201、301 这4个数据段向应用层提交并向发送端发送一个“确认号”为401的确认数据段,从而可以从“接受窗口”中删除这4个数据段,释放“接受窗口”;然后再把 601、501、801 这3个数据段先缓存在“接受窗口”中,直到接受到了401号数据段,再按 401、501、601 顺序重组并向应用层提交,接着发送一个“确认号”为701的确认数据段,从而又可以从“接受窗口”中删除这三个数据段,释放“接受窗口”,但此时“接受窗口”中仍缓存有801号数据段,因为701号数据段还没有得到确认。
“超时重传” 是TCP保证数据可靠的另一个重要机制,其原理是在发送某一个数据段以后就开启一个超时重传计时器(Retransmission Timer,RTT)。如果在这个定时器时间内没有收到来自对方的某个数据段的确认,发送端启动重传机制,重新发送对应的数据段,知道发送成功为止。需要注意的是,并不是 RTT 定时器一到,就会立即重传数据,毕竟从“发送窗口”缓存中找到对应的数据段,然后安排重新发送都是需要时间的,实际上,超时重发时间间隔(Retransmission Time Out)要大于 RTT 值。
这里涉及两个重要问题:一是如何确定 RTT 值,另一个是如何计 RTO 值。表明上看起来 RTT 值很容易确定,因为它就是一个数据段往返发送端和接收端的时间总和,但在 TCP 通信中,中间可能经过了多个性能不一样的网络,而且不同时刻网络的拥塞程度可能不一样,这就造成了不同数据段的 RTT 时间并不一致,甚至波动非常大。但 TCP 必须适应这种情况,必须能动态地跟踪这些变化并相应地改变其超时重传时间。
正因为 RTT 值不是固定的,所以就出现平滑 RTT(SRTT)的概念,就是在充分考虑历史 RTT 值的情况下所设计的一个 RTT 值计算公式。在最初的 RFC793 中,SRTT 的计算公式如下:
SRTT 的初始值就是第一个 RTT 值。这里 α 是一个平滑因子,它决定了旧的 SRTT 值所占的权重, 0≤α<1 。RFC2988 推荐的 α 典型值为0.125. 如果 α 很接近1,则表示新的 SRTT 值与旧的 SRTT很接近,变化不大。相反,则表示变化很大。显然,用这种方法计算得出的各个时刻的 SRTT 值更加平滑,更加接近当时的网络环境。
虽然可以通过公式计算出 SRTT 值,但是 RTP 的得出仍不是一件简单的事,因为到底是 RTT 定时器到后就重传,还是要再等多长时间才重传,这是必须考虑的问题。正常情况下,TCP 利用 β SRTT 作为重传超时间隔 ( β>1) ,而且最初的值总为2(也就是两倍的 SRTT 时间后还没有收到对应数据段的确认才重传该数据段)。
但经验表明,采用常数的 β 值不够灵活。于是,在1988年,Jacobson 提出使用平均偏差作为标准偏差(就是 β SRTT)的新估计法,要求维护另一个被平滑的偏差 RTTD 。当一个确认数据段到达时,可以得出 SRRT 的新的 RTT 样本值之间的偏差 RTTD=(SRTT−RTT) 。
第一次测量时, RTTD 为测量到的 RTT 样本值的一半,在以后的测量中按照以下的公式进行计算:
这里的 α 可能与计算 SRTT 时的值相同,也可能不同,通常取值 0.25. SRTT 是平滑 RTT 值, RTT是新的 RTT 样本值。虽然 RTTD 并不能完全等同于标准偏差,但已能足够的反应 SRTT 值的动态变化。现在大多数 TCP 实现都是使用这个算法,并且将超时重传时间设置为:
这里有一个重要的问题,那就是对于重传的数据段如何计算其 RTT 的值。假设发送端发送了一个数据段,但在重传定时器内没有收到该数据段的确认数据段,于是重新发送了这个数据段,可过了一段时间,发送端又收到了该数据短短额确认数据段。这是发送端就迷糊了,这个确认数据段到底是对原来发送的那个数据段确认,还是对重发的数据段的确认呢?这两个数据段的序号是一样的,如果在上次发送的数据段后没有再重新发送新的数据段的话,接收端返回的确认数据段中的确认号都可能一样。如何收到的确认数据段是对重传数据段的确认,但却被发送端认为是对原数据段的确认,则这样计算出的 SRTT 和 RTO 值可定会偏大,否则会偏小。为此,以为发现这个问题的无线电爱好者 Karn 提出了一个建议,就是在计算加权平均 RTT 时,只要数据段被重发就不采用其往返时间作为计算 SRTT 和 RTO 的样本,这样得出的加权平均 SRTT 值和 RTO 值就比较准确了。
在上面介绍的 TCP 重传机制中,如果在重传定时器超时后仍没收到一个数据段的确认,则可能会重传对应序号后面的所有数据段,因为后面的这些数据段均暂时不会被确认,这明显大大降低了 TCP 数据传输性能。
同样假设每个数据段大小为 100 字节,接受端已接受到了 1、101、201、401、501 这5个序号的数据段,其中 301 号数据段没有收到。按照前面介绍的确认机制,虽然 401、501 这两个序号的数据段均已收到,但因为 301 号这个数据段一直没收到,所以仍然不能像向发送端确认。在某个时间上,如果 301 号数据段重传定时器超时了,则发送端肯定会重传这个数据段,但毕竟重传的 301 数据段到达对端也是要时间的。在这个 301 号数据段的重传过程中,401 号,甚至 501 号数据段的重传定时器也可能超时,这是发送端可能由对 401号、501 号这两个数据段进行重传。这样的重传显然是不必要的,也会造成接收端重复接受 401 号、501 号这两个数据段。
为了避免这种现象的出现,在 RFC3517 中出现了一种称为 “选择性确认” (SACK)的机制,就是在 TCP 数据段格式的头部 “可选项” 字段中添加一个代表支持 SACK 选项。但这个选项在不同的数据段格式的头部 “可选项” 字段中添加一个代表支持 SACK 的选项。但这个选项在不同的数据段中有不同的字段名称和不同的含义。
首先,必须在建立 TCP 传输连接时的 SYN 数据段中包含 “SACK-Permit” (SACK 允许)字段选项,表示在今后的传输中希望收到 SACK 选项,然后在其他的数据段中包括需要包含 “SACK”字段,在这个字段中,会包含本段要告诉对端已经接收到的不连续数据段。原来的 “确认号” 字段同样有效,但此时的 SACK 扩展选项也仅在确认数据段(“ACK” 字段值为1)中才有效。
“SACK-Permit” 字段中包括 Kind (类型)和 Length(长度)两个子字段。,如下图所示。Kind 字段值固定为4,占8位,表示允许使用 SACK 扩展确认选项;Length 字段的值固定为2,占8位,表示在 SYN 数据段中 SACK 允许扩展选项占两字节。
在其他非 SYN 数据段的 “SACK-Permit” ,包括 Kind、Length,以及各不连续数据段的起始字节号和结束字节号,如下图所示。Kind 字段固定值为5,占8位,表示这是非 SYN 数据段的 SACK 选项;Length 字段值可变,占8位,以字节为单位表示 SACK 扩展选项的长度。后面是 n 个标识不连续数据段起始和结束序号的部分,每个序号占32位。
由于 TCP 数据段头部的“可选项”字段最长为40字节,Kind 和 Length 这两部分一共占了2字节,而表示一个不连续数据段的起始序号和结束序号要占用8字节(因为一个序号要占4字节)。因此,实际上在一个数据段中最多可以标识4个不连续数据段,因为标识4个数据段的起始序号和结束序号一共要占用32字节,加上前面 Kind 和 Length 部分,就有34字节。而要标识5个数据段的起始序号和结束序号,则还要8字节,超出了“可选项”字段40字节的限制范围。所以,在“Length”子字节中,最大值其实就是34.
发送端通过识别接收端返回到确认数据段中的 SACK 扩展选项就可以得知接收端已收到了哪些不连续序号的数据段,这样发送端就可以不再发送这些数据段,而只发送已经丢失的数据段(发送端已经发送,且在重传定时器超时后接收端仍没有收到的数据段)。
假设接收端已经收到 1、101、201、401、501 这五个序号的数据段,在发送确认号为 301 的确认数据段时,在 SACK 扩展选项中标记 401(起始序号为 401,结束序号为 600)这两段不连续的数据段。这时发送端就会知道,不需要再发送 401 和 501 这两个数据段了,只需要发送301号数据段即可。这样大大节省了网络资源,也提高了数据传输的效率。