TCP连接的建立与关闭
今天在某个技术群里水群的时候,忽然看到一个有意思的比较有意思的问题:HTTP请求中的TCP连接是由谁来关闭的,服务端还是客户端?然后追问了一下,实际表达的意思是在一个TCP连接中,发出第一个关闭请求的是服务端还是客户端?有什么场景会出现服务端先关闭的情况。
然后今天有空的时候抓了一下包,来专门测试了一下这种情况。
TCP连接的全双工性
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。
首先要明确的一个概念是TCP的连接是基于全双工连接的,也就是说在同一时刻通信的两方能够进行两个方向上的双向传输。明确了这个概念之后再来考虑TCP连接关闭的情况。
TCP连接的建立–三次挥手
在TCP建立连接的开始,显示客户端发送一个报文段,来向服务端发起连接请求,并将TCP报文首部的SYN字段设置为1.
第二步是服务器主机发送一个ACK报文段来确认TCP的连接允许,这个ACK报文段中SYN比特仍然被设置为1.
最后一步是客户端发出的对于服务端发送的连接允许的ACK确认,用来确认已经接收到服务端主机的报文,这一步中将SYN设置为0。
TCP连接的关闭–四次挥手
在标准的模式中,TCP关闭连接首先是由客户端发出一个含有FIN比特并且设置为1的报文段,用来通知服务端将要关闭连接。
随后服务端向客户端发送一个ACK报文,执行被动关闭。
一段时间后接收FIN报文段的服务端一端将会调用close关闭套接字,TCP将发送一个FIN。
接收最终FIN的端,即最早发送FIN的客户端端将会发送一个ACK确认。
上面提到的TCP连接关闭是标准状态的连接关闭,也就是由客户端申请的关闭连接。
实际抓包测试
在下午的时候,我抓了一下在TCP连接的建立和关闭过程中的TCP数据包,查了一下在关闭的时候究竟怎么关闭的情况。然后发现两种情况都是可能出现的,也就是说存在服务器主动发起关闭连接的情况和客户端主动发起关闭连接的情况。在测试用例前,我先ping了一下 www.baidu.com, 得到了百度的ip119.75.213.61。
TCP连接的抓包测试
我是通过在已经开启的浏览器中,打开新建标签页访问119.75.213.61对百度进行连接的方式打开的。能够很清楚的看到在TCP开始时共有三个TCP包,分别承载SYN和ACK来建立连接。
当TCP成功建立连接,浏览器将会发送一条HTTP指令,申请服务器资源访问:GET / HTTP/1.1。
之后的传输是将服务器的资源传输到浏览器上。
TCP保证连接存活的机制
在发送第一个HTTP请求的时候,在报文段头部包含了一些HTTP的设置,其中通过对Connection的设置,来进行持续连接的设置。
将Connection设置为keep-alive来确保打开的连接是一个持续连接,也就是请求完一个对象之后不关闭连接。
在连接开启过程中,探测到我的主机周期性的向服务端发送TCP Keep-Alive报文,并且等待服务端的确认来确保TCP连接的开启。
客户端主动关闭TCP连接
这篇文章的主要目的是为了讨论客户端或者服务端主动关闭TCP连接的情况,然后抓包测试过程中,我发现仅通过在浏览器对百度的请求条件下就能够进行这两种测试。
第一种条件是我主动关闭浏览器,然后我这边将会向服务端发送FIN报文,这种状况就是正常的TCP连接关闭的状态。
在整个关闭过程中,浏览器发送第一个FIN包:Seq=345 ACK=31994,服务端接收并且发送一个ACK报文:Seq=31994 ACK=346。然后紧接着服务端发送一个对于服务端关闭连接的FIN报文,在这里能够看到的是服务端对这个报文的改变是刚刚传输的ACK报文段中修改了FIN状态:Seq=31994 ACK=346,浏览器接收到这个报文段,然后发送一个确认ACK:Seq=346 ACK=31995。连接就正式关闭了。
在这整个过程中能看到的是发起关闭连接申请的是浏览器,服务器对其进行相应。
服务端主动关闭连接
在关闭状态还存在一种非常奇妙的情况,也就是服务端主动关闭连接。
上一个测试例子,我是访问百度之直接关闭浏览器,在这一种情况下,我关闭的仅仅是标签页,也就是浏览器仍然开启,TCP连接建立和前期传输两者间并没有区别。
服务端是在60s左右对我发送了一个FIN报文,但是,实际上我在41s左右的时刻就已经关闭了标签页了,也就是说,在我关闭标签页之后,浏览器仍然维持着这一条TCP连接,知道服务器检测到我长久不使用这个连接,才开始发送报文将连接关闭。
在这种情况的时候,服务端会发送一个FIN报文段:Seq=32039 Ack=356,然后浏览器对这个报文段进行相应,发送一个FIN的报文段:Seq=356 Ack=32040,等服务器接收到之后,会发送一个ACK报文段:Seq=32040 Ack=357,连接关闭,当然这里关闭的连接,是服务端对客户端的连接,在这个状态出现之后,仍然能够看到浏览器向服务端发送的一些保持TCP连接的报文。
所以,在服务端主动关闭TCP连接的情况下,TCP的关闭阶段只会进行三次包的传输
,因为要保证用户可能存在的继续使用连接的情况,所以服务端不会向用户发送关闭确认的ACK,而是继续保持该连接一段时间,当达到某段时间后,如果依然没有进行过TCP的相应,那么服务端会直接发送RST报文段,将连接复原。
在整理这篇文章的时候,忽然考虑到服务端是怎样检测浏览器已经关闭了标签页的,然后打开了一个百度的页面进行测试。结果发现,哪怕是没有关闭标签页,持续开启百度页面,服务器依然会在一定时间后向我的浏览器发送FIN报文,然后进入上述状态,直到发送RST报文复原TCP连接。这样之后会在我再次访问当前百度页面的时候进行重新的TCP连接。
这个状态是与我是否关闭标签页没关系的,只要浏览器不关闭,这个打开的TCP连接只会在超时未响应或者重复开启TCP连接的时候进行复原,而这个超时连接时间应该是服务端进行设置的。