注:TCP 连接的建立和释放在网络协议中是比较重要的,由于本人理解也不是很透彻,欢迎各位批评指正。
前言
TCP 是面向连接的、可靠的字节流协议。因此,在传输数据之前通信双方必须建立一个 TCP 连接,建立 TCP 连接需要在服务器和客户端之间进行三次握手。通信双方数据传输完毕之后进行连接释放,释放连接需要在通信双方之间进行四次挥手。
TCP 状态机
TCP 所谓的“连接”,只是通信双方维护一个“连接状态”,让它看上去好像有连接一样,其实 TCP 连接是虚拟的连接,不是电路连接。首先看下 TCP 的状态机,状态机是 TCP 连接与释放的全过程。如下图所示:
下面针对 TCP 状态机所出现的各个状态进行简要的分析:
- CLOSED:表示初始状态。对服务端和客户端双方都一样。
- LISTEN:表示监听状态。服务端调用了 listen 函数使其处于监听状态,此时可以开始 accept (接收)客户端的连接。
- SYN_SENT:表示客户端已经发送了 SYN 报文段,则会处于该状态。当客户端调用 connect 函数发起连接请求时,首先发 SYN 给服务端,然后自己进入 SYN_SENT 状态,并等待服务端发送 ACK+SYN 作为请求应答。
- SYN_RCVD:表示服务端收到客户端发送 SYN 报文段。服务端收到这个报文段后,进入 SYN_RCVD 状态,然后发送 ACK+SYN 给客户端。
- ESTABLISHED:表示 TCP 连接已经成功建立,通信双方可以开始传输数据。服务端发送完 ACK+SYN 并收到来自客户端的 ACK 后进入该状态,客户端收到来自服务器的 SYN+ACK 并发送 ACK 后也进入该状态。
- FIN_WAIT_1:表示主动关闭连接。无论哪方调用 close 函数发送 FIN 报文都会进入这个这个状态。
- FIN_WAIT_2:表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的 ACK 后,会进入该状态。
- TIME_WAIT:表示收到对方的 FIN 报文并发送了 ACK 报文,就等 2MSL 后即可回到 CLOSED 状态了。如果 FIN_WAIT_1 状态下,收到对方同时带 FIN 标志和 ACK 标志的报文时,可以直接进入 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。
- CLOSING:表示双方同时关闭连接。如果双方几乎同时调用 close 函数,那么会出现双方同时发送 FIN 报文的情况,就会出现 CLOSING 状态,表示双方都在关闭连接。
- CLOSE_WAIT:表示被动关闭方等待关闭。当收到对方调用 close 函数发送的 FIN 报文时,回应对方 ACK 报文,此时进入 CLOSE_WAIT 状态。
- LAST_ACK:表示被动关闭方发送 FIN 报文后,等待对方的 ACK 报文状态,当收到 ACK 后进入CLOSED状态。
TCP 连接的建立
TCP 连接的正常建立过程通信双方需要三次握手,其过程如下图所示:
TCP 协议提供可靠的连接服务,采用有保障的三次握手方式来创建一个 TCP 连接。三次握手的具体过程如下:
- 客户端进程向服务端发出连接请求,请求报文的报文段首部中的控制位标志 SYN=1(有关 TCP 控制位信息参考《TCP 协议》),由于是首次请求建立连接,因此,控制位标志 ACK=0,该报文段包含计算机随机生成的初始序号 seq=x。发送请求连接的 TCP 报文段,此时客户端进程进入 SYN_SENT 状态,这是 TCP 连接的第一次握手。
- 服务端收到客户端发来的请求报文后,若同意建立连接,则向客户端发送确认。确认报文中的控制位 SYN=1,ACK=1,确认应答号 ack=x+1(即在接收到序列号值基础上加 1 ),并且发送自己的一个初始序列号 seq=y(即请求与客户端连接)。此时,服务端进入SYN_RCVD状态,这是TCP连接的第二次握手。
- 客户端进程收到服务端进程的确认报文后,还要向服务端发出确认信息。确认报文段的控制位 ACK=1,确认应答号 ack=y+1(即在接收到序列号值基础上加 1 ),此时,客户端进入 ESTABLISHED 状态。服务器收到来自客户端的确认应答信息也进入 ESTABLISHED 状态。这是TCP连接的第三次握手。此时,TCP 连接成功建立。
同时打开连接请求
正常情况下,通信一方请求建立连接,另一方响应该请求,但是如果出现,通信双方同时请求建立连接时,则连接建立过程并不是三次握手过程,而且这种情况的连接也只有一条,并不会建立两条连接。同时打开连接时,两边几乎同时发送 SYN,并进入 SYN_SENT 状态,当每一端收到 SYN 时,状态变为 SYN_RCVD,同时双方都再发 SYN 和 ACK 作为对收到的 SYN 进行确认应答。当双方都收到 SYN 及相应的 ACK 时,状态变为 ESTABLISHED。其过程入下:
TCP 连接释放
由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。原则是主动关闭的一方发送一个 FIN 报文来表示终止这个方向的连接,收到一个 FIN 意味着这个方向不再有数据流动,但另一个方向仍能继续发送数据,直到另一个方向也发送 FIN 报文。TCP 连接释放的过程如下图所示:
以下是释放连接的四次挥手过程:
- 客户端进程主动向服务端发出连接释放请求报文段,并停止发送数据,主动关闭 TCP 连接。释放连接报文段中控制位 FIN=1,序列号为 seq=i,发送该报文段之后客户端进入FIN_WAIT_1(终止等待1)状态,等待服务器的确认。这是 TCP 连接释放的第一次挥手。
- 服务器收到连接释放请求报文段后即发出确认释放连接的报文段,该报文段中控制位 ACK=1,确认应答号为 ack=i+1,然后服务器进入CLOSE_WAIT(关闭等待)状态。此时 TCP 处于半关闭状态,即客户端已经不向服务器发送数据,但服务器仍可向客户端发送数据。这是TCP连接释放的第二次挥手。
- 客户端收到服务器的确认信息后,就进入了FIN_WAIT_2(终止等待2)状态,等待服务器发出连接释放请求报文段,若没有数据需要传输,服务器被动向客户端发出链接释放请求报文段中,报文段中控制位 FIN=1,序列号 seq=j,此时服务器进入LAST_ACK(最后确认)状态,等待客户端的确认应答。这是 TCP 连接释放的第三次挥手。
- 客户端收到服务器的连接释放请求后,必须对此发出确认。确认报文段中控制位 ACK=1,确认应答号 ack=j+1,客户端发出确认应答信息之后后进入TIME_WAIT(时间等待)状态。在这段时间内 TCP连接并没有释放,必须等待 2MSL 时间后,客户端才进入 CLOSED 状态。服务器收到了客户端的确认应答后,就进入了 CLOSED 状态。直到客户端和服务器都进入 CLOSED 状态后,连接就完全释放了,这是TCP连接释放的第四次挥手。
同时关闭连接
正常情况下,通信一方请求连接关闭,另一方响应连接关闭请求,并且被动关闭连接。但是若出现同时关闭连接请求时,通信双方均从 ESTABLISHED 状态转换为 FIN_WAIT_1 状态。任意一方收到对方发来的 FIN 报文段后,其状态均由 FIN_WAIT_1转变到 CLOSING 状态,并发送最后的 ACK 数据段。当收到最后的 ACK 数据段后,状态转变化 TIME_WAIT,在等待 2MSL 时间后进入到 CLOSED 状态,最终释放整个 TCP 传输连接。其过程入下:
TCP 相关疑问
为什么在 TCP 协议里,建立连接是三次握手,而关闭连接却是四次握手?
因为当处于 LISTEN 状态的服务器端收到来自客户端的 SYN 报文(客户端希望新建一个TCP连接)时,它可以把 ACK (确认应答)和 SYN (同步序号)放在同一个报文里来发送给客户端。但在关闭 TCP 连接时,当收到对方的 FIN 报文时,对方仅仅表示对方已经没有数据发送给你了,但是你自己可能还有数据需要发送给对方,则等你发送完剩余的数据给对方之后,再发送 FIN 报文给对方来表示你数据已经发送完毕,并请求关闭连接,所以通常情况下,这里的 ACK 报文和 FIN 报文都是分开发送的。
为什么一定要进行三次握手?
当客户端向服务器端发送一个连接请求时,由于某种原因长时间驻留在网络节点中,无法达到服务器端,由于 TCP 的超时重传机制,当客户端在特定的时间内没有收到服务器端的确认应答信息,则会重新向服务器端发送连接请求,且该链接请求得到服务器端的响应并正常建立连接,进而传输数据,当数据传输完毕,并释放了此次 TCP 连接。若此时第一次发送的连接请求报文段延迟了一段时间后,到达了服务器端,本来这是一个早已失效的报文段,但是服务器端收到该链接请求后误以为客户端又发出了一次新的连接请求,于是服务器端向客户端发出确认应答报文段,并同意建立连接。如果没有采用三次握手建立连接,由于服务器端发送了确认应答信息,则表示新的连接已成功建立,但是客户端此时并没有向服务器端发出任何连接请求,因此客户端忽略服务器端的确认应答报文,更不会向服务器端传输数据。而服务器端却认为新的连接已经建立了,并在一直等待客户端发送数据,这样服务器端一直处于等待接收数据,直到超出计数器的设定值,则认为客户端出现异常,并且关闭这个连接。在这个等待的过程中,浪费服务器的资源。如果采用三次握手,客户端就不会向服务端发出确认应答信息,服务器端由于没有收到客户端的确认应答信息,从而判定客户端并没有请求建立连接,从而不建立该连接。
为什么需要在 TIME_WAIT 状态必须等待 2MSL 时间,而不直接给进入 CLOSED 状态?
主要有两个原因:
- TIME_WAIT 确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到 ACK,就会触发被动端重发 FIN。因为最后一次确认应答 ACK 报文段很有可能丢失,因而使被动关闭方处于在LAST_ACK 状态的,此时被动关闭方会重发这个 FIN+ACK 报文段,在这等待的 2MSL 时间内主动关闭方重新收到这个被动关闭方重发的 FIN+ACK 报文段,因此,主动关闭方会重新发送确认应答信息,从而重新启动 2MSL 计时器,直到通信双方都进入 CLOSED 状态。如果主动关闭方在 TIME_WAIT 状态不等待一段时间就直接释放连接并进入 CLOSED 状态,那么主动关闭方无法收到来自被动关闭方重发的 FIN+ACK 报文段,也就不会再发送一次确认 ACK 报文段,因此被动关闭方就无法正常进入CLOSED 状态。
- 有足够的时间让这个连接不会跟后面的连接混在一起。防止已失效的请求连接出现在本连接中。在连接处于 2MSL 等待时,任何迟到的报文段将被丢弃,因为处于 2MSL等待的、由该插口(插口是IP和端口对的意思,socket)定义的连接在这段时间内将不能被再用,这样就可以使下一个新的连接中不会出现这种旧的连接之前延迟的报文段。
参考资料:
《TCP/IP 详解》
《TCP连接的建立和释放》
《TCP/IP状态变迁图和TCP三次握手与四次挥手》
《TCP 的那些事儿》