欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

详解TCP三次握手/四次挥手

程序员文章站 2022-06-14 11:18:01
...

详解TCP三次握手/四次挥手

连接管理机制:
建立连接是可靠传输的前提,唤起对方的注意

客户端与服务器之间没有明确的界限,
谁先发起请求,谁就是客户端

三次握手:

三次握手的目的是双方确认自己与对方能够正常发送和接收数据,即建立可靠的TCP连接,同时同步双方的序号和确认号并交换TCP窗口大小信息。
三次握手(Three-way Handshake),指在建立TCP连接时,需要通信双方一共发送三个数据包。
在socket编程中,客户端执行connect()时,将触发三次握手
详解TCP三次握手/四次挥手
- 第一次握手:客户端向服务器发送带有SYN标志的TCP报文,表示建立连接请求。
- 第二次握手:服务器收到SYN报文段后,需要对这个SYN报文段进行确认,同时自己还要发送SYN请求信息,来与客户端建立连接。因此,服务器将SYN+ACK标志放到一个报文段中,一并发给客户端
- 第三次握手:客户端收到服务器发来的SYN+ACK报文后,需要进行对服务器的连接请求进行确认,即发送一个带有ACK的报文段给服务器,发送完毕后,客户端和服务器都进入ESTABLISHED状态。

这里我们要重点理解两个状态:
1.LISTEN:listen()函数调用后,服务器处于被动状态,等待客户端来建立连接。(好比手机开机,信号良好)
2.ESTABLISHED:通信双方完成了三次握手,处于稳定连接的状态(好比电话拨通,等待说话),进入这种状态后双方就能够发送数据了

♥♥♥ 为什么不是两次握手,而是三次握手?

假设有这么一种情况,客户端向服务器发送了建立连接的请求,但是由于网络原因长时间滞留在某处,此时客户端迟迟收不到来自服务器的确认报文,于是会重传一次连接请求,重新建立连接。若一段时间后,第一次客户端发送的请求报文到达了服务器,这本来就是一个早已经失效的报文段,但是服务器却不知情,以为是客户端要来建立新的连接,就会回应一个确认报文同意建立连接。
若采用的是两次握手机制,此时连接就稳定建立了,由于客户端并没有发送连接建立请求,也就没有数据要向客户端发送,而服务器会一直等待对端发来数据,这样就会浪费服务器资源。
三次握手机制就会避免上述问题的发生,客户端并不想要建立连接,即使收到服务器的报文,也不会发送确认报文,那么服务器收不到来自客户端的确认,也就懂了对方的意思。

四次挥手:

是通信双方断开连接的过程。
详解TCP三次握手/四次挥手

  • 第一次挥手:主机1调用close()后,向主机2发送FIN报文,表示它已经没有数据要发送了,进入FIN_WAIT1状态,但这时主机1还是可以接收来自主机2的数据。
  • 第二次挥手:主机2收到主机1发来的FIN报文后,先发送一个ACK报文,表示已收到FIN,知道主机1发送完数据了,接着进入CLOSE_WAIT状态,主机1收到来自主机2的ACK后进入FIN_WAIT2状态。
  • 第三次挥手:主机2调用close()后,向主机1发送FIN报文,表示自己发送完数据,请求关闭连接,同时进入LAST_ACK状态。
  • 第四次挥手:主机1收到来自主机2的FIN后,向主机2发送ACK报文同时进入TIME_WAIT状态,主机2收到主机1的ACK后就关闭连接,主机1等待2MSL后依然未收到回复,说明主机2已经正常关闭,这时,主机1就可以放心的关闭连接了。

建立请求时先发送请求的一定是客户端,而断开时双方都有可能主动发起,任何一方执行close,即可产生挥手操作。

几个重要的状态:

  • FIN_WAIT1/FIN_WAIT1:这两个状态都是等待对方的FIN报文,FIN_WAIT1是在ESTABLISHED状态下,一端想要主动断开连接,就向对方发送 FIN进入FIN_WAIT1状态。当对方回应ACK后,则进入FIN_WAIT2状态。FIN_WAIT1状态一般比较难见到,因为不管对端什么处于情况,一旦受到FIN,内核会立马回复一个ACK报文。而FIN_WAIT2状态还有可能见到,可通过netstat命令查看。
  • CLOSE_WAIT:等待关闭,等待服务器调用close,若有大量该状态,说明未关闭socket ,处于半关闭状态,说明挥手只挥了一半
  • TIME_WAIT:当进入此状态,说明进程没有了,但连接还在,防止最后的ACK丢包,保证最后的ACK丢包后还有机会重传
    谁先尝试主动断开连接,谁就进入TIME_WAIT

当服务器端先断开连接时,我们立即再次启动服务器,会发现bind失败,因为当一个TCP连接处于TIME_WAIT状态时,我们无法立即使用该连接占用着的端口来建立一个新连接,为了解决这个问题,我们可以使用setsockopt()函数设置SO_REUSEADDR选项为1来重用这个端口号
在绑定端口号之前,加上下面的这段代码:

int opt=1;
setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

当有大量客户端和服务器进行连接时,并且每个连接的生存时间很短,但每秒都有大量的客户端来请求时,若都由服务器主动关闭连接,此时就会出现大量的TIME_WAIT连接,
当TIME_WAIT状态太多时,端口号可能不够用,

♥♥♥ 为什么客户端最后要等待2MSL?
详解TCP三次握手/四次挥手

MSL是TCP报文最大生存时间,一个数据从一端走到另一端所消耗的最长时间。这个时间在不同系统下可能不太一样,Centos7上默认为60秒,可通过cat /proc/sys/net/ipv4/tcp_fin_timeout来查看
其一,是为了防止最后的ACK丢失,还有机会重传
假设最坏情况下,客户端发给服务器的ACK确认报文在即将到达时丢失了,那么服务器在收不到ACK的情况下会重传FIN,客户端就能在这2MSL时间段内收到重传的FIN,然后自己会重传ACK,之后重启2MSL计时。
其二,是为了防止上面提到的“已失效的连接请求报文段”再次出现在本连接中。客户端发送完最后一个ACK后,再经过2MSL就可以使本连接产生的所有报文都从网络中消失,这样下一个新的连接中就不会出现这次旧的连接中

♥♥♥ 为什么连接的时候是三次握手,断开连接是四次挥手?

在建立连接时,服务器处于LISTEN状态,一旦收到来自客户端的连接请求SYN时,把ACK和SYN放在一个报文段发给对方。即双方各自向对方发送连接请求,并且双方各自向对方发送确认,一共是四次,只是其中有两次合二为一,所以是三次握手。
而关闭连接时,服务器收到客户端的FIN后,只表示对方对方不再发送数据了,但是还可以接收数据,自己也可能还有数据要发送给对方。ACK是内核收到FIN后直接发送的,而服务器的FIN是在自己无数据向客户端发送时,要用户手动调用close才会发送FIN,因此ACK和FIN无法合并

♥ 保活计时器的作用

当客户端和服务器建立了稳定的连接,而此时客户端突然出现了故障,那么服务器就不能收到客户发来的数据,那么,服务器不能一直等下去浪费资源。为此,TCP设置了一个保活计时器,服务器每收到一次客户端的请求都会重新复位这个计数器,时间通常为2小时,若2小时没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次,若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

服务器与客户端的状态变化

详解TCP三次握手/四次挥手

详解TCP三次握手/四次挥手