TCP学习过程中的疑问与解答

TCP连接问题与解答

本篇博客罗列出了我学习TCP过程中产生的疑问以及相应的解答,我假设阅读本博客的人已经了解了基本的TCP工作机制,所以不会花大篇幅系统的讲解TCP具体的工作流程,而是集中于解决我产生的疑问。

全文共包括以下四个部分:
   1 TCP为什么需要三次握手?
   2 TCP握手时的初始序号为什么要随机选择?
  3 TCP关闭连接时为什么需要四次握手?
  4 整个过程中的各种异常以及处理方式


1. TCP为什么需要三次握手

首先简单回顾一下三次握手的过程:
这里写图片描述
状态转移说明:
客户端
    a. 当主动调用connect()时会进入SYN-SENT阶段:这一阶段,由于上层调用了connect(),于是发送SYN并等待ACK。

    b. SYN_SENT收到ACK后就进入了ESTABLISHED阶段:进入这一阶段即可开始正常传输数据了。

服务器端:
    a. 调用listen()进入LISTEN状态:这一状态被动的等待来自客户端的连接。

    b. 在LISTEN阶段收到SYN后进入SYN_RCVD阶段:这一阶段会将这一连接放入 未完成的连接 队列中,等待ACK。如果出现大量处于SYN_RCVD阶段的线程,可以考虑遭受到了恶意攻击。

    c. SYN_RCVD收到ACK后就进入了ESTABLISHED阶段:会将连接由未完成的连接移入已完成的连接队列。利用accept()可从已完成连接队列中获得一个已建立的连接。进入这一阶段即可开始正常传输数据了。

了解了三次握手的过程,让我们回到问题:为什么TCP需要三次握手?
    可以看到,服务器的连接实际是在第三次握手中建立的。那么假设只有两次握手,会出现什么情况呢?由于只有两次握手,那么服务器必须在第二次握手中回复ACK时就建立连接,设想如下情景:A发送SYN请求与B建立连接,但是由于网络的原因,这个SYN没有及时的到达B,所以,A会重发SYN,这时B收到了,回复ACK并且建立连接。巧的是,在某个时刻,第一个SYN姗姗来迟的到了,这时,B以为这是一个新的连接,所以也会回复ACK并且建立连接,但是A实际上并没有请求这个连接,所以这个请求就是一个没有客户的请求,而B却要为他维持状态,保留资源,就造成了极大的浪费。
    那三次握手是怎么解决这个问题的呢?在三次握手中,B只有收到了A的ACK才会真正的维护这个连接,所以,当第一个SYN到达时,B发送ACK+SYN给A,但是A此时明白自己没有请求这个连接,于是,并不回复ACK,这样,B就知道不用管接收到的SYN了,可以立马释放资源,从而不会出现上述问题。


2. TCP握手时的序号为什么要随机选择?

原因主要由两个:

    a. 避免两个连接之间的数据包混淆。我们知道,TCP包根据(源IP,目的IP,源端口,目的端口)这个四元组来定位,由于端口数有限,在一个端口上开的TCP连接关闭之后又重新用这个端口开一个新的TCP连接并不奇怪。那么在这种场景下,如果每次都使用相同的初始序列号,那么假设在使用第二个TCP连接的过程中,第一个TCP连接的包由于网络的原因现在才到达,这时,由于两个连接的初始序列号一致,那么使得这个无效的TCP包的序号正巧位于当前TCP连接的接收窗口内的概率大大提升,这样,就很可能将这个无效的包当作有用的包接收,那这样就出现了大问题。
    所以,通过使用完全随机的初始序列号,使得这两个连接用的序列号范围重合的概率大大减小,就能有效的避免上述问题。

    b. 使用随机序列号的第二个原因是为了安全。如果每次连接都使用相同的序列号,那么黑客将很容易猜出序列号并伪造TCP包代替你与服务端通信。而使用随机的序列号,将使得猜测难度大大提高,从而更好的保护通信的安全。


3. TCP为什么需要四次挥手?

同理,也让我们来先回顾一下四次挥手的过程:

注: MSL表示的是:Max Segment Lifetime,指报文最大生存时间,他是任何报文在网络上存在的最长时间。而客户端与服务器端是相对的概念,首先调用close()主动关闭的一方称为主动方,也即客户端。而被动关闭的一方则称为服务器端。

