计算机网络之(2):TCP 相关知识——累积确认,三次握手,四次挥手,滑动窗口,超时重传、快速重传

TCP 概述

  • TCP 是一个传输层协议,他的作用是为上层的应用层提供服务;传输层处于应用层和网络层之间
  • 当数据从网络层不断地运送到传输层,TCP 将这些打包好的数据(IP 数据报)进行重新解包,并拼接成字节流的形式进行处理;处理完之后再向上递交到应用层,供应用层的各种应用使用
  • TCP 是面向 连接的、可靠的、字节流形式 的传输服务

socket 的作用和存在意义

  • 因为 TCP 是面向连接的协议,所以发送和接收数据的时候,都要指定收发双方的 IP 地址和端口号;

    • IP 地址是对计算机终端在网络中位置的唯一标识,通过 IP 地址可以找到网络中的某台计算机
    • 端口号 port 是对这台计算机中的某个应用服务进程进行唯一标定,让数据能够在计算机内部找到对应的应用进程
  • 因为 TCP 的字节流需要向下交付到网络层,然后进行打包成 IP 数据报再发送

  • 如果没有 socket,那么数据每次向下传输到网络层的时候都需要附带传一个 5元组,这样增大了层与层之间的数据压力,而且每次都如此很麻烦

  • 有了 socket 就相当于把这个 5 元组通过 socket 打包成了一个值,每次向网络层传递这个值,网络层就明白了,哦原来是想往这个目标地址和端口号发送

如何保证传输的可靠性

  • 可靠性包含这么几个方面:
    • 建立起可靠的传输:三次握手
    • 数据不能有丢包,解决这个问题要靠:
      • 重传机制 重发丢失的数据包来保证数据整体的完整
      • 流量控制 避免收发速度不匹配导致的缓冲区满造成丢包问题;流量控制的过程中涉及到 TCP 的接收和发送缓冲区的设置以及 滑动窗口 的使用
      • 同样的,流量控制重传机制 只能最大程度缓解 TCP 本身的丢包问题,但是网络拥堵导致的丢包问题,他们无能为力,但是为了可靠的传输过程,TCP 同样也想到了 拥塞控制 来实时感受网络的拥塞程度,进而对发送方的发送速度进行限制
    • 数据传输过程中不能有错误,解决这个靠 检验和(checksum)
    • 信息不能乱序:通常信息的乱序问题是由于丢包或者网络情况不好引起的,所以这篇文章中把如何保证消息的顺序也包含在上述各种技术和算法的介绍中
    • 可靠的连接断开:四次挥手

累积确认机制

  • 累积确认是一个很重要的技术,因为通过累积确认我们就可以判断出来传输过程中是否存在 乱序 或者 丢包 的情况,从而决定是否进行 超时重传 或者 快速重传

  • 累积确认依靠的是 TCP head 中的 确认号 Acknowledgement 以及 ACK 标志位
    在这里插入图片描述
    在这里插入图片描述

  • 在 TCP 发送端 A 发送一条消息的过程:

    • 假设该条消息的序号(sequence number)= x
    • SYN 标志位 置 1 表示其发送方身份
    • 然后发送给接收方
  • 在 TCP 的接收端 B 接收并确认这条消息的过程:

    • acknowledgement number = x+1
    • ACK 标志位 置 1 表示其接收方身份
    • 将消息发送给 A
    • 在这个过程中如果 B 也想给 A 发送消息,消息的 sequence number = y
    • SYN 标志位 置 1
    • 将消息发送给 A
  • acknowledgement number = x+1 代表对于 x+1 之前的所有信息在接收方都已经确认接收完毕,接收方 B 期望发送方 A 发送 x+1 及其后面的消息 这就是累积确认,每次确认的都是前面所有的内容已经接收完毕

  • 在这里我们暂且用 3 次握手建立的过程来展示累积确认的过程(这里的重点不是三次握手,而是了解累积确认运行的机制和原理)
    在这里插入图片描述

