一、tcp链接中的正常四次挥手过程?
刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:
1、客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。2、接着,当服务端在 read 数据的时候,最后⾃然就会读到 EOF,接着 read() 就会返回 0,这时服务端应⽤程序如果有数据要发送的话,就发完数据后才调⽤关闭连接的函数,如果服务端应⽤程序没有数据要发送的话,可以直接调⽤关闭连接的函数,这时服务端就会发⼀个 FIN 包,这个 FIN 报⽂代表服务端不会再发送数据了,之后处于 LAST_ACK 状态;。
3、客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
4、等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
5、客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
6、服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
7、客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。
二、为什么挥手需要四次
服务器收到客户端的 FIN 报⽂时,内核会⻢上回⼀个 ACK 应答报⽂,但是服务端应⽤程序可能还有数据要发送,所以并不能⻢上发送 FIN 报⽂,⽽是将发送 FIN 报⽂的控制权交给服务端应⽤程序:
1、关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
2、服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
从上⾯过程可知,是否要发送第三次挥⼿的控制权不在内核,⽽是在被动关闭⽅(上图的服务端)的应⽤程序,因为应⽤程序可能还有数据要发送,由应⽤程序决定什么时候调⽤关闭连接的函数,当调⽤了关闭连接的函数,内核就会发送 FIN 报⽂了,所以服务端的 ACK 和 FIN ⼀般都会分开发送。
FIN 报⽂⼀定得调⽤关闭连接的函数,才会发送吗?不一定
如果进程退出了,不管是不是正常退出,还是异常退出(如进程崩溃),内核都会发送 FIN 报⽂,与对⽅完成四次挥⼿。
三、粗暴关闭 vs 优雅关闭
前⾯介绍 TCP 四次挥⼿的时候,并没有详细介绍关闭连接的函数,其实关闭的连接的函数有两种函数:
- close 函数,同时 socket 关闭发送⽅向和读取⽅向,也就是 socket 不再有发送和接收数据的能⼒。如果有多进程/多线程共享同⼀个 socket,如果有⼀个进程调⽤了 close 关闭只是让 socket 引⽤计数-1,并不会导致 socket 不可⽤,同时也不会发出 FIN 报⽂,其他进程还是可以正常读写该 socket, 直到引⽤计数变为 0,才会发出 FIN 报⽂。
- shutdown 函数,可以指定 socket 只关闭发送⽅向⽽不关闭读取⽅向,也就是 socket 不再有发送数据的能⼒,但是还是具有接收数据的能⼒。如果有多进程/多线程共享同⼀个 socket,shutdown 则不 管引⽤计数,直接使得该socket 不可⽤,然后发出 FIN 报⽂,如果有别的进程企图使⽤该 socket, 将会受到影响。
如果客户端是⽤ close 函数来关闭连接,那么在 TCP 四次挥⼿过程中,如果收到了服务端发送的数据,由于客户端已经不再具有发送和接收数据的能⼒,所以客户端的内核会回 RST 报⽂给服务端,然后内核会释放连接,这时就不会经历完成的 TCP 四次挥⼿,所以我们常说,调⽤ close 是粗暴的关闭。
当服务端收到 RST 后,内核就会释放连接,当服务端应⽤程序再次发起读操作或者写操作时,就能感知到连接已经被释放了:
- 如果是读操作,则会返回 RST 的报错,也就是我们常⻅的Connection reset by peer。
- 如果是写操作,那么程序会产⽣ SIGPIPE 信号,应⽤层代码可以捕获并处理信号,如果不处理,则默 认情况下进程会终⽌,异常退出。
相对的,shutdown 函数因为可以指定只关闭发送⽅向⽽不关闭读取⽅向,所以即使在 TCP 四次挥⼿过程中,如果收到了服务端发送的数据,客户端也是可以正常读取到该数据的,然后就会经历完整的 TCP 四次挥⼿,所以我们常说,调⽤ shutdown 是优雅的关闭。
但是注意,shutdown 函数也可以指定「只关闭读取⽅向,⽽不关闭发送⽅向」,但是这时候内核是不会发送 FIN 报⽂的,因为发送 FIN 报⽂是意味着我⽅将不再发送任何数据,⽽ shutdown 如果指定「不关闭发送⽅向」,就意味着 socket 还有发送数据的能⼒,所以内核就不会发送 FIN。
四、什么情况会出现三次挥⼿?
当被动关闭⽅(上图的服务端)在 TCP 挥⼿过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第⼆和第三次挥⼿就会合并传输,这样就出现了三次挥⼿。
然后因为 TCP 延迟确认机制是默认开启的,所以导致我们抓包时,看⻅三次挥⼿的次数⽐四次挥⼿还多。
什么是 TCP 延迟确认机制?
当发送没有携带数据的 ACK,它的⽹络效率也是很低的,因为它也有 40 个字节的 IP 头 和 TCP 头,但却没有携带数据报⽂。为了解决 ACK 传输效率低问题,所以就衍⽣出了 TCP 延迟确认。
TCP 延迟确认的策略:
- 当有响应数据要发送时,ACK 会随着响应数据⼀起⽴刻发送给对⽅
- 当没有响应数据要发送时,ACK 将会延迟⼀段时间,以等待是否有响应数据可以⼀起发送
- 如果在延迟等待发送 ACK 期间,对⽅的第⼆个数据报⽂⼜到达了,这时就会⽴刻发送 ACK