HTTP 拾遗——TCP建立连接为什么需要三次握手

2020年3月21日 | 作者 Siran | 2300字 | 阅读大约需要5分钟
归档于 计算机 | 标签 #协议

在整理HTTP 协议的时候,同事问我为什么TCP传输层协议在建立连接的时候需要三次才能建立起一个真正的可靠连接,可是为什么是三次呢,不可以是两次,四次等等呢。 报着这个问题,查阅了一番资料,故记录总结。

三次握手

试想一个问题:浏览器发送一个报文过去并且报文没有丢失,但是因为在网络上发生滞留,导致延迟。 连接释放以后的某个时间,服务端才达到接收到这个报文,但是这个报文已经失效了。服务端误以为A再次发出的一个新的连接请求,于是服务端就向浏览器又发出确认报文,表示同意建立连接。 那么这个时候浏览器并没有发出建立连接的请求(请求过时了),所以不会向服务端发送报文,服务端则没有收到报文就会一直等待,这样就会浪费很多资源。

问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。

假如没有三次握手,浏览器和服务端要进行通信:

  • 第一次:首先浏览器发送一个(SYN)到服务端,意思是浏览器要和服务端建立连接进行通信;
    • 如果是只有一次握手的话,这样肯定是不行的,浏览器压根都不知道服务端是不是收到了这个请求。就如上面说的问题一样
  • 第二次服务端收到浏览器要建立连接的请求之后,发送一个确认(SYN+ACK)给浏览器,意思是收到浏览器的消息了,服务端这里也是通的,表示可以建立连接;
    • 如果只有两次通信的话,这时候服务端不确定浏览器是否收到了确认消息,有可能这个确认消息由于某些原因丢了。
  • 第三次:浏览器如果收到了服务端的确认消息之后,再发出一个确认(ACK)消息,意思是告诉服务端,这边是通的,然后浏览器和服务端就可以建立连接相互通信了;
    • 这个时候经过了三次握手,浏览器和服务端双方确认了两边都是通的,可以相互通信了,已经可以建立一个可靠的连接,并且可以相互发送数据。
  • 第四次:这个时候已经不需要服务端再发送一个确认消息了,两边已经通过前三次建立了一个可靠的连接,如果再发送第四次确认消息的话,就浪费资源了。
    • 如果第二个报文段服务端发出的(SYN+ACK)分别发送的话,也是可以理解为四次,但是被优化了,一起发送了。

超时重传机制

  1. 如果第一个包,浏览器发送给服务端请求建立连接的报文(SYN)如果丢掉了,浏览器会周期性的超时重传,直到服务端发出确认(SYN+ACK);
  2. 如果第二个包,服务端发送给浏览器的确认报文(SYN+ACK)如果丢掉了,服务端会周期性的超时重传,直到浏览器发出确认(ACK);
  3. 如果第三个包,浏览器发送给服务端的确认报文(ACK)如果丢掉了:
    • 浏览器在发送完确认报文之后,单方面会进入ESTABLISHED的状态,服务端还是SYN_RCVD状态

    • 如果此时双方都没有数据需要发送,服务端会周期性的超时发送(SYN+ACK),直到收到A的确认报文(ACK),此时服务端也进入ESTABLISHED状态,双方可以发送数据;

    • 如果浏览器有数据发送,浏览器发送的是(ACK+DATA),服务端会在收到这个数据包的时候自动切换到ESTABLISHED状态,并接受数据(DATA);

    • 如果这个时候服务端要发送数据,服务端是发送不了数据的,会周期性的超时重传(SYN+ACK)直到收到浏览器的确认(ACK)服务端才能发送数据。


三次握手牵扯到的状态转换

  • LISTEN 表示socket已经处于listen状态了,可以建立连接;

  • SYN_SENT 表示socket在发出connect连接的时候,会首先发送SYN报文,然后等待另一端发送的确认报文(ACK),表示这端已经发送完SYN报文了;

  • SYN_RCVD 表示一端已经接收到SYN报文了;

  • ESTABLISHED 表示已经建立连接了,可以发送数据了。


四次挥手

说完TCP建立连接的时候为什么是三次,相对的就会想到为什么断开连接的时候是需要四次呢,而不是三次,五次等等呢;

本质的原因是tcp是全双公的,要实现可靠的连接关闭,浏览器发出结束报文FIN,收到服务端确认后浏览器知道自己没有数据需要发送了,服务端知道浏览器不再发送数据了,自己也不会接收数据了,但是此时浏览器还是可以接收数据,服务端也可以发送数据;当服务端发出FIN报文的时候此时两边才会真正的断开连接,读写分开。


四次挥手牵扯到的状态装换

  • FIN_WAIT_1 表示在等待另一方的FIN报文,和FIN_WAIT_2的区别是,FIN_WAIT_1表示socket现在要主动关闭连接,在发送完FIN报文后socket进入FIN_WAIT_1状态,当收到另一方发送FIN的ACK之后立即进入FIN_WAIT_2状态;
  • FIN_WAIT_2 同上,此时需要做的事情是可能还会接收数据,然后等待另一方的FIN;
  • TIME_WAIT 存在主动关闭的一方,表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL(Max Segment Lifetime))后即可回到CLOSED可用状态了,需要等一段时间时原因是网络是不可靠的,不能保证这个ACK发送成功了,如果失败了,对端会超时重传FIN;
  • CLOSING 表示在发送FIN之后,没有收到对方的ACK,而是收到了对方的FIN,这中情况很少见,只有在两端几乎同时关闭同一个socket的时候才会出现CLOSING状态;
  • CLOSE_WAIT 表示收到对方的FIN之后,回给对方ACK,此时处于CLOSE_WAIT状态,等待关闭,要看自己是否还有数据要发送;
  • LAST_ACK 表示收到对方的FIN之后,回给对方ACK,然后自己也要关闭发送FIN,等待另一方的ACK时候的状态;
  • CLOSED 这个状态表示连接已经断开。