TCP 连接

TCP 是面向连接、可靠的、基于字节流的传输层协议。

为了达到以上效果,TCP协议有以下规定 数据分片、到达确认、超时重发、滑动窗口、失序处理、重复处理、数据校验。

1. TCP 报文格式

图片源自 wikipedia
TCP 头格式

  1. 来源连接端口 \ 目的连接端口。
  2. 序列号码,一般说的 Seq,是一个相对值,在 TCP 连接过程中赋值。
  3. 确认号码,一般说的 ACK,也是一个相对值,初始值在 TCP 连接过程中赋值。
  4. 资料偏移,报文头大小,单位是 4 字节,可以看到使用 4 位,最大值为 15,因此 TCP 头部最多只有 60 字节,固定数据 20 字节,可选项最大 40 字节。
  5. 控制位/保留位(共12比特),表明报文属性。除了请求连接,ACK 字段必须为 1,因此除了第一个包,每一个包都会携带 ACK 信息。
  6. 窗口大小,16位,最大值 64K,但是在可选项中有一个缩放因子选项,使用 2 字节表示,最大允许为 14,表示缩放倍数为,因此窗口最大约为 214 + 16 = 2G 大小。
  7. 校验和,用于确认报文在传输中是否出错。
  8. 紧急指针,窗口为零时也可发送。
  9. 可选项
    1. 窗口扩大选项(type: 3)。见窗口大小
    2. 时间戳(type: 8)。发送方包含此选项,接收方收到后,将此值复制到应答字段。用于计算 RTT,防止序号绕回。
    3. NOP(type: 1)。前面说了,头部大小是4字节为单位,不足需要补齐。
    4. MSS(type: 2)。告知 MSS 大小。
    5. SACK(type: 4)。支持 SACK 选项。

2. 示例

以下为一次 GET 请求的抓包示例,高亮部分正好是一次重传,和上一个报文相比,只是各校验值和时间戳有变:
Wireshark 抓包示例

截图中第一行内容进行说明:

74 46358 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=3532023249 TSecr=0 WS=128

74: 数据包大小,构成是:

  1. 两个 MAC 地址 0x0800 表示 IPv4 协议,共14字节;
  2. IP 协议头,20 字节,里面可以看到表示 IP 数据包大小为 60 字节;
  3. TCP 协议数据,20 字节 TCP 数据头,20 字节可选字段。

46358 -> 80 源端口和目标端口,46358 是客户端,80 是服务端。

[SYN] 表示 TCP 包中的对应标志位为 1。

Seq 序号,Win 窗口大小,Len 数据长度,MSS 最大分段长度,SACK_PERM 支持 SACK,TSval 时间戳,TSecr 对应请求的时间戳,WS 窗口缩放因子。

ACK、SEQ、SYN、FIN 等标志位是以 bit 位形式存在,所以会出现一个数据包包含多种信息。比如说,连接建立过程中的服务端的 SYN+ACK,四次挥手部分情况下优化之后的三次挥手 FIN+ACK

最大传输单元 MTU(Maximum Transmission Unit)

最大分段大小 MSS(Maximum Segment Size)

3. TCP三次握手

  1. C -> S: SYN SEQ=X(SYN_SEND)截图第一行 Seq 显示为 0,这是一个 SYN 包,Wireshark 将随机的 Seq 视为 0,后续的 Seq 也是计算的相对值。
  2. C <- S: SYN SEQ=Y ACK=(X+1)(SYN_RECV)截图第二行 Seq 为 0,ACK 为 1,ACK 是相对对方的 Seq 来计算的。ACK 为 1,表示对 SYN 的应答。
  3. C -> S: ACK=(Y+1) (Established) 截图第三行,Seq 为 1,长度为 0,表示没有数据传输,ACK 为 1,表示对服务端 SYN 的应答。

在 TCP 握手过程中,会将以下参数进行告知对方:

  1. 交换 MSS 值。
  2. 滑动窗口大小。
  3. Seq 初始值,Seq 值减去初始值即为已收到的数据大小

