TCP 协议

TCP 协议

TCP 概述

TCP 是面向连接的、可靠的、基于字节流的全双工传输层通信协议

报文格式

TCP报文格式
源端口号 (16 bit)
目标端口号 (16 bit)
序列号 (32 bit)
确认应答号 (32 bit)
校验和 (16 bit)
紧急指针 (16 bit)
窗口大小 (16 bit)
首部
长度
(4 bit)
保留
(6 bit)
U
R
G
A
C
K
R
S
T
S
Y
N
F
I
N
P
S
H
选项 (长度可变)
数据

TCP 首部信息:

报文编号

TCP隐式地对字节流中的每一个字节编号,序列号是当前报文段首字节的字节流编号,确认号为希望对方主机发送的下一报文段所含数据的首字节的编号
序号到达2^32-1后重新从0开始

一条 TCP 连接的双方均可随机地选择初始序列号,这样做可以减少两台主机的新建连接受到仍在网络中存在的过期连接的报文段的影响

序列号 = 上一次发送的序列号 + len(上一报文的数据长度)
特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则改为上一次发送的序列号 + 1
确认号 = 上一次收到的报文中的序列号 + len(上一报文的数据长度)
特殊情况,如果收到的是 SYN 报文或者 FIN 报文,则改为上一次收到的报文中的序列号 + 1

TCP 采用累积确认/累积应答模式,接收方发送确认号 X 代表 X 之前的所有数据都已收到

延迟确认

没有携带数据的 ACK 报文也有 40 个字节的 IP 头和 TCP 头,单独发送的效率很低
为了解决 ACK 传输效率低问题,衍生出了 TCP 延迟确认

TCP 延迟确认的策略:

  • 当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方
  • 当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送
  • 如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK

拆包&粘包

TCP 传输协议是面向流的,没有数据包界限,客户端向服务端发送数据时,可能将一个完整的报文拆分成多个小报文进行发送,也可能将多个报文合并成一个大的报文进行发送,因此就有了拆包和粘包

产生原因:

拆包解决方案:
TCP 将数据依照最大报文段长度(MSS)分割,加上 TCP 首部后形成多个 TCP 报文段,然后下发至网络层

MSS

MSS通常根据最初确定的由本地发送主机发送的最大链路层帧长度(即所谓的最大传输单元(MTU))来设置
TCP 与 IP 首部长度之和通常为40bytes,以太网和 PPP 链路层协议都具有1500字节的 MTU,因此 MSS 的典型值为1460字节
TCP 通过 MSS 保证一个完整 TCP 报文段在 IP 层不被分片,以免包含部分 TCP 报文段的单个 IP 分片丢失时需要重传整个 TCP 报文,开销过大
为了达到最佳的传输效能, TCP 协议在建立连接的时候通常要协商双方的 MSS 值

粘包解决方案:定义应用层的通信协议

连接管理

可靠性传输

TCP 的可靠数据传输为 GBN 和 SR 的混合体

超时重传

TCP 采用超时/重传机制来处理报文段的丢失问题,在发送数据时设定定时器,指定时间内未收到对方的 ACK 报文则重传

超时重传时间 RTO 根据往返延时(RTT)动态确定,应该略大于报文往返 RTT 的值,取得无效重传和等待重传时间过长问题间的平衡

9.jpg (1158×888) (xiaolincoding.com)|600

推荐的初始 RTO 值为1秒,在第一次收到确认报文前的超时都将使 RTO 值成倍增加,以保证接收到首个确认报文段并更新 RTO
在 Linux 下,α = 0.125,β = 0.25, μ = 1,∂ = 4

此外如果超时重发的数据再次超时又需要重传时,TCP 加倍超时间隔

快速重传

快速重传指发送方在定时器到期之前收到对同一报文段的3个额外 ACK,则立刻重传该报文段
快速重传避免了超时重传等待时间过长的问题

SACK

快速重传因为不确定丢失报文的数量,面临重传一个还是重传所有报文的问题
SACK 方法通过确认已收到的数据信息解决该问题
选择性确认 SACK在 ACK 报文的 TCP 头部选项字段里加入已收到的数据的信息
发送方由此可以知道哪些数据已被收到,只重传丢失的数据

发送方
接收方
100~199
ACK 200
300~399
200~299
ACK 200
SACK 300~400
400~499
ACK 200
SACK 300~500
500~599
ACK 200
SACK 300~600
200~299
ACK 600

|650

D-SACK

Duplicate SACK 又称 D-SACK 使用 SACK 来告诉发送方有哪些数据被重复接收

用途:

13.jpg (962×1082) (xiaolincoding.com)|550

流量控制 - 滑动窗口

滑动窗口指定发送方无需等待 ACK 报文而可以继续发送数据的最大值

通常窗口大小由接收方的窗口大小决定,依靠 TCP 头部接收窗口字段交流控制

发送方滑动窗口 swnd

19.jpg (1428×513) (xiaolincoding.com)|825

