TCP连接的建立与断开

TCP是面向连接的。“三次握手、四次挥手”形象的描述了TCP连接和建立的过程,这一篇文章我们就讲一讲TCP连接的建立与断开。

TCP连接的建立

假设运行在一台主机上的进程相与另一台主机上的一个进程建立一条连接。客户应用进程首先通知客户TCP,它想建立一个与服务器上的某个进程之间的连接。客户中的TCP会用以下方式与服务器中的TCP建立一条TCP连接:

第一次握手

客户端的TCP首先向服务器的TCP发送一个特殊的TCP报文段,该报文段不包含任何的应用层数据,但是报文段的首部中的一个标志位(即SYN比特)被置为1。因此这个特殊的报文段被称为SYN报文,另外客户端随机选择一个初始序号(client_isn),并且将该序号放置在该起始的TCP SYN报文段的序号字段中。该报文段被封装在一个IP数据报中,并发送给服务器。

第二次握手

一旦包含TCP SYN报文段的IP数据保护到达服务器主机,服务器会从该数据保护中提取出TCP SYN报文段,为该TCP连接分配缓存和变量,并向该客户TCP发送允许连接的报文段,这个连接也不包含应用层数据。但是,在报文段的首部却包含3个重要的信息。首先,SYN比特被置为1。其次,该TCP报文段的确认字段被置为client_isn+1。最后,服务器选择自己的初始序号(server_isn),并将其放置到TCP报文段首部的序号字段中。这个允许连接的报文段实际表明了:“我收到了你发起建立连接的SYN分组,该分组带有初始序号client_isn。我同意建立该连接。我自己的初始序号是server_isn”。允许连接的报文段有时被称为SYNACK报文段

第三次握手

在收到SYNACK报文段后,客户端要给该连接分配缓存和变量。客户主机则向服务器发送另一个报文段,这最后一个报文段对服务器的允许连接的报文段进行了确认(该客户通过将值server_isn+1放在TCP首部中的确认字段来完成此项工作)。因为连接已经建立了,所以该SYN比特被置为0,ACK比特置为1,该报文称为ACK报文。该三次握手的第三个阶段可以在报文段中携带客户到服务器的数据。

一旦完成了这三个步骤,客户端和服务器之间就可以相互发送包含数据的报文段了,在以后的每个报文段中,SYN比特都被设置为0.

TCP连接的断开

天下没有不散的宴席,对于TCP连接也是这样。参与TCP连接的两个进程中的任何一个都可以终止该连接。当连接结束后,主机中的”资源”(即缓存和变量)将被释放。

TCP连接断开的过程大概是这样的。如上图所示,我们以客户端主动要求断开连接为例:客户端向服务器发送FIN标志比特置为1的特殊报文标志自己想要断开连接,服务器收到该报文之后,则发送ACK比特置为1的报文表示确认,之后服务器接着发送自己的终止报文段,其FIN比特被置为1.最后客户端对服务器的终止报文段表示确认。此时,在两台主机上用于该连接的所有资源都被释放。

TCP状态变迁

客户端TCP状态变迁

  • CLOSED
    客户端的起始状态是CLOSED。
  • SYN_SENT
    当应用层程序发起向某一服务器进程通信的指令时,客户端TCP将向该服务器进程发送SYN报文段,发送出去之后,客户端TCP进入到SYN_SENT状态。
  • ESTABLISHED
    客户端TCP就需要接收服务器TCP发送的SYNACK报文段,并且客户端TCP向服务器发送ACK报文段表示确认。之后客户端TCP进入到ESTABLISHED状态。处于这个状态后客户端与服务器就可以进行正式通信了。
  • FIN_WAIT_1
    现在假设客户端提出断开连接,客户端向服务器发送一个FIN比特置为1的特殊报文段,向服务器TCP表明自己需要断开连接。发送完成后,客户端TCP就进入了FIN_WAIT_1状态。在这个状态中客户端TCP等待服务器端发来的ACK确认报文。
  • FIN_WAIT_2
    当客户端TCP接收到了服务器的ACK之后,客户端TCP就进入了FIN_WAIT_2状态了。在这个状态中客户端TCP等待服务端发送自己的FIN报文段。
  • TIME_WAIT
    当客户端TCP接收到了服务器端TCP的FIN报文段之后,客户端TCP就会向服务器端TCP发送ACK报文以表示确认。这个最后的确认报文发送完成了之后,客户端TCP就进入了TIME_WAIT状态。
  • CLOSED
    在TIME_WAIT状态,客户端TCP需要等待两倍的报文段的最大生存时间(Double Maximun Segment Life),在Linux上报文段的最大生存时间通常是30秒,两倍的MSL就是一分钟,也就是60秒。60秒之后,客户端TCP重新回到CLOSED状态。

