TCP三次握手和四次挥手

一、概述

  TCP的三次握手和四次挥手是一个老生长谈的问题,当然也是在面试中被问到的一个大概率的问题。之所以被经常问到是因为这个知识点是可以区别初级、中级、高级开发者的一个分水岭,可以达到筛选应聘者的目的。另外原因是公司可能真的会用到这方面的内容,例如:有些时候我们会选用websocket作为数据传输工具,在这种场景下如果想要做的严谨,就需要模拟TCP的三次握手以保证数据传输能够准确无误的到达。例如在物联网中:app和安卓板中间件通讯,中间件又和硬件通讯。

  在说三次握手和四次挥手之前先来了解一些基础的概念:SYN、ACK、seq、ack、FIN

  1.确认ACK:占1位,仅当ACK=1时确认号ack才有效。等于0时确认号无效

  2.SYN:建立连接时用于同步序列号。当SYN=1,ACK=0时代表是一个请求报文。如若服务端同意连接则服务端的响应报文SYN=1,ACK=1。因此SYN=1是一个连接请求或者请求接受报文。SYN这个标志位只有在TCP建立连接的时候才会被置为1,握手完成后SYN=0.

  3.seq:序列号,占四个字节。用来标记数据段的顺序,TCP把连接中的所有字节都编上一个序号,第一个字节的序号右本地随机产生。给字节分配序号后就给每一个报文段分配一个序号。序列号seq就是这个报文段中的第一个字节的编号。

  4.ack:代表确认号,占四个字节,表示接收到对方下一个报文段的第一个数据字节的编号。当前报文段最后一个字节编号+1=确认号

  5.FIN:用来释放一个连接。FIN=1表示此链接的发送方已经发送完毕,要求释放连接。

  废话不多说,接下来直接看三次握手和四次挥手的原理。

二、三次握手

  TCP的三次握手是指建立一个TCP连接时需要客户端和服务器总共发送三个包。进行三次握手的作用是为了确认客户端和服务端的接收和发送能力是否正常,并指定自己的序列号为以后的可靠传输做准备。

  刚开始的时候客户端处于Closed状态,而服务端处于Listen状态。

  第一次握手:客户端发送一个SYN报文给服务端,并指定SYN=1,seq=x。此时客户端处于SYN_SEND状态

  第二次握手:服务端收到客户端发送的SYN报文,表示服务端已经收到客户端的请求。并发送一个自己的SYN报文给客户端,其中SYN=1,seq=y,ACK=1,ack=x+1。此时服务端进入SYN_REVD状态。

  第三次握手:客户端接收到服务端发送过来的SYN报文,向服务端发送一个ACK报文。SYN=1,ACK=1,seq=x+1,ack=y+1.随后客户端进入ESTABLISHED状态,服务端收到客户端发送过来的ACK报文后也会进入EXTABLISHED状态。

  下面给出一个手写的流程示意图:

  

  分析完三次握手以后我们分析一下三次握手可能面临的问题:

  1.1为什么需要三次握手?

    想弄清楚这个问题我们就需要知道三次握手的目的是什么?

    第一次握手:客户端发送SYN报文,服务端收到报文。服务端就可以确认自己的接收能力和客户端的发送能力没有问题。

    第二次握手:服务端发送SYN报文,客户端接收到服务端发送过来的报文。客户端就可以确认,自己的接收、发送能力和服务端的接收和发送能力都没问题。但是此时服务端并不知道客户端的接收能力是否正常。

    第三次握手:客户端发送ACK报文给服务端,服务端接收报文。服务端就可以确认客户端和服务端的发送和接收都没有问题。之后就可以正常的通讯了。

    综上所述,TCP的三次握手就是要让服务端和客户端都确认对方的接收和发送都没有问题,以便为之后的数据可靠传输做准备,所以需要三次握手。

  1.2.两次握手行不行?

    从1.1的第二步我们知道如果是两次握手,虽然客户端知道自己和服务端的发送接收能力都没问题,但是问题是服务端不知道客户端的接收能力是否正常。举个栗子:客户端向服务器端发送了一个SYN报文,由于这个报文在网络中的某个节点上停留时间较长,客户端并没有收到服务端的确认报文,此时客户端会从新发送一条,这一条服务端正常收到,而客户端也收到了服务端的SYN报文,数据正常连接,知道数据连接关闭。但是此时客户端发送的第一条SYN报文终于到达了服务器,而服务器接收到客户端的SYN报文后,会发送自己的SYN给客户端,此时服务端就认为连接已经建立了,此时客户端忽略服务端发送过来的确认,服务端会一直等待客户端发消息,浪费资源。

  1.3.什么是半连接队列和全连接队列?

    半连接队列:服务器第一次收到客户端的SYN报文后,服务端发送自己的SYN报文给客户端,此时服务端处于SYN_REVD状态。此时双方并没有建立连接。服务器会把这种状态的连接放在服务器的一个队列中,而这个队列就称为半连接队列。ps:如果服务端发送SYN报文后未收到客户端发送过来的确认,则服务端会有一个重传机制。如果重传次数超过系统规定的最大的次数服务端就会把这个半连接从队列中移除。重传时间一般为2的幂函数。

    全连接队列:服务端会把经过三次握手的链接放入一个队列中,而这个队列就称为全连接队列。

  1.4.序列号是固定的吗?

    序列号是不固定的,且序列号是随机的。

  1.5.序列号为什么要设计成随机的?

    为了防止攻击者能够预判出三次握手的序列号,从而攻击服务器。序列号随时间变化,每个连接的序列号都是不一样的。举个栗子:三次握手中有一个非常重要的功能是客户端和服务端交换序列号,以便对方知道接下来接收到的数据如何按照序列号的顺序组装数据。如果序列号固定,那么攻击者很容易猜出后续的序列号的值。所以序列号要搞成随机的。

  1.6.三次握手过程中可以携带数据吗?

    三次握手中第一次和第二次不可以携带数据,而第三次可以携带数据。之所以第一次二次不可以携带数据是因为如果攻击者发送攻击在第一次握手的时候携带大量的数据过来,服务器就会耗费非常多的时间和内存来接收数据。(第一次握手最容易遭到攻击)

    第三次是可以携带数据的,因为双方已经建立了链接,是否携带数据其实都无所谓。

  1.7.SYN攻击是什么?

    SYN攻击是指客户端伪造大量的不存在的IP地址,并向服务器不断的发送SYN包,服务端则回复确认包,并等待客户端确认。由于客户端的大量的IP地址都是伪造的,所以服务端会不停的发送确认包,直到链接超时,从半连接队列中移除。而这些伪造的半连接会长时间的占用半连接队列,导致正常的半连接请求因为队列已满而被丢弃,从而引起网络阻塞甚至系统瘫痪。

三、四次挥手

  刚开始时客户端和服务端都处于EXTABLISHED装填。

  第一次挥手:客户端发送一个FIN报文,报文中会指定一个序列号seq=u,此时客户端处于FIN_WAIT1状态,等待服务端的确认

  第二次挥手:服务端收到收到客户端发送过来的FIN报文,并回复一个ACK报文给客户端,其中ACK=1,seq=w,ack=u+1.此时服务端处于CLOSE_WAIT状态,此时TCP处于半关闭状态。客户端在接收到服务端的确认报文后进入FIN_WAIT2状态。

  第三次挥手:服务端确认报文都发送完了,会向客户端发送一个FIN报文,FIN=1,ACK=1,ack=u+1,seq=v。此时服务端处于LAST_ACK状态。

  第四次挥手:客户端收到服务端发送过来的FIN报文后会想服务端发送一个ACK报文,ACK=1,ack=v+1,seq=u+1,此时客户端进入TIME_WAIT状态,过2MSL时客户端进入CLOSE状态,服务端收到客户端发送的ACK报文后会直接进入CLOSE状态。

  四次挥手示意图:

   3.1.为什么要四次挥手

    在服务器收到客户端发送过来的FIN报文时,有可能不会理解关闭Socket,因为有可能服务端还有报文未发送出去,所以只能先给客户端发送一个ACK的报文,告诉客户端我已知道。等服务端把所以报文都发送完了才会发送FIN报文给客户端,因此这两条报文不能同时发送,所以需要经过四次挥手。

   3.2.解释一下2MSL等待

    MSL:指的是报文最长生命周期,它是任何报文在网络上存在的最长时间,一旦超过这个时间报文将被丢弃。

   3.3.第四次挥手释放连接时等待2MSL的意义

    为了保证客户端发送的最后一条ACK报文能够顺利的到达服务端。因为在这个过程中,第一发送的报文有可能丢失,从而导致LAST_ACK状态下的服务端收不到FIN_ACK报文。如果再规定的时间服务端收不到就服务端就会从新发送FIN报文,最后保证客户端和服务端都能够正常的关闭。假设不使用2MSL,一旦客户端发送的最后一条ACK报文丢失,而客户端又处于CLOSE状态,服务端就永远也收不到客户端发送的最后一条ACK报文,此时服务端会一直处于LAST_ACK状态,而无法正常关闭。

   3.4.为什么TIME_WAIT要经过2MSL才能返回CLOSED状态

    理论上来讲在客户端处于TIME_WAIT状态后是可以立即进入CLOSED状态的,但是由于网络的不可靠性,有可能到只客户端发送给服务端的ACK包丢失,所以在TIME_WAIT状态下等待2MSL是为了服务端在规定时间内未收到ACK报文后会从新向客户端发送FIN报文,而客户端会再次把ACK报文发送给服务端。宗旨就是保证服务端能够正常的关闭。

原文地址:https://www.cnblogs.com/tony-yang-flutter/p/12492803.html