TCP的要点包括: 可靠传输、流量控制、拥塞控制、连接管理(建立连接、释放连接)
一、可靠传输
1、停止等待ARQ协议
- ARQ(Automatic Repeat-reQuest), 自动重传请求
- 如上图(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
, 就会认为连接出问题, 会主动断开
连接
- 如上图(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
报文
2、连续ARQ协议 + 滑动窗口协议
- 上图(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个字节, 代表一个数据段的数据
- 每一组给一个编号
- 在TCP通信过程中, 如果发送序列中间某个数据包丢失(比如1、2、
3
、4、5中的3
丢失了) - TCP会通过重传最后确认的分组后序的分组(最后确认的是2, 会重传
3
、4、5) - 这样, 原先赢正确传输的分组也可能重复传送(比如4、5)
- 为改善上述情况, 发展出了SACK (Selective acknowledgment, 选择性确认)技术
- 告诉发送方那些数据丢失, 哪些数据已经提前收到
- 使TCP只重新发送丢失的包(比如
3
), 不用发送后序所有的分组(比如4、5)
- TCP首部的长度是20~60个字节, 其中固定长度20字节, 可选长度40字节
- SACK信息会放在TCP首部的选项部分
- Kind: 占
1
字节, 值为5
代表这是SACK选项 - Length: 占
1
字节, 表明SCAK选项共占多少个字节 - Left Edge: 占
4
字节, 左边界 - Right Edge: 占
4
字节, 右边界
- Kind: 占
- 假设发送窗口, 一次性发送了10组共1000个字节的数据, 其中3、5、7、9四组没有送到
- 那么接收端会发送确认TCP数据包, 其中TCP首部的可选部分会说明哪些数据已经收到
- 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
个字节
- SACK选项最多携带
- TCP数据传递, 大致如下图所示:
4、思考一个问题
- 为什么选择在传输层就将数据 "大卸八块" 分成多段, 而不是等到网络层再分片传递给数据链路层
- 如下图所示, 传输层的数据共
600
字节, 传递到网络层分层6
段, 每段100
字节, 然后在向下传递, 最后发送
- 需要明确的是: 可靠传输是在传输层进行控制的, 网络层, 数据链路层, 物理层并没有可靠传输控制
- 所以, 当
1-6
段数据都发送后, 如果其中的第3
段数据丢失, 没有被接收到
- 此时, 接收端会返回确认数据
ACK=1, Seq=1, Ack=201
SACK
:L1=301, R1=401
L2=401, R2=501
L3=501, R3=601
- 发送端收到确认数据后, 就知道其中的
201-300
未发送成功 - 于是传输层又将
600
个字节发送给网络层, 网络层分层6
段后, 全部向下发送, 其中的1-200
和301-600
字节也全部重新发送了一次
- 因为整个传输层的数据数据全部重传, 就浪费了很多资源, 降低了重传的性能
- 最好的办法就是只传递
3
段201-300
字节 - 而
重传控制
是在传输层
, 所以就要在传输层
分段
- 总结:
- 如果在传输层不分段, 一旦出现数据丢失, 整个传输层的数据都得重传
- 如果在传输层分了段, 一旦出现数据丢失, 只需要传送丢失的那段即可
二、流量控制
- 在网络通信中, 如果接收方的缓存区满了, 发送方还在疯狂的发送数据
- 接收方只能把收到的数据包丢掉, 大量的丢包会极大的浪费网络资源
- 所以要进行流量控制
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
抓取数据, 查看接收窗口大小
2、一种特殊情况
- 有一种特殊情况
- 一开始, 接收方给发送方发送了0窗口的报文段
- 后面, 接收方又有了一些存储空间, 给发送方发送的非0窗口的报文段丢失了
- 发送方的发送窗口一直为0, 双方陷入僵局
- 解决方案
- 当发送方接收到0窗口通知时, 这时发送方停止发送报文
- 并且同时开启一个定时器, 隔一段时间就会发个测试报文去询问接收方最新的窗口大小
- 如果接收的窗口大小还是为0, 则发送方再次刷新启动定时器
三、拥塞控制
1、拥塞现象
- 如下图所示, 是一个网络的模拟图
R1-R3
的链路吞吐量是700M/s
R2-R3
的链路吞吐量是600M/s
R3-R4
的链路吞吐量是1000M/s
600 + 700 = 1300 > 1000
, 所以当R1
和R2
传输的数据量过大时, 在R3
处必然出现过载,R3
就会丢掉过载的数据- 并且,
R3-R4
的链路吞吐量, 理论上是1000M/s
, 但实际上并不会达到1000M/s
就会出现拥塞现象
这就是同一个局域网内同一时间上网的用户过多时, 网速就会下降的原因
为了解决链路上出现拥塞现象, 就需要进行拥塞控制
- 拥塞控制
- 防止过多的数据注入到网络中
- 避免网络中的路由器或链路过载
相比而言, 流量控制是点对点通信的控制
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)
- 即 发送窗口的大小 = 拥塞窗口的大小 和 接收窗口的大小之中, 值最小的那一个
- MSS (Maximum Segment Size): 每个段最大的数据部分大小
(1) MSS
MSS的值, 是在TCP三次握手建立连接时确定的
- TCP数据包, 在传输过程中会切割成多个段, 然后发送
- 由数据链路层限制, 网络层传递到数据链路层的数据, 最大是
1500
个字节 - 网络层
IP首部
大小是20-60
字节,固定部分20字节, 可选部分40字节
, 所以传输层传递到网络层的数据最大是1480
个字节 - 传输层
TCP首部
大小是20-60
字节,固定部分20字节, 可选部分40字节
, 所以传输层数据部分最大是1460
个字节
- 在浏览器中输入
icp.chinaz.com
, 然后使用Wireshark
抓取数据 - 可以看到如下图中的三次握手过程
- 点击浏览器请求建立连接这一条
SYN=1, ACK=0
- 可以看到,
TCP首部
长度不是20
字字节, 而是44
字节, 说明有TCP首部
中有可选项部分
- 点击可选项, 就可以看到
MMS
的值, 这是浏览器
发送给服务器
的MSS
- 点击浏览器同意建立连接的这一条
SYN=1, ACK=1
- 可以看到,
TCP首部
长度不是20
字字节, 而是32
字节, 说明有TCP首部
中有可选项部分
- 点击可选项, 就可以看到
MSS
的值, 这是服务器
发送给浏览器
的MSS
- 通过这两次通信, 就确定了
MMS
的具体值是1412
, 取两个MSS
中的最小值
(2) 拥塞窗口、接收窗口、发送窗口
- 如下图所示,
客户端
和服务器
进行数据通信- 假设双方的
发送窗口
和拥塞窗口
大小一直不变
- 假设双方的
- 在通信过程中,
客户端
和服务器
都会发送自己的接收窗口
大小 - 客户端:
- 接收窗口:
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
字节- ......
-
像这种, 一开始
拥塞窗口
很比较小, 收到确认报文
后,翻倍调整拥塞窗口大小
的方式, 称之为慢开始
- cwnd的初始值比较小, 然后随着数据包被接收方确认(收到一个Ack), cwnd就成倍增长(指数级)
4、拥塞避免
首先要知道几个概念: 慢开始阈值、拥塞避免、乘法减小
- ssthresh(slow start threshold): 慢开始阈值, cwnd达到阈值后, 以线性方式增加
- 慢开始阈值的初始大小, 不同系统可能会不同, 也可以人为的设置大小
- 拥塞避免(加法增大): 拥塞窗口缓慢增大, 以防止网络过早出现拥塞
- 乘法减小: 只要网络出现拥塞, 把ssthresh减为拥塞峰值的一半, 同时执行慢开始算法(cwnd又恢复到初始值)
- 拥塞避免如上图所示
发送方
通过慢开始
发送数据, 在不断收到接收方
发送的Ack
确认报文后, 指数级增加cwnd(拥塞窗口)大小- 当cwnd达到阈值后, 会从
指数级规律增长
, 变为加法增大
, 以防止网络过早出现拥塞 - 当遇到网络拥塞后,
cwnd
会进行乘法减小
, 回到慢开始初始时的cwnd
, 然后重新进入慢开始阶段
当网络出现频繁拥塞时, ssthresh值就下降的很快
5、快重传
- 快重传: 当收到
3
个连续的, 对某一数据段的重复确认后, 立即重传丢失数据段 - 如下图所示, 发送方共发出
M1-M7
共7
段数据,其中的M3
发送失败, 接收方没有收到接收方
在收到M4
后, 发现并不是紧接着M2
的数据, 于是发出确认收到M2
的报文接收方
在收到M5
后, 发现并不是紧接着M2
的数据, 于是发出确认收到M2
的报文接收方
在收到M6
后, 发现并不是紧接着M2
的数据, 于是发出确认收到M2
的报文
- 在
发送方
连续收到3
次确认M2
的报文后, 就会立刻知道M3
没有发送到接收方
, 立刻重传M3
4、快恢复
- 当发送方连续收到三个重复确认, 说明网络出现拥塞
- 就执行
乘法减小
算法, 把ssthresh(慢开始阈值)
减为拥塞峰值的一半
- 就执行
- 与慢开始不同之处是, 现在不执行慢开始算法, 即cwnd现在不恢复到初始值
- 而是把
cwnd(拥塞窗口)
值设置为新的ssthresh(慢开始阈值)
值(减小后的值) - 然后开始执行拥塞避免算法("加法增大"), 使拥塞窗口缓慢地线性增大
- 而是把
5、快重传 + 快恢复
- 快重传 + 快恢复, 如下图所示
- 慢开始, 设置初始
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
抓取数据, 筛选出抓取的目标内容, 如下图所示
- 点击一条
服务器
发送给浏览器
的数据, 查看序号和确认号
- 可以看到序号和确认号各有两条
服务器
序号:- 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
- 我们再选择一条
服务器
发送给浏览器
的数据查看
- 可以看到序号和确认号各有两条
服务器序号
:- 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连接中,
服务器
的序号初始值
和确认号初始值
是固定的 - 我们再看看
浏览器
发给服务器
的数据
- 可以看到
浏览器
的序号
和确认号
- 序号
- 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
- 点击第二次握手
服务器的序号初始值 = 1778525433
- 通过三次握手, 浏览器和服务器都确定了
序号初始值
和确认号初始值
3、TCP传输过程中, 序号和确认号的变化
- 下图是
浏览器
向服务器
发送HTTP
请求的过程模拟图,浏览器
的序号初始值
是s1
,服务器
的序号初始值
是s2
① - ③
是三次握手的过程①: 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
- 序号 = 初始值 + 1, 已发送
④
是浏览器
发送的HTTP
请求数据ACK=1, Seq=s1+1, Ack=s2+1, Len=a1
- 序号 = 初始值 + 1, 已发送
0
字节数据 - 确认号 = 初始值 + 1, 已收到
0
字节数据
- 序号 = 初始值 + 1, 已发送
⑤ - ⑧
是服务器
发送的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
字节数据
- 序号 =
- 整个过程中,
服务器
都没有收到浏览器
的任何数据, 所以确认号
保持不变
⑨
是浏览器
对于服务器
传递数据的确认报文
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
字节数据
- 序号 =
- 可以通过
Wireshark
查看抓取的数据, 观察浏览器
和服务器
之间通信中的序号和确认号
五、建立连接
1、三次握手
- TCP是通过"三次握手"建立连接的
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
3、窗口缩放系数
- 在TCP交互过程中, 会传递接收窗口的大小, 而传递的窗口数据, 并不是实际的大小
- 如上图所示, 客户端发送给服务器的窗口值为
4096
, 而实际的窗口大小事262144
实际的窗口大小
是通过窗口值 * 窗口缩放系数
得出的, 即:实际窗口 = 窗口值 * 窗口缩放系数
- 而窗口缩放系数, 是在三次握手时确定的, 且客户端和服务器的窗口缩放系数可能不同
- 第一次握手, 客户端发送了自己的窗口缩放系数
- 第二次握手, 服务器发送了自己的窗口缩放系数
- 真实的窗口缩放系数 = 2 ^ 窗口缩放系数值
- 客户端的窗口缩放系数 =
2 ^ 6
=64
- 服务器的窗口缩放系数 =
2 ^ 7
=128
- 客户端的窗口缩放系数 =
4、疑问
为什么简历连接的时候需要
3
次握手,2
次不行么?
2
次握手建立连接, 模拟图如下所示, 当服务器
接收到连接请求
后, 直接进入连接已经建立
状态
- 假设
客户端
发出的第一个请求连接
, 因为网络异常, 在连接释放以后的某个时间到达服务器
- 本来是一个早已失效的
请求连接
, 但服务器
收到此失效的请求后, 误认为是客户端
再次发出的一个新的连接请求 - 于是,
服务器
就向客户端
发出确认请求连接
报文, 同意建立连接, 直接进入连接已经建立(ESTABLISHED)
状态 - 如果不采用
3次握手
, 那么只要服务器
发出确认报文, 新的连接就建立了 - 由于现在
客户端
并没有真正想连接服务器
的意愿,因此不会理睬服务器
的确认,也不会向服务器
发送数据 - 但
服务器
却以为新的连接已经建立,并一直等待客户端
发来数据,这样,服务器
的很多资源就白白浪费掉了
- 采用
3次握手
的办法可以防止上述现象发生 - 例如上述情况,
客户端
没有向服务器
的确认发出确认,服务器
由于收不到确认,就知道客户端
并没有要求建立连接
5、几种异常
- 第一种:
请求连接
无法送达服务器
- 当
客户端
发送请求连接
报文后, 如果请求连接
没有到达服务器
, 那么服务器
就不会发出请求报文确认
客户端
在等待一段时间后, 都没有收到服务器
发送的请求报文确认
, 就会重新发送请求连接
- 如果多次重发
请求连接
后, 依然没有收到服务器
发送的请求报文确认
, 就会关闭连接 - 具体重发多少次, 不同系统有不同的设置
- 当
- 第二种:
请求连接
到达服务器
,服务器
发出连接请求确认
, 但是连接请求确认
无法到达浏览器
- 当
客户端
发送请求连接
报文后,请求连接
到达服务器
服务器
发送的连接请求确认
无法到达客户端客户端
在等待一段时间后, 都没有收到服务器
发送的请求报文确认
, 就会重新发送请求连接
- 如果多次重发
请求连接
后, 依然没有收到服务器
发送的请求报文确认
, 就会关闭连接 - 具体重发多少次, 不同系统有不同的设置
- 当
- 第三种:
请求连接
到达服务器
,服务器
发出连接请求确认
,连接请求确认
到达浏览器
,浏览器
发出确认
报文, 但是服务器收不到确认
报文- 当
客户端
发送请求连接
报文后,请求连接
到达服务器
服务器
发出连接请求确认
报文后,连接请求确认
到达浏览器
客户端
发送确认
报文后,确认
报文没有到达服务器
服务器
在等待一段时间后, 都没有收到浏览器
发送的确认
报文, 就会重新发送连接请求确认
- 如果多次重发
连接请求确认
后, 依然没有收到浏览器
发送的确认
报文, 就会关闭连接 - 当服务器关闭连接后, 客户端已经进入
连接已经建立
状态, 就会开始发送数据
, 但是浏览器
不在返回任何信息 - 当
浏览器
重复发送数据后, 依然得不到服务器
的回应, 就会关闭连接 - 具体重发多少次, 不同系统有不同的设置
- 当
- 第四种:
请求连接
到达服务器
,服务器
发出连接请求确认
, 但是连接请求确认
无法到达浏览器
,浏览器
再次发出的请求连接
也无法到达服务器
- 当
客户端
发送请求连接
报文后,请求连接
到达服务器
服务器
发送的连接请求确认
无法到达客户端客户端
在等待一段时间后, 都没有收到服务器
发送的请求报文确认
, 就会重新发送请求连接
, 结果这次客户端
发送的请求连接
没有到达服务器
服务器
发出连接请求确认
后, 没有收到客户端
发送的确认
报文, 会再次发送请求报文确认
, 结果依然没有送达客户端
- 从此, 双方都再未收到对方的信息, 等超过一定时间, 双方都会关闭连接
- 当
- 在
服务器
多次发送连接请求确认
报文后, 没有收到浏览器
的确认
报文, 就会断开连接, 同时发送RST=1
包, 强制关闭连接
六、释放连接
1、四次挥手
- 客户端和服务器之间的连接, 需要经过
四次挥手
, 才会断开
客户端
发送完所有数据后, 就会发送释放连接
报文, 同时进入终止等待1
状态服务器
收到客户端
发送过来的释放连接
报文后, 会发送确认
报文, 同时进入关闭等待
状态客户端
收到释放连接-确认
报文后, 就会进入终止等待2
状态服务器
发送完所有数据后, 就会发送释放连接
报文, 同时进入最后确认
状态客户端
收到释放连接
报文后, 就会发送确认
报文, 同时进入时间等待
状态服务器
收到释放连接-确认
报文后, 就会关闭连接客户端
过了时间等待
阶段后, 就会关闭连接
2、状态解读
FIN-WAIT-1
: 表示想主动关闭连接- 向对方发出FIN=1报文, 是进入
FIN-WAIT-1
状态
- 向对方发出FIN=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
状态, 表示双方都正在关闭连接
- 如果
主动方
在FIN-WAIT-1
状态中, 收到了对方带有FIN
和ACK
的报文, 那么就会跳过FIN-WAIT2
状态, 直接进入TIME-WAIT
状态
TCP/IP协议栈, 在设计上, 允许任何一方先发起断开请求, 这里演示的是
客户端
主动要求断开
3、细节
客户端
发送ACK后, 需要有个TIME-WAIT
阶段, 等待一段时间后, 再真正关闭连接- 一般是等待
2
倍的MSL
(Maximum Segment Lifetime, 最大分段生存期)MSL
是TCP报文在Internet
上的最长生存时间- 每个具体的
TCP
实现, 都必须选择一个确定的MSL
值, RFC 1122建议是2分钟 - 可以防止本次连接中产生的数据包, 误传到下一次连接中(因为本次连接中的数据包, 都会在
2MSL
时间内消失)
- 一般是等待
(1) 第一种情况: 确认报文延迟到达
- 如下图所示, 当
客户端
发出释放连接-确认
报文后, 直接关闭连接, 直接在原端口上建立新的连接, 而释放连接-确认
由于网络原因, 传输的非常慢 - 当新的连接简历后,
释放连接-确认
才发送到服务器
, 那么服务器
就会直接断开新的连接
(2)第二种情况: 服务器释放连接确认未送到
- 如下图所示, 当客户端发出
服务器
的释放连接-确认
, 直接关闭连接 - 结果,
服务器
的释放连接-确认
未送到, 服务器又重新发送了释放连接
报文 - 此时,
客户端
不会再响应, 会造成服务器
的资源浪费
为了防止上诉类似的这种现象, 所以需要等待
2MSL
的时间后, 客户端再断开
此时, 本次TCP连接在网络中的信号已经全部消失, 再关闭连接, 就不会出现这种情况
- TCP从建立到释放的流程, 如下图所示
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连接
- 表示
5、三向握手
- 当
服务器
接收到客户端
的FIN
时, 如果服务器
后面也没有数据发送给客户端` - 这时,
服务器
就可以将第2
、3
次握手合并, 同时告诉`客户端``两件事- 已经知道
客户端
没有数据要发 服务器
没有数据要发了
- 已经知道