TCP 首部与连接的建立和释放

1,028 阅读6分钟

1. TCP 首部格式

image.png

  • 源端口(Source Port): 使用 TCP 协议传输数据的端口号
  • 目的端口(Destination Port): 数据传输目的主机所对应的端口号
  • 序号(Sequence Number): 表示在该报文段中的数据相对于要发送的所有数据中的偏移量 (从上层传下来的数据一般在传输层就已经切分了, 这样如果有一个报文段丢失/出错的话, 发送端就只需要传输对应的报文段即可(TCP 差错控制). 若是到网络层或数据链路层再切分, 则若出错, 需要重新发送整个数据)
  • 确认号(Acknowledgement Number): 表示期望对方发送过来的报文段的序号(Sequence Number)
  • 数据偏移(Data Offset): 指数据部分在整个 TCP 报文段中的偏移量(即 TCP 首部的长度)
  • 控制位
    • URG(Urgent): 表示是否有紧急情况, 当值为 1 时, 从"数据偏移"开始的"紧急指针"个字节会优先发送
    • ACK(Acknowledgement): 当值为 1 时, 对应确认号(ack)表示期望对方接下来发送过来的数据的序号(seq)
    • PSH(Push): 当 PSH 值为 1 时, 接收到该报文段的主机会尽快将数据交付给应用程序, 而不会等到缓存满了之后再交付给上层
    • RST(Reset flag): 当值为 1 时, 表示网络状态不好, 需要释放连接, 并重新建立连接
    • SYN(Synchronize flag): 当值为 1 时, 表示期望与对方建立连接
    • FIN(Final flag): 释放连接
  • 窗口大小(Window Size): 接收/发送窗口的大小
  • 校验和(Checksun): 对 TCP 报文段进行校验

2. 建立连接(三次握手)

image.png

  1. 客户端向服务端发起建立连接请求(仅 TCP 首部): SYN = 1, ACK = 0, seq = x
  2. 服务端向客户端返回一个信息(仅 TCP 首部): SYN = 1, ACK = 1, seq = y, ack = x + 1
  3. 客户端向服务端再次返回一个信息(可以选择携带数据): ACK = 1, seq = x + 1, ack = y + 1

这里的 x 和 y 都是随机生成的 ISN (Initial Sequence Number)

2.1 为什么 seq 不是从 1 开始, 而是要从一个随机数开始?

2.1.1 可能会导致数据混淆

  1. 第一次建立完连接后, 主机 A 向主机 B 发送数据, seq = 1, 而后断开连接
  2. 第二次建立连接, 上一次 A 向 B 发送的数据到达 B, B 会误认为这个数据是新连接建立后 A 发送过来的

2.1.2 TCP 序列预测攻击

如果 seq 是从固定值开始的话, 那么只要有一个监听者监听 A, B 主机的连接流量变化, 就可以推断出对应的 seq 值从而伪造数据冒充 A 或 B 进行通信

2.2 为什么是三次握手而不是两次握手?

2.2.1 两次握手建立的连接不能双向发送数据

  1. 第一次握手, 客户端向服务端发送建立连接请求(SYN = 1), 并将自己的 ISN(Initial Sequence Number) 发送给服务端(seq = x)
  2. 第二次握手, 服务端向客户端发送允许建立连接的信息(SYN = 1, ACK = 1), 将自己的 ISN 发送给客户端(seq = y) , 同时告诉客户端自己已经准备好接收其发来的数据了(ack = x + 1)
  3. 第三次握手, 客户端告知服务端, 自己已经准备好接收数据了(ACK = 1, ack = y + 1) 如果没有第三次握手, 则只能由客户端向服务端发送数据, 而服务端不能向客户端发送数据(因为客户端并没有准备好接收来自服务端的数据)

2.2.2 两次握手可能会导致服务器资源被浪费

  1. A 首先向 B 发送建立连接的请求, 由于网络状况, A 发送的请求迟迟没有到达 B, 导致 A 一直没有等到来自 B 的回应, 于是再一次向 B 发送建立连接的请求
  2. A 第二次发送的建立连接的请求被 B 响应了, A 与 B 互相发送数据后, 断开连接
  3. B 收到了来自 A 发送的建立连接的请求(A 第一次发送的请求经过网络的延迟终于到达了 B), 于是向 A 发送对于该连接的确认, 然而 A 并不认为自己向 B 发送过建立连接的请求, 因此会忽略掉来自 B 的连接确认, 而 B 又自认为与 A 建立了连接, 于是一直等待着从 A 发送过来的数据. B 的资源就此浪费

如果是三次握手的话, B 向 A 发送连接确认消息, 同时自己的状态变为 SYN_RCVD, 在此状态下, 如果一段时间内等不到来自 A 的确认消息(ACK = 1), 则 B 会重复发送确认消息(SYN = 1, ACK = 1), 如果 B 多次发送确认消息(SYN = 1, ACK = 1)后没有收到来自 A 的确认消息(ACK = 1), 则 B 会发送 RST 给 A, 并断开连接

image.png

3. 释放连接

image.png

  1. A 向 B 发送 FIN = 1, 表示自己没有要发送的数据了, 想要断开连接(此后, A 不会向 B 主动发送数据).
  2. B 收到来自 A 的断开连接消息, 给出回复(ACK = 1). 但是 B 可能还有数据想要发送给 A, 所以没有发送 FIN = 1 给 A
  3. B 向 A 发送完所有的数据后, 再发送 FIN = 1 给 A, 表明 B 也想断开连接(如果收到来自 A 的 FIN = 1 的消息时, B 没有要发送给 A 的数据的话, 可以直接发送 FIN = 1, ACK = 1 给 A)
  4. A 收到消息后, 向 B 发送确认信息 ACK = 1, B 收到后立即断开连接, 而 A 则在发送后等待 2MSL 再断开连接(如果在这期间有收到来自 B 的数据, 则会将时间重置为 2MSL)

3.1 为什么 A 需要等待 2MSL 后才断开连接?

可以保证本次连接所发送的数据不会干扰到下一次连接

MSL, 即 Maximum Segment Lifetime, 表示一个报文段在网络上的最大生存时间(默认为 2 分钟).
等待 2MSL 再断开连接, 可以保证本次连接所发送的数据会在断开连接之前消失在网络中, 不会干扰到下一次连接(从 A 向 B 发送数据是一个 MSL, 而 B 向 A 发送的数据又是一个 MSL)