TCP 三次握手建立连接

  • TCP 是面向连接的协议,那么为什么连接要通过三次握手来建立呢,为什么不直接一来一回就可以了呢?
  • 一个很重要的原因就是确定通信双方的起始 sequence number
  • 在上面的累积确认中我们都知道 A 方是从 sequence number = x 作为起始来发送消息的,而 B 方通过确认 A 的连接请求也知道 A 的 sequence number 起始值为 x;同样的,A 方也需要通过确认 B 方的起始 sequence number = y 才能进行下面的可靠通信,为什么这样呢?
  • 因为在 TCP 通信过程中,A 和 B 可能会建立多次连接,如果每次不指定双方起始的 sequence number,那么很容易就和上次连接的信息弄混了,这样容易导致各种各样的问题。

举个最极端的例子;如果现在通信双方 A, B 的每次连接之后发送消息的 sequence number 的起始值都从 0 开始,

  • A 的第一条消息是 “hello”,然后给 B 发过去了,这个消息因为各种原因在网络中卡住了,一直没有到达 B,而这个时候 A 断掉了与 B 的连接。
  • 一段时间之后,A 重新建立了与 B 的连接,这时候发了一个 “world” ,sequence number 依然是从 0 开始,而这个 “world” 也因为各种原因丢掉了,
  • 但这个时候刚好之前发的 “hello” 到达了 B 端,由于这确实是 A 对 B 发送的消息,所以 B 就回了一个 ACK=1 确认 B 确实收到这个消息,
  • 然后 A 收到了 B 的 ACK=1 的确认,A 以为 B 收到了 “world” 但其实 B 收到的是 “hello”
  • 在上面的例子中体现出了两个重要的问题:
    • 双方都需要对当前建立的连接的初始的 sequence number 进行指定,只有通过 3 次握手才能保证双方的 sequence number 相互约定完成 如果只有两次握手,那么 B 就不能确认 A 是否已经知晓了 B 的 sequence number
    • 双方的 sequence number 的指定需要通过某种方式来指定,而不能简陋地每次都从 0 开始。

总结三次握手

  • 三次握手要用到的标志位:
    • SYN 发送方
    • ACK 接收方
  • 三次握手的作用是为了约定双方的起始 sequence number,进而保证通信是可靠的,保证此次通信不受前面的连接的影响

四次挥手

  • 四次挥手是TCP 连接断开的时候采用的方式
  • 通信的双方都可以发起连接断开的请求
  • 这个过程中使用的标志位是 FIN
  • A 首先发送给 B 一个断开的请求 FIN=1
  • B 接收请求并回复 ACK 表示 B 收到了 A 的断开请求
  • A 关闭对 B 的数据传送;但是这个时候 B 依然可以发消息给 A,A 也可以发内容给 B,但是不能发送数据了(发送的内容可以包含 TCP head 的其他部分)
  • 这时候如果 B 也想断开连接了,那么 B 也发送一个 FIN=1 给 A,A 接收之后回复一个 ACK,那么这个时候 A,B 之间的连接彻底中断(这个 connection 断掉)
  • 当 A 或者 B 处在单方面断开的情况我们称之为 half-closed 即半关闭状态,在上文的描述中,当 A 主动发送 FIN 并收到 B 的 ACK 之后,依然可以接收和回复 B 的状态就处于 half-closed 状态。
  • 再重申一遍,当一方发送 FIN 的时候并不代表他对接收方的 connection 断掉了,而仅仅是关闭 数据发送,但是依然有方式回复对方的(数据之外)的请求
    在这里插入图片描述

总结四次挥手

  • 使用到的标志位有:

    • ACK 确认收到了对方的 终止连接 请求
    • FIN 表示主动想要结束连接的请求
  • 当一方的 FIN 请求得到 ACK 时,整个连接处于 half-closed 的状态

  • 但其实四次挥手依然存在问题:

当最后一次 ACK 出现丢失的问题,那么这个问题其实没有什么好的办法解决

TCP 连接突然终止

在这里插入图片描述

  • 在本图的例子中,A 已经成功地接收到 B 的 ACK 消息,然后 A 端的程序崩溃,这个时候 A 会向对方发送 RST 来表示让对方释放此连接的资源

  • RST 是一个单方向的任务,也就是说当 B 收到了 A 的 RST 之后不会通过 ACK 来确认这个 RST

  • 因此 RST 的传输是不可靠的

  • 关于 RST 更多的使用情况可以参考这篇文章
    https://blog.csdn.net/qq_25518029/article/details/119903292

滑动窗口机制