这个图比较详细的描述了TCP正常断开连接时的状态转移,下面做一个简单的说明:
客户端:
    a. 应用程序调用close(),发送FIN后,进入FIN-WAIT-1阶段:这个阶段一般比较少见,因为服务器端理论上会立马回复ACK,这时进入了FIN-WAIT-2阶段。

    b. FIN-WAIT-1收到ACK后进入FIN-WAIT-2阶段:这是比较常见的一种情况,客户端在等待服务器端关闭连接。但是,如果出现了大量的FIN-WAIT-2状态,则有可能是因为服务器端程序bug,程序中漏写close(),网络出现异常等导致的,应该引起注意!

    c. FIN-WAIT-2收到服务器端FIN进入TIME-WAIT阶段并发送ACK:这是一个正常关闭的TCP连接都会经历的阶段。这个阶段关键的在于等待2MSL。这样做的目的有两个一是保证残留在网络中的报文不会被新的连接接收而产生数据错乱,二是保证服务器端收到了自己发送的ACK。如果ACK丢了,那服务器端将会重发FIN,这时由于客户端的连接还没有关闭,依然可以回复ACK,从而使得连接正常关闭。如果不等待直接关闭连接,当服务器端重发FIN时,将会收到RST报文,这时服务器会以为是连接错误而把问题报告给高层,虽然这样不会出现数据的丢失,但是却造成了假的异常,显然是不合理的。由于等待的目的之一是要使得ACK丢了之后可以重传,那这个时间至少要等于ACK从客户端到服务器端(经过这个时间,服务器可以检测出丢包从而重传FIN)+FIN从服务器端到客户端,所以,采取2MSL是比较合理的选择。
    有一点需要注意的是,如果服务器上会出现大量的短链接,使用2MSL的等待时间可能会导致大量的资源占用,应根据实际情况适当调小等待时间。

    d. 经过等待时间后就进入CLOSED状态:正常关闭该TCP连接,文件描述符,端口等资源可以被重新利用了。

    e. CLOSING:这是一个比较特殊的状态,出现于两边几乎同时发送FIN的情况,这时,在FIN-WAIT-1的情况下没有收到ACK而收到了FIN,就进入了该状态。当该状态收到了对方回复的ACK之后,就会进入TIME-WAIT状态,最终关闭。

服务器端:
    a. 收到来自客户端的FIN后进入CLOSE_WAIT阶段:在这一阶段,回复ACK,并且等待来自上层的close()调用。如果出现大量CLOSE_WAIT阶段,应考虑是否程序中忘记主动关闭连接了!

    b. 收到上层的close()调用后发送FIN给客户端并进入LAST_ACK阶段:在这一阶段等待来自客户端的ACK确认,超时将重传FIN,收到后直接关闭连接,资源可以重新利用。

了解了这一过程,再回到我们的问题:为什么需要四次挥手?
    我们知道,TCP是面向连接的,全双工的传输协议,如果某一方在不确定对方是否还有数据传输时就擅自关闭连接,显然是不行的。所以,两次FIN告知是必须的。而当发送了FIN后,为了确保对方确实收到了这个消息,ACK回复也是必须的。综上,TCP需要四次握手。


4. 整个过程中的各种异常以及处理方式

上面的分析主要是基于正常工作情况下的TCP展开的,那我不免要想,如果某一个环节出错了TCP是怎么处理的呢?
下面将给出说明与解释:

建立连接时:
    a. 第一次握手时SYN未收到回应:多次重发,达到一定阈值后还未成功则报错。如 6s没有来自服务器的SYN分节响应,则重发SYN,24s后无响应再重发,75s后无响应则返回错误ETIMEOUT。
    b. 第二次握手时ACK+SYN未收到回应:由于建立连接请求已到达服务器,所以服务器会多次重发,如果服务器重发达到一定阈值,则从队列中移除这一连接并释放资源。
    c. 第一次&第二次握手均收到回应,将连接从未完成的连接队列移入已完成的连接队列,连接正式建立。

传输数据时:
    a. 客户端异常退出:分为两种情况,一是客户端机器关闭了,由于客户端的退出,服务器将长时间得不到来自客户端的响应与数据,当达到一定的时间后,服务器将自动关闭连接并释放资源。二是客户端进程退出了,在这种情况下,服务器发送数据给客户端时,将会收到RST包,这使得服务器将异常报告给上层调用者,
    b. 服务器异常退出:与客户端异常退出类似。

关闭连接时:
    a. 客户端FIN未收到回应:重发,一定时间未收到响应,直接退出(CLOSED)。
    b. 客户端的FIN收到了ACK,处于FIN_WAIT_2,但迟迟未收到来自服务器端的FIN,超时后直接关闭(CLOSED)。
    c. 服务器端处于CLOSED_WAIT阶段但一直未收到上层的close()调用,很可能由于程序未写close()而导致,同样超时会直接CLOSED,但是这个超时时间一般很长(如43200S),所以,这种情况很容易导致服务器奔溃,要特别留意!
    d. 服务器FIN未收到回应:重发。如果由于糟糕的网络环境等因素而导致客户端已经关闭了连接但服务器还在重发FIN的话会导致客户端返回一个RST包,从而被认为这是一个错误报告给上层。


补充说明:

  由于文中多次提到了RST包,于是简单列举一下TCP头部中六个标志位SYN,ACK,FIN,RST,URG,PSH的作用:

  • SYN:表示同步序号,用来建立连接。
  • ACK:此标志表示TCP头部中的ACK Num有效。
  • FIN: 置1时表示发端完成发送任务。用来释放连接,表明发送方已经没有数据发送了。
  • RST:这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包;通俗点讲,就是重建连接(or 复位连接),意思就是说这个连接不可用了,需要关闭连接,重新建立连接。
  • PSH:这个标志位表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队。
  • URG:此标志表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据。

猜你喜欢

转载自blog.csdn.net/qq_41854763/article/details/79632343