4. TCP 数据传输

记录部分可靠报文的实现,用于帮助理解 TCP/IP 协议的实现。

可靠报文的实现:

1. 停止-等待协议

  1. 发送一组数据,等待确认,之后发送下一组报文。当一定时间未收到应答,认为数据丢失,进行重发。
  2. 出现差错。接受方丢弃数据,等待发送方超时重传。
  3. 超时重传时间。太长,效率低;太短,不必要的重传。
  4. 发送方发送数据后需要等待接收到应答才删除对应数据。
  5. 发送方需要对数据分组编号,这样才能根据应答\超时进行重传。
  6. 接收方收到了重复数据包,需要丢弃数据,同时向发送方发送应答信息。
  7. 每一组数据发出之后,需要等待确认,一个 RTT(Round Trip Time)只发送一组数据,由此导致信道利用率低下。

这类超时自动发起重传的协议,被称为 自动重传请求 ARQ(Automatic Repeat reQuest)

2. 连续 ARQ 协议

为避免出现一个 RTT 只发送一组数据,可以选择一次发送多组数据,后续会陆续收到应答,通过流水线作业,提高通道的利用率。

比如,有100字节需要传输,每个数据包5字节,每次发送5组数据。这样,接收方收到一组数据之后,陆续返回 5 组数据的应答,这样,一个 RTT 时间加上 4 组数据的处理时间,就成功传输了 5 组数据,以120 km为例,光传输时间需要 4 ms,再加上中间路由的处理,假定总共需要 20 ms,应用层读写数据效率远高于网络传输时间。因此达到了提效的目的。接收方也不需要每接收到一组数据就开始应答,TCP 规定接收方只需要告知发送方最后收到的一组数据编号,由此表明之前的数据全部成功接收。

那么是不是可以一次将所有的数据进行传输呢?一次发送(生产)过多数据,另一端应用层读取(消费)速度过慢,传输层缓存大小有限,无法全部接收,只能丢弃掉数据,发送方未接收到应答包,又会继续重传,这样甚至会导致整个数据链路上的设备(缓存都是有限的)都出现大量的无用数据,数据分发效率降低,导致网络出现拥塞。

3. 滑动窗口

WireShark 截图可以看到,每一个 TCP 数据包都有一个 Win 属性,这个表示的是窗口大小,表示基于此数据包的应答数据,还能接收的数据大小。

窗口的简单理解就是一个首尾双指针,先这么理解,首指针代表刚刚进入的数据,尾指针代表即将移出窗口的数据,当收到应答包,尾指针前移

在 TCP 中,发送方和接收方都存在着对应的缓冲区,比如当前有 100 字节数据需要传输,发送方缓冲区大小为 20字节,已发送 12 字节,此时接收方已确认收到了 10 字节,接收方告知的窗口大小为 15 字节,意味着接收方缓存足够容纳到第 25 字节,那么当前发送方可以将第 10 字节及前面数据清理掉,窗口指向 [11, 30],发送方接下来会将 13-25 字节,当接收方陆续收到数据,会发送应答包告知发送方当前已收到的数据包。

当接收方滑动窗口已填满,接收方告知 Win 为 0,发送方不再发送数据。等待一定时间,发送探测数据包。避免接收方应答包丢失导致互相等待。

注意事项:

  1. 由于网络滞后,发送窗口和接收窗口大小关系不确定。
  2. 乱序到达的数据,标准并未规定如何处理。
  3. TCP 要求接收方必须有累计应答功能。为避免不必要的重传:应答延迟时间不应超过 0.5s。对于一连串的最大长度报文,每隔一个报文段必须发送一个确认。
发送方数据小

由于IPv4 TCP 包数据头 40 字节,如果只传输 1 个字节,会导致传输效率极低。

TCP 数据发送时机:

  1. 数据大小达到 MSS 大小时发送
  2. 应用进程指明立即发送
  3. 计时器

