目录
📕引言
建立连接:三次握手
断开连接:四次挥手
握手:发送一个不携带业务数据的数据报,不起到任何业务的作用,只是用来"打招呼"。
🌴三次握手四次挥手总览
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接
整体过程如下:
该过程对应着很多种状态的转换
服务端状态转化
- [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态,等待客户端连接;
- [LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文。
- [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了。
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT;
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK,彻底关闭连接
客户端状态转化
- [CLOSED -> SYN_SENT] 客户端调用connect,发送同步报文段;
- [SYN_SENT -> ESTABLISHED] connect调用成功,则进入ESTABLISHED状态,开始读写数据;
- [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时,向服务器发送结束报文段,同时
- 进入FIN_WAIT_1;
- [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2,开始等待服务器的结束报文段;
- [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出LAST_ACK;
- [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态。
下图是TCP状态转换的一个汇总:
较粗的虚线表示服务端的状态变化情况;
较粗的实线表示客户端的状态变化情况;
CLOSED是一个假想的起始点,不是真实状态
上述只是TCP三次握手四次挥手的一个总览,接下来我将具体为大家介绍以下连接是如何建立的,又是如何断开的,以及为什么要叫三次握手四次挥手
🌳三次握手(建立连接)
建立连接就是通信双方各自保存对端的信息,具体完成过程需要经过三次网络交互。
三次握手其实就是客户端服务器三次的通信的过程
三次握手的第一次,一定是客户端先发起的。这个数据报不携带任何业务数据,也就是载荷部分是空着的,只有TCP报头部分,这个TCP报头中,其中6个标志位的SYN这一位为1(意味着它就是发起请求的数据报,叫做"同步报文")
这个SYN也就是synchronize,译为"同步",在多线程中的"同步"代表互斥,在TCP中的"同步"则是希望服务器和客户端之间,达成某种配合关系,达成某种有关联的状态。
当客户端给服务器发送SYN(我想和你建立连接),此时服务器就会给客户端回应一个应答报文(ACK),这个应答报文也只是告诉客户端说,我收到你的请求了,并不是愿意跟你建立连接,这个时候紧接着服务器就会给客户端发起一个同步报文。
当客户端收到服务器发来的请求之后,也会返回一个应答报文。
上述流程,客户端和服务器各自给对方发送SYN,在各自给对方返回一个ACK,那么我们发现,这里面有四次交互,我们不是说三次交互吗?其实中间这两次是可以合并成一次的(提升效率),都是服务区给客户端返回的数据,这返回的数据里面既是一个ACK,也是一个SYN,关键在于中间的两次交互,ACK和SYN可以合并成一个网络数据。
在六个标志位中,ACK第二位为1,SYN第五位为1,所谓合并就是让这一个TCP数据报,报头中同时把这两个bit位都设置为1.
上述就是三次握手的基本流程
🚩为什么是各自给对方发送SYN
比如有这样的场景
🚩三次握手的意义
三次握手解决了什么问题?
📌"投石问路"
在正式传输业务数据之前,先确认一下通信链路是否畅通。
📌验证双方的接听发送能力是否正常
进行三次握手,本质上就是完成上述确认过程。
这里也为大家举个例子吧,还是一对情侣,他们异地,然后他们现在要打游戏连麦,交互过程如下
-
男:听的我说话吗?
-
女:听得到,你听的到我说话吗?
-
男:听的到,那我们开始吧!
初始情况下双方都不知道麦克风和听筒的情况。
第一次交互,这个过程中,当女生听到男生说得话,这时候
女生:
- 知道男生的麦(发送能力)正常,不知道男生听筒(接收能力)是否正常
- 不知道自己麦(发送能力)是否正常,知道自己听筒(接收能力)正常
男生:
- 不知道女生麦(发送能力)是否正常,不知道女生听筒(接收能力)是否正常
- 不知道自己麦(发送能力)是否正常,不知道自己听筒(接受能力)是否正常
第二次交互,女生给男生回应(前提是女生听到了男生说的话 ),这时候
女生:
- 知道男生的麦(发送能力)正常,不知道男生听筒(接收能力)是否正常
- 不知道自己麦(发送能力)是否正常,知道自己听筒(接收能力)正常
男生:
- 知道女生麦(发送能力)正常,知道女生听筒(接收能力)正常
- 知道自己麦(发送能力)正常,知道自己听筒(接受能力)正常
第三次交互,男生给女声回应(前提是女生的说的话男生听见了),这时候女生收到后
女生:
- 知道男生的麦(发送能力)正常,知道男生听筒(接收能力)正常
- 知道自己麦(发送能力)正常,知道自己听筒(接收能力)正常
男生:
- 知道女生麦(发送能力)正常,知道女生听筒(接收能力)正常
- 知道自己麦(发送能力)正常,知道自己听筒(接受能力)正常
这时候他们就知道对方和自己的发送能力和接收能力都正常,就可以开始通信。
TCP的三次握手也是如此。设想一下,三次握手变成了两次握手是否可行?那当然是不行的,此时服务器这边对于"发送能力""接收能力"的认知是不完整的,需要第三次交互,把客户端掌握的完整情报告知给服务器。
📌三次握手过程中,还需要协商一些必要的参数
通信双方共同商量一下,有的参数不是单方面就能确认,需要双方共同来确认出来。
要协商的东西有很多,其中TCP通信时使用的序号,就是协商出来的。
需要往往不是从 1/0开始的,而是三次握手的时候,通信双方协商出来的一个数字,第一次连接和第二次连接,协商出来的其实序号也会不同,而且差异很大!
原因:
TCP考虑到这样一个情况,避免出现"前朝的剑,斩本朝的官"
我们想象这样一个场景:
我们建立连接之后,进行若干次通信,考虑一个极端的情况,某个数据报在网络通信过程中迷路了,走了一个非常绕远的路线(不是丢包),等到这个数据报绕了半天,终于到达服务器的时候,之前的连接已经断开了,当前是一个新的连接。
服务器收到这个数据报之后,服务器会直接丢弃!
因为新的连接对应的服务器进程,可能都不是同一个程序了,那么业务逻辑也就不一样了。当然不可行。
既然要直接丢弃,那么服务器如何区分当前收到的数据报是否是"前朝"的数据报呢??
通过序号就可以区分出来,每次建立的连接,都是从一个新的数字作为起始的序号,当前本朝的数据,序号一定是沿着我们起始序号往下的数字(不会相差很多),如果突然收到一个数据报,序号和当前的起始序号差别非常大,就可以认为这个数据报是"前朝"数据报了。
总结:
三次握手主要的意义三个方面:
- 1.投石问路,确认通信路径是畅通的
- 2.验证通信双方的发送能力和接收能力
- 3.协商必要的参数,比如起始序号
经典面试题:
- TCP为什么要三次握手??
- TCP为啥必须要三次握手??两次行不行??四次行不行??
- 谈谈TCP建立连接的流程!!(可画图来解释)
🚩建立连接阶段涉及到的两个重要状态:
- LISTEN:服务器的状态. 表示服务器已经准备就绪,随时可以有客户端来建立连接了. 相当于手机开机信号良好,随时可以有人来打电话了.
- ESTABLISHED:客户端和服务器都有. 连接建立完成接下来就可以正常通信了. 相当于电话拨打过去,对方接通了.
🎄四次挥手(断开连接)
具体流程:
注意:三次握手,第一次发送请求一定是客户端第一次发起,而四次挥手,则不一定,客户端和服务器都可以主动发起!
此处以客户端主动提起为例:
当前客户端要进行断开连接,什么时候会触发断开连接,当客户端代码调用 socket.close 或者客户端进程结束,这样的情况就会触发tcp的四次挥手。
一旦close,客户端就会给服务器发起一个FIN(结束报文)数据报,也就是TCP的六个标志位为1,服务器收到后立即返回ACK,返回应答报文之后,服务器也会给客户端发起FIN报文,客户端返回ACK。最终完成断开连接的过程(通信双方把对端之前保存的信息删了)。
TCP中期望达成的效果是双方互删,所以双方都要发送FIN。
那么四次挥手中间两次交互是否可以合并??
有的时候可以合并,有的时候不可以合并,常规情况下可以,特殊情况下不能。
三次握手过程中,服务器收到客户端发来的请求后,SYN 和 ACK都是内核自动控制发送的返回,返回的时机其实都是内核控制的,同一时机。
而四次挥手,当服务器收到客户端发来的FIN(假设客户端主动提出断开连接),此时服务器就会立即返回ACK,这也是由内核控制的,当我们在代码中调用close或者直接结束进程的时候,才会触发服务器给客户端发送FIN(应用程序控制的)。这两个操作不是同一时机,中间可能会隔很久。
🚩不可合并
我们查看TCP服务器和客户端的搭建中的代码:【JavaEE初阶】TCP服务器和客户端的搭建-CSDN博客
其中服务代码:
这里的close操作就是服务器触发FIN的时机,当服务器发现scanner没有next了,才会结束循环,进入close操作。什么时候会发现没有next了,客户端调用close或者客户端进程退出,也就是客户端触发FIN发送给服务器之后,此时服务器就能感知到scanner没有next了。
服务器先是感知到FIN来了,然后服务器结束循环,在执行close,很明显,感知到FIN来了,这是内核的事情,一感知到内核立即返回ACK,然后才是break结束循环,到达finally进行close。
只是当前代码比较干脆,感知到scanner没有next了(客户端发来FIN),就立即结束循环,立即close,但是如果代码很复杂,在没有next到close中间,又写了很多别的代码逻辑,指不定close什么时候执行了,甚至更极端的情况,忘记执行close了......
所以由于这两个数据报触发的时机不同,因此就难以合并。
🚩可以合并
在特殊情况下上述两个数据报可以合并。
TCP中还有一个机制,叫做"延时应答"(要返回ack,但不是马上,而是稍等一会儿)。这种情况就可以合并。"延时应答"机制后面会详细讲述。
最终,上述的合并情况,属于"特殊"情况,对一般情况是不能合并的,所以,最终还是把断开连接称为"四次挥手"。
🚩四次挥手中涉及到的两个重要的状态.
📌CLOSE_WAIT
- 出现在被动发起断开连接的一方.
- 等待关闭(等待调用close方法关闭socket)
注意:对于 CLOSE WAIT,一般而言,对于服务器上出现大量的 CLOSE_WAIT 状态,原因就是服务器没有正确的关闭 socket,导致四次挥手没有正确完成。这是一个 BUG。说明代码忘记调用close,或者close的调用不及时。
注意:调用close不影响服务器进程的结束!并且调用close方法不等于删除对端信息。当接收方收到FIN时立即返回ACK,且进入COLSE_WAIT状态(等待应用程序调用close),当应用程序调用close方法后,接收方给发送方发送FIN,发送方收到接收方发来的FIN之后,返回ACK,这时通信双方才会删除对端信息。
📌TIME_WAIT
- 出现在主动发起断开连接的一方.
- 假设是客户端主动断开连接.
- 当客户端进入TIME_ WAIT状态的时候,相当于四次挥手已经挥完了.
TIME_WAIT会持续一段时间等待对方重传的FIN,会持续多久?
面试题:如果服务器端出现大量的TIME_WAIT,如何处理?
出现大量的TIME_WAIT,说明服务器这边触发了大量的主动断开TCP连接的操作,这个操作对于服务器来说,很可能是不科学的,一般都是由客户端主动断开连接。