系列文章目录
【网络通信基础】网络中的常见基本概念
【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序(万字博文)
【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法)
【网络原理】TCP协议的相关机制(确认应答、超时重传)
文章目录
前言
一、建立连接(三次握手)
建立连接(三次握手)的意义
1)投石问路,确认当前通信路径是否畅通
2)验证通信双方,发送能力和接收能力是否正常
3)三次握手的过程中,通信双方也会协商一些必要的参数
二、断开连接(四次挥手)
三、连接管理过程中涉及到的TCP状态转换
前言
接上文,我们介绍了TCP协议中,保证可靠性传输的核心机制,确认应答和超时重传机制。
而TCP协议的连接管理机制,是整个网络原理中,最高频的问题,没有之一!因此,这里单独用一篇博客,详细介绍TCP协议的连接管理机制。
TCP协议的连接管理机制主要包括三次握手建立连接和四次挥手断开连接。
一、建立连接(三次握手)
在前面的文章中,我们写过TCP回显客户端和服务器程序。
当客户端执行 clientSocket = new Socket(serverIp, serverPort); 这个操作的时候,就是在建立连接。此处只是调用了 socket API,真正连接建立的过程,是由TCP协议完成的。
TCP协议建立连接的这个过程,就称为“三次握手”。
这里谈到的连接,是“虚拟的,抽象的”连接。目的是让通信双方都能保存对方的相关信息。
此处又涉及到六位标志位的其中一位=>SYN,代表同步(synchronize)。所谓的SYN,是一个特殊的,带有SYN标志位的TCP数据包:
- 没有载荷,不带有应用层的数据.
- 六位标志位的第五位,将该位设置为1.
虽然SYN数据包不带有载荷,但是SYN数据包会包含IP报头(如果在IP网络中传输)或以太网数据帧帧头(如果在以太网中传输),以及TCP报头。这些头部包含了一些必要的信息,比如源IP地址、目标IP地址、源端口、目标端口等。这个过程,也是客户端在告诉服务器,我是谁。
了解了SYN数据包后,再来看下三次握手的大致过程。首先要清楚,客户端是主动的一方,第一次交互,一定有客户端主动发起的:
上图过程中,是有四次交互,简单来说:
- 客户端先向服务器发送SYN请求
- 服务器回复客户端一个ACK应答报文
- 服务器再给客户端发送一个SYN请求
- 客户端回复服务器一个ACK应答报文
这个建立连接的过程,本质上就是通信双方各自给对方发起一个SYN,各自给对方回应一个ACK。但实际上,中间的两次交互(2和3),是能够合二为一的。
因为回复一个带ACK标志位的数据包,再回复一个带SYN标志位的数据包,可以合并成一个数据包,这个数据包同时带有ACK标志位和SYN标志位。即第2位(ACK)和第5位(SYN)都为1.
此时这个数据包就同时起到了两个作用,既能应答上个请求,也能发起SYN。
这么做的原因是:网络传输过程中要涉及到多次的封装和分用,两个包合并成一个包,就可以减少一次封装分用的过程,整体的效率就提升了。
经过合并,四次交互也就变成了三次,也就形成了“三次握手”
因此,三次握手的具体过程如下:
-
客户端向服务器发送SYN请求: 客户端首先向服务器发送一个带有SYN标志位的数据包,其中包含了客户端的初始序列号(ISN)。
-
服务器发送SYN-ACK响应: 服务器收到客户端的SYN请求后,会回复一个带有SYN和ACK标志位的数据包,称为SYN-ACK响应。这个响应中,服务器确认了客户端的SYN请求,并指定了服务器的初始序列号(ISN)。
-
客户端发送ACK确认: 最后,客户端收到服务器的SYN-ACK响应后,会发送一个带有ACK标志位的数据包,表示确认了服务器的响应。这个ACK数据包中,客户端会确认收到了服务器的SYN响应,并指定了下一个要发送的序列号。
至此,三次握手完成,TCP连接建立成功,双方可以开始进行数据传输了。
前面给出的都是简图,更适合我们在面试过程中给出,因为更稳,要是画详图,画错了,那就得不偿失了。以下是三次握手的详图:
再通俗地解释一下三次握手的过程:
- 客户端向服务器发起建立连接的请求,发送一个SYN,此处的SYN就表示:我想跟你建立连接.
- 服务器大概率是会同意请求的(除非客户端太多了,负载极高,服务器无法响应),服务器收到SYN之后,会返回ACK(确认应答),表示收到!同时,服务器还会返回SYN,这个SYN的意思就是,我接受你的连接.
- 最后,客户端再给服务器返回一个ACK,表示:我也知道你同意我的请求了.
建立连接(三次握手)的意义
1)投石问路,确认当前通信路径是否畅通
例如地铁,每天早上会空车跑一趟,就是为了验证,这个路线是否畅通,是否存在一些问题。
因此,三次握手,对于可靠传输也是有意义的,但是相对于确认应答以及超时重传,起到的作用就稍逊一筹了。
2)验证通信双方,发送能力和接收能力是否正常
比如,张三和李四要开黑打游戏了。通常,在组队队伍的时候,双方都不知道自己的麦克风和听筒是否能正常工作,都先进行确认,这个过程其实就是三次握手,如下图:
上图中,张三扮演的就是客户端的角色,李四扮演服务器的角色。经过上述过程,张三和李四就够确认各自的麦克风和听筒都能正常工作了。此处,麦克风就对应发送能力,听筒就对应接收能力。
理解了上述图中的过程,我们就能清晰地理解,为什么是三次握手,而不是两次或四次握手:
- 四次握手显然是可以的,但是没必要。
- 如果是两次,一定是不行的。因为两次握手,李四(服务器)这边无法确认自己的发送能力是否正常,也无法确认张三(客户端)的接收能力是否正常。
3)三次握手的过程中,通信双方也会协商一些必要的参数
这些参数往往是以“选项”部分来体现的,前面说过,选项的范围是0~40字节。
其中有一个参数是很关键的,即TCP通信的序号:
- 客户端在发送SYN报文时会包含一个随机生成的序列号(ISN),用于标识客户端发送数据的起始位置。
- 服务器在回复ACK报文时会确认客户端的序列号,并生成自己的序列号(SSN),用于标识服务器发送数据的起始位置。
这样设计的原因,是为了避免出现一个情况,“前朝的剑,斩本朝的官”。具体过程如下:
- 第一次连接的过程中,客户端传输的一个数据包,在路上堵车了,迟迟没有发送到对端。
- 等到到了对端的时候,此时已经不是第一次连接了,而是新的连接了。
- 此时,这份数据就应该被丢弃。
- 由于,数据包是按照 IP + 端口号,来识别对端的。
- 如果恰好出现,两次连接在同一台主机,都是同一个端口号的情况(这种情况是客户端的概率比较低,但服务器概率就比较大了),此时,再要处理这份数据就不合适了。
要解决这一问题,就是通过序号来区分当前数据是否是本次连接的数据。如果是,才进行处理,否则,就丢弃该数据。
二、断开连接(四次挥手)
连接,本质上就是让通信双方保存对方的信息。每个客户端/服务器,都要保存很多的对端信息。
信息一旦多了,就要用到数据结构。断开连接的本质目的,就是把对端的信息,从数据结构中删除/释放掉。
此处谈到的“四次挥手”,指的是谈恋爱中“和平分手”的这种情况。而单方面分手的情况,四次挥手不一定适用,这里会有其他方式来断开连接,因此,不意味着断开连接一定是四次挥手。
此处,又要介绍一个标志位,FIN,FIN => finish(结束)。即六位标志位中的第六位,用于请求关闭连接。
与三次握手不同的是,四次挥手,不一定非得是客户端先发FIN,服务器也可能先发FIN。
- 调用socket.close(),会触发FIN
- 进程直接结束,也会触发FIN
四次挥手的过程简图如下(客户端先发起关闭连接请求):
从上图可以看出,四次握手就是通信双方各自给对方发一个FIN,且各自给对方反馈一个ACK。
而且,这里的过程和刚开始的“四次握手”的过程非常相似。那四次挥手能否像三次握手一样,将中间的两次交互(ACK和FIN)合二为一呢?
- 在标准的TCP四次挥手过程中,ACK和FIN通常是分开发送的。
- 然而,在某些情况下,确实可能会发生ACK和FIN合并发送的情况,因为在理论上,ACK和FIN可以在同一数据包中发送。这种情况主要依赖于延迟应答和捎带应答机制(后面再说)。
如果实际通信过程中,ACK和第二个FIN时间间隔比较长,此时就无法进行合并了。以下是四次挥手的详图:
三、连接管理过程中涉及到的TCP状态转换
在前面三次握手和四次挥手的详图中,每进行一次握手或挥手,图中都有一个对应的状态。
TCP状态和之前多线程中谈到的“线程”状态,是类似的概念。TCP会根据收到的数据包以及协议规定的状态转换规则来更新连接状态:
-
分析收到的数据包: TCP协议栈会分析收到的数据包的头部信息,包括源端口、目标端口、标志位等,以确定数据包所处的状态以及下一步的操作。
-
执行状态转换规则: TCP协议定义了一系列状态转换规则,规定了在收到不同类型的数据包时应该如何更新连接状态。根据这些规则,TCP协议栈会根据收到的数据包来进行状态转换。
根据这些状态,TCP就能确定当前应该干什么,对于确保网络通信的可靠性和有效性至关重要。
这个图看起来非常复杂,实际上也真的很复杂,但是只需要关注几个比较重要的状态即可。
先来看三次握手中比较重要的状态:
客户端还未发起SYN前,服务器处于LISTEN状态,表示服务器这边创建好 serverSocket 了,并且绑定端口号完成(手机开机了,信号良好,随时可以接听电话)
运行之前写的TCP回显服务器程序,通过命令行输入 netstat -ano | findstr 9090 这条命令(在这个命令中,netstat -ano
的作用是显示所有网络连接及其相关的进程ID(PID),而 findstr 9090
则是在输出包含端口号9090的所有进程),查看当前的状态:
ESTABLISHED 已确立的。表示客户端和服务器连接已经建立完毕(有人给我打电话,我接通了,此时我们就可以聊天了)
运行之前写的TCP回显客户端程序(可以运行多次),再次通过这个命令查看当前状态:
此处由于客户端和服务器在同一个机器上运行,因此客户端和服务器都会查出来。
再看四次挥手中比较重要的状态:
谁被动断开连接,谁进入 CLOSE_WAIT 状态。这个状态表示:
- 接收方已被通知对方不再发送数据,但它仍然可以发送数据给对方。接收方需要执行应用层的关闭操作,并发送一个FIN包来关闭剩余的方向,即关闭对发起方的数据传输。
CLOSE_WAIT 状态不太容易观察到,代码中会比较快速地关闭socket,这个时候,状态就已经从 CLOSE_WAIT -> LAST_ACK 了。但是如果服务器/客户端出现大量 CLOSE_WAIT 意味着代码可能出现bug了,比如忘记关闭 socket。
谁主动断开连接,谁进入 TIME_WAIT 状态:
- 表示本端给对端发起FIN之后,对端也给我发FIN了,此时本端进入 TIME_WAIT 状态。这给最后一个ACK的重传留有一定的时间。
TIME_WAIT 状态在Windows上也不太容易观察。它存在的意义,主要是防止最后一个ACK丢失。具体的:
- 在四次挥手的过程中,会涉及到确认应答和超时重传,如果没有收到ACK就视为丢包,此时就会重传FIN。
- 进入TIME_WAIT 状态的这一方,如果在重传FIN这个环节,把TCP连接关闭了,保存的对端信息也随之释放了。此时意味着重传FIN的这一方就无法被返回ACK了。
此处 TIME_WAIT 也不是无休止的等待,通常等2MSL(MSL是TCP协议中的一个参数,表示数据段在网络中的最大存活时间,通常以秒为单位。MSL的值取决于具体的实现和环境,但通常为30秒到2分钟之间)
进入TIME_WAIT 状态的这一方等这么长时间,如果对方还没有重传,就说明不会再重传了。同时也确保了对方收到了最后的ACK确认。
至此,TCP协议的连接管理机制就介绍完了,并且前面还介绍了确认应答和超时重传机制,这三个机制都是保证TCP协议可靠传输的重要机制。连接管理机制总结如下:
三次握手(Three-way Handshake):
- 第一步(Client -> Server): 客户端发送一个带有SYN标志的数据包给服务器,表示请求建立连接,并选择一个初始序列号(ISN)。
- 第二步(Server -> Client): 服务器收到客户端的SYN包后,回复一个带有SYN和ACK标志的数据包给客户端,表示同意建立连接,并确认客户端的ISN,同时选择自己的ISN。
- 第三步(Client -> Server): 客户端收到服务器的确认后,再次发送一个带有ACK标志的数据包给服务器,确认服务器的ISN。此时,连接已建立,双方可以开始传输数据。
四次挥手(Four-way Handshake):
- 第一步(Client -> Server): 客户端发送一个带有FIN标志的数据包给服务器,表示不再发送数据,但仍可以接收数据。
- 第二步(Server -> Client): 服务器收到客户端的FIN包后,回复一个带有ACK标志的数据包给客户端,确认收到了关闭请求。
- 第三步(Server -> Client): 服务器发送一个带有FIN标志的数据包给客户端,表示服务器也不再发送数据,但仍可以接收数据。
- 第四步(Client -> Server): 客户端收到服务器的关闭请求后,回复一个带有ACK标志的数据包给服务器,确认收到了关闭请求。此时,连接终止。
通过三次握手,客户端和服务器建立起了可靠的连接;而通过四次挥手,双方安全地终止了连接,确保数据的可靠传输。