计算机网络(三) TCP协议详解

上一篇:计算机网络(二)- TCP/IP协议群介绍

1、概述

​ TCP协议全名是 Transport Control Protocol ,是一个可以提供 可靠的、支持全双工、连接导向的协议,因此在客户端和服务端之间传输数据的时候,是必须先建立连接的。

image-20211202230056757

1.1、什么是建立连接

  • 连接本身是个虚拟、抽象的概念。他能让两个通信的程序之间确保彼此都在线
  • 建立连接可以加快相应请求的速度
  • 连接也被称为 会话(Session)
  • 建立连接可以使得通信更加的稳定、安全
  • 但是同样建立连接也会消耗相应的资源

1.2、单工、半双工、全双工

  • 单工: 任何时刻数据只能单向传输
  • 半双工:允许数据在两个方向上传输, 但是在某一个时刻(同一时刻),只允许数据在一个方向上传输。
  • 全双工: 任何时刻数据都可以双向传输。

image-20211202231220450

1.3、可靠性

  • 可靠性是为了保证数据的无损传输
  • 使用无序的数据恢复原有顺序
  • 多播时每个接收方都获得无损的副本。

2、特点

  • 1、基于连接的(点对点的通信)

    • 传输数据之前是要建立好连接的
  • 2、是双工通信的

    • TCP协议一旦建立连接, 就可以在连上上实现双向的通信
  • 3、基于字节流而非报文--保证了TCP协议的可靠性

    • 将数据按字节大小进行编号,接收端通过ACK来确认收到的数据编号,通过这个机制保证TCP协议的有序性和完整性
  • 4、拥塞控制

    • TCP协议通过 慢启动、拥塞避免、拥塞发生、快速恢复 在不同的过程中的算法来控制拥塞的
  • 5、流量控制能力

    • 通过滑动窗口控制数据的发生速率,滑动窗口的本质是动态缓冲区,接收区根据自己的能力在 TCP 的请求头header中动态调整窗口大小,通过ACK应答包通知到给发送端,发送端根据窗口的大小调控发送速率。

3、TCP协议详解

在解释TCP工作流程之前呢, 先解释一下几个名词,这样更有助于我们去了解其过程。

3.0、TCP首部信息组成以及各个部分的作用

下图是TCP协议报文段的组成图片

image-20211202233308672

image-20211202235746756

3.0.1、源端口和目的端口
  • 源端口,用来存放 发送TCP报文的进程对应的端口号。占 2个字节(16位)
  • 目的端口,用来存放 接受TCP报文段的进程对应的端口号。 占 2个字节(16位)
3.0.2、32位序列号 Sequence Number
  • 占用4个字节(一个字节(byte)8个比特位(bit))。
  • TCP的序列号对数据包进行标记,以便达到目的地后重新组装数据包,假设当前序列号为 s ,发送数据长度为 l,则下一次发送数据时的序列号为 s+l
  • 在建立连接时通常由计算机生成随机数作为序列号的初始值。
3.0.3、32位确认号 Acknowledgement Number
  • 占用 4个字节(一个字节(byte)8个比特位(bit))
  • 是有接收端计算机使用,用于重组分组的报文成最初形式。
  • 如果设置了ACK控制位, 那么这个值表示准备接受的下一个包的序列号
3.0.4、数据偏移 offset
  • 占用4位,即0.5个字节
  • 这部分实际上是指出TCP报文的首部的长度,即TCP报文段数据起始位置距离TCP报文的起始位置有多远(这里指 TCP拆包后的报文段的起始位置 与 TCP整个报文的起始位置有多远)
3.0.5、保留字 Reserved
  • 占6位,即 0.75个字节
  • 保留为以后使用,当前是值为零。
3.0.6、标志位 Tcp Flags
  • 每个标志位占1 位,一共6个标志位 。即 1.5个字节。
  • URG - 紧急指针有效标识
    • 此标志位用来表示TCP包的紧急指针域有效,用来保证TCP连接不会被终端,并且督促中间层设备要尽快处理这些数据。相当于告诉接收端,有高优先级的数据。
  • ACK - 确认序号有效标识
    • 只有当ACK=1的时候,确认号字段才有效。
    • ACK=0的时候,确认号是无效的。
  • PSH - 用来表示接收方应尽快将这个报文段交给应用层
    • 接收到PSH = 1 的TCP报文段, 应尽快的交付接收报文段的应用进程, 而不再等待整个缓存都填满后再交付
  • RST - 重建连接标识
    • RST = 1 时,表明TCP连接出现严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新简历连接。
  • SYN - 表示同步序号,用来建立连接。
    • SYN = 1 时,表示这是一个连接请求或者连接接受请求。
  • FIN - 发送完成任务标识,用来释放一个连接
    • FIN = 1 表明此报文段的发送端和数据已经发送完成了