参考资料:
https://blog.csdn.net/weixin_45519487/article/details/107764416

  • 在建立 TCP 连接的双方,即 TCP 的发送端和接收端分别都使用滑动窗口
    在这里插入图片描述
  • 滑动窗口是一个 buffer (缓存)
  • 对于发送端 S 来说,滑动窗口内部的那些内容是已经发出去但是尚未得到接收方 R 确认(ACK) 的内容
  • 对于接收端来说,滑动窗口内部的内容是没有被上层的 app 取走的数据

因为 TCP 是为上层的应用层提供数据支持(服务)的,因此什么时候取走 TCP 接收端缓存中的数据完全取决于上层的 app;因此在接收端,如果数据没有被上层的 app 取走,滑动窗口就不会向后移动

  • 数据发送和接收的过程应该如下:
    • 发送方 S:滑动窗口的尺寸 size = 50byte,因为滑动窗口内可以包含 5 个数据段,每个数据段的长度假设是 10 个 byte
    • 接收方 R:滑动窗口的尺寸 size = 50 byte
    • 根据上面所说的,一开始 S 会将格子标号 1- 41 的内容全部发送给 R,然后等待 R 返回的确认 ACK
    • 假设没有任何丢包的情况发生,R 应该返回 ACK=51 表示前 50 byte 的数据都收到了,希望下一次 S 从 51 byte 及其以后的数据开始发送;除了这个 ACK=51 的回执之外,由于 R 方的 buffer(滑动窗口)已经满了,暂时不需要 S 往这边发新的数据了,因此 S 还会收到 R 关于接收方滑动窗口剩余空间大小的描述;这个信息写在 advertised window 里面
    • 通过这种方式,其实是 R 方决定了 S 方是否能继续发数据,发多少数据;
      在这里插入图片描述
    • 假设当前的 R 方的滑动窗口中的内容被上层的 app 取走了 20 byte,也就是说 buffer 中多了 20 byte 的空间,那么这个时候,R 会向 S 发送一个 TCP head,里面的 ACK 依然是 51, 但是这个时候的 advertised window 的内容变成了 20 byte,也就是告诉 S 可以继续发送信息了,依然还是从第 51 byte 开始发送,发送长度是 20 byte
    • 因此 S 的滑动窗口会向后滑动到包含 51, 61 这两个格子;但是这就产生了一个问题:
    • 根据上面提到的:滑动窗口内部的字节都是等待确认的字节,这个时候的滑动窗口到底是下面两种情况中的哪种呢?
      在这里插入图片描述
      在这里插入图片描述

  • 其实之前的那种阐述不准确,只是为了帮助理解而粗略的对滑动窗口进行了简化,其实滑动窗口有两个主要的部分组成:
    • 第一部分就是那些发送出去但是没有收到 ACK 的字节
    • 第二部分就是那些在发送窗口范围内(允许发送)但是尚未发送的字节
    • 在这个例子中,由于 R 的剩余窗口大小仅为 20 byte,因此 51, 61 作为第一个部分发送出去,但是 71,81, 91 这三个格子的 byte需要等到 R 有剩余的窗口空间的时候才会被发送出去
      在这里插入图片描述

总结滑动窗口

在这里插入图片描述

  • 发送方 S:
    • last byte written 是上层应用向下写入的最后一个字节的位置
    • last byte acked 是 S 方最新的被确认的字节
    • last byte can send 是 S 方最多可以发送的字节,相当于上文中的标号为 61 的格子
    • EffectiveWindow:发送端还需要发送的窗口大小。
  • 接收方 R:
    • Last Byte Read:上层应用已经读完的最后一个字节的位置。
    • Next Byte Needed:接收到发送端的 连续序列号 的最大值的位置
    • LastByteRcved:已经收到的最后一个字节的位置。
    • AdvertisedWindow:接收端还能接收的窗口大小。
  • 这个时候就有下面这个问题:

为什么接收方会出现这种空白的情况。也就是 next byte needed 和 last byte received 不是同一个位置

  • 这是因为 S 在发送的过程中发送的 byte 到达的顺序取决于网络,可能后发的 byte 先到达,也可能先发的信息后到达
  • 而这个空白的部分如果长时间没有被补齐,就会导致 快速重传