因为TCP连接是双向的,所以在关闭连接的时候,两个方向各自都需要关闭。先发FIN包的一方执行的是主动关闭;后发FIN包的一方执行的是被动关闭。主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留两倍的MSL时长
为什么主动关闭的一方不直接进入CLOSED状态,而是进入TIME_WAIT状态,并且停留两倍的MSL时长呢?这是因为TCP是建立在不可靠网络上的可靠的协议。例子:主动关闭的一方收到被动关闭的一方发出的FIN包后,回应ACK包,同时进入TIME_WAIT状态,但是因为网络原因,主动关闭的一方发送的这个ACK包很可能延迟,从而触发被动连接一方重传FIN包。极端情况下,这一去一回,就是两倍的MSL时长。如果主动关闭的一方跳过TIME_WAIT直接进入CLOSED,或者在TIME_WAIT停留的时长不足两倍的MSL,那么当被动关闭的一方早先发出的延迟包到达后,就可能出现类似下面的问题:

1
2
旧的TCP连接已经不存在了,系统此时只能返回RST包
新的TCP连接被建立起来了,延迟包可能干扰新的连接

不管是哪种情况都会让TCP不再可靠,所以TIME_WAIT状态有存在的必要性。

服务端TCP状态变迁

  • CLOSED
    同样的服务器端TCP初始时是CLOSED状态
  • LISTEN
    服务器应用程序创建一个监听套接字,这时服务器TCP监听主上的某一个端口,等待客户机上的TCP的连接。此时的TCP处于LISTEN状态。
  • SYN_RCVD
    当接收到客户TCP发来的请求连接的SYN报文之后,服务端TCP为该连接分配缓存和变量,然后SYNACK报文至客户端TCP,发送成功后,服务器端TCP就进入了SYN_RCVD状态。
  • ESTABLISHED
    在SYN_RCVD状态的服务端TCP下一步需要接收客户端TCP发送过来的ACK报文,待成功接收到了这个ACK报文之后,服务端的TCP就进入到了ESTABLISHED状态了。当进入ESTABLISHED状态之后,服务端TCP就可以与客户端TCP进行正式通信了。
  • CLOSE_WAIT
    处于ESTABLISHED状态的服务端TCP如果收到客户端TCP的FIN标志位设为1的报文段,这表明客户端需要发送的数据发送完了,客户端需要断开TCP连接,这时服务器TCP就要发送一个ACK表示确认,告诉客户端TCP:”我知道你要断开了,等我准备好哈”。服务器发往客户端的这个ACK报文发送成功之后,服务器TCP就进入到了CLOSE_WAIT状态了。
  • LAST_ACK
    当服务器TCP也准备好了断开连接的话,服务器TCP会向客户端TCP发送自己的FIN报文,向客户端表明自己也已经转备好了。发送完了这个FIN之后,服务器TCP就进入到了LAST_ACK状态了。
  • CLOSED
    当服务器接收到客户端TCP发送过来的最后一个ACK之后,服务器就可以关闭连接,释放与该连接有关的变量和缓存。这一切完成之后,服务器TCP又进入到了CLOSED状态。