3.0.7、窗口大小 Window Size
  • 占 16位 即两个字节
  • 该字段表明指出了现在允许对方发送的数据量,他告诉对方本端TCP连接缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
  • 窗口大小的值 指的是 从本报文段的首部中的确认号算起, 接方法目前允许对法发送的数据量。
3.0.8、校验和 TCP Checksum
  • 占用16位,即两个字节。
  • 由发送端填充,接收端对TCP报文段执行CRC算法,以校验TCP报文段在传输过程中是否损坏,如果损坏则丢失。
  • 检验范围包括首部和数据两个部分
  • 这也是TCP可靠性的一个重要保障。
3.0.9、紧急指针 Urgent Pointer
  • 占用16位,即两个字节
  • 这部分仅在标志位 URG = 1的时候才有意义,他指出本报文段中的数据的字节数。
  • URG = 1 时,发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面任然是普通数据。
  • 因此, 紧急指针指出了数据的末尾在报文段中的位置。

3.1、TCP协议的三次握手四次挥手

参考文档 :https://www.cnblogs.com/qdhxhz/p/8470997.html

先看一下TCP从建立连接到传输数据到断开连接的完整过程

image-20211204110002227

3.1.0、三次握手建立连接的过程

image-20211204100545362

流程如下:

  • 第一次握手(由客户端发起的)

    • 客户端发送 SYN=1 seq = x 的请求建立连接的报文
      • (SYN 标志位中的同步序列号,用来建立连接的)
      • seq 是 32位序列号 Sequence Number,即在建立连接的时候, 随机生成的初始序列号x。
    • 此时,客户端进程进入了 SYN-SENT(同步已发送状态)状态
    • 此次建立连接的报文是不能携带数据的。
  • 第二次握手(由服务端发起)

    • 服务器接收到请求建立连接的报文后,如果同意连接, 则发出确认报文。
      • 标志位 ACK = 1, 确认号 ack = x+1,这个确认号为 x+1 就是基于上面请求的的seq随机生成的初始序列号+1得出的。
      • 标志位 SYN = 1, 确认号 seq = y , 这个seq 也是服务端自己随机生成的一个初始序列号。
    • 此时 , 服务器进程进入到 SYN-RCVD(同步收到状态)的状态。
    • 这个报文也不能携带数据。
  • 第三次握手(由客户端发起)

    • 客户端收到 服务器发送的的确认报文后, 还要再向服务器发送确认报文。
      • 标志位ACK = 1 , 确认号ack = y+1 ,序列号 seq = x+1
    • 此时 TCP连接建立,客户端和服务端都进入 ESTABLISHED(连接已建立) 状态。
  • 举例解释一下为什么要三次握手

    • 当客户端发送第一个连接的请求报文, 但是由于网络不好,这个请求没有立即到达服务器, 而是在某个节点停留了,知道某个时间才到达服务器,本来这已经是一个失效的报文了, 但是 服务端接收到这个请求报文后,还是会像client 发出确认报文,表示同意建立连接。
    • 假设 不采用三次握手,那么只要服务器发出去确认报文,新的连接就建立了,但是其实这个请求已经超时并失效了,客户端是不会理睬服务器的确认信息的 ,也不会向服务端发送确认的请求。
    • 但是此时的服务器是已经认为连接建立了, 换句话说就是处于ESTABLISHED(连接已建立)的状态,并且一直在等待客户端发来数据。
    • 这样的话, 服务器端就有很多资源浪费了。
    • 而采用三次握手的话, 服务端如果收不到确认报文话,就知道客户端没有服务端建立连接不成功了。
3.0.2、TCP 数据传输过程

客户端 与 服务端 建立连接之后,就可以相互传输数据了。

  • 如上图所示,正常传输数据的过程如下:

    image-20211204105540187

    • 主机A 开始发送数据的时候,假设初始的 seq = 1200 ,滑动窗口大小为100,向主机B发送数据传递
    • 主机B在完全接收到数据后, 为了确认收到 ,需要向主机A 发送ACK包,设置的 值为 1200+100+1 .
      • ACK = seq + 传递数据的字节数 + 1
    • 主机A在接收到 主机B 的确认信息后,开始发送下个报文, 此时 seq = 1301 ,同样滑动窗体的大小为100的数据。
    • .......
    • 注: 与 三次握手协议相同, 最后加1 是为了告诉对象要传递 seq信号的。
  • 如上图所示 , 数据丢包的情况过程如下:

    image-20211204110857472

    • seq = 1301 数据包向主机B 传递 100字节的数据,但中间发生了错误,主机B 未收到。
    • 经过一段时间的等待后, 主机A 任然未收到 Seq = 1402 的确认号,此时就会尝试数据重新传递。
    • 为了完成数据包的重传, TCP Socket套接字每次发送数据包时都会启动定时器, 如果在一定时间内没有收到目标主机的传回的ACK包, 那么定时器超时, 数据包会重传。
3.0.3、四次挥手断开连接