快速重传 & 超时重传

  • 快速重传相对应的是慢速重传,那么什么样的重传操作是快速的,什么样的是慢速的呢?
  • 超时重传是慢速的重传方式,即当发送方 S 在设定的时间之内没有收到来自接收方 R 的确认信息,这个时候 S 会认为 R 并没有收到这条消息,因此 S 会重新向 R 再发送一遍,这个过程叫做 超时重传
  • 快速重传是:当 S 接收到 3 个冗余 ACK 的时候,会立刻将信息进行重发。这么描述有点抽象,我们来看一个例子
  • 发送方 S 发送了从 31 到 80 byte 的字节流到接收端 R
  • 但是 R 没有接收到 51 - 60 byte 的字节,而后面的 61-90 的字节也陆续到达
  • 当 50 字节到达时,R 应该向发送方发送 ACK=51 来表示 R 接收到了 50 以前所有的 byte,这是第一次 ACK
  • 当 61-70 byte 到达的时候,由于累积确认的机制,这时候 R 还是会向 S 发送 ACK=51,这是第一次冗余 ACK
  • 以此类推,当 81-90 字节到达结束,R 会向 S 发送 ACK=51 这是第三次冗余 ACK
  • 也就是说当 3 次冗余 ACK 在 S 端被接收到的时候,就证明在最后一次 ACK 的位置出现了丢包,这个时候 S 端会立即重传 51-60 byte 的信息给 R 端,因为这个过程一般比 timeout 的节点要提前很多,因此叫做 快速重传
    在这里插入图片描述
    在这里插入图片描述

超时重传的时间设置

  • 如何设置一个时间为超时的时间是一件不容易的事情,因为超时时长设的太长或者太短都容易造成问题

超时重传的算法

发送方

  • 对于待发的字节设置一个定时器 timer
  • 当 timer 的时间耗尽如果还是没有收到 ACK,那么这个消息就会被重传并且重新设置定时器
  • 重复这个过程直到收到 ACK 为止

接收方

  • 接收到一个 packet p
  • 检查这个 p 是否能够通过检验和(checksum)的检测;如果通过检测,那么证明这个 packet 的完整性有保证,就会给 S 端发送一个 ACK 表明自己收到了
  • 如果当前的字节没有被 app 取走使用,就将字节交付给上层的 app
    在这里插入图片描述
  • 我们定义一个 RTT 为从 S 端发出一个消息经过网络达到 R 端的时间 + R 端发出一个 ACK 这个消息到达 S 端的时间

过短的超时时间

  • 如果 Timeout 的时间小于 RTT 就会导致冗余的重发,因为即使是没有丢包的情况下,RTT 的时间也是信息在网络中跑一个来回的最短时间了,因此这样的设定不合适

过长的超时时间

  • 如果 Timeout 的时间远大于 RTT,那么即使真的丢数据了,S 也会在很久之后才意识到要重传这个 packet ,会使得整个系统的效率变得很低
    在这里插入图片描述

动态调整的超时时间

  • 现在我们知道网络环境是很复杂的,如果贸然设置一个较大或者较小的网络超时时间是不符合常理的,因此我们想让这个超时时间的设定根据实际情况自己做出调整。
  • 设置每个被发送的 TCP segment 都有一个重传 timer (retransmission timer RTO):
    • 最开始使用默认值来初始化这个 RTO,然后根据网络的实际情况做出调整

问题练习

在这里插入图片描述

  • 太长的 timeout 会导致系统效率降低
  • 太短的 timeout 会导致频繁的重传,进而导致冗余的 packet
    在这里插入图片描述
  • 使用 checksum 机制来保证 packet 没有损坏
  • 对于冗余的 packet 直接丢弃
  • sender 没有办法得知,所以才需要重传

在这里插入图片描述

  • host A, B 可以对 C 的 6789 端口同时发消息,但是他们是建立在两个不同的 socket 上的

在这里插入图片描述

  • 如果直接使用 process ID 来取代 port 的话,因为 process ID 经常会变,而且是操作系统分配的,因此以这种方式建立的连接会依赖于操作系统的行为(os-dependent)

在这里插入图片描述

  • 对于 (host1, p) -> (host2, q),在同一时间只能有一个
  • 同一时间,建立在 host1 的 p 端口的连接可以有很多个,例如 host2 的多个端口同时与 host1 的 p 端口通信
  • 10
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暖仔会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值