HarmonyOS-ArkUI Web控件基础铺垫1-HTTP协议-数据包内容
HarmonyOS-ArkUI Web控件基础铺垫2-DNS解析
HarmonyOS-ArkUI Web控件基础铺垫3--TCP协议- 从规则本质到三次握手-CSDN博客
接上文,上文我们讲解了:
- 数据在四层网络岑层架构中流转方式
- 传输层存在的意义
- TCP协议具备的能力分析
- TCP数据包部分字段设计由来
- TCP建立连接的三次握手。
本文续上文讲。将TCP是如何断开连接的。
TCP的全双工
TCP连接是全双工的。双工的意思就是,同时可以进行信号的双向传输。也就是 A->B, 与 B->A可以同时发生。
我们在上文中其实提到过,TCP建立连接之前,会让操作系统为在内核区划分一些缓存。等到真正的建连之后,又会新建自己专门处理TCP事宜的缓冲区。这些缓存如果注意的话,您会发现均有一个,
- 发送缓冲区。
- 接收缓冲区。
开了俩缓冲区,就是为了更好的实现全双工!如图:
TCP协议,全生命周期中,都是按照全双工来处理的。包括三次握手这么早的操作也是全双工。 所以我们才会那么强调 发送包与响应包要--对得上,因为可能会收到"野包"!所以三次握手的时候,便强调了seq字段和ack字段的搭配规则,因为要用他们排除野包。
TCP断联,最主要断的是什么
TCP断开连接其实就是AB双方比较安全的不再对数据包进行处理的过程。确定了可以不再处理之后便可以释放自己本地的资源。我们知道了全双工这种支持,无论是A还是B,其代码运行时中必然会包含:
- 发送数据处理逻辑,以及运行时发送相关占用的资源,如内核中的发送缓冲区。
- 接收数据处理逻辑,以及运行时接收相关占用的资源,如内核中的接收缓冲区。
如下图所示,A和B如果他俩想断联,回收掉自己占用的资源,则必须打断二者通路:
而TCP中的断联,就是将这套全双工通路断联,也就是两个方向的传输全部断联!
断掉一个方向上的传输是由哪一方决定的?
答: 发送方!
断掉一个方向上的传输发起者,无非是发送方,和接收方。
- 假设发送方是发起者
- 发送方最了解自己的数据,什么时候结尾它最清楚!发完了就断联,也顺理成章。
- 假设接收方是发起者
- 接收方就是个被动接收者,着实不知道对方传来的数据是什么情况。 传没传完不知道,提前断联数据会丢失。 断联的太晚了,发送方数据早传输完了它还不断联,自己的资源被占用啥活也干不成。
综上,发起断联者必定是,发送方!
而 A 和 B这套系统里,因为是全双工工作, 他俩是互为发送方的!
仅断掉一个方向上的传输,怎么做?
那就是发送方向接收方发送一个数据, 这个数据里的内容可以让对方检测出,自己想要断掉这条连路,以后不会传输业务数据了! 解决办法就是用 FIN 字段标记:
FIN为finish的缩写,表示发送方已经完成数据传输,并请求关闭连接。占用1个Bit位。其核心作用便是通知对端,本方向的数据传输已经结束了。
如图所示为单方向断掉连接发生的细节, 也是TCP四次挥手中,前两次挥手的细节!尤其要注意一下最左侧的红色备注,这个关系到对各个字段设置的理解:
上图中值得注意的是,第一次的挥手包, 我们的ACK=0, ack序列也是无效的。但是之后服务端处理的时候,它的ACK倒是1, ack也是正常的。 这是为什么呢??如果您之前了解四次挥手,会晓得第三次挥手,服务端发过来的包ACK = 1 !如下图为四次挥手的全部字段变化:
挥手 |
方向 |
SYN |
ACK |
FIN |
seq |
ack |
第一次挥手 |
A -> B |
0 |
0 |
1 |
a |
- |
第二次挥手 |
B -> A |
0 |
1 |
0 |
b |
a+1 |
第三次挥手 |
B -> A |
0 |
1 |
1 |
b+ n(半关闭数据长度) |
a+1 |
第四次挥手 |
A -> B |
0 |
1 |
0 |
a + 1 |
b+ n+ 1 |
因为断开联接是一个事务!不可被打断,不可被颠倒!
A第一次挥手的时候ACK=0, 既代表着A以后都不打算发送业务数据包了,这个包就是一个控制包,还是自己主动发起的!并且以此为TCP断开连接的起点。之后不容打断。。也就是假设A已经关闭了 A -> B的通路时候,万一收到一个包,这个包的格式正是服务端发起的第一次挥手包, 就直接被扔掉不予处理。避免混乱!
TCP全双工断联--四次挥手
到目前为止我们已经处理完一半了,如果具象化的话,现在A 和 B之间应是这样的场景!
也就是此时其实B仍然可以向A发数据的!可能有信息还没有传完,需要继续传输。 也可能也没什么数据好传了。但任务总有完成的那一刻。完工后便要发起 B -> A 方向的断联了!
接下来是第三次挥手和第四次挥手的流程:
四次挥手总结:
我们上文着重讲了下原理。下方为四次挥手的流程归纳:
第一次挥手(Client → Server)
字段值:SYN=0, ACK=0, FIN=1, seq=u, ack=v(无效)
- seq=u:u为客户端最后发送数据的下一字节序号(例:最后数据字节序号为100,则u=101)
- ACK=0:因主动发起关闭,无需确认对方数据
- FIN=1:触发关闭流程,占用1序列号(下次seq=u+1)
- 状态变化:Client →
FIN_WAIT_1
2. 第二次挥手(Server → Client)
字段值:SYN=0, ACK=1, FIN=0, seq=v, ack=u+1
- ack=u+1:确认客户端的FIN(FIN占1序列号,故+1)
- seq=v:v为服务器当前数据流位置(与客户端FIN无关)
- FIN=0:仅确认关闭请求,不立即关闭本方连接
- 状态变化:Server →
CLOSE_WAIT
, Client →FIN_WAIT_2
3. 第三次挥手(Server → Client)
字段值:SYN=0, ACK=1, FIN=1, seq=w, ack=u+1
- seq=w:w = v + 半关闭期间数据长度(例:第二次挥手后发送80字节数据,则w=v+80)
- ack=u+1:仍为首次挥手的FIN确认(客户端无新数据)
- FIN=1:服务端数据发送完毕,关闭本方通道
- 状态变化:Server →
LAST_ACK
4. 第四次挥手(Client → Server)
字段值:SYN=0, ACK=1, FIN=0, seq=u+1, ack=w+1
- seq=u+1:首次挥手的FIN消耗序号u,故下一序号为u+1
- ack=w+1:确认服务端的FIN(FIN占1序列号,故w+1)
- ACK=1:必须置1以确认FIN
- 状态变化:Client →
TIME_WAIT
(2MSL后关闭),Server →CLOSED
为什么挥手要四次
仍是一个八股问题。以下是贴的答案:
服务器在收到客户端的 FIN 报文段后,可能还有一些数据要传输,所以不能马上关闭连接,但是会做出应答,返回 ACK 报文段.
接下来可能会继续发送数据,在数据发送完后,服务器会向客户单发送 FIN 报文,表示数据已经发送完毕,请求关闭连接。服务器的ACK和FIN一般都会分开发送,从而导致多了一次,因此一共需要四次挥手。