3.5 面向连接的传输:TCP
3.5.1 TCP 概述
- TCP 实现了 点对点 的通信
- TCP 提供了 可靠的、按顺序的 字节流服务
- 字节流代表需要上层自己维护边界
- TCP 是流水线协议,即一次可以发送多个报文段
- TCP 提供了流量控制,发送方根据接收方的接收能力进行发送速率的调整
3.5.2 TCP 的报文段结构
TCP 的报文段就是本层的数据单元 PDU,其由这几个部分构成
字段 | 长度(位) | 描述 |
---|---|---|
源端口号 | 16 | 指明发送端口 |
目的端口号 | 16 | 指明接收端口 |
序列号 | 32 | 标识数据段的顺序 |
确认号 | 32 | 对收到的数据进行确认 |
首部长度 | 4 | 表示 TCP 头部的长度 |
保留 | 6 | 保留字段,未使用,置为 0 |
控制位 | 6 | 标志位,用于控制连接和数据传输 |
窗口大小 | 16 | 接收端 可接收的数据量 |
校验和 | 16 | 用于检测传输过程中的错误 |
紧急指针 | 16 | 指示紧急数据的位置 |
选项和填充 | 可变 | 传输额外的控制信息和填充字节 |
<1> 序号和确认号
TCP 将数据分割成一个一个报文段来发送出去,需要由编号来标识发出去的报文段的顺序。
- 序号:报文段的首个字节的的编号
- 确认号:期望 从另一端接收到的数据的编号,比如说现在接收到的编号为 53 那确认号就是 54 即其下一个需要的报文段的编号,与上一节中提到的协议的 ACK 相差 1
看一个案例
主机 A 向主机 B 发送一个字符,主机 B 将其回显出来;主机 A 发送标号为 42 的报文,同时请求标号为 79 的报文,B 返回标号为 79 的报文并且返回 ACK 请求标号为 43 的报文。
<2> 控制位
通过不同的控制位可以指示数据包的类型、状态或要执行的操作。
TCP报文头部的控制位包括以下几个:
- URG(Urgent): 表示紧急指针字段是否有效,用于指示该段是否包含紧急数据。
- ACK(Acknowledgment): 表示确认序号字段是否有效,用于指示该段是否包含确认数据。
- PSH(Push): 表示接收方应尽快交付数据给应用层,而不是等到缓冲区满或者等待更多数据。
- RST(Reset): 表示重置连接,用于中止连接或者响应异常情况。
- SYN(Synchronize): 表示建立连接,用于发起TCP连接的握手过程。
- FIN(Finish): 表示关闭连接,用于结束TCP连接的四次挥手过程。
3.5.2 TCP 的往返延时和超时控制
- 平滑 RTT 估计(Smoothed Round-Trip Time): TCP 使用平滑 RTT 估计来计算当前的往返时间。每次发送数据段后,记录发送时间,并在收到对应的确认时计算 RTT。然后,使用加权移动平均等方法来计算平滑 RTT。
- 偏差估计(RTT Variation Estimate): TCP 还会估计 RTT 的变化范围,即 RTT 的偏差。这个值反映了网络延迟的 不确定性。TCP 使用偏差估计来确定超时时间的安全边界,以确保在不稳定的网络环境下也能保证数据的可靠传输。
- 超时时间设置: 综合考虑平滑 RTT 和 RTT 的偏差,TCP 设置超时时间为平滑 RTT 加上一个安全边界。这个安全边界通常是 RTT 的一个倍数,例如,可以设置为平滑 RTT 加上四倍的 RTT 偏差。
平滑的往返时间估计值:
E
s
t
i
m
a
t
e
d
R
T
T
=
(
1
−
a
)
∗
E
s
t
i
m
a
t
e
d
R
T
T
+
a
∗
S
a
m
p
l
e
R
T
T
EstimatedRTT = (1- a)*EstimatedRTT + a*SampleRTT
EstimatedRTT=(1−a)∗EstimatedRTT+a∗SampleRTT
估计出来往返时间的值,利用这个值来估计偏差
D
e
v
R
T
T
=
(
1
−
β
)
×
D
e
v
R
T
T
+
β
×
∣
S
a
m
p
l
e
R
T
T
−
E
s
t
i
m
a
t
e
d
R
T
T
∣
DevRTT=(1−β)×DevRTT+β×∣SampleRTT−EstimatedRTT∣
DevRTT=(1−β)×DevRTT+β×∣SampleRTT−EstimatedRTT∣
- DevRTT 是偏差估计值;
- SampleRTT 是当前的往返时间样本值;
- EstimatedRTT 是平滑的往返时间估计值;
- β 是用于平滑的系数,通常取值在0到1之间,用于控制新样本值对于估计的影响程度。
最后将超时时间设置为平滑的往返时间 + 四倍的估计偏差,通过这种设置可以降低拥塞。
3.5.3 TCP 实现可靠数据传输概述
🍀 TCP 在 IP 提供的不可靠服务的基础上建立了 RDT(可靠数据传输)。
- TCP 具有管道化的报文段
- 其具有累计确认的特点(像 GBN)
- 发送端有单个重传定时器(像 GBN)
- 接收端是否接收乱序没有规范,又接收端自己决定
🍀 TCP 发送方通过以下两种事件触发重传机制
- 超时,重发最早的未确认段
- 快速重传:收到重复的确认,在收到一个有效的确认后又收到三个 冗余的确认
3.5.4 TCP 发送方
上图中标注了发送方 TCP 实体在 收到应用层数据、超时以及收到 ACK 的处理机制。
- 收到应用层数据的时候,TCP 实体船舰报文段和起始序列号(
create segment, seq
),发送的报文段的下一个数据的序列号设置为NextSeqNum + length(data)
,这就是上面提到的偏移量,比如我本次发送的数据的seq = 92
发送了8
个字节的数据,那下次数据的序列号就是92 + 8 - 1 + 1
也就是100
,然后启动一个定时器。 - 当超时的时候,重发具有最小序号的未确认段,而不是全部重发一遍
- 当收到 ACK 的时候,滑动窗口前移到
y
(累计确认,不一定是移动一位),如果现在还有未确认的段,就重新启动定时器,或者所有需要发送的数据已经确认完毕就终止定时器。
3.5.5 TCP 的重传机制
<1> 超时重传以及接收方 ACK 产生建议
先明确一点,无论发送方发送什么,接收方的 ACK 总是提出它此时需要的数据。
比如上图中的第二个例子,发送方因为数据过早超时再次发送了 seq = 92
的数据,但此时接收方已经接收到了编号 seq = 120
以前的所有数据,所以此时请求的是 seq = 120
上图描述的是 TCP 接收方的累计确认机制,即其 ACK 中请求的报文,表示这个报文以前的所有报文都已经收到,而不是类似于 SR 协议那样每次收到一个报文都发送一个 ACK。
关于产生 TCP ACK 的建议:
- 第一条建议就是延迟确认机制,即等待一段时间如果没有新的报文段到来再发送 ACK,避免对发送方造成过多的干扰。
- 第二条建议即积累两个报文段一起发送,比如说现在到达了
seq = 92
8
个字节,先暂时等待不发,此时又到了一个seq = 100
20
个字节,此时直接发送 ACK(120)。 - 第三种和第四种情况处理的是乱序接收的情况,即当收到乱序的报文段后立即发送 ACK,或者说收到的报文段填充了从
gap
起点的一部分gap
,此时也立即发送新的 ACK。
<2> 快速重传
当报文段丢失的时候,会引起多个重复的 ACK
比如这个例子,和上面提到的 TCP ACK 的建议相同,乱序到达的时候接收方会立即发送请求填补空缺的 ACK,即在 40
60
70
80
到达的时候均请求 50
的报文段,当冗余请求累积到三个的时候,发送方会触发快速重传机制,立即 重发 seq = 50
的报文段。
快速重传算法
3.5.6 TCP 的流量控制
<1> 概述
因为上层读取数据需要时间,所以暂时未读取的数据要存在接收方的缓冲区(buffer)中,这个缓冲区同时也接收发送方发送的数据,剩余的缓冲区就是总缓冲区的大小减去此时缓冲区中的数据大小。
<2> 捎带技术
发送方得知了接收方的剩余缓冲区大小的时候,保证其发送的字节数 ≤ rwnd
值
下面来看一个例子
农民 1 卖给农民 2 一群羊,农民 2 寄信来说羊收到了
然后农民 2 再次寄信将钱发给农民 1
<3> 剩余缓冲区的计算
- 第一个字段是最后一次读的数据,第二个是最后一次收到的数据
- 这两个编号相减就是此时 存储在 缓冲区中的数据,再用总的缓冲区大小减去这个数据,就得到剩余的总大小。
3.5.7 TCP 连接管理
<1> TCP 连接建立 - 三次握手
- 客户端发送连接请求:客户端首先向服务器发送一个 SYN(同步)报文段,其中设置了一个初始序列号(ISN)用于标识数据流的起始位置,并指明客户端希望建立连接的端口号。
- 服务器确认连接请求:服务器收到客户端的 SYN 报文段后,会回复一个 SYN+ACK 报文段,其中 SYN 标志位表示同步,ACK 标志位表示确认。服务器也会为自己生成一个 ISN,并将其发送给客户端,同时确认客户端的序列号。
- 客户端确认连接请求:客户端收到服务器的 SYN+ACK 报文段后,会发送一个 ACK 报文段作为确认。此时,TCP 连接已经建立起来,通信双方可以开始进行数据传输。
<2> 两次握手会带来哪些问题?
- 连接重用问题:在两次握手的情况下,服务器在发送 SYN+ACK 报文段后,就已经认为客户端的连接请求被接受,可以开始传输数据。但是客户端可能并没有接收到服务器的确认,因此可能会导致服务器误认为连接已经建立,从而发送数据。如果这些数据到达客户端,而客户端并没有发送对应的 ACK 确认,那么服务器会误以为这是一个新的连接请求,导致资源浪费和安全问题。
- 连接状态不对称:在两次握手的情况下,客户端和服务器都会发送 SYN 报文段,但只有服务器在发送 SYN+ACK 报文段时会确认客户端的序列号,而客户端在收到服务器的确认后并不会再次确认服务器的序列号。这样会导致连接状态在客户端和服务器端之间不对称,可能会引发一些问题,例如对方不是真正的对方。
- 欺骗性连接问题:在两次握手的情况下,攻击者可以发送一个伪造的 SYN 报文段给服务器,如果服务器回复了 SYN+ACK 报文段,那么就建立了一个 不完整的连接,攻击者可以利用这种连接进行攻击,例如进行拒绝服务攻击。
<3> TCP 连接的关闭 - 四次挥手
- 发送关闭连接请求: 当一方决定关闭连接时(通常是应用层发出关闭连接的指令),它会向另一方发送一个 FIN 包,表示它不再发送数据了,但仍然愿意接收数据。
- 确认关闭连接请求: 接收到关闭请求的一方会发送一个 ACK 包作为确认,表示它收到了关闭请求。
- 关闭连接: 发送确认后,这一方就开始进入关闭连接的状态,它不再发送新的数据,但仍然会继续接收数据。它也可以继续发送剩余的数据。
- 发送关闭连接请求: 当这一方确定自己的数据都发送完毕后,就会发送一个 FIN 包给另一方,表示它也要关闭连接了。
- 确认关闭连接请求: 接收到关闭请求的另一方会发送一个 ACK 包作为确认。
- 关闭连接: 发送确认后,另一方也开始关闭连接。至此,连接就彻底关闭了。