连接管理
- 建立连接:三次握手
- 断开连接:四次挥手
网络中的握手/挥手,就是发送不携带业务数据(没有载荷,只有报头)的数据包,但是能起到“打招呼”这样的效果。次数就是指网络通信的次数。
建立连接:三次握手
此处建立连接发送的 SYN
是同步的意思,可以延伸成:客户端希望服务器和它统一步调,来完成手续的传输
建立连接是一个“双向的操作”,连接是“抽象的连接”,代表通信双方各自保存对方的信息
- A 需要给 B 说:我想和你建立连接(A 想保存 B 的信息),之后返回应答(
ACK
) - B 也需要给 A 说:我也想和你建立连接(B 想保存 A 的信息),之后返回应答(
ACK
)
本来是四次握手,但是中间两次合并了,就成了三次握手,合并是很重要、很有意义的,要是分两次发送,效率就会打折扣,因为每个数据包都需要进行一系列封装分用
- 收到
ACK/SYN
都会使报头中的对应值由 0 变为 1
三次握手的意义
三次握手要解决什么问题?为什么要三次握手?意义何在?
三个方面:
- 投石问路的效果,初步验证通信的链路是否畅通
先通过一些没什么业务意义的报文,来验证一下这个路是不是通的,这是进行可靠传输的“前提条件”
例如:地铁需要在每天开始第一班车之前,空跑一趟,进行“链路畅通的验证”
- 可以确认通信双方的发送能力和接受能力是否都正常
除了关注中间的路线,还要关注两头的双方的接法能力是否正常
例如:你要和朋友一起玩游戏,事先你们先要确保双方的耳机和麦克风都是正常的
- 第一次挥手:
- 你这边仍然都不了解双方的能力
- 朋友那边听到“嘿”,意味着他知道了你的麦克风正常,他的耳机正常。但他不知道我的耳机和他的麦克风是否正常
- 第二次挥手:
- 朋友说“喵喵”,他仍不确定他自己的麦克风和我的耳机是否正常
- 你听到了“嘿嘿”,你就知道了你的耳机正常,朋友的麦克风正常。由于你们事先约定好了,他一定要在听到你说“嘿”之后再回复,所以你也就知道了你的麦克风正常,他的耳机正常。此时,你就知道了双方的耳机和麦克风都是正常的
- 你的验证完成
- 第三次挥手:
- 我再说“嘿嘿嘿”,把我掌握的信息同步给他
- 朋友听到“嘿嘿嘿”,由于我们实现约定好我一定是在听到他说 "嘿嘿"之后我再回复。因此,他听到了“嘿嘿嘿”,意味着他刚才说的“嘿嘿”被我收到了,他也就知道了他的麦克风正常,你的耳机正常
- 朋友的验证完成
- 针对 TCP 来说,必须要通过三次握手来验证双方的发送和接收能力。但其他协议就不一定是三次了
- 让通信双方在进行通信之前,对通信过程中需要用到的一些关键参数进行协商
TCP 通信时,起始数据的序号,就是通过三次握手协商确定的(换而言之,TCP 序号并不是从 1 开始的)
- 每次建立连接,TCP 的起始序号都不同,而且故意差别很大
- 避免出现“前朝的剑,斩本朝的官”
- A 和 B 后来又建立连接,虽然还是 A B 两个主机之间的连接,但可能变成不同的应用程序了
- 在传输业务数据的过程中,可能有某个数据包“迷路”了,饶了一大圈,最终才到达对端。当他到达的时候,已经“改朝换代了”,已经不是原来那个连接了
- 针对这样的“迟到”的数据包,就要将其丢弃掉,不能按照正常的流程处理这里的数据了,避免其在新的程序中产生负面的影响
- 对于 B 来说,就要区分当前收到的数据是“本朝”还是“前朝”的
这样,就可以给每个连接都协商不同的起始序号,如果发现收到的数据和起始序号以及和最近收到的数据序号差别都很大的话,就视为这个数据就是“前朝”的数据
网上有些资料说“TCP 的可靠性是通过三次握手体现的”,这句话是有些问题的
- 三次握手对于可靠性,是有一定的支持的
- 但是不能说可靠性就是三次握手体现的
- 因为三次握手只是建立连接的时候进行的,一旦连接建立好了之后,数据开始传输了,就和三次握手没关系了
- 传输数据过程中的可靠性是通过“确认应答”和“超时重传”来体现的
断开连接:四次挥手
优雅地断开连接,双方各自删除掉保存的对方的信息
断开连接不一定是“客户端主动”,服务器也可以主动断开
- 通信双方各自给对方发送
FIN
,各自给对方返回ACK
- A:“B 兄,我要把你删了“==>
FIN
- B:“好的“==>
ACK
- B:“A 兄,那我也把你给删了哦”==>
FIN
- A:“好的”==>
ACK
- A:“B 兄,我要把你删了“==>
三次握手和四次挥手的执行区别
三次握手,只有三次是因为中间两次的交互合并在一起了。但对于四次握手来说,中间两次却不一定能合并(大概率是不能)
- 因为对于三次握手来说,中间的两次(
ACK+SYN
)都是在内核中由操作系统负责进行的,时间都是在收到SYN
之后,此时同一时机,就可以合并了 - 对于四次挥手来说,
ACK
是由内核控制的(就是说系统只要收到FIN
就会立刻返回一个ACK
),但是FIN
的触发则是通过应用层程序调用close
/进程退出
来触发的- 代码中:
socket.close()
==>系统内部:发送FIN
- 如果代码没有执行
close()
,系统内部就不会发送FIN
,所以中间发送ACK
和FIN
之间是有时间间隔的
- 代码中:
TCP的状态
LISTEN
:服务器进入的状态,服务器把端口绑定好,就相当于进入了LISTEN
状态,此时服务器就已经初始化完毕,准备好随时迎接客户端了(手机开机,信号良好,随时可以有人打电话)ESTABLISHED
:TCP
连接建立完成(保存好了对方的信息了),接下来就可以进行业务数据的通信了(电话接通,可以说话)CLOSE_WAIT
:被动断开连接的一方会进入的状态,先收到FIN
的一方。等待代码执行close
方法。- 如果发现服务器这边存在大量的
CLOSE_WAIT
状态的TCP
连接,说明了什么? - 说明此时服务器代码可能有
bug
,排查close
是否写了或者是否执行到了
- 如果发现服务器这边存在大量的
TIME_WAIT
:主动断开连接的一方会进入的状态,此处的TIME_WAIT
按照时间来等待,达到一定时间后,连接也就释放了- 为什么不直接释放呢?
TCP
传输过程中,任何一个数据都会丢包,但丢包重传就好了,只要此处的连接还在,就能很好的处理这里的重传操作。保留TIME_WAIT
状态就是为防止最后一个ACK
丢包,这样即使丢包了也能进行重传- 如果最后一个
ACK
丢包了,此时B
这边就会重传一次FIN
,需要A
这边再发一次ACK
,但能够再发一次ACK
的前提是A
这边的连接还没有释放(如果连接释放了,就不知道对方的信息,无法返回任何数据了) - 如果
TIME_WAIT
状态保留了一段时间后,也没有收到重传的FIN
,就说明刚才的ACK
应该就是到了,就可以释放这里的连接了
TIME_WAIT
存在的时间称为2MSL
(MXL
==>数据包在网络传输中消耗的最大时间))
- 为什么不直接释放呢?