Nagle 算法,如果数据不满一个段,或者是滑动窗口的一半,等接收到 ACK 包再发送。相当于一个 RTT + ACK 延时的计时器。

接收方数据小

应用层每次只处理一个字节,即 Win 大小一直为 1。

接收方等待缓存剩余空间不小于 MSS 或者是滑动窗口的一半,在发送 ACK 包。

4. 拥塞控制

拥塞发生原因:资源需求 > 可用资源

比如说前面提到一次发送所有数据,网络上节点缓存有限,丢掉部分数据,导致后续不停的发送重传数据,加剧网络负载。

AIMD 拥塞控制算法

  1. 慢开始
  2. 拥塞避免
  3. 快速重传
  4. 快速恢复

慢开始:发送方维持一个拥塞窗口的变量 cwnd(一般为 MSS 大小,RFC 2581规定不能超过 2xMSS)发送窗口等于此值,数据发送之后成功收到应答,cwnd 增加不超过 MSS 大小的值,1个包发送之后,收到应答,cwnd 翻倍,接下来发送数据为两倍,接收到两个应答报文,cwnd 再次翻倍,如此以指数方式增长,慢开始是指启动时的值小,之后指数式增长。如此增长下去,很快就会超过负载,所以还有一个阀值,ssthresh(默认 16*MSS),超过此阀值之后使用拥塞避免方法。

拥塞避免:拥塞避免就是让 cwnd 的增长由指数式增长变为线性增长。每经过一个 RTT 时间,增加 MSS 大小。(一个大概的实现就是每收到一个应答,增加 MSS / cwnd * MSS)

当出现拥塞,即没有及时收到应答,执行以下操作:ssthresh = cwnd / 2,cwnd = 1,重新执行慢开始算法。为什么要从 1 开始?出现拥塞之后,为避免网络情况进一步恶化,使设备有时间处理已有的信息。

快重传:接收方收到乱序报文就发送一个应答,如,第一个包收到,应答,第二个包丢失,第三四五个包收到,重复应答1,这样发送方收到第一个包的应答,就会立即重传第二个包。此时执行 ssthresh = cwnd / 2,cwnd = ssthresh,开始执行拥塞避免算法。因为收到了后续包,认为网络没有那么糟糕。

5. TCP 四次挥手

  1. C -> S: FIN

  2. C <- S: ACK

  3. C <- S: FIN

  4. C -> S:ACK

    TCP 断开连接通过四次挥手完成,Client 和 Server 分别想对方发送 FIN 指令,接收到之后回复 ACK 应答。FIN 指令的发起没有先后顺序,只是代表自己需要发送的数据已全部完成。协议在设计上允许任何一方先发起断开过程。

    发送 FIN 指令之后,并且缓冲区数据已全部发送给应用,应用层再次读取数据,传输层协议,就会告知应用数据已全部接收完成。

    套接字删除

    由于最后的 FIN 指令对应的 ACK 可以会丢失,FIN 指令会重复收到,此时,为了保证协议栈还有对应的信息来发送 ACK 应答,Socket 不会立马被删除,具体等待时间,协议中没有规定,和重传机制相关,一般时间为2倍最长报文时间 MSL(Maximum Segment Lifetime)。

    MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s,可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看。

    由于端口会被复用,立马删除套接字,新建立连接正好选择了此端口,重传回来的 FIN 会导致新的套接字执行断开操作。

6. 提问

  1. TCP 连接中的不同包,经过的路由设备是一样的吗?
    TCP 基于 IP 协议完成,IP 协议是基于数据包交换的不可靠协议。在 TCP 传输的过程中,IP 包会出现丢包、延迟、乱序等情况,经过一定时间没有收到对应的 ACK 信息,就会进行重传。IP 通道上的物理设备即便出现掉线,路由器会重新寻找对应的物理链路,重新寻找最短路径,路由重收敛
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值