TCP这种传输层协议必须是有连接的。连接管理,就是TCP当中管理如何建立连接、如何断开连接的方式。
目录
TCP建立连接的方式(三次握手)
合并两次连接之后变成"三次握手"
站在具体发送什么报文的视角,理解三次握手
发送报文之前
第一步:客户端主动向服务端发送(第一次握手)
第二步:服务端主动向客户端发送(第二次握手)
第三步:客户端主动向服务端发送(第三次握手)
第四步:三次握手建立连接完成之后
为什么三次握手必须是三次,不可以是四次
四次握手可以合并成三次的原因:
三次握手,为什么不可以是两次
总结一下:三次握手的作用(两个)
TCP四次挥手
四次挥手,为什么不可以像三次握手一样合并中间两次握手
第一步:客户端主动发起FIN:
第二步:服务端主动发送ack
第三步:服务端主动发送FIN
第四步:客户端收到FIN之后,再次发送ack
第二、第三次握手为什么不可以合并
四次挥手当中涉及到的两个重要的TCP状态
服务端出现大量的close_wait状态是什么原因
TCP建立连接的方式(三次握手)
通信双方需要彼此记录对方的信息,彼此之间需要"相互认同"。
下面,通过一个故事,来理解一下"三次握手"。
所谓建立连接,一定是客户端与服务端之间建立连接
一天,小明去对小红表白(建立连接);
发生了以下的场景:
时间轴 | 小明(客户端) | 小红(服务端) |
t1 | 小红小红,你可以成为我npy吗? | |
t2 | 好啊好啊 | |
t3 | 小明小明,你可以成为我npy吗? | |
t4 | 好啊好啊 |
以上场景,每通话一次,相当于一次"握手"。在通话完成之后,小明与小红就正式成为NPY,也就是建立连接。
我们都说,TCP是通过三次握手建立连接的。但是,在上述的场景当中,可以看到:出现了4次"握手",本质上也就是4次交互。但是,为什么又说是"三次"呢?
合并两次连接之后变成"三次握手"
可以看到,在t2,t3两个时刻,小红发出了两次消息,如果把这两次消息合并成为1次。这样,4次握手,就变成了三次握手了。
合并之后的握手情况:
下面是具体的读取情况:
时间轴 | 小明 | 小红 |
t1 | 小红小红,你可以成为我npy吗? | |
t2 | 好啊好啊,你可以成为我的npy吗? | |
t3 | 好啊好啊 |
站在具体发送什么报文的视角,理解三次握手
发送报文之前
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
CLOSE | LISTEN | LISTEN的含义:(相当于手机开机,信号良好) ①表示服务端已经准备就绪,随时可以等待客户端来建立连接了。 ②这个状态为服务端专有的状态,含义是"监听"用于服务端监听某一个端口的状态;
|
第一步:客户端主动向服务端发送(第一次握手)
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | SYN_SEND | syn | 同步报文段(syn标志位为1); 客户端发送了syn报文之后,客户端进入了syn_send的状,等待服务端确认 |
第二步:服务端主动向客户端发送(第二次握手)
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | SYN_RECV | syn+ack | 同步报文段(syn标志位为1) 应答报文段(ack标志位为1) syn和ack都属于内核态发送的文 服务端发送之后进入SYN_RECV状态 |
第三步:客户端主动向服务端发送(第三次握手)
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | 进入步骤4的状态 | 进入步骤4的状态 | ack | 应答报文段(ack标志位为1) 此报文可以携带数据,但是前两次不可以携带数据。 |
第四步:三次握手建立连接完成之后
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
ESTABLISHED | ESTABLISHED | 此状态为客户端与服务端均可以有的状态,表示客户端与服务端已经经过三次握手建立好了连接 读音: [ɪˈstæblɪʃt] |
所以,综上所述,客户端与服务端之间三次握手的流程图大致为下面这样:
为什么三次握手必须是三次,不可以是四次
在上述的通信过程当中,每发送一次报文,就相当于封装、分用一次。
关于什么是封装、分用,也已经在这一篇文章当中提到了。
初识网络:IP、端口、网络协议、TCP-IP五层模型_革凡成圣211的博客-CSDN博客TCP/IP五层协议详解https://blog.csdn.net/weixin_56738054/article/details/128666970?spm=1001.2014.3001.5501简而言之:
封装就发送方把报文从应用层报文-->数据链路层进行的一个封装;
分用就是接收方把报文从数据链路层--->应用层的一个过程;
而每封装、分用一次,都是需要开销的;因此,在确保发送方、接收方都可以确保双方可以接收消息的情况下,应当尽可能地减少封装、分用的次数。也就从4次握手削减为了三次;
也就是合并了接收方回复发送方的那一次发送报文。
图解一下:三次握手:
四次握手可以合并成三次的原因:
原因1:
三次握手的过程,是在操作系统内核当中完成的,也就是三次握手发生在内核态,应用程序无法干涉;
也就是在第二次握手的时候,服务端的系统收到syn之后,就会立即发送ack给客户端。
ack的发送与接收,都属于内核态的工作。
因为握手的过程都发生在内核态,因此可以合并中间两次握手。
原因2:
多一次握手,就会多一次分装分用的过程。每封装分用一次,都是需要比较大的开销的。
三次握手,为什么不可以是两次
如果变成了两次,就会出现下面的结果(图解)
给定下面一个场景:假设小明和小红连麦打游戏,他们之间需要彼此确认双方的话筒和耳机是否正常;
时间轴 | 小明 | 小红 |
t1 | 小红小红,你听到我说话了嘛? | |
t2 | 小明小明,我听到你说话啦 |
可以看到,t1时刻,小明向小红发送了一个语音。然后小红听到了小明的语音之后(t2时刻),说明了两个事实:
事实1:小明(发送方)的语音(发送功能)正常;
事实2:小红(接收方)的耳机(接收功能)正常;
也就是说:作为发送方,它可以正常发送消息;作为接收方,它可以正常接收消息。
但是,却无法验证另外两个事实,那就是:
事实3:小红(接收方)的语音(发送功能)是否正常;
事实4:小明(发送方)的耳机(接收功能)是否正常;
总结一下:三次握手的作用(两个)
作用1:确保了发送方与接收方通信上面的彼此认同;
作用2:验证了发送方与接收方的发送和接收能力是否正常;
需要注意的是:
TCP的可靠性和有无连接没有关系
可靠性是依靠确认应答机制(ack)+超时重传机制来保证的。
而建立连接是依靠三次握手机制来完成发送方和接收方之间的自检。
TCP四次挥手
三次握手,是建立连接的过程。四次挥手,是断开连接的过程。
经过了四次挥手之后,客户端与服务端之间就断开连接了。
下面,通过一个场景,模拟四次挥手的过程:
假设小明想跟小红提出分手:
时间轴 | 小明(客户端) | 小红(服务端) |
t1 | 小红小红,我们分手吧 | |
t2 | 好啊好啊 | |
t3 | 小明,我们分手吧 | |
t4 | 好啊好啊 |
经历了以上的四次挥手之后,小明和小红就"成功分手了",也就相当于客户端与服务端主动断开连接了。
四次挥手,为什么不可以像三次握手一样合并中间两次握手
下面,站在具体发送报文的角度,理解一下四次挥手:
首先,需要了解一个前提条件:
当报文段的ack标志位为1的时候,这个报文的发送和接收都工作在内核态;
当报文段的FIN标志位为1的时候,这个报文段的发送和接收都工作在用户态;
在发起四次挥手之前,客户端和服务端都在建立连接时候的状态(ESTABLISHED)
第一步:客户端主动发起FIN:
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | FIN_WAIT1 | FIN | ①发送报文的FIN标志位为1 ②FIN报文段的发送与接收,都发生在用户态 ③客户端发送了FIN之后进入FIN_WAIT1状态 |
第二步:服务端主动发送ack
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | CLOSE_WAIT | ack | ①发送报文的ack标志位为1。 ②ack报文段的发送发生在内核态 ③服务端发送了第一次ack之后之后进入了CLOSE_WAIT状态 |
第三步:服务端主动发送FIN
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | LAST_ACK | FIN | ①发送报文的FIN标志位为1 ②FIN报文段的发送与接收,都发生在用户态 ③服务端发送了FIN之后,进入了LAST_ACK状态 |
第四步:客户端收到FIN之后,再次发送ack
客户端 | 客户端状态 | 服务端 | 服务端状态 | 报文类型 | 备注(其他) |
主动发送 | TIME_WAIT | ack | 客户端发送的ack标志位为1。 发送的过程发生在内核态。 客户端发送了最后一次ack之后,自身进入了TIME_WAIT状态。 |
ps :经过了第四步之后,客户端进入了TIME_WAIT状态,服务端在收到了ack之后,立刻进入了close状态。客户端在TIME_WAIT状态持续2MSL之后,也进入了TIME_WAIT状态。
第二、第三次握手为什么不可以合并
FIN的发起,不是由内核控制的,而是由应用程序发起的。当调用socket的close方法(或者进程退出)才会触发FIN。
ACK则是由内核控制的,是在收到客户端发送的FIN之后,立即返回ACK。
由于FIN和ACK一个工作在用户态,另外一个工作在内核态,因此不可以合并。
但是,在特殊情况下面,也有可能合并。
回到这一篇文章当中,实现一个简单的TCP。
(1条消息) 实现一个TCP客户端——服务端协议_革凡成圣211的博客-CSDN博客_tcp客户端实现https://blog.csdn.net/weixin_56738054/article/details/128750183?spm=1001.2014.3001.5501
挥手次数 | 发起方 | 发送报文类型 | 说明 |
1 | 客户端 | FIN | 服务端的 scanner.hasNext()读取到的值为false,因为内核收到了客户端发送的FIN数据报。(第一次挥手) |
2 | 服务端 | ack | 在读取到scanner.hasNext()的值为false的时候,立即返回ack报文。 步骤2在内核当中完成 |
3 | 服务端 | FIN | 应用程序在finally代码块当中调用close方法,相当于发送FIN给客户端 |
4 | 客户端 | ack | 客户端在收到了步骤3发送的FIN之后,立刻在内核当中发送ack给服务端 |
关于第二次挥手和第三次挥手,如果它们之间间隔的时间比较短,那么救有可能被合并成为一次。
图解一下:客户端与服务端的发送与接收过程:下图来源于《小林coding》
四次挥手当中涉及到的两个重要的TCP状态
状态的名称 | 状态发生的场景(假定是客户端主动断开连接,服务端被客户端断开连接) | 说明 | 出现在哪一方 |
CLOSE_WAIT | 如果客户端TCP主动断开服务端TCP连接,那么服务端TCP的状态就是CLOSE_WAIT,其实就是等待关闭 | 等待关闭:(等待调用close方法关闭连接) | 被断开连接的一方 |
TIME_WAIT | 四次挥手结束之后,客户端进入的状态; | 此时,这里的TIME_WAIT要保持当前的TCP连接状态不要立即释放,原因:
TIMEWAIT最大时间:2MSL,也就是如果服务端隔了2MSL没有发送给客户端,那么就会由服务端再次发送一次FIN
| 出现在主动断开连接的一方; |
服务端出现大量的close_wait状态是什么原因
说明服务端的程序没有调用 close 函数关闭连接,也就是说,服务端没有发送FIN报文段。