TCP建立和释放连接的三次握手和四次挥手

目录

  • 理清TCP socket编程和三次握手四次挥手的关系
  • 理清TCP重要报头文字
  • 理清三次握手的过程
  • 为什么建立连接需要三次握手呢
  • 理清四次挥手
  • 为什么断开连接需要四次挥手呢

理清TCP socket编程和三次握手四次挥手的关系

TCP建立和释放连接的三次握手和四次挥手_第1张图片
服务器端

  • 服务器端和客户端首先创建socket,服务器调用bind,绑定指定端口和IP。绑定端口主要是为了内核收到数据包知道交给哪个进程,绑定IP主要是因为机器可能有多个网卡,需要选择监听哪个网卡。
  • 然后服务器调用listen,将套接字转成被动套接字。
  • 接下来服务器就阻塞在accept等待客户端的请求到来。

客户端

  • 客户端调用完socket后,就需要调用connect向服务器发起连接请求。其中,发起connect,就是主动发起三次握手,三次握手完成。客户端和服务器随后就可以进行read和write数据了。因为TCP是全双工的,所以read和write过程是双向的。
  • 通信完毕后,其中的一方(服务端 or
    客户端)调用close,就会给对方发送一个FIN包,表示我不再给你发送数据了。对方读到FIN,read返回0,感知到对端的关闭连接请求,随后自己也调用close,通知对方,我也关闭了。

理清TCP重要报头文字

TCP建立和释放连接的三次握手和四次挥手_第2张图片TCP为了保证可靠的传输,设计了复杂的头部

  • 源端口和目的端口:主要是决定数据发给哪个应用程序的。
  • 序列号:序号主要是为了解决乱序问题。
  • 确认号:发送出去的包都需要确认,如果没有收到对方的确认包,就重新发送,直到送达。
  • 首部长度:TCP头部的大小。
  • 标志位:
    CWR:拥塞窗口减少。
    ECE:显示拥塞提醒回应。
    URG:紧急指针。
    ACK:设置为1时,确认号有效。
    PSH:设置为1时,接收方应尽快将这个报文交给应用层。
    RST:为1时,释放连接,重连。
    SYN:为1时,发起一个连接。
    FIN:为1时,关闭一个连接。
  • 窗口大小:主要用于流量控制。
  • 校验和:对TCP头和数据进行校验。
  • 紧急指针:当URG位为1,这个指针有效。

理清三次握手的过程

TCP建立和释放连接的三次握手和四次挥手_第3张图片

前面已经说了,调用connect函数要进行三次握手。

调用connect报错
connnect只有在成功或失败时才返回。错误返回可能有以下几种情况:

  1. 客户端发出的 SYN 包没有任何响应,则返回 TIMEOUT 错误。常见的原因时IP写错了。
  2. 对客户端的SYN响应的是RST,客户端返回CONNECTION REFUSED错误。这种原因一般是端口没有开启。
  3. 客户发出的 SYN 包在网络上有中间某个路由器引起了一个"destination unreachable"的ICMP错误,这种情况一般是客户端和服务器之间路由不通。

调用connect成功
connect调用成功,就会在底层自动执行三次握手。下面看看三次握手的具体过程:

  1. 客户端内协议栈向服务器发送SYN 数据包,告诉服务器客户端的当前序列号为i,客户端进入SYN_SENT状态。
  2. 服务器内核协议栈对客户端进行应答,应答号是i+1,同时也发送一个SYN包,告诉客户端自己的序列号是j,服务器进入SYN_RCVD状态。
  3. 客户端收到服务器的ACK后,就从connect返回,表示从客户端到服务器的单向连接建立成功,客户端进入ESTABLISHED状态。
  4. 客户端的应答包到达服务器,这时accept返回,表示从服务器到客户端的单项连接成功,服务器进入ESTABLISHED状态。

为什么建立连接需要三次握手呢

(1)为了满足在信道不可靠的情况下建立双向连接。
我们来假设网络环境不好,经常丢包,客户端发起了一个连接SYN,但是可能丢包了,也可能超时了,总之没有得到服务器的确认。这时客户端就不断的重发,服务器终于收到了SYN包,他就知道有人要连接自己,回应一个SYN和ACK。我们还是假设网络环境还是不好,那这个回应包也有可能丢失或者超时,当然也可能此时客户端已经挂了。服务器的回应经过多次重发,终于到了客户端,客户端就认为连接建立成功,因为客户端认为我的消息有去有回,此路就是通的。服务器也会等待客户端的应答消息,因为对于服务器来说,我的消息有去有回才算通了。所以,三次握手,能够以最小通信次数,确定在两个方向上的全双工信道是联通的。