SND.WND:表示发送窗口的大小(由接收方指定)
SND.UNA (Send Unacknowledged):一个绝对指针,指向已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节
SND.NXT:一个绝对指针,指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节
指向 #4 的第一个字节是个相对指针,值为 SND.UNA + SND.WND
可用窗口大小 = SND.WND -(SND.NXT - SND.UNA)

接收方滑动窗口 rwnd

20.jpg (1429×498) (xiaolincoding.com)|825

RCV.WND:表示接收窗口的大小,会被通告给发送方
RCV.NXT:一个绝对指针,指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节
指向 #4 的第一个字节是个相对指针,值为 RCV.NXT + RCV.WND

SND.NXT 即为下一 TCP 报文的序列号首部字段
RCV.NXT 即为下一 TCP 报文的确认号首部字段

滑动窗口的大小受系统内存缓冲区的限制,当缓冲区不够的情况下,如果服务器应用程序没有及时读取接收数据,接收窗口会不断收缩并通告客户端

当服务端系统资源非常紧张时,操作系统可能会直接减少接收缓冲区大小,进而减少接收方窗口大小,这时如果应用程序无法及时读取缓存数据,会出现数据包丢失的现象
为了防止这种情况发生,TCP 规定不允许同时减少缓存和收缩窗口,采用先收缩窗口,过段时间再减少缓存的方式避免丢包情况

零窗口

当发送方收到接收窗口为 0 的 ACK 报文时,将通过持续计时器间断发送仅1字节的窗口探测报文段,接收方收到后通过 ACK 报文反馈当前接收窗口大小
窗口探测的次数一般为 3 次,每次大约 30-60 秒
如果 3 次过后接收窗口还是 0 的话,部分 TCP 实现就会发 RST 报文来中断连接

糊涂窗口综合症

糊涂窗口综合症指接收方窗口过小导致发送方每个 TCP 报文都传输小数据,浪费资源

解决方案:

拥塞控制

网络所需传输的数据超过了网络的处理能力即引发网络拥塞

拥塞的代价:

常见的拥塞控制方法包括端到端拥塞控制网络辅助拥塞控制
端到端拥塞控制中端系统通过对网络行为的观察推断网络拥塞
网络辅助拥塞控制中路由器向发送方端系统提供网络拥塞的显式反馈信息

IP层不向端系统提供显式的网络拥塞反馈,因此TCP使用端到端拥塞控制,每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率

TCP 连接的每一端都是由一个接收缓存、一个发送缓存和几个变量组成,运行在发送方的 TCP 拥塞控制机制跟踪一个额外的变量拥塞窗口 cwnd
发送窗口的值 swnd = min(cwnd, rwnd)

TCP 通过报文的超时感知网络拥塞,同时采用四种算法控制拥塞:

TCP 拥塞控制算法

TCP使用ACK来触发(或计时)增大它的拥塞窗口长度,感知到丢包时降低其发送速率

TCP拥塞控制算法使用AIMD(线性增、乘性减少),包括三个部分

  1. 连接刚建立时,cwnd 通常置为1(单位为 MSS),连接处于 慢启动(slow-start, SS)阶段
    • 接收到的每个 ACK 报文使 cwnd += 1,因此每经过1RTT 后 cwnd *= 2 (指数型增长)
  2. 当拥塞窗口 cwnd 大小超过慢启动门限 ssthresh 时进入拥塞避免(congestion-avoidance, CA)阶段
    • 接收到的每个 ACK 报文使 cwnd 增加1/cwnd,也即每经过1RTT 后 cwnd += 1
  3. 发生超时重传时,ssthresh = cwnd/2,cwnd 重设为初始值(1),再次进入 SS 阶段
  4. cwnd 增长至 ssthresh 值后再次进入 CA 阶段
  5. 发生快速重传时,ssthresh = cwnd/2cwnd = ssthresh+3 (将3个冗余 ACK 计算在内),进入 FR 阶段
    • 对于每个冗余 ACK,cwnd += 1
      • 如缺失报文段最终超时,则 cwnd 重设为初始值,进入 SS 阶段
      • 收到缺失报文段的 ACK 后,令 cwnd = ssthresh,进入 CA 阶段

一般来说 ssthresh 的初始大小是 65535 bytes

|600
|600

快速恢复阶段分析:

公平性

TCP-Socket 编程

format,png-20230309230545997.png (1188×1007) (xiaolincoding.com)|600

  1. 服务端和客户端初始化 socket,得到文件描述符
  2. 服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口
  3. 服务端调用 listen,通过监听 socket 进行监听
  4. 服务端调用 accept,等待客户端连接
  5. 客户端调用 connect,向服务端的地址和端口发起连接请求
  6. 服务端 accept 返回用于传输的已连接 socket 的文件描述符
  7. 客户端调用 write 写入数据;服务端调用 read 读取数据
  8. 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭

accpet 系统调用并不参与 TCP 三次握手过程,它只是负责从 TCP 全连接队列取出一个已经建立连接的 socket

客户端可以自己连自己形成连接(TCP 自连接),也可以两个客户端同时向对方发出请求建立连接(TCP 同时打开),这两个情况都有个共同点,就是没有服务端参与,也就是没有 listen,就能 TCP 建立连接