发出Web请求到服务器都经历了什么(五)访问结束TCP挥手

109 阅读4分钟

当浏览器收到数据之后,收发数据的过程就结束了。TCP连接中客户端和服务端都能主动发起关闭请求。Web使用的HTTP协议规定,当Web服务器发送完响应消息之后,应该主动执行断开操作,因此Web服务器会首先调用close来断开连接。但HTTP1.1中,一次连接中收发多个请求和响应,在这种情况下,所有数据都请求完成之后,浏览器会主动触发断开连接操作。

HTTP1.0中,有content-length,已知长度的情况下客户端会主动调用close;如果不知道长度由浏览器主动断开。

HTTP1.1中,有content-length,已知长度的情况下客户端会主动调用close;没有content-length但是带Transfer-encoding:chunked,因为每块开始会标识当前块长度,所以客户端可以知道body的长度,还是由客户端主动断开;不带Transfer-encoding:chunked由浏览器主动断开。

TCP挥手(以客户端主动断开为例)

  1. 客户端开启FIN控制位,确认要结束会话。

FIN Finish结束; ACK Acknowledgement确认;

在前面HTTP请求响应的时候,序号和确认号不断递增,这里假设序号为xxxx,确认号为yyyy。

调用Socket中的close/shutdown。套接字会记录下断开操作的相关信息。

  1. 服务端发送ACK进行确认。

服务端这里的序号是对方的确认号即yyyy,服务器的确认号为对方的序号+1,即xxx+1。但这时客户端不会立刻关闭,因为可能服务器还有未发送完成的数据。

收到后,接收方会将自己的套接字标记为断开操作状态,向服务器进程发出EOF(end of file)。

如果客户端发送的shutdown,那么服务端还是可以继续向客户端发送任意多的消息,客户端也可以接收,直到服务端发送FIN确认后才关闭,也叫半关闭。如果客户端发送的close,这时服务器无论调用send还是recv都会失败。

  1. 服务端发送FIN+ACK

等服务端确认发送完成以后,会向客户端发送FIN+ACK来进行最后的确认。服务端这里的序号是对方的确认号即yyyy,服务器的确认号为对方的序号+1,即xxx+1。与步骤2相同。因为这里没有一来一回,只是多了控制位FIN进行确认步骤。

调用Socket的close。 4. 客户端发送ACK。

客户端得到最终结束确认以后,会发送ACK进行确认。此时序号为服务端的确认号,确认号为服务端的序号+1。

如果最后客户端返回的ACK号丢失了,结果会如何呢?这时,服务器没有接收到ACK号,可能会重发一次FIN。

到此,客户端和服务器的通信就全部结束了。

删除套接字

和服务器的通信结束之后, 用来通信的套接字也就不会再使用了,这时我们就可以删除这个套接字了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。等待这段时间是为了防止误操作。具体等待多长时间,这和包重传的操作方式有关。

误操作

例如:因为步骤四客户端丢失ACK,服务端重发FIN,但是此时客户端已经删除套接字了。删除套接字后,套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。这时可能有应用程序创建套接字时被分配到了该端口。有可能正好收到了这个由服务器重发的FIN。新套接字就会断开操作。

常见问题

为什么要四次挥手

TCP是全双工通信,当一方收到FIN信息时就知道接下来对方就不会再发送数据了,但是一方没什么要发送的,另一方可能还有想发送的,这在TCP协议中被称为半关闭,half-close即shutdown。中间服务器要发送ACK以及FIN+ACK两步是因为可能存在未发送完毕的数据。

参考:

  1. 四次挥手与Socket API