(2)减少恶意伪造数据包的用户对服务器攻击。
大量的攻击数据包占用着服务器的未完成三次握手队列,使得正常需要提供服务的连接进不来。有了第三次握手,如果服务器收不到攻击数据包的ACK,就会尝试重发SYN+ACK报文,如果多次重试,都连接不上,就会关闭连接。这样就能有效降低SYN攻击带来的资源损害。
(3)减少异常情况下的服务器端资源占用情况。
在实际建立连接的过程中,因为通信双方都需要维护连接,换句话说,都要占用系统资源。因为服务器和客户端是1:n的,所以,服务器资源就显得尤为宝贵。n次握手中,我们不担心前几次丢失,我们最担心第n次丢失。因为第n次丢失会造成,连接建立发起方认为连接建立好,而连接建立确认方会认为连接没有建立好,这种认知不一致的情况,会造成一定时间的链接建立确认方的资源浪费。能解决吗?很难,因为我们本来就在一个不稳定的网络环境中,任何一个报文丢失都有可能。既然无法避免,那就转移危害。而奇数次握手能做到将危害转移到连接发起方。三次握手就是比较精简的做法。一般主动发起链接的都在客户端,进而能有效保护服务器资源。

理清四次挥手

TCP建立和释放连接的三次握手和四次挥手_第4张图片

  • 主动关闭的一方调用close,发送一个TCP的FIN包,表示要关闭连接。之后就进入FIN_WAIT_1状态。
  • 接着,接收到FIN包的一端执行被动关闭。这个FIN由TCP协议栈处理,TCP协议栈为FIN包插入一个文件结束符EOF到接收缓冲区之后,应用程序通过read感知到这个FIN包。被动接受的一方就进入到CLOSE_WAIT状态。
  • 被动方read到EOF,调用close,导致TCP发送一个FIN,被动关闭方就进入了LAST_ACK。
  • 主动方收到FIN,发起确认包ACK,主动方进入TIME_WAIT,被动方收到ACK进入CLOSED。主动方在2倍的MSL时间后,也进入了CLOSED状态。

在Linux中MSL(最长报文段寿命)的值固定为30秒,所以TIME_WAIT的时间为60秒。为什么要等待2倍的MSL呢?

(1)如果没有这个TIME_WAIT,端口可以复用于新连接了。这时被动方的FIN报文可能再次到达,可能是路由器重复发的,也可能是被动方没有收到ACK重发的。这样正常的新连接就会被重复发送的旧的FIN误关闭。保留了TIME_WAIT就可以应付重发的FIN。

(2)等待2倍MSL,其实是允许ACK丢失一次,如果一个ACK丢失,被动方重发的FIN就会在第二个MSL内到达,TIME_WAIT就可以应付。那为什么不是4MSL或者更多呢?这是因为就算是网络很差,丢包率为1%,那么连续丢两包的可能性也只有万分之一,这个概率太小了。同时,等待2MSL时间,能够保证双向上的历史数据在网络中消散。
两个方向都要FIN和ACK,所以关闭是四次挥手。

为什么断开连接需要四次挥手呢

原因是:

  • TCP不允许连接处于半打开状态时就单向传递数据,所以在三次握手建立连接时,服务器会把SYN和ACK放在一起发送给客户端,数据包中的ACK是用来打开客户端的发送通道,SYN是用来打开服务器端的发送通道。所以原本的四次握手就变成了三次。
  • 但是当处于半关闭状态时,TCP允许单向发送数据。当主动关闭方关闭连接,被动方在不调用close的状态下,可以长时间发送数据,此时连接处于半关闭状态。这一特性是TCP的双向通道相互独立导致的,也导致了关闭连接必须进行四次挥手。

由于处于半连接状态接收数据是我们在实际程序开发中经常用的特性,下面用客户回射服务器来测试处于半关闭状态接收数据,我的测试用例是,客户连续发送两个数据立马发送FIN,服务器隔五秒后再回射数据,看看客户端能不能正常收到数据。

你可能感兴趣的:(JavaWeb)