云计算百科
云计算领域专业知识百科平台

Linux之TCP理论<3>

确认应答机制

确认应答(ACK,Acknowledge)是 TCP 实现可靠传输最基础的机制。 发送方发出数据后,接收方收到数据,返回ACK 确认报文,告诉发送方:哪些数据我已经成功收到了。

TCP 是面向字节流的,不是按报文确认,而是按字节序号确认。 ACK 报文中的确认序号含义:确认序号 N,表示【序号 N 之前的所有字节,接收方全部收到了】,下一次我期望收到序号 N 开始的数据。

确认应答机制保证了历史报文的可靠性

当比如丢是3001的ack报文,但是收到4001时,也是确认之前的报文全部都收到了,提高了效率

超时重传机制

基于前面的确认应答机制,有一个关键问题:如果发送出去的数据,一直收不到对方返回的 ACK 确认报文,该怎么处理?

我们不能无限期原地等待。如果一直等不到应答,传输就卡住了,所以 TCP 设计了超时重传机制来解决丢包问题。

触发超时重传的两种场景

收不到 ACK 有两种可能性,发送方是无法区分到底是哪一种:

  • 客户端发出的数据报文,在网络传输途中丢失,服务端根本没有收到数据,自然不会返回 ACK。
  • 数据报文已经成功到达服务端,但是服务端回复的 ACK 确认报文,在半路丢失了。
  • 不管是数据丢包还是 ACK 丢包,发送方看到的现象都是:超时时间内没有收到 ACK。 于是 TCP 会判定本次传输失败,重新发送这份报文,并且重新启动超时计时器,再次等待 ACK。

    如果反复多次重传之后,依旧收不到任何确认应答,TCP 就判定网络完全不通,主动发送 RST 复位报文,强制关闭这条 TCP 连接。

    超时时间 RTO 该如何设定?

    超时时间不能随便写死,这里存在权衡:

    • 如果超时时间设置太短:网络只是轻微延迟,ACK 还在路上没回来,发送方就提前重发报文,产生大量多余重复数据包,浪费网络带宽。
    • 如果超时时间设置太长:报文真的丢包了,发送方需要等待很久才会重传,降低传输效率。

    所以 TCP 不会使用固定的超时时间,超时时间是动态计算出来的。 TCP 会持续采集报文往返耗时 RTT(Round-Trip Time,报文发出到收到 ACK 的时间),根据采样到的 RTT 不断平滑更新,动态调整重传超时时间 RTO,适配当前网络状态。网络波动大的时候 RTO 自动变大,网络稳定时 RTO 适当缩小。

    连接管理机制

    在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接

    三次握手

    TCP 是全双工通信,客户端和服务器双方都可以同时收发数据。 想要建立连接,本质上需要确认两件核心事情:

  • 双方有通信意愿,愿意建立 TCP 连接;
  • 双方收发通道正常:客户端能发能收、服务端也能发能收。
  • 理论上完成这两次确认需要四次报文交互,但是服务端收到连接请求后,可以把「SYn 同步请求」和「ACK 确认应答合并到同一个报文,所以最终只需要三次报文交互,也就是三次握手。

    三次握手确认双方收发能力与通信意愿;服务端把 SYN 和 ACK 合并发送,由四次简化为三次;两次握手无法验证服务端发送能力,还会产生无效连接。

    TCP 建立连接时,客户端与服务端的 TCP 内核状态会随报文交互不断切换。当两端状态都变成ESTABLISHED,代表 TCP 连接已经在内核中建立完成。 注意:三次握手由操作系统内核完成,accept 只是应用层接口,作用是从内核的全连接队列取出已建立好的连接。就算不调用 accept,内核依然可以完成三次握手。 listen 函数的第二个参数 backlog 代表全连接队列的长度,用来存放已经完成三次握手的连接。在操作系统实际实现中,允许排队的连接数量会略大于 backlog。

    没有accept也可以建立连接

    接收端维持的没有被accept接收的连接(全连接队列)个数是有上限的,一般是listen的第二个参数+1,—->保证了服务器的满载率


    四次挥手

    四次挥手是可以由任意一方断开连接,但通常是client先关闭连接

    TCP 是全双工通信,连接可以看作两条独立的单向通道(一条客户端→服务端,一条服务端→客户端),读写是分开的。 FIN报文的含义:我这边不再发送新数据,关闭我方的写通道;但读通道依然保持打开,还能继续接收对方发来的数据。

    这就是四次挥手不能合并成三次的根本原因:双方可以独立关闭自己的写方向。

    调用close就会将连接关闭

    但是我们也有其他的系统调用shotdown

    shutdown:专门用来关闭单向通道,可以选择只关闭写端,保留读端,完美实现 TCP 半关闭。不受文件描述符引用计数影响,直接操作内核 TCP 连接。

    为什么会没有立即关闭???

    原因 1

    第四次挥手的 ACK 报文在网络传输时有可能丢失。 如果客户端收到 FIN 之后直接关闭连接:

    • 服务端收不到 ACK,会一直重发 FIN 报文,持续占用系统资源;
    • 此时客户端连接已经销毁,收到重传 FIN 只能回复 RST,导致服务端无法正常关闭。

    客户端保持TIME_WAIT状态等待 2MSL: 只要服务端重发 FIN,客户端内核就可以重新回复 ACK,保证服务端正常完成关闭流程。

    原因 2

    网络传输存在延迟,可能存在迟到的旧数据包(陈年报文)在网络里游荡。 如果客户端立刻释放端口,马上用相同 IP + 端口新建 TCP 连接: 旧的滞留报文到达后,包头的序列号、端口刚好匹配,新连接会错误接收这份不属于自己的旧数据,造成数据错乱。

    2MSL 是报文在网络中最大存活时间。等待满 2MSL,可以确保:上一轮连接所有残留报文,全部在网络中失效消失,不会干扰后续新连接。

     TCP 在新建连接时,初始序列号 ISN 会使用随机值。就算有残留报文,序列号大概率对不上,进一步降低旧报文被误接收的概率,作为辅助防护手段。

    应用层的 fd 已经关掉了,但是内核还要 “站岗等 2MSL”,防止报文滞留,所以端口还被内核占着,2MSL 超时后内核才彻底释放端口与 TCP 资源。

    流量控制

    Client 不停发消息,Server 应用层处理速度跟不上,Server 内核的 TCP 接收缓冲区被填满。 当接收缓冲区满之后,新到达的 TCP 报文段就会被丢弃,造成网络资源浪费。 👉 解决这个问题的机制就是 TCP 流量控制

    TCP 提供流量控制机制:根据接收端实际的数据处理能力,动态限制发送端的数据发送速率,避免发送方发送速度过快,接收方来不及读取,最终导致内核接收缓冲区溢出、报文丢失,浪费网络资源。

    TCP 报头中存在16 位窗口大小字段,该字段承载的值就是接收窗口 rwnd(Receive Window)。 接收方在回复的 ACK 报文中,将当前内核接收缓冲区剩余可用字节数填入该字段,告知发送方:我当前最多还能接收多少字节的数据。

    核心规则

  • 窗口大小数值越大,代表接收端空闲缓冲区空间越多,通信的吞吐量越高。
  • 当接收端监测到自身接收缓冲区快要被填满时,会在 ACK 报文中填入更小的窗口值,通知发送方降低发送速率。
  • 发送方收到更新后的窗口值,主动减慢数据发送速度,防止接收缓冲区溢出丢包。
  • 若接收端缓冲区完全占满,则将窗口大小置为0:
    • 发送方收到rwnd=0后,停止发送新的数据报文;
    • 但发送方需要定期发送窗口探测报文段,持续询问接收端最新窗口大小。

    探测报文的意义:如果接收端后续窗口恢复的 ACK 报文在网络中丢失,发送方会永久阻塞等待。窗口探测用来避免这种通信死锁。

  • 滑动窗口

    滑动窗口本质是发送缓冲区里的一段区间,它代表当前可以直接发送、不需要等待前面每一段确认应答的数据范围。

  • 绿色:已确认数据 数据已经发送出去,并且收到了对方的 ACK 确认。这部分数据使命完成,可以直接从缓冲区清除,释放空间。
  • 红色:滑动窗口(窗口内待发送 / 已发送未确认) 这就是滑动窗口覆盖的区域。窗口内的数据可以直接发送出去,不需要等前面每一条报文的 ACK。
    • 一部分可能已经发出去,正在等待 ACK;
    • 一部分还在缓冲区,随时可以发送。
  • 蓝色:待发送数据(窗口外) 在滑动窗口外面,暂时不能发送。只有当收到 ACK,窗口向右滑动、包含这部分数据之后,才允许发送。
  • start_win = 1001,end_win = 5001 窗口大小:end_win – start_win = 4000字节。 窗口内序号范围:[1001, 5000],这一段数据可以一次性连续发送,不用等每一段 ACK。 主机 A 把窗口内的数据分成 4 段,依次发送给主机 B。

    收到 ACK 报文

    主机 B 收到1001~2000的数据之后,回复 ACK 报文:

    • 确认序号:2001,含义:序号 2001 之前的数据全部收到,期望下一次收到序号 2001 开始的数据
    • ACK-win(rwnd 接收窗口)=4000,代表主机 B 还能接收 4000 字节

    更新窗口边界公式

  • 新的start_win = ACK确认序号
  • 新的end_win = 新start_win + ACK-win
  • 滑动窗口的大小是会发生变化的

    滑动窗口会向左滑动吗????

    不可能!!!

    确认序号只会越来越大,所以start_win只能不断向右(序号增大方向)移动,不可能回退、向左移动。


    快重传机制

    TCP 超时重传有一个缺点:需要等待超时计时器到期之后,才会重传丢失报文。一旦超时时间比较长,传输就会卡顿,降低吞吐量。快重传(快速重传)就是用来优化这个场景,不用等待超时,提前触发重传。

    我们可以思考一下这个过程中对方的确认序号是什么????

    如果发送端主机连续三次收到了同样一个 "4001" 这样的应答, 就会将对应的数据 4001-5000 重 新发送; • 这个时候接收端收到了 4001 之后, 再次返回的ACK就是7001了(因为2001 – 7000)接收端其实之前 就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;

    快重传核心规则

  • 触发条件:发送方收到3 个重复 ACK(冗余 ACK),直接判定报文丢失,立即重传丢失报文,不等待超时重传定时器。
  • 原理:接收方收到乱序报文时,会持续发送重复 ACK,告诉发送方缺少哪一段数据。多个重复 ACK 代表这个丢包概率很高。
  • 只重传丢失的那一段报文,不需要重传滑动窗口内所有数据。
  • TCP 接收方是按序接收字节流,只有补齐当前缺失的最前面一段,才能继续确认后面的数据。

    哪怕后面的报文已经到达接收缓冲区,只要前面一段空缺,就不会产生更大的确认序号,只会不断重复 “我缺当前窗口最开头这一段”。 所以:窗口内任意位置丢包,都会等价于当前窗口起点处丢包,只需要重传缺失的这一段,不需要重传整个窗口全部数据。

    两种重传触发方式

  • 快重传:收到 3 个相同的重复 ACK,立刻重传丢失报文,不等待超时。
  • 超时重传:兜底方案,迟迟收不到足够重复 ACK,定时器到期后重传。
  • 拥塞控制

    延时应答

    捎带应答

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux之TCP理论<3>
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!