image-20211204135443345

  • 首先TCP连接断开, 是一个客户端主动关闭, 服务端被动关闭的过程。

  • 关闭连接需要经过4次会话,具体的流程如下:

    • 第一次挥手
      • TCP 连接的客户端发送一个 标志位FIN(结束)=1 的报文,用来请求关闭客户端到服务端的连接。
      • 客户端进程发出释放连接的报文。 释放连接报文的首部,FIN =1,其序列号是 seq = u (序列号等于前面已传送过来的数据的最后一个字节的序号加1)
      • 客户端在发送释放连接的请求后进入FIN-WAIT-1(终止等待1)的状态。
      • TCP协议规定, FIN 报文即时不携带数据也要消耗一个序号。
    • 第二次挥手
      • 服务端收到客户端发送的FIN报文时,客户端会先发送一个 ACK(确认)的报文给客户端。
      • 服务端回复的ACK报文中, ACK = 1 seq = v , ack = u + 1
        • ACK = 1 表示确认报文
        • ack = u + 1 是针对上面第一次挥手服务端的 seq = u 再加上1
        • seq = v 是服务端自己随机生成的序列号。
      • 此时,服务端就进入到CLOSE-WAIT(关闭等待状态)
        • TCP 会通知上层应用进程, 客户端向服务器的方向就释放了。
        • 此时,是处于半关闭状态, 即客户端已经没有数据要发送了, 但是服务器若要发送数据,客户端依然要接收。
        • 这个状态还要持续一段时间, 也就是整个CLOSE-WAIT状态持续的时间。
      • 客户端 在接收到服务器的 断开连接的确认ack报文后,此时
        • 客户端就进入了FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文。
    • 第三次挥手
      • 服务端发送完 ACK 报文后,在确认数据传输完毕后,会发送一条 FIN(结束)的报文到客户端。
      • 由于处于半关闭状态, 服务器可能又发送可一些数据,假定此时的序列号为 seq = w
      • FIN = 1 , ack = u + 1
      • 此时, 服务器就进入 LAST-ACK(最后确认)状态了。
    • 第四次挥手
      • 最后, 客户端收到了服务端的FIN = 1 的报文后,知道客户端已经没有数据需要传输了,由客户端最后再向服务器发送 ACK = 1 的确认报文。并 ack = w(服务端发送的seq)+1 以及 seq = u+2.
      • 此时 客户端进入 TIME-WAIT(时间等待)状态。
      • 注意,此时TCP 连接还没有释放, 必须经过 2 ** MSL(最长报文段寿命) 的时间后, 当前客户端撤销相应的TCB后才进入 CLOSED状态。
      • 服务器端只要接收到客户端发送的确认, 就立即进入CLOSED状态。
      • 综上所述: 服务器端结束TCP连接的时间要比客户端早一些。
  • 思考(一):为什么是 4次挥手?

    • 首先肯定是为了保证数据能个完成全部的传输过程。
    • 当关闭连接时, 当收到对方发送的FIN报文是, 仅仅表示对方没有数据发送给你了。但未必是你发送给对方的数据是否全部发送完毕了,所以你未必可以马上就关闭SOCKET 套接字。所以你可能需要继续传输数据给对方。数据发送完完毕后再发送一个FIN报文给对方。 所以此时服务端的 ACK报文 和 FIN 报文是分开来发送的。
  • 思考(二):数据传输过程中客户端突然挂了怎么办?
    • 正常连接时, 客户端会挂了,如果没有措施处理这种情况的话, 那么就会出现客户端和服务端出现长时间空闲挂起。
    • 解决办法就是在服务器端设置保活计时器,每当服务器收到客户端的消息, 就将计时器复位。计时器的超时时长通常设置为2小时。
    • 如果服务器超过2小时没收到客户端的消息, 他就发送探测报文段,若发送10个探测报文段, 每个相隔75s,还没有相应, 就认为客户端出现故障了, 因而终止该连接。
  • 思考(三): 为什么客户端最后还要等待 2MSL?
    • MSL(Maximun Segment Lifetime) 最长报文寿命

    • 1、保证客户端发送的最后一个ACK报文能够顺利服务器,因此这个报文在数据传输过程中可能会丢失

      • 站在服务器角度来看,服务器已经发送了FIN+ACK 报文请求断开了, 客户端没有给我相应,应该是我发送的请求断开的报文,客户端没有接收到, 于是服务器又会重新发送一次,而客户端就可以再2MSL时间内, 重传这个报文,接着给出ACK报文后, 会重启2MSL计时器。
    • 2、防止已经失效的连接请求报文段出现在在本连接中。

      • 客户端发送完最后一个确认报文后,在这个2MSL时间中, 就可以是本连接吃的时间内所产生的所有报文段都从网络中小时。这样新的连接中不会出现连接的请求报文。
    • 3、那么等待2MSL 就一定没问题了嘛?

      • 不,还有一个超时机制, 超时了,即使没有收到回复也会关闭连接。
  • 思考(4): SYN(洪流)攻击(后面有机会再补充)?
原文地址:https://www.cnblogs.com/qianzhengkai/p/15642114.html