网络协议基础学习(八): TCP的几个要点

244 阅读32分钟

TCP的要点包括: 可靠传输、流量控制、拥塞控制、连接管理(建立连接、释放连接)

一、可靠传输

1、停止等待ARQ协议

  • ARQ(Automatic Repeat-reQuest), 自动重传请求

image.png

  • 如上图(a)所示, A和B之间正常通信
    • A发送M1
    • B收到A发送的M1后, 发送确认M1
    • A收到B发送的确认M1后, 发送M2
    • B收到A发送的M2后, 发送确认M2
    • A收到B发送的确认M2后, 发送确认M3
    • B收到A发送的M3后, 发送确认M3
  • 如上图(B)所示, A和B之间通信发生异常, 部分数据未送达, 就会自动进行重传
    • A发送M1
    • B没有收到A发送的M1, 不做响应
    • A发送M1后的一段时间后, 没有收到B发送过来的确认M1
    • 于是A自动重新发送M1(超时重传M1)
    • B收到A发送的M1后, 发送确认M1
    • A收到B发送的确认M1后, 才会发送M2
    • 如果A经过多次重传依然没有收到B发来的确认M1, 就会认为连接出问题, 会主动断开连接

image.png

  • 如上图(a)所示, A和B之间通信, 但是B的确认M1发送失败
    • A发送M1
    • B收到A发送的M1后, 发送确认M1
    • A发送M1后的一段时间后, 没有收到B发送过来的确认M1
    • 于是A自动重新发送M1(超时重传M1)
    • B再次收到M1, 知道A没有收到B发出的确认M1, 于是B丢掉重复的M1, 重新发送确认`M1``
    • A收到B发送的确认M1后, 发送M2
    • 如果A经过多次重传依然没有收到B发来的确认M1, 就会认为连接出问题, 会主动断开连接
  • 如上图(b)所示, A和B之间通信发生异常, 部分数据延迟送达, 就会自动进行重传, 延迟达到数据不再做处理
    • A发送M1
    • B收到A发送的M1后, 发送确认M1, 但是因为网络问题, 确认M1在网络中传输很慢
    • 因为网速原因, A发送M1后的一段时间后, 没有收到B发送过来的确认M1
    • 于是A自动重新发送M1(超时重传M1)
    • B再次收到M1, 知道A没有收到B发出的确认M1, 于是B丢掉重复的M1, 重新发送确认M1
    • A收到B发送的确认M1后, 发送M2
    • 此时, A才收到了B第一次发送过来的确认M1, 因为之前已经收到过确认M1, 不再对这次确认M1数据做处理, 什么也不做

疑问: 若有个包重传了N次还是失败, 会一直持续重传到成功位置么?

  • 这个取决于系统的设置, 比如有些系统, 重传5次还未成功就会发送reset报文

image.png

2、连续ARQ协议 + 滑动窗口协议

image.png

  • 上图(a)中是停止等待协议, 每次发送数据后, 都需要等待对方确认后才会继续发送数据, 这样传输的效率非常低
  • 上图(b)中是连续ARQ协议和滑动窗口协议
    • A有多个数据分组, 可以一次性发送多条数据给B
    • A有一个发送窗口, 发送窗口中有4个分组, 即M1-M4
    • A同时将发送窗口中的全部数据发送给B
    • B收到数据后, 会返回一条确认信息, 表示收到了M1-M4全部数据
    • A收到确认后, 发送窗口会滑动到M5-M8分组处, 再将M5-M8发送给B
    • B收到数据后, 会返回一条确认信息, 表示收到了M5-M8全部数据
    • A收到确认后, 发送窗口会滑动到M9-M12分组处, 再将M9-M12发送给B
    • 以此类推
  • 滑动窗口一次发送多条数据, 只需要收到一次确认即可, 大大提升了发送效率

3、SACK (选择性确认)

  • 现在假设有1200个字节, 分为12组, 每一组数据是100个字节, 代表一个数据段的数据
  • 每一组给一个编号

image.png

  • 在TCP通信过程中, 如果发送序列中间某个数据包丢失(比如1、2、3、4、5中的3丢失了)
  • TCP会通过重传最后确认的分组后序的分组(最后确认的是2, 会重传3、4、5)
  • 这样, 原先赢正确传输的分组也可能重复传送(比如4、5)
  • 为改善上述情况, 发展出了SACK (Selective acknowledgment, 选择性确认)技术
    • 告诉发送方那些数据丢失, 哪些数据已经提前收到
    • 使TCP只重新发送丢失的包(比如3), 不用发送后序所有的分组(比如4、5)

image.png

  • TCP首部的长度是20~60个字节, 其中固定长度20字节, 可选长度40字节
  • SACK信息会放在TCP首部的选项部分
    • Kind: 占1字节, 值为5代表这是SACK选项
    • Length: 占1字节, 表明SCAK选项共占多少个字节
    • Left Edge: 占4字节, 左边界
    • Right Edge: 占4字节, 右边界
  • 假设发送窗口, 一次性发送了10组共1000个字节的数据, 其中3、5、7、9四组没有送到
  • 那么接收端会发送确认TCP数据包, 其中TCP首部的可选部分会说明哪些数据已经收到

image.png

  • 1、2两组全部收到, 所以确认号ACK=201, 表示要发送端从标记为201的字节开始继续发送数据
  • 3、5、7、9四组没有送到, SCAK会标记已收到的分组
    • L1: 301, R1: 401: 表示接收到301-400
    • L2: 501, R2: 601: 表示接收到501-600
    • L3: 701, R3: 801: 表示接收到701-800
    • L4: 901, R4: 1001: 表示接收到901-1000
  • 这样, 发送端就知道, 哪些分组接收端没有收到, 就会重新发送这些丢失的分组

那么, TCP首部最多可以携带多少的SACK选项数据?

  • 一堆边界信息需要占用8字节, 分别是Left Edge 4个字节, Right Edge 4个字节
  • 由于TCP首部的选项部分最多40字节, 所以
    • SACK选项最多携带4组边界信息
    • SACK选项的最大占用节数 = 4 * 8 + 2 = 34 个字节
  • TCP数据传递, 大致如下图所示:

4、思考一个问题

  • 为什么选择在传输层就将数据 "大卸八块" 分成多段, 而不是等到网络层再分片传递给数据链路层
  • 如下图所示, 传输层的数据共600字节, 传递到网络层分层6段, 每段100字节, 然后在向下传递, 最后发送

image.png

  • 需要明确的是: 可靠传输是在传输层进行控制的, 网络层, 数据链路层, 物理层并没有可靠传输控制
  • 所以, 当1-6段数据都发送后, 如果其中的第3段数据丢失, 没有被接收到

image.png

  • 此时, 接收端会返回确认数据
    • ACK=1, Seq=1, Ack=201
    • SACK:
      • L1=301, R1=401
      • L2=401, R2=501
      • L3=501, R3=601
  • 发送端收到确认数据后, 就知道其中的201-300未发送成功
  • 于是传输层又将600个字节发送给网络层, 网络层分层6段后, 全部向下发送, 其中的1-200301-600字节也全部重新发送了一次

image.png

  • 因为整个传输层的数据数据全部重传, 就浪费了很多资源, 降低了重传的性能
  • 最好的办法就是只传递3201-300字节
  • 重传控制是在传输层, 所以就要在传输层分段

image.png

  • 总结:
    • 如果在传输层不分段, 一旦出现数据丢失, 整个传输层的数据都得重传
    • 如果在传输层分了段, 一旦出现数据丢失, 只需要传送丢失的那段即可

二、流量控制

  • 在网络通信中, 如果接收方的缓存区满了, 发送方还在疯狂的发送数据
    • 接收方只能把收到的数据包丢掉, 大量的丢包会极大的浪费网络资源
    • 所以要进行流量控制

1、什么是流量控制?

* 让发送方的发送速率不要太快, 让接收方来得及接收处理
  • 原理
    • 通过确认报文中窗口字段来控制发送方的发送速率
    • 发送方的发送窗口大小, 不能超过接收方给出窗口大小
    • 当发送方收到接收窗口的大小为0时, 发送方停止发送数据

  • 上图中, A是发送方, 有发送窗口, B是接收方, 有接收窗口
    • 一开始B发给A信息, 告诉A, B的接收窗口是400个字节
    • 所以, A只发送400个字节给B
    • B收到数据后, 发送确认报文, 告诉A, B的接收窗口是300个字节
    • 所以, A只发送300个字节给B
    • B收到数据后, 发送确认报文, 告诉A, B的接收窗口是100个字节
    • 所以, A只发送100个字节给B
    • B收到数据后, 发送确认报文, 告诉A, B的接收窗口是0个字节
    • 所以, A不再发送字节给B
    • 过了一段时间, 发送确认报文, 告诉A, B的接收窗口是400个字节
    • A继续给B发送数据
    • ......

在网络通信过程中, 接收方的接收窗口大小, 是一直变化的

  • 可以通过Wireshark抓取数据, 查看接收窗口大小

image.png

2、一种特殊情况

  • 有一种特殊情况
    • 一开始, 接收方给发送方发送了0窗口的报文段
    • 后面, 接收方又有了一些存储空间, 给发送方发送的非0窗口的报文段丢失了
    • 发送方的发送窗口一直为0, 双方陷入僵局
  • 解决方案
    • 当发送方接收到0窗口通知时, 这时发送方停止发送报文
    • 并且同时开启一个定时器, 隔一段时间就会发个测试报文去询问接收方最新的窗口大小
    • 如果接收的窗口大小还是为0, 则发送方再次刷新启动定时器

三、拥塞控制

1、拥塞现象

  • 如下图所示, 是一个网络的模拟图
    • R1-R3的链路吞吐量是 700M/s
    • R2-R3的链路吞吐量是 600M/s
    • R3-R4的链路吞吐量是 1000M/s
    • 600 + 700 = 1300 > 1000, 所以当R1R2传输的数据量过大时, 在R3处必然出现过载, R3就会丢掉过载的数据
    • 并且, R3-R4的链路吞吐量, 理论上是1000M/s, 但实际上并不会达到1000M/s就会出现拥塞现象

image.png

这就是同一个局域网内同一时间上网的用户过多时, 网速就会下降的原因
为了解决链路上出现拥塞现象, 就需要进行拥塞控制

  • 拥塞控制
    • 防止过多的数据注入到网络中
    • 避免网络中的路由器或链路过载

相比而言, 流量控制是点对点通信的控制

2、拥塞控制的方法

  • 拥塞控制的方法
    • 慢开始 (slow start, 慢启动)
    • 拥塞避免 (congestion avoidance)
    • 快速重传 (fast retransmit)
    • 快速回复 (fast recovery)
  • 还有几个需要知道的英文缩写
    • MSS (Maximum Segment Size): 每个段最大的数据部分大小
      • 在建立连接时确定
    • cwnd (congestion window): 拥塞窗口
    • rwnd (receive window): 接收窗口
    • swnd (send window)L 发送窗口
      • swnd = min(cwnd, rwnd)
      • 即 发送窗口的大小 = 拥塞窗口的大小 和 接收窗口的大小之中, 值最小的那一个
(1) MSS

MSS的值, 是在TCP三次握手建立连接时确定的

  • TCP数据包, 在传输过程中会切割成多个段, 然后发送
  • 由数据链路层限制, 网络层传递到数据链路层的数据, 最大是1500个字节
  • 网络层IP首部大小是20-60字节, 固定部分20字节, 可选部分40字节, 所以传输层传递到网络层的数据最大是1480个字节
  • 传输层TCP首部大小是20-60字节, 固定部分20字节, 可选部分40字节, 所以传输层数据部分最大是1460个字节

image.png

  • 在浏览器中输入icp.chinaz.com, 然后使用Wireshark抓取数据
  • 可以看到如下图中的三次握手过程

image.png

  • 点击浏览器请求建立连接这一条SYN=1, ACK=0
  • 可以看到, TCP首部长度不是20字字节, 而是44字节, 说明有TCP首部中有可选项部分
  • 点击可选项, 就可以看到MMS的值, 这是浏览器发送给服务器MSS

image.png

  • 点击浏览器同意建立连接的这一条SYN=1, ACK=1
  • 可以看到, TCP首部长度不是20字字节, 而是32字节, 说明有TCP首部中有可选项部分
  • 点击可选项, 就可以看到MSS的值, 这是服务器发送给浏览器MSS

image.png

  • 通过这两次通信, 就确定了MMS的具体值是1412, 取两个MSS中的最小值
(2) 拥塞窗口、接收窗口、发送窗口
  • 如下图所示, 客户端服务器进行数据通信
    • 假设双方的发送窗口拥塞窗口大小一直不变

image.png

  • 在通信过程中, 客户端服务器都会发送自己的接收窗口大小
  • 客户端:
    • 接收窗口: 3000 字节
    • 拥塞窗口: 3000 字节
  • 服务器:
    • 接收窗口: 5000 字节
    • 拥塞窗口: 2000 字节
  • 所以, 发送窗口分别是
    • 客户端: min(客户端拥塞窗口, 服务器接收窗口) = min(3000, 5000) = 3000 字节
    • 服务器: min(服务器拥塞窗口, 客户端接收窗口) = min(2000, 3000) = 2000 字节
  • 客户端发送数据时, 将数据切割成多个长度为1000字节的数据段, 会连续发送3个数据段, 共3000字节
  • 服务器发送数据时, 将数据切割成多个长度为1000字节的数据段, 会连续发送2个数据段, 共2000字节

3、慢开始

  • 假设接收方和发送方建立连接后, MSS=100, 接收方的接收窗口是3000

  • 这意味着, 接收方可以一次性接收30个大小是100的数据包

  • 如下图所示,

    • A初始拥塞窗口100字节
    • A发送100字节数据给B
    • B收到数据后, 发送确认报文给A
    • A收到确认报文后, 将拥塞窗口大小翻倍, 调整为200字节
    • A发送200字节数据给B
    • B收到数据后, 发送确认报文给A
    • A收到确认报文后, 将拥塞窗口大小翻倍, 调整为400字节
    • A发送400字节数据给B
    • B收到数据后, 发送确认报文给A
    • A收到确认报文后, 将拥塞窗口大小翻倍, 调整为800字节
    • ...... image.png
  • 像这种, 一开始拥塞窗口很比较小, 收到确认报文后, 翻倍调整拥塞窗口大小的方式, 称之为慢开始

image.png

  • cwnd的初始值比较小, 然后随着数据包被接收方确认(收到一个Ack), cwnd就成倍增长(指数级)

4、拥塞避免

首先要知道几个概念: 慢开始阈值、拥塞避免、乘法减小

  • ssthresh(slow start threshold): 慢开始阈值, cwnd达到阈值后, 以线性方式增加
    • 慢开始阈值的初始大小, 不同系统可能会不同, 也可以人为的设置大小
  • 拥塞避免(加法增大): 拥塞窗口缓慢增大, 以防止网络过早出现拥塞
  • 乘法减小: 只要网络出现拥塞, 把ssthresh减为拥塞峰值的一半, 同时执行慢开始算法(cwnd又恢复到初始值)

image.png

  • 拥塞避免如上图所示
  • 发送方通过慢开始发送数据, 在不断收到接收方发送的Ack确认报文后, 指数级增加cwnd(拥塞窗口)大小
  • 当cwnd达到阈值后, 会从指数级规律增长, 变为加法增大, 以防止网络过早出现拥塞
  • 当遇到网络拥塞后, cwnd会进行乘法减小, 回到慢开始初始时的cwnd, 然后重新进入慢开始阶段

当网络出现频繁拥塞时, ssthresh值就下降的很快

5、快重传

  • 快重传: 当收到3个连续的, 对某一数据段的重复确认后, 立即重传丢失数据段
  • 如下图所示, 发送方共发出M1-M77段数据,其中的M3发送失败, 接收方没有收到
    • 接收方在收到M4后, 发现并不是紧接着M2的数据, 于是发出确认收到M2的报文
    • 接收方在收到M5后, 发现并不是紧接着M2的数据, 于是发出确认收到M2的报文
    • 接收方在收到M6后, 发现并不是紧接着M2的数据, 于是发出确认收到M2的报文
  • 发送方连续收到3次确认M2的报文后, 就会立刻知道M3没有发送到接收方, 立刻重传M3

image.png

4、快恢复

  • 当发送方连续收到三个重复确认, 说明网络出现拥塞
    • 就执行乘法减小算法, 把ssthresh(慢开始阈值)减为拥塞峰值的一半
  • 与慢开始不同之处是, 现在不执行慢开始算法, 即cwnd现在不恢复到初始值
    • 而是把cwnd(拥塞窗口)值设置为新的ssthresh(慢开始阈值)值(减小后的值)
    • 然后开始执行拥塞避免算法("加法增大"), 使拥塞窗口缓慢地线性增大

5、快重传 + 快恢复

  • 快重传 + 快恢复, 如下图所示

image.png

  • 慢开始, 设置初始cwnd(拥塞窗口)
  • 在不断收到Ack确认报文后, cwnd指数级增长
  • cwnd达到初始的ssthresh(慢开始阈值)后, 进入拥塞避免状态, 执行加法增大
  • 发送方收到3个重复的确认报文后, 执行快重传算法, 发送丢失的数据段, 此时已经进入网络拥塞状态
  • 同时, 进入快恢复阶段, 执行乘法减小, 将ssthresh变为当前cwnd(拥塞窗口)的一半
  • 然后再将cwnd变为当前ssthresh的值(减小后), 即cwnd变为网络拥塞时的一半
    • cwnd等于ssthresh, 都是拥塞峰值的一半
  • 然后, 进入拥塞避免阶段, 执行加法增大
  • ......

以上, 就是 快重传 + 快恢复 的整个过程
要想正常的使用网络, 避免网络拥塞状态, 需要整个网络中所有的设备, 路由器, 电脑共同努力

6、发送窗口的最大值

  • 发送窗口的最大值: swnd = min(cwnd, rwnd)
  • rwnd < cwnd 时, 是接收方的接收能力限制发送窗口的最大值
  • cwnd < rwnd 时, 则是网络的拥塞限制发送窗口的最大值

四、序号、确认号

  • 在TCP通信中, TCP数据部分每一个字节, 都有一个编号, 从1开始
  • 序号: 本次发送的数据部分的第1个字节的编号
  • 确认号: 已经收到的数据的长度 + 1

1、原生值和相对值

  • 在浏览器中输入icp.chinaz.com, 并使用Wireshark抓取数据, 筛选出抓取的目标内容, 如下图所示

image.png

  • 点击一条服务器发送给浏览器的数据, 查看序号和确认号

image.png

  • 可以看到序号和确认号各有两条
  • 服务器序号:
    • Sequence number: 1641 (relative sequence number)
    • Sequence number (raw): 1778527074
  • 服务器确认号
    • Acknowledgment number: 675 (relative ack number)
    • Acknowledgment number (raw): 2625252398
  • 其中, 序号的第1条数据和确认号的第1条数据, 是相对值
    • 服务器序号的相对值: 1641
    • 服务器确认号的相对值: 675
  • 其中, 序号的第2条数据和确认号的第2条数据, 是原生值
    • 服务器序号的原生值: 1778527074
    • 服务器确认号的原生值: 2625252398
  • 我们再选择一条服务器发送给浏览器的数据查看

image.png

  • 可以看到序号和确认号各有两条
  • 服务器序号:
    • Sequence number: 4201 (relative sequence number)
    • Sequence number (raw): 1778529634
  • 服务器确认号:
    • Acknowledgment number: 675 (relative ack number)
    • Acknowledgment number (raw): 2625252398
  • 其中, 序号的第1条数据和确认号的第1条数据, 是相对值
    • 服务器序号的相对值: 4201
    • 服务器确认号的相对值: 675
  • 其中, 序号的第2条数据和确认号的第2条数据, 是原生值
    • 服务器序号的原生值: 1778529634
    • 服务器确认号的原生值: 2625252398

那么, 相对值和原生值, 是如何得出的?

2、初始值

  • 序号确认号初始值, 可以通过原生值减去相对值计算得出
    • 初始值 = 原生值 - 相对值
  • 第一条数据
    • 序号原生值 - 序号相对值 = 1778529634 - 1641 = 1778525433
    • 确认号原生值 - 确认号相对值 = 2625252398 - 675 = 2625251723
  • 第二条数据
    • 序号原生值 - 序号相对值 = 1778527074 - 4201 = 1778525433
    • 确认号原生值 - 确认号相对值 = 2625252398 - 675 = 2625251723
  • 上面通过原生值 - 相对值计算出的结果, 就是初始值
    • 服务器-序号初始值 = 1778525433
    • 服务器-确认号初始值 = 2625251723
  • 可以看出, 同一次TCP连接中, 服务器序号初始值确认号初始值是固定的
  • 我们再看看浏览器发给服务器的数据

image.png

  • 可以看到浏览器序号确认号
  • 序号
    • Sequence number: 1 (relative sequence number)
    • Sequence number (raw): 2625251724
  • 确认号
    • Acknowledgment number: 1 (relative ack number)
    • Acknowledgment number (raw): 1778525434
  • 初始值
    • 浏览器-序号初始值: 2625251724 - 1 = 2625251723
    • 浏览器-确认号初始值: 1778525434 - 1 = 1778525433
  • 即:
    • 浏览器确认号初始值, 就是服务器序号初始值
    • 服务器确认号初始值, 就是浏览器序号初始值
  • 序号初始值确认号初始值是在TCP建立连接的三次握手时创建的
  • 点击第一次握手
    • 浏览器的序号初始值 = 2625251723

image.png

  • 点击第二次握手
    • 服务器的序号初始值 = 1778525433

image.png

  • 通过三次握手, 浏览器和服务器都确定了序号初始值确认号初始值

image.png

3、TCP传输过程中, 序号和确认号的变化

  • 下图是浏览器服务器发送HTTP请求的过程模拟图, 浏览器序号初始值s1, 服务器序号初始值s2

image.png

  • ① - ③是三次握手的过程
    • ①: SYN=1, ACK=0, Seq=s1, Ack=0, Len=0
      • 发送序号初始值: s1
      • 确认号: 0
      • 浏览器-序号初始值: s1
    • ②: SYN=1, ACK=1, Seq=s2, Ack=s1+1, Len=0
      • 发送序号初始值: s2
      • 确认号: s1 + 1
      • 服务器-序号初始值: s2
      • 服务器-确认号初始值: s1
    • ③: SYN=0, ACK=1, Seq=s1+1, Ack=s2+1, Len=0
      • 序号 = 初始值 + 1, 已发送0字节数据
      • 确认号 = 初始值 + 1, 已收到a1字节数据
      • 浏览器-序号初始值: s1
      • 浏览器-确认号初始值: s2

image.png

  • 浏览器发送的HTTP请求数据
    • ACK=1, Seq=s1+1, Ack=s2+1, Len=a1
      • 序号 = 初始值 + 1, 已发送0字节数据
      • 确认号 = 初始值 + 1, 已收到0字节数据

image.png

  • ⑤ - ⑧服务器发送的HTTP响应数据
    • ⑤: ACK=1, Seq=s2+1, Ack=s1+a1+1, Len=b1
      • 序号 = 初始值s2 + 1, 已发送0字节数据
      • 确认号 = 初始值s1 + a1 + 1, 已收到a1字节数据
    • ⑥: ACK=1, Seq=s2+b1+1, Ack=s1+a1+1, Len=b2
      • 序号 = 初始值s2 + b1 + 1, 已发送b1字节数据
      • 确认号 = 初始值s1 + a1 + 1, 已收到a1字节数据
    • ⑦: ACK=1, Seq=s2+b1+b2+1, Ack=s1+a1+1, Len=b3
      • 序号 = 初始值s2 + b1 + b2 + 1, 已发送b1+b2字节数据
      • 确认号 = 初始值s1 + a1 + 1, 已收到a1字节数据
    • ⑧: ACK=1, Seq=s2+b1+b2+b3+1, Ack=s1+a1+1, Len=4
      • 序号 = 初始值s2 + b1 + b2 + b3 + 1, 已发送b1+b2+b3字节数据
      • 确认号 = 初始值s1 + a1 + 1, 已收到a1字节数据
    • 整个过程中, 服务器都没有收到浏览器的任何数据, 所以确认号保持不变

image.png

  • 浏览器对于服务器传递数据的确认报文
    • ACK=1, Seq=s1+a1+1, Ack=s2+b1+b2+b3+b4+1, Len=b2
      • 序号 = 初始值s1 + a1 + 1, 已收到a1字节数据
      • 确认号 = 初始值s2 + b1 + b2 + b3 + b4 + 1, 已发送b1+b2+b3+b4字节数据

image.png

  • 可以通过Wireshark查看抓取的数据, 观察浏览器服务器之间通信中的序号和确认号

image.png

五、建立连接

1、三次握手

  • TCP是通过"三次握手"建立连接的

image.png

  • SYN只有在请求连接连接请求确认时等于1
    • SYN=1, ACK=0: 请求连接
    • SYN=1, ACK=1: 连接请求确认
  • 一开始, 客户端处于关闭状态, 服务器处于监听状态
  • 第一次握手:
    • 客户端服务器发出请求连接(SYN=0, ACK=1)
    • 同时客户端进入同步已发送(SYN-SENT)状态
  • 第二次握手:
    • 服务器收到客户端发来的请求连接(SYN=0, ACK=1)
    • 服务器同意请求连接, 并发出连接请求确认(SYN=1, ACK=1)
    • 同时服务器进入同意已接收(SYN-RCVD)状态
  • 第三次握手:
    • 客户端收到服务器发送来的连接请求确认(SYN=1, ACK=1)
    • 客户端发出确认(SYN=0, ACK=1)
    • 同时客户端进入连接已经建立(ESTABLISHED)状态
    • 服务器收到客户端发来的确认(SYN=1, ACK=1), 也进入连接已经建立(ESTABLISHED)状态
  • 上诉的几种状态:
    • CLOSED: 客户端处于关闭状态
    • LISTEN: 服务器处于监听状态,等待客户端连接
    • SYN-RCVD: 表示服务器接受到了SYN报文,当收到客户端ACK报文后,它会进入到ESTABLISHED状态
    • SYN-SENT: 表示client已发送SYN报文,等待server的第2次握手
    • ESTABLISHED: 表示连接已经建立

2、前两次握手的特点

  • SYN都等于1
    • SYN=1, ACK=0: 请求连接
    • SYN=1, ACK=1: 确认请求连接
  • 数据部分长度都为0
  • TCP首部的长度大于20, 包含可选项
    • 固定长度: 20
    • 可选长度: 20-60
  • 双方会交换确认一些信息
    • MSS: 每个数据段的最大长度
    • WS: 窗口缩放系数
    • SACK: 会否支持SACK image.png

3、窗口缩放系数

  • 在TCP交互过程中, 会传递接收窗口的大小, 而传递的窗口数据, 并不是实际的大小

image.png

  • 如上图所示, 客户端发送给服务器的窗口值为4096, 而实际的窗口大小事262144
  • 实际的窗口大小是通过窗口值 * 窗口缩放系数得出的, 即: 实际窗口 = 窗口值 * 窗口缩放系数
  • 而窗口缩放系数, 是在三次握手时确定的, 且客户端和服务器的窗口缩放系数可能不同
  • 第一次握手, 客户端发送了自己的窗口缩放系数

image.png

  • 第二次握手, 服务器发送了自己的窗口缩放系数

image.png

  • 真实的窗口缩放系数 = 2 ^ 窗口缩放系数值
    • 客户端的窗口缩放系数 = 2 ^ 6 = 64
    • 服务器的窗口缩放系数 = 2 ^ 7 = 128

4、疑问

为什么简历连接的时候需要3次握手, 2次不行么?

  • 2次握手建立连接, 模拟图如下所示, 当服务器接收到连接请求后, 直接进入连接已经建立状态

image.png

  • 假设客户端发出的第一个请求连接, 因为网络异常, 在连接释放以后的某个时间到达服务器
  • 本来是一个早已失效的请求连接, 但服务器收到此失效的请求后, 误认为是客户端再次发出的一个新的连接请求
  • 于是, 服务器就向客户端发出确认请求连接报文, 同意建立连接, 直接进入连接已经建立(ESTABLISHED)状态
  • 如果不采用3次握手, 那么只要服务器发出确认报文, 新的连接就建立了
  • 由于现在客户端并没有真正想连接服务器的意愿,因此不会理睬服务器的确认,也不会向服务器发送数据
  • 服务器却以为新的连接已经建立,并一直等待客户端发来数据,这样,服务器的很多资源就白白浪费掉了

image.png

  • 采用3次握手的办法可以防止上述现象发生
  • 例如上述情况,客户端没有向服务器的确认发出确认,服务器由于收不到确认,就知道客户端并没有要求建立连接

image.png

5、几种异常

  • 第一种: 请求连接无法送达服务器
    • 客户端发送请求连接报文后, 如果请求连接没有到达服务器, 那么服务器就不会发出请求报文确认
    • 客户端在等待一段时间后, 都没有收到服务器发送的请求报文确认, 就会重新发送请求连接
    • 如果多次重发请求连接后, 依然没有收到服务器发送的请求报文确认, 就会关闭连接
    • 具体重发多少次, 不同系统有不同的设置

image.png

  • 第二种: 请求连接到达服务器, 服务器发出连接请求确认, 但是连接请求确认无法到达浏览器
    • 客户端发送请求连接报文后, 请求连接到达服务器
    • 服务器发送的连接请求确认无法到达客户端
    • 客户端在等待一段时间后, 都没有收到服务器发送的请求报文确认, 就会重新发送请求连接
    • 如果多次重发请求连接后, 依然没有收到服务器发送的请求报文确认, 就会关闭连接
    • 具体重发多少次, 不同系统有不同的设置

image.png

  • 第三种: 请求连接到达服务器, 服务器发出连接请求确认, 连接请求确认到达浏览器, 浏览器发出确认报文, 但是服务器收不到确认报文
    • 客户端发送请求连接报文后, 请求连接到达服务器
    • 服务器发出连接请求确认报文后, 连接请求确认到达浏览器
    • 客户端发送确认报文后, 确认报文没有到达服务器
    • 服务器在等待一段时间后, 都没有收到浏览器发送的确认报文, 就会重新发送连接请求确认
    • 如果多次重发连接请求确认后, 依然没有收到浏览器发送的确认报文, 就会关闭连接
    • 当服务器关闭连接后, 客户端已经进入连接已经建立状态, 就会开始发送数据, 但是浏览器不在返回任何信息
    • 浏览器重复发送数据后, 依然得不到服务器的回应, 就会关闭连接
    • 具体重发多少次, 不同系统有不同的设置

image.png

  • 第四种:请求连接到达服务器, 服务器发出连接请求确认, 但是连接请求确认无法到达浏览器, 浏览器再次发出的请求连接也无法到达服务器
    • 客户端发送请求连接报文后, 请求连接到达服务器
    • 服务器发送的连接请求确认无法到达客户端
    • 客户端在等待一段时间后, 都没有收到服务器发送的请求报文确认, 就会重新发送请求连接, 结果这次客户端发送的请求连接没有到达服务器
    • 服务器发出连接请求确认后, 没有收到客户端发送的确认报文, 会再次发送请求报文确认, 结果依然没有送达客户端
    • 从此, 双方都再未收到对方的信息, 等超过一定时间, 双方都会关闭连接

image.png

  • 服务器多次发送连接请求确认报文后, 没有收到浏览器确认报文, 就会断开连接, 同时发送RST=1包, 强制关闭连接

image.png

六、释放连接

1、四次挥手

  • 客户端和服务器之间的连接, 需要经过四次挥手, 才会断开

image.png

  • 客户端发送完所有数据后, 就会发送释放连接报文, 同时进入终止等待1状态
  • 服务器收到客户端发送过来的释放连接报文后, 会发送确认报文, 同时进入关闭等待状态
  • 客户端收到释放连接-确认报文后, 就会进入终止等待2状态
  • 服务器发送完所有数据后, 就会发送释放连接报文, 同时进入最后确认状态
  • 客户端收到释放连接报文后, 就会发送确认报文, 同时进入时间等待状态
  • 服务器收到释放连接-确认报文后, 就会关闭连接
  • 客户端过了时间等待阶段后, 就会关闭连接

2、状态解读

  • FIN-WAIT-1: 表示想主动关闭连接
    • 向对方发出FIN=1报文, 是进入FIN-WAIT-1状态
  • CLOSE-WAIT: 表示等待关闭
    • 当对方发送FIN=1给自己, 自己会回应一个ACK报文给对方, 此时则进入CLOSE-WAIT状态
      • 回应的ACK报文, 确认号FIN报文的序号 + 1, 尽管FIN报文中没有传递任何数据
      • 例如: FIN报文中序号u, 那么ACK报文中, 确认号就是u + 1
    • 在此状态下, 需要考虑自己是否还有数据要发给对方, 如果没有, 发送FIN报文给对方
  • FIN-WAIT-2: 还要对方发送ACK确认后, 主动方就会处于FIN-WAIT-2状态, 然后等待对方发送FIN报文
  • LAST-ACK: 被动关闭一方在发送FIN报文后, 最后等待对方的ACK报文
    • 当收到ACK报文后, 立即进入CLOSE状态
  • TIME-WAIT: 表示收到对方发送的FIN报文后, 并发送了ACK报文, 就等2MSL后即可进入CLOSE状态
    • MSL: 报文在网络传递中的时间限制, 如果超过MSL时间, 报文就会失效
  • CLOSE: 关闭状态

除了上诉几种状态之外, 还有一种罕见的例外状态

  • CLOSING:
    • 主动方发送出FIN报文后, 进入了FIN-WAIT-1状态, 等待对方发送的ACK报文
    • 但是主动方并没有收到对方的ACK报文, 反而收到了对方的FIN报文
  • 如果双方几乎在同时准备关闭连接的话, 那么就出现了双方同时送FIN报文的情况, 也即会出现CLOSING状态, 表示双方都正在关闭连接

image.png

  • 如果主动方FIN-WAIT-1状态中, 收到了对方带有FINACK的报文, 那么就会跳过FIN-WAIT2状态, 直接进入TIME-WAIT状态

image.png

TCP/IP协议栈, 在设计上, 允许任何一方先发起断开请求, 这里演示的是客户端主动要求断开

3、细节

  • 客户端发送ACK后, 需要有个TIME-WAIT阶段, 等待一段时间后, 再真正关闭连接
    • 一般是等待2倍的MSL(Maximum Segment Lifetime, 最大分段生存期)
      • MSL是TCP报文在Internet上的最长生存时间
      • 每个具体的TCP实现, 都必须选择一个确定的MSL值, RFC 1122建议是2分钟
      • 可以防止本次连接中产生的数据包, 误传到下一次连接中(因为本次连接中的数据包, 都会在2MSL时间内消失)
(1) 第一种情况: 确认报文延迟到达
  • 如下图所示, 当客户端发出释放连接-确认报文后, 直接关闭连接, 直接在原端口上建立新的连接, 而释放连接-确认由于网络原因, 传输的非常慢
  • 当新的连接简历后, 释放连接-确认才发送到服务器, 那么服务器就会直接断开新的连接

image.png

(2)第二种情况: 服务器释放连接确认未送到
  • 如下图所示, 当客户端发出服务器释放连接-确认, 直接关闭连接
  • 结果, 服务器释放连接-确认未送到, 服务器又重新发送了释放连接报文
  • 此时, 客户端不会再响应, 会造成服务器的资源浪费

image.png

为了防止上诉类似的这种现象, 所以需要等待2MSL的时间后, 客户端再断开
此时, 本次TCP连接在网络中的信号已经全部消失, 再关闭连接, 就不会出现这种情况

  • TCP从建立到释放的流程, 如下图所示

TCP完整流程02.png

4、疑问: 为什么四次挥手

  • TCP是全双工模式, 既连接双方可以同时收发数据
  • 第1次挥手: 当主机1发出FIN报文段时
    • 表示主机1告诉主机2, 主机1已经没有数据要发送了, 但是, 此时主机1还是可以接收主机2发送的数据
  • 第2次挥手: 当主机2返回ACK报文时 * 表示主机2已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1
  • 第3次挥手:当主机2也发送了FIN报文段时
    • 表示主机2告诉主机1主机2已经没有数据要发送了
  • 第4次挥手:当主机1返回ACK报文段时
    • 表示主机1已经知道主机2没有数据发送了。随后正式断开整个TCP连接

image.png

5、三向握手

image.png

  • 服务器接收到客户端FIN时, 如果服务器后面也没有数据发送给客户端`
  • 这时, 服务器就可以将第23次握手合并, 同时告诉`客户端``两件事
    • 已经知道客户端没有数据要发
    • 服务器没有数据要发了

image.png