📕 TCP 概述

吞佛童子2022年6月20日
  • 计算机网络
  • 传输层
  • TCP
大约 6 分钟

📕 TCP 概述

1. 是什么

1) TCP 概念

  • 面向连接
  • 一对一
  • 可靠的
  • 面向字节流
  • 工作在传输层
  • 数据传输服务
  • 用 TCP 传输的网络包是 无损坏、无间隔、非冗余、有序的

2) TCP VS UDP

TCPUDP
连接建立三次握手不需要建立连接,即可传输数据
对象数一对一一对一、一对多、多对多
可靠性可靠传输尽最大努力交付,不保证一定可靠
首部长度[20, 60] 字节固定 8 字节
传输方式面向字节流面向报文
数据分片TCP 会在传输层进行分片传输层不负责分片,IP 层可能进行分片操作
适用场景文件传输、http/https音视频传输、广播通信

2. 头格式

img.png

1) 源端口号 & 目的端口号

  1. 源端口号
    • > 1023 的 16 位数
  2. 目的端口号
    • 接受者所用的端口号

2) 序列号 SEQ

  • 初始序列号 [ISN] 的确定
    • 建立连接时,由客户端随机生成
    • 基于时钟,4 ms 自动 ++,转一圈大约需要 4.55 小时
    • ISN = M [计时器生成的值] + hash(源 ip,源端口号,目的 ip,目的端口号)
  • 后续改变
    • 每发送一个字节数据,SEQ ++
    • 达到 max [== 32 位无符号整数] 后序列号回绕,再次从 0 开始
  • 序列号有效性问题
    • 当在一个速度足够快的网络中传输大量数据时,序列号的回绕时间会变得很短,此时无法有效判断到底是历史序列号还是当前序列号
  • 如何解决
    • 添加 TCP 时间戳,tcp_timestamps 参数是默认开启的,此时就进入了 PAWS 机制
    • 开启后,TCP 头部就会有个时间戳选项,当收到数据包中的时间戳不是递增的,就表示该数据包是过期数据包,直接丢弃
    • 作用:
      • 便于精确计算 RTT
      • 防止序列号的回绕
    • 时间戳也是 32 位数据,若是也发生回绕,如何避免?
      • 增大时间戳的位数,可以设置为 64 位

3) 确认号

  • 确认号 == 初始序列号 + TCP 段长度
  • 下一次期望收到的对方数据的序列号
  • 在这个序列号之前的数据本方已经全部接收完毕,用来解决丢包问题

4) 首部长度 & 保留位 & 控制位 & 窗口大小

  1. 首部长度 [4 bit]
    • 表示 TCP 头的长度
    • 2^4 ∈ [0, 15],每个 1 代表 4 字节
    • 因此可以得出结论,TCP 头部 max = 15 * 4 = 60 字节
    • 首部长度 min == 20 字节
  2. 保留位 [6 bit]
    • 未使用,置零
  3. 控制位 [6 bit]
    • UAG
      • 本报文段发送的数据是否含有 紧急数据,只有当 UAG == 1 时,紧急指针才有效
    • ACK
      • 只有当 ACK == 1 时,前面的 确认号才有效,除了第一次握手没有 ACK 置一外,之后均应该 == 1
    • PSH
      • PSH == 1 时,表示对方应立即从 TCP 接收缓冲区中读走数据,为接收后续数据让出空间
    • RST
      • RST ==1 时,说明需要释放连接,并重新建立连接 | 上次发送给主机的数据有问题,主机拒绝响应
    • SYN
      • 只有当 SYN == 1 时,表示处于第一次握手 | 第二次握手阶段
    • FIN
      • FIN == 1 时,说明本方数据已经发送完,希望断开连接
  4. 窗口大小
    • 现在允许对方发送的数据量
    • 达到阈值后,需要本方 ACK 确认后,对方才可继续发送后面的数据

5) 校验和 & 紧急指针

  1. 校验和
    • 提供额外的可靠性
  2. 紧急指针
    • 标记紧急数据在数据字段的多少位,只有当 UAG == 1 时才有效

6) 选项

  • 最长可为 40 字节,必须为 4 字节的整数倍,才能保证 TCP 首部长度为 4 字节的整数倍

3. Socket 编程

1) 流程

2) 说明

  1. 服务端 & 客户端初始化 socket,得到文件描述符
  2. 服务端调用 bind(),绑定客户端与自己建立 TCP 连接的 ip & port
  3. 服务端调用 listen(),监听 port
    • int listen (int socketfd, int backlog)
    • socketfd 文件描述符
    • backlog 早期为半连接队列大小,目前通常认为是全连接队列大小
  4. 服务端调用 accept(),等待客户端连接
  5. 客户端调用 connect(),向服务器 ip & port 请求建立连接,通过 3 次握手建立连接
    • connect() 成功,表示第二次握手 - 客户端成功接收 ACK 报文 成功
  6. 服务端调用 accept(),返回客户端用于传输的 socket 文件描述符
    • accept() 成功,表示第三次握手成功,双方连接建立完毕
  7. 客户端调用 write(),写请求数据,发送给服务端
  8. 服务端调用 read(),读取客户端来的数据
  9. 服务端调用 write(),返回客户端响应数据
  10. 客户端调用 read(),读取服务端传来的响应报文
  11. 客户端端口连接时,会调用 close(),发送给服务端
    • close() 调用后,客户端会向服务端发送 FIN 报文,进入 fin_wait_1 状态
  12. 服务端接收到客户端 FIN 报文后,TCP 协议栈为 FIN 包插入一个文件结束符 EOF 到接收缓冲区
    • 服务端调用 read() 读取报文时,可以识别到 EOF,知道客户端数据发送完毕,服务端进入 close_wait 状态
    • 服务端发完要发的数据后,调用 close() 发送 FIN 包给客户端,服务端进入 lack_ack 状态
  13. 客户端收到服务端的 FIN 包后,发送 ACK 包给服务端,进入 time_wait 状态
  14. 服务端收到 ACK 包后,进入 close 状态
  15. 客户端经过 2MSL 时间后,也进入 close 状态

4. 粘包问题

1) TCP 面向字节流

  • TCP 传输数据时,消息可能会被分成多个 TCP 报文,因此一个 TCP 报文并不能代表一条完整是用户信息
  • 数据如何进行拆分,取决于发送窗口、拥塞窗口、当前发送缓冲区的大小等因素
  • 因此 TCP 不能称为面向报文,而是称作面向字节流

2) TCP 粘包问题

  1. 当 2 个消息的某个部分被分到一个 TCP 报文中时,就是 TCP 粘包
  2. 如何解决?
    • 分成固定长度的消息
      • 接收方接收固定消息后,就认为这个内容是完整的
    • 特殊字符作为分包边界
      • 接收方读到某个特殊字符后,认为之前的内容是完整的
    • 自定义消息结构
      • 双方协商好消息结构,通过消息结构对数据进行区分,例如消息结构中包含了本次包的长度,那么读取到这个长度的数据后,之前的内容就是完整的
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou