TCP协议三次握手、数据收发、四次挥手,及抓包分析

  • Post author:
  • Post category:其他


以下分析,均在linux环境下进行抓包;

抓包命令:

tcpdump -i any port 端口 -s 0 -w 文件

,使用root用户进行抓包;

并用wireshark进行包分析。

以下为博主机器下的抓包信息:

在这里插入图片描述



1.三次握手



1.1建立握手

在这里插入图片描述

三次握手连接的步骤:


  1. 首先,客户端先对服务端发起SYN包(请求建立连接)(seq = 0),发送完毕之后,客户端将自己状态设置为SYN_SENT状态;

  2. 服务端收到客户端的连接请求,将自己状态设置为SYN_RCVD,并且给客户端发送SYN包(请求建立连接),ACK包(

    该包有两层含义:①当前服务端收到了客户端发来的SYN包(计算该包的大小),②下一次客户端给服务端发送数据包的时候,包序为该ack的值)

    (seq = 0, ack = 1);

  3. 客户端收到服务端发来的SYN + ACK数据包之后,表明建立连接成功了,将自身状态设置为ESTABLISHED状态,并给服务端发送ACK数据包(

    该数据包中,①包含客户端下一次发送的包序,②服务端下一次给客户端发送数据时的包序)

    (seq = 1, ack = 1),服务端将状态设置为ESTABLISHED。


包序:在客户端和服务端中,各存在着一套包序。建立连接时,SYN(seq = 0),表明当前客户端(服务端)的包序是从0开始的,则后面发送数据包时,其包序应该大于当前的包序(seq = 0),包序也不一定是从0开始的。


ACK数据包不消耗包序,是用来确认发送的数据包是否被接收到,如果在一段时间内发送数据方没有收到ACK数据包,则会对数据进行重发。


为什么客户端还要发送一次ACK确认数据包呢?



主要是为了防止已失效的连接请求数据包(SYN)突然又传到了服务端;


  1. 正常情况:客户端发起连接请求,但因连接请求数据包(SYN)丢失而没有收到确定数据包(ACK),于是客户端再重传一次连接请求(SYN),后续收到了确认数据包(ACK),建立了连接。数据传输完毕之后就释放了连接,客户端发送了两次连接请求数据包,第一个丢失,第二个抵达了服务端,没有“已失效的连接请求数据包”。


  2. 异常情况:客户端发出的第一个连接请求数据包(SYN)并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达服务端,这是一个已经失效的数据包(SYN),但服务端收到此失效的连接请求数据包(SYN)后,误认为客户端又发起了一次新的连接,于是向客户端发出了确认数据包(ACK),同意建立连接。如果不采用三次握手,那么服务端发出确认数据包(ACK)后,新的连接就建立了;


  3. 但是目前客户端并没有发起连接请求(SYN),则不会对服务端的确定数据包(ACK)进行确认,也不会对服务端发送数据。但服务端却以为新的连接已经建立,并一直等待客户端发送数据,服务端的资源就被白白浪费了;


  4. 三次握手,就可解决上面的问题,客户端不会向服务端发送确认数据包(ACK),服务端由于收不到确认,就知道客户端并没有请求建立连接。



1.2 抓包分析

在这里插入图片描述

客户端对服务端发起连接。

				源端口			目的端口			发送数据包					数据长度
客户端			37828			9999			  SYN	seq = 0  			len = 0
服务端			9999			37828			SYN ACK seq = 0 ack = 1 	len = 0
客户端			37828			9999			ACK seq = 1 ack = 1			len = 0


连接请求数据包SYN不发送数据,但消耗一个包序。



2.数据收发

在这里插入图片描述

三次握手连接建立后,就可以正常的数据收发。

上图中,客户端先给服务端发送数据(nihaoa),该数据长度为6个字节;自三次握手建立之后,客户端维护的seq序列为1,则服务端给客户端确认应答时,ack = 1 + 6 = 7;

服务端再给客户端发送数据(wohenhao),该数据长度为8个字节;自三次握手建立之后,服务端维护的seq序列为1,则客户端给服务端确认应答时,ack = 1 + 8 = 9;

在这里插入图片描述

				源端口			目的端口			发送数据包					数据长度
客户端			37828			9999			PSH ACK	seq = 1 ack = 1  	len = 6
服务端			9999			37828			ACK seq = 1 ack = 7 		len = 0
服务端			9999			37828			PSH ACK	seq = 1 ack = 7  	len = 8
客户端			37828			9999			ACK seq = 7 ack = 9			len = 0



3.四次挥手



FIN数据包不发送数据,但消耗一个包序。


四次挥手步骤:

  1. 客户端先发起断开连接,客户端状态变为

    FIN_WAIT_1

    ,序列号为前面数据收发之后的序列号;

  2. 服务端收到连接释放请求,将状态设置为

    CLOSE_WAIT

    ,并对释放请求进行应答,

    ACK(seq = 9, ack = 8)

    ;此时TCP连接处于

    半关闭状态

    ,即

    客户端已经没有要发送的数据了,但若是服务端要发送数据,客户端仍需要接收,服务端到客户端这个方向的连接并未关闭

    ,可能会持续一段时间。

  3. 客户端收到服务端的

    ACK

    应答后,就进入

    FIN_WAIT_2

    状态,等待服务端发出的连接释放请求;

  4. 若服务端没有向客户端发送的数据了,则服务端发送FIN数据包,并将状态设置为

    LAST_ACK

    ,等待客户端的确定,然后断开连接;

  5. 客户端收到服务端的释放请求后,必须对该请求进行应答,发送ACK数据包,并且进入到

    TIME_WAIT

    状态。在

    TIME_WAIT

    状态下,TCP连接还没有释放掉,必须经过时间等待计时器(

    TIME-WAIT timer

    )设置的时间

    2MSL

    后,客户端才进入到

    CLOSED

    状态;

  6. MSL:最长报文段寿命,RFC793建议设置为2分钟,TCP允许根据实际情况设置更小的MSL值,因此从客户端进入到

    TIME_WAIT

    状态之后,要经过4分钟才能进入到

    CLOSED

    状态,才能开始建立下一个新的连接。


为什么客户端在TIME_WAIT必须等待2MSL的时间?


  1. 为了保证客户端最后一次发送的

    ACK

    数据包能够到达服务端,这个

    ACK

    数据包很可能会丢失,则服务端(

    LAST_ACK

    状态)接收不到对方的

    ACK

    应答,服务端就会超时重传这个

    FIN

    数据包,而客户端在2MSL时间内能收到这个FIN数据包,接着客户端重传一个ACK应答,重新启动

    2MSL

    计时器,最后客户端和服务端都会进入到

    CLOSED

    状态。如果不这样,服务端就无法按照正常步骤进入

    CLOSED

    状态;
  2. 防止“已失效的连接请求数据包”出现在本连接中,客户端在发送完最后一个

    ACK

    应答后,再经过

    2MSL

    时间,就可以使本连接持续时间内产生的所有数据包都消失,这样就可以使下一个新的连接种不会出现这种旧的连接请求数据包。

2MSL = 丢失ACK的MSL + 重传的FIN报文的MSL。


客户端先断开连接,但是客户端要等待2MSL时间,所以一般情况下,都是客户端先断开连接,服务端作为被断开方,可节约服务端的资源。



3.1 TIME_WAIT状态导致服务端无法快速启动的问题


TIME_WAIT

状态只有主动断开连接方才会拥有:


  1. 当进程正常调用close之后,进程很快的就结束掉了,但之前进程所占的端口并没有被释放;

  2. 原因在于主动断开链接方的状态是

    TIME_WAIT

    状态到CLOSED状态需要等待2MSL的时间。

ACK + 重传的FIN的MSL。

既然要收到重传MSL的FIN报文,那么刚刚监听/使用的端口一定不能被释放,如果释放了,从网卡中接收数据,经过网络协议栈层层分用之后,到达TCP之后,TCP就无法处理这个数据包,因为不知道这个数据之前是哪一个链接的。


主动断开连接方在TIME_WAIT到CLOSED这个状态之间,之前监听/使用的端口并没有释放。

设置端口复用:

int opt = 1;
setsockopt(listen, SOL_SOCKET, SO_REUSERADDR, &opt, sizeof(opt));
参数 解释
listen 侦听套接字
SOL_SOCKET 套接字选项
SO_REUSERADDR 重用端口
opt 设置为1,则开启端口重用



3.2 抓包分析

在这里插入图片描述

这是博主机器上抓的,只有三次挥手。

分析为什么只有三次挥手:


  1. ACK作为TCP协议的头部,则无论是否有数据发送,ACK都是存在的;

  2. 客户端先发送一个FIN ACK数据包,代表此时客户端没有数据要发送了,需要断开连接了;

  3. 服务端此时,也没有数据需要发送,则不单独发送一个ACK应答,服务端也直接进行断开连接请求(FIN) + ACK ,这里的ACK也就直接应答了客户端的FIN请求;

  4. 客户端对服务端的断开连接请求进行应答(ACK)。
				源端口			目的端口			发送数据包					数据长度
客户端			37828			9999			FIN ACK	seq = 7 ack = 9  	len = 0
服务端			9999			37828			FIN ACK	seq = 9 ack = 8  	len = 0
客户端			37828			9999			ACK seq = 8 ack = 10		len = 0



版权声明:本文为w903414原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。