当前位置: 首页 > news >正文

浅谈网络 | 传输层之TCP协议

目录

    • TCP 包头格式
    • TCP 的三次握手
    • TCP 的四次挥手
    • TCP 的可靠性与"靠谱"的哲学
    • TCP流量控制
    • TCP拥塞控制

上一章我们提到,UDP 就像我们小时候一样简单天真,它相信“网之初,性本善,不丢包,不乱序”,因此只提供了传输层必须的最基本字段,例如端口号,用于标识通信的双方。

但随着时间推移,我们逐渐长大,了解了“社会的复杂与残酷”,就像 TCP 协议那样成熟却复杂。相比 UDP 的简单信任,TCP 更加谨慎,它秉承“性恶论”的设计理念,默认认为网络环境是恶劣的,充满了丢包、乱序、重传和拥塞等问题。为了保证数据能够顺利送达,它从算法层面引入了许多机制来保障可靠性。

TCP 包头格式

我们先来看 TCP 头的格式。从这个图上可以看出,它比 UDP 复杂得多。

在这里插入图片描述
相比 UDP 的简单,TCP 包头显得复杂得多。这种复杂性正是为了应对网络的不确定性和可靠性问题。让我们逐步解析 TCP 包头,探讨它背后的设计逻辑和价值。

  1. 端口号:不可或缺的“地址标签”
    源端口号 和 目标端口号 是必不可少的字段,与 UDP 一样,用来标识数据的来源和去向。
    如果没有端口号,数据就像一封没有写收件人地址的信,完全无法送达应用程序。
    这是传输层与应用层沟通的基础。

  2. 包的序号:解决乱序的“稳重派”
    序号 是 TCP 的关键字段,作用是给数据包编号,用于解决乱序问题。
    TCP 是“社会老司机”,面对数据乱序,它会一件一件地处理,确保所有数据稳重有序地到达接收方。
    为什么需要序号?
    如果没有编号,接收方就无法判断哪个包应该先来,哪个应该后到。通过序号,TCP 能将“错乱的数据”还原为有序的字节流。

  3. 确认序号:保证可靠的“承诺派”
    确认序号 是 TCP 的另一大特点,主要用于实现可靠性。
    每个发送的数据包都需要收到确认。若未收到确认,TCP 会重传,直到对方确实收到为止。
    为什么需要确认?
    IP 层不保证可靠性,包可能会丢失,而 TCP 通过“重传机制”尽力弥补丢包的不足。
    这种设计体现了 TCP 的“靠谱”:承诺一定送达,即便网络环境差,也会通过努力完成任务。

  4. 状态位:维护连接的“社交礼仪”
    TCP 的状态位是它实现“面向连接”特性的核心。
    例如:

     SYN:发起连接时使用,表示“你好,我要建立连接了”。ACK:确认收到数据包,表示“好的,我知道了”。FIN:结束连接时使用,表示“再见,我们的通信结束了”。RST:异常情况时重置连接。
    

    这些状态位就像“社交礼仪”:

     初次见面时礼貌寒暄(SYN),交谈时互相回应(ACK),离开时正式告别(FIN)。
    

人与人之间的信任需要经过多次交互建立,TCP 连接的维护也同样如此。

  1. 窗口大小:懂分寸的“流量控制器”

TCP 的 窗口大小 是实现流量控制的重要字段。
通信双方通过窗口字段声明各自当前的处理能力,以避免以下两种情况:

  • 发得太快,接收方处理不过来(“撑死”)。
  • 发得太慢,浪费带宽资源(“饿死”)。

窗口控制就像老司机的“分寸感”:既能合理提出要求,又不强人所难,确保通信双方的平衡。

  1. 拥塞控制:知进退的“自我控制”
    TCP 的拥塞控制则更进一步:
    当网络环境变差(丢包、延迟增加)时,TCP 会主动降低发送速度,避免加剧网络拥堵。

为什么需要拥塞控制?

  • 如果网络通路已经堵塞,再大量发包只会导致更多丢包和更高延迟。
  • TCP 的拥塞控制机制让它学会“自我克制”,以保护整个网络的稳定性。

这是 TCP 的“成熟之道”:无法改变外部环境时,就通过改变自己适应环境。

重点解析 TCP 协议的五大问题
通过解析 TCP 包头,我们可以总结其核心设计思路:

  1. 顺序问题:稳重不乱

通过序号字段,确保数据按序到达。

  1. 丢包问题:承诺靠谱

通过确认序号和重传机制,保证数据可靠送达。

  1. 连接维护:有始有终

借助状态位(如 SYN、FIN),实现完整的连接管理。

  1. 流量控制:把握分寸

使用窗口大小,调节发送速率,避免“撑死”或“饿死”。

  1. 拥塞控制:知进知退

通过算法调整发送行为,适应网络状况,保护通信效率。

TCP 的三次握手

在网络通信中,TCP 的连接建立被称为 三次握手,是确保通信可靠性和序号同步的核心过程。这个过程看似简单,但背后蕴含了丰富的逻辑与机制。以下我们以“请求 -> 应答 -> 应答之应答”的形式逐步解析三次握手的原理、必要性及细节优化。

为什么需要三次握手?
为什么不能两次握手? 在一个不可靠的网络环境中,客户端 A 发送建立连接的请求(SYN 包),但可能发生以下情况:

请求丢失:网络传输中,SYN 包可能未抵达服务器 B。
绕路延迟:即使 SYN 包没有丢失,也可能在网络中绕路或超时。
无响应:目标服务器 B 因忙碌或配置原因未响应。
因此,A 无法直接判断连接是否建立,需要继续重发 SYN 包。假设某次请求最终到达 B,B 知道了 A 的连接请求,若 B 不愿意建立连接,A 的重试最终超时,连接失败即可。如果 B 同意连接,则 B 会回复 A 一个 SYN+ACK 包。然而:

仅两次握手存在风险:如果 B 在发送 SYN+ACK 后,A 未响应(例如 ACK 包丢失),B 将误以为连接已成功,导致连接状态的不一致。甚至,旧的 SYN 包重传还可能诱发无效连接。

因此,两次握手不足以确保双方均能正确确认连接状态。

为什么不采用四次或更多握手? 虽然增加握手次数可以提高可靠性,但网络的天然不确定性(例如丢包、延迟等)决定了握手次数过多也无法完全避免问题。事实上,TCP 设计的目标是在合理的开销内确保双方消息有去有回即可。一旦双方完成三次握手:
A 确认了 B 收到自己的 SYN 并愿意建立连接。
B 确认了 A 收到自己的 SYN+ACK 并发送了 ACK。
从此时起,双方可以安全地通信。

三次握手的过程
以下是 TCP 三次握手的具体流程及每步状态变化:

第一步:A -> B(SYN)
客户端 A 发送 SYN 包,表明希望建立连接,并处于 SYN-SENT 状态。

第二步:B -> A(SYN+ACK)
服务端 B 收到 SYN 包后,同意连接请求,同时发送自己的 SYN 包并确认 A 的 SYN(即 ACK)。此时,B 进入 SYN-RCVD 状态。

第三步:A -> B(ACK)
客户端 A 收到 B 的 SYN+ACK 后,回复 ACK,确认连接成功。此时,A 进入 ESTABLISHED 状态,同时 B 收到 ACK 后也进入 ESTABLISHED 状态,连接正式建立。

序号同步:三次握手的关键任务
三次握手不仅用于建立连接,还用于同步双方的 TCP 包序号。每个连接需要独立的序号以避免数据混淆:

  • A 告诉 B:起始序号从 X 开始。
  • B 告诉 A:起始序号从 Y 开始。

为什么序号不能从固定值(如 1)开始?
假设序号固定,可能发生以下问题:

A 与 B 建立连接并发送数据 {1,2,3}。因网络问题,包 3 延迟到达。
A 重新连接 B,序号再次从 1 开始,发送数据 {2,4}。此时延迟到达的旧包 3 会被误认为是当前连接的数据,导致错误。
为避免冲突,TCP 序号采用动态生成,通常以系统启动时间为基准,每 4ms 增加 1。32 位序号的重复周期接近 4 小时,而 IP 包的 TTL(生存时间)通常不足此时长,因此不会因序号重复导致数据混淆。

连接建立后的优化
在三次握手完成后,A 和 B 的通信即将开始,但仍需考虑以下问题:

  1. 后续数据传输解决丢包问题

    如果三次握手中的最后一个 ACK 包丢失,B 不会立即关闭连接。A 后续发送的数据包会间接确认连接已建立。
    如果 A 长时间不发送数据,B 可通过 KeepAlive 机制 定期发送探测包,检测连接状态。

  2. 空闲连接的资源释放

    如果客户端 A 在建立连接后未发送数据,B 可以主动关闭长时间空闲的连接,以释放资源给其他客户端。

三次握手与状态机
三次握手对应的 TCP 状态变化如下:

  1. 初始状态:

    客户端:CLOSED → 发送 SYN 后进入 SYN-SENT。
    服务端:监听端口,处于 LISTEN 状态。

  2. 握手过程:

    服务端收到 SYN 后,进入 SYN-RCVD,并回复 SYN+ACK。
    客户端收到 SYN+ACK 后,进入 ESTABLISHED,并回复 ACK。
    服务端收到 ACK 后,也进入 ESTABLISHED。
    至此,双方均完成状态确认,开始正常通信。
    在这里插入图片描述

TCP 的四次挥手

在 TCP 协议中,连接的关闭过程被称为 四次挥手。这个过程确保双方优雅断开连接,既支持双方数据的完整传输,又能避免因旧连接导致的新问题。以下以对话形式详细解析四次挥手及异常处理机制。
四次挥手的过程
四次挥手的过程对应客户端 A 和服务端 B 的状态转换,是连接终止的核心步骤:

第一步:A -> B(FIN)
A 表示自己不再发送数据,向 B 发送 FIN 包,进入 FIN_WAIT_1 状态。此时,A 可能还有一些数据需要接收。

第二步:B -> A(ACK)
B 收到 A 的 FIN 包后,回复一个 ACK 表示确认,但 B 可能还有未完成的任务,因此进入 CLOSE_WAIT 状态。此时,A 收到 ACK 后进入 FIN_WAIT_2 状态,等待 B 的 FIN 包。

第三步:B -> A(FIN)
当 B 完成数据传输后,向 A 发送 FIN 包,进入 LAST_ACK 状态,等待 A 的最终确认。

第四步:A -> B(ACK)
A 收到 B 的 FIN 包后,发送 ACK 确认连接关闭,并进入 TIME_WAIT 状态,等待一段时间以确保 B 收到 ACK。B 收到 ACK 后进入 CLOSED 状态,至此连接完全关闭。A 等待超时后也进入 CLOSED 状态。
在这里插入图片描述

四次挥手的设计原理
为什么需要四次挥手? 与连接建立的三次握手类似,关闭连接的四次挥手也是为了避免状态不同步问题。TCP 是全双工协议,允许双方独立关闭各自的发送通道:

  • A 发送 FIN,表示不再发送数据,但仍然可以接收数据。
  • B 在回复 ACK 后,可能仍有数据需要发送,因此不能立即关闭连接。

四次挥手的设计确保双方能独立完成数据的发送与接收,同时避免意外断开连接导致数据丢失。

为什么需要 TIME_WAIT 状态?

  • 可靠性保障:A 在发送最后的 ACK 后,可能会因网络问题导致 B 未收到 ACK。B 会重传 FIN,A 需要处于 TIME_WAIT
    状态以重新发送 ACK 确保 B 正确关闭连接。
  • 防止端口复用冲突:TIME_WAIT 确保旧连接的所有数据包(包括延迟的或重复的)在网络中失效后,端口才可以重新分配,避免干扰新连接。

四次挥手的状态转换详解
四次挥手的状态转换如下:

  1. A 发送 FIN,进入 FIN_WAIT_1 状态:A 主动发起关闭。
  2. B 收到 FIN,进入 CLOSE_WAIT 状态:B 确认 A 的 FIN,但继续处理自己的事务。
  3. B 发送 FIN,进入 LAST_ACK 状态:B 表示自己也完成数据传输。
  4. A 收到 FIN,发送 ACK,进入 TIME_WAIT 状态:A 确认连接关闭,等待 2MSL 时间。
  5. B 收到 ACK,进入 CLOSED 状态:B 完全关闭连接。A 等待超时后进入 CLOSED 状态。

异常情况处理
A 发送 FIN 后直接关闭
如果 A 发送 FIN 后直接断开,B 无法确认连接是否已关闭,可能重试发送数据或 FIN 包,导致连接不一致。解决方法:

  • A 等待 TIME_WAIT 时间:在 TIME_WAIT 期间,A 可响应 B 的重传 FIN,并重新发送 ACK 确保连接关闭。

B 未发送 FIN 或超时未收到 ACK
如果 B 在发送 FIN 后长时间未收到 A 的 ACK,B 会重传 FIN。但超过 2MSL 后,B 若仍未收到 ACK,会放弃等待并发送 RST 包强制关闭连接。

端口复用问题
如果 A 在 TIME_WAIT 状态期间立即重新使用同一端口,新应用可能收到旧连接中的延迟数据包。通过 TIME_WAIT,确保旧连接的数据包失效后再分配端口。

MSL(Maximum Segment Lifetime)的作用
MSL 是 TCP 报文在网络中生存的最大时间,通常用于确定 TIME_WAIT 的等待时间:

  • 确保 ACK 能被成功接收:TIME_WAIT 持续 2 倍 MSL,足够时间覆盖 FIN 重传及 ACK 的传递。
  • 避免端口复用导致的数据混淆:等待 2MSL 后,旧连接中的所有数据包都已失效,端口可安全复用。

实际应用中的 MSL:

  • 协议标准规定 MSL 为 2 分钟,但实际实现中常设置为 30 秒或 1 分钟以加快端口回收。

TCP 状态机
状态机简介
TCP 状态机包含两大核心流程:

  1. 连接建立(握手):通过三次握手完成通信通道的初始化,确保双方对连接的状态和序号达成一致。

  2. 连接断开(挥手):通过四次挥手终止通信,保障数据完整传输,并防止连接复用冲突。

加粗的实线表示客户端 A 的状态变迁,加粗的虚线表示服务端 B 的状态变迁。时序以 阿拉伯数字(连接) 和 大写中文数字(断开) 表示,清晰标记了各步骤的顺序。

在这里插入图片描述

连接建立

  1. CLOSED → LISTEN(服务端启动监听) 服务端 B 启动监听端口,进入 LISTEN 状态,等待客户端连接。
  2. CLOSED → SYN_SENT(客户端发起连接) 客户端 A 主动发起连接,发送 SYN 包,进入 SYN_SENT 状态。
  3. LISTEN → SYN_RCVD(服务端收到 SYN) 服务端 B 收到 SYN 包,发送 SYN+ACK,同时进入 SYN_RCVD
    状态。
  4. SYN_SENT → ESTABLISHED(客户端收到 SYN+ACK,回复 ACK) 客户端 A 收到 SYN+ACK 后,发送
    ACK,进入 ESTABLISHED 状态。
  5. SYN_RCVD → ESTABLISHED(服务端收到 ACK) 服务端 B 收到客户端的 ACK 后,进入 ESTABLISHED
    状态,连接建立完成。

连接断开
四次挥手的核心流程:

(一)ESTABLISHED → FIN_WAIT_1(客户端主动断开)
客户端 A 发送 FIN 包,进入 FIN_WAIT_1 状态,表明不再发送数据。

(二)ESTABLISHED → CLOSE_WAIT(服务端确认 FIN)
服务端 B 收到 FIN 包,发送 ACK 确认后进入 CLOSE_WAIT 状态。

(三)FIN_WAIT_1 → FIN_WAIT_2(客户端确认 ACK)
客户端 A 收到 ACK 后,进入 FIN_WAIT_2 状态,等待服务端的 FIN。

(四)CLOSE_WAIT → LAST_ACK(服务端主动断开)
服务端 B 完成最后的数据传输后,发送 FIN 包,进入 LAST_ACK 状态。

(五)FIN_WAIT_2 → TIME_WAIT(客户端确认 FIN)
客户端 A 收到服务端的 FIN 包后,发送 ACK 确认,进入 TIME_WAIT 状态。

(六)LAST_ACK → CLOSED(服务端关闭)
服务端 B 收到 ACK 后,进入 CLOSED 状态,连接完全关闭。

(七)TIME_WAIT → CLOSED(客户端关闭)
客户端 A 在等待 2MSL 后,确保所有残留包失效,关闭连接。

TCP 的可靠性与"靠谱"的哲学

TCP 协议以其严谨的设计和稳健的可靠性,成为互联网通信的基石。如果将 TCP 比喻成一个想要成为 “靠谱” 的人的话,那么它的每一个机制都蕴含着深刻的人生智慧。以下从现实职场管理和 TCP 的传输原理出发,优化并阐释 TCP 是如何成为一个“靠谱的人”。

玄奘出网关:公网中的挑战
玄奘西行,离开国境进入陌生的土地,需要面对未知的风险和挑战。同样,TCP 在公网传输中也需要面对网络的 不可靠性:

  1. 数据可能丢失:网络拥堵导致的数据包丢失。
  2. 数据可能延迟:数据绕远路或者等待资源。
  3. 数据可能重复:网络中的残留包导致重复接收。

为了在这种环境下做到 可靠传输,TCP 必须具备恒心(坚持重试)和智慧(巧妙的算法设计)。

"靠谱"的定义:TCP 的可靠传输哲学
如何成为一个靠谱的人?TCP 用自己的方式给出了答案:

  • 及时响应:任务交代后,必须有应答,不让事情“石沉大海”。
  • 主动重试:对未完成的任务持续跟进,直到明确结果。
  • 有序管理:任务有明确的编号和顺序,防止遗漏和混乱。
  • 高效协作:支持多任务并行,不浪费彼此时间。

TCP 的核心机制正是基于这些理念设计的。以下我们结合职场场景和 TCP 的工作原理,具体展开。

发送端和接收端的缓存管理及流量控制

发送端的缓存管理
发送端需要维护缓存以跟踪数据包的状态,缓存划分为四个部分:

  1. 已发送且确认的部分(可靠完成)

    这些是发送端交代的任务,并已经收到接收端的确认,标志着任务完成,可从缓存中移除。
    数据结构:LastByteAcked 记录这一部分的最后一个字节。

  2. 已发送但未确认的部分(待确认任务)

    已发送给接收端,但尚未收到确认。TCP 会等待确认,如果超时,则重新发送。
    数据结构:LastByteAcked 和 LastByteSent 之间的部分。

  3. 未发送但可发送的部分(计划任务)

    这些任务尚未分配,但可随时发送。受限于接收端的 AdvertisedWindow,只有接收端表示可以接受时,才能发送。
    数据结构:LastByteSent 和 LastByteAcked + AdvertisedWindow 之间的部分。

  4. 未发送且暂不可发送的部分(未来任务)

    超出了接收端的处理能力,暂时无法发送的任务。
    数据结构:LastByteAcked + AdvertisedWindow 之后的部分。

关键数据结构:

LastByteAcked:表示已确认部分的最后一个字节。
LastByteSent:表示已发送但未确认部分的最后一个字节。
AdvertisedWindow:接收端通告的可接收窗口,决定发送端可发送数据的范围。

在这里插入图片描述

接收端的缓存管理
接收端的缓存相对简单,但仍需要维护以下三部分:

  1. 已接收且确认的部分

    接收端已成功接收并确认的数据,可供应用层使用。
    数据结构:LastByteRead 记录这一部分的最后一个字节。

  2. 未接收但可接收的部分

    接收端能够接收的最大数据量,表示其最大工作能力。
    数据结构:由 NextByteExpected 和 MaxRcvBuffer 决定。

  3. 未接收且不可接收的部分

    超出接收端缓存能力的部分,接收端无法处理。

关键数据结构:

LastByteRead:应用层已读取的最后一个字节。
NextByteExpected:下一个期望接收的字节,用于标识数据包的顺序。
MaxRcvBuffer:接收端的总缓存容量。

窗口大小计算:

AdvertisedWindow = MaxRcvBuffer - ((NextByteExpected - 1) - LastByteRead)
NextByteExpected + AdvertisedWindow 定义了可接收窗口的边界。

在这里插入图片描述
流量控制与流畅协作
TCP 的流量控制机制类似于项目管理中的“把握分寸”原则,通过接收端的 AdvertisedWindow 限制发送端的任务量,避免资源过载:

  • 动态调整窗口大小:接收端根据自己的处理能力动态调整

  • AdvertisedWindow,通知发送端实时更新。

  • 空闲与过载防控:

    窗口过小:发送端过于保守,导致接收端空闲。
    窗口过大:接收端超载,可能导致数据丢失。

通过窗口的动态调整,TCP 实现了任务分配的“适量性”,既不让资源闲置,也不超负荷。

有序性与累计确认
TCP 的可靠性不仅体现在数据的传输成功,还体现在数据的顺序正确性:

  1. 序列号:每个数据包都带有唯一的序列号,发送端和接收端通过序列号确保数据按顺序处理。
  2. 累计确认:接收端发送的 ACK 表示所有小于当前序列号的数据包均已收到,未确认的部分将被重传。
  3. 乱序处理:即使数据包乱序到达,接收端仍能根据序列号重新排序,确保数据连续性。

累计确认的优势:

  • 减少 ACK 数量,提高效率。
  • 允许部分乱序,提高容错能力。

顺序问题与丢包问题的总结
TCP 协议在不可靠的网络环境中,通过精细的设计解决了数据包顺序问题与丢包问题。以下从确认与重传机制的核心逻辑出发,结合具体例子,总结 TCP 的应对策略及优化设计。

顺序问题与丢包问题的例子
假设发送端和接收端的状态如下:

发送端:

1、2、3:已发送并确认,无问题。
4、5、6、7、8、9:已发送但未确认。
10、11、12:未发送但可发送。
13、14、15:接收端没有空间,不可发送。

接收端:

1、2、3、4、5:已接收并确认,但尚未被应用层读取。
6、7:未接收但可接收。
8、9:已接收但未确认(因为前面的 6、7 尚未到达)。

问题分析:

顺序问题:发送端的包 6、7 丢失,8、9 虽然到达,但因数据乱序,接收端无法确认。
丢包问题:包 5 的 ACK 丢失,导致发送端以为包 5 未被接收,可能会触发不必要的重传。

确认与重传机制
TCP 为解决顺序问题与丢包问题,设计了多种重传机制,其中包含超时重传、快速重传和选择性确认(SACK)。

1. 超时重传
当发送端发送的数据包未收到 ACK,TCP 会通过超时定时器触发重传。以下是其核心逻辑:

  • 设定定时器:对每个未确认的数据包,启动定时器。

  • 超时重传:如果超过预设时间未收到 ACK,重新发送该数据包。

  • 自适应超时(RTT 估算):

    TCP 通过采样 RTT(往返时间),动态调整超时时间。
    超时时间公式:

    EstimatedRTT=(1−α)×EstimatedRTT+α×SampleRTT
    TimeoutInterval=EstimatedRTT+4×DevRTT
    

    其中 DevRTT 表示 RTT 的波动范围,用于适配网络环境的变化。

指数退避(Exponential Backoff):

  • 当网络状况恶劣时(连续超时),每次超时时间间隔加倍,避免频繁重传导致拥堵。

优点:适应网络延迟变化,确保丢失的数据最终被重传。
缺点:超时触发的周期可能较长,影响传输效率。

2. 快速重传
为了缩短等待超时的时间,TCP 提供了快速重传机制。核心逻辑:

  1. 冗余 ACK:

    当接收端检测到乱序数据包时,发现数据中断(如丢失包 7),会重复发送 ACK。
    例如,接收端收到包 8 和 9,未收到包 7,则会连续发送三个 “ACK6”,表明期望收到的是包 7。

  2. 触发重传:

    当发送端收到 3 个相同的冗余 ACK 后,认为对应的数据包丢失,立即重传该包,而无需等待超时。

示例:

  • 接收端收到 6、8、9,但未收到 7,则发送三个 ACK6。
  • 发送端收到 3 个冗余 ACK6,立即重传包 7。

优点:显著缩短丢包后的恢复时间,提高网络传输效率。
缺点:只适用于乱序或轻度丢包的情况,不能标识多个不连续丢失的数据包。

TCP流量控制

流量控制机制,在对于包的确认中,同时会携带一个窗口的大小。
假设窗口不变的情况,窗口始终为 9。4 的确认来的时候,会右移一个,这个时候第 13 个包也可以发送了。
在这里插入图片描述
这个时候,假设发送端发送过猛,会将第三部分的 10、11、12、13 全部发送完毕,之后就停止发送了,未发送可发送部分为 0。
在这里插入图片描述
当对于包 5 的确认到达的时候,在客户端相当于窗口再滑动了一格,这个时候,才可以有更多的包可以发送了,例如第 14 个包才可以发送。
在这里插入图片描述
如果接收方实在处理的太慢,导致缓存中没有空间了,可以通过确认信息修改窗口的大小,甚至可以设置为 0,则发送方将暂时停止发送。

我们假设一个极端情况,接收端的应用一直不读取缓存中的数据,当数据包 6 确认后,窗口大小就不能再是 9 了,就要缩小一个变为 8。
在这里插入图片描述
这个新的窗口 8 通过 6 的确认消息到达发送端的时候,你会发现窗口没有平行右移,而是仅仅左面的边右移了,窗口的大小从 9 改成了 8。
在这里插入图片描述
如果接收端还是一直不处理数据,则随着确认的包越来越多,窗口越来越小,直到为 0。

在这里插入图片描述
当这个窗口通过包 14 的确认到达发送端的时候,发送端的窗口也调整为 0,停止发送。
在这里插入图片描述
如果这样的话,发送方会定时发送窗口探测数据包,看是否有机会调整窗口的大小。当接收方比较慢的时候,要防止低能窗口综合征,别空出一个字节来就赶快告诉发送方,然后马上又填满了,可以当窗口太小的时候,不更新窗口,直到达到一定大小,或者缓冲区一半为空,才更新窗口。

这就是我们常说的流量控制。

TCP拥塞控制

TCP 的拥塞控制通过 拥塞窗口(cwnd) 和 接收窗口(rwnd) 协同工作,确保网络利用率的最大化,同时避免拥塞的发生。
基本概念:滑动窗口与拥塞窗口

  • 滑动窗口(rwnd): 防止接收端缓存溢出,由接收端通告的可接收窗口大小。
  • 拥塞窗口(cwnd): 防止网络拥堵,由发送端动态调整。
  • 核心公式:
    LastByteSent−LastByteAcked≤min(cwnd,rwnd)
    
    发送方已发送但未确认的字节数必须小于两个窗口中较小的那个。

TCP 如何判断网络是否满?
TCP 对网络的状态一无所知,只能通过以下反馈间接推测:

  • ACK 的返回速度: 如果 ACK 返回缓慢,可能说明网络传输延迟增加。

  • 丢包或超时: 网络设备的缓存溢出会导致数据丢失。

  • 发送速率与接收速率的平衡: 当发送速度大于网络或接收端处理能力时,会导致拥塞。

TCP 类比为往水管中灌水:

  • 水管粗细(带宽): 每秒能传输的最大数据量。

  • 水管长度(延迟): 数据从一端到另一端的往返时间(RTT)。

  • 通道容量:

    通道容量=带宽×RTT
    

在这里插入图片描述
如图所示,假设往返时间为 8s,去 4s,回 4s,每秒发送一个包,每个包 1024byte。已经过去了 8s,则 8 个包都发出去了,其中前 4 个包已经到达接收端,但是 ACK 还没有返回,不能算发送成功。5-8 后四个包还在路上,还没被接收。这个时候,整个管道正好撑满,在发送端,已发送未确认的为 8 个包,正好等于带宽,也即每秒发送 1 个包,乘以来回时间 8s。

拥塞控制的核心机制

  1. 慢启动(Slow Start)

    工作原理:

    初始设置:cwnd = 1。
    每次收到一个 ACK,cwnd 增加 1,形成指数性增长:
    第 1 轮:cwnd = 1,发送 1 个包;
    第 2 轮:cwnd = 2,发送 2 个包;
    第 3 轮:cwnd = 4,发送 4 个包;
    第 4 轮:cwnd = 8,发送 8 个包。
    指数增长直到达到慢启动阈值(ssthresh)。
    优点:

    能够快速填满通道,适应较大的网络带宽。
    结束条件:

    cwnd 达到 ssthresh。
    检测到拥塞(例如包丢失)。

  2. 拥塞避免(Congestion Avoidance)

    进入条件:
    cwnd 超过 ssthresh。
    增长方式:
    从指数增长转为线性增长:
    每次收到 ACK,cwnd 增加 1/cwnd
    假设 cwnd = 8,收到 8 个 ACK,则 cwnd 增加 1。
    下次 cwnd = 9。
    目的:
    缓慢提升窗口,避免网络过载。

  3. 拥塞检测与恢复
    快速重传算法
    超时重传:
    触发条件: 数据包超时未收到 ACK。
    处理策略:
    ssthresh = cwnd / 2。
    cwnd = 1,重新进入慢启动。
    缺点:
    重置为 1 的策略过于保守,导致传输速率骤降。
    快速恢复(Fast Recovery):
    触发条件: 接收端发送 3 个冗余 ACK(快速重传机制)。

    处理策略:

    cwnd 减半:cwnd = cwnd / 2。
    ssthresh 设置为当前 cwnd。
    从 cwnd = ssthresh 开始线性增长,而不是重新慢启动。
    优点:
    避免“一夜回到解放前”,更适合高吞吐量网络。

TCP 拥塞控制的综合过程

  1. 阶段 1:慢启动

    cwnd 从 1 开始指数增长。
    达到 ssthresh 或检测到拥塞时结束。

  2. 阶段 2:拥塞避免

    cwnd 开始线性增长。
    若检测到拥塞:
    超时重传: cwnd 重置为 1,重新慢启动。
    快速恢复: cwnd 减半,从 ssthresh 开始线性增长。

  3. 阶段 3:拥塞检测

    包丢失或超时:
    轻微拥塞(快速重传): 减小 cwnd,但保持较高速率。
    严重拥塞(超时重传): cwnd 重置为 1,重新慢启动。

示例应用
假设:
RTT = 8s,带宽 = 1 个包/s,每个包 1024 字节。
通道容量 = 8 个包。
过程:

  1. 第 1 秒:

    发送包 1,cwnd = 1。

  2. 第 2 秒:

    收到包 1 的 ACK,cwnd = 2,发送包 2 和 3。

  3. 第 3 秒:

    收到包 2 和 3 的 ACK,cwnd = 4,发送包 4、5、6、7。

  4. 第 4 秒:

    cwnd 达到 8,通道满载。

  5. 拥塞发生:

    包丢失或超时触发快速恢复或超时重传。

TCP 的拥塞控制是动态且自适应的,通过 慢启动 和 拥塞避免 保持传输效率,通过 快速恢复 降低丢包的冲击。这种机制保证了 TCP 在复杂网络条件下的可靠性与高效性,同时最大程度地利用了网络资源

TCP 的问题与 BBR 拥塞控制算法优化总结

TCP 的传统拥塞控制机制旨在避免两个主要问题:丢包 和 缓存填满导致的超时重传。然而,传统 TCP 拥塞控制也存在以下局限性:
第一个问题是丢包并不代表着通道满了,也可能是管子本来就漏水。例如公网上带宽不满也会丢包,这个时候就认为拥塞了,退缩了,其实是不对的。

第二个问题是 TCP 的拥塞控制要等到将中间设备都填充满了,才发生丢包,从而降低速度,这时候已经晚了。其实 TCP 只要填满管道就可以了,不应该接着填,直到连缓存也填满。

为了优化这两个问题,后来有了TCP BBR 拥塞算法。它企图找到一个平衡点,就是通过不断的加快发送速度,将管道填满,但是不要填满中间设备的缓存,因为这样时延会增加,在这个平衡点可以很好的达到高带宽和低时延的平衡。

在这里插入图片描述

相关文章:

浅谈网络 | 传输层之TCP协议

目录 TCP 包头格式TCP 的三次握手TCP 的四次挥手TCP 的可靠性与"靠谱"的哲学TCP流量控制TCP拥塞控制 上一章我们提到,UDP 就像我们小时候一样简单天真,它相信“网之初,性本善,不丢包,不乱序”,因…...

‌Kotlin中的?.和!!主要区别

目录 1、?.和!!介绍 2、使用场景和最佳实践 3、代码示例和解释 1、?.和!!介绍 ‌Kotlin中的?.和!!主要区别在于它们对空指针的处理方式。‌ ‌?.(安全调用操作符)‌:当变量可能为null时,使用?.可以安全地调用其方法或属性…...

【漏洞复现】代付微信小程序系统 read.php 任意文件读取漏洞

免责声明: 本文旨在提供有关特定漏洞的信息,以帮助用户了解潜在风险。发布此信息旨在促进网络安全意识和技术进步,并非出于恶意。读者应理解,利用本文提到的漏洞或进行相关测试可能违反法律或服务协议。未经授权访问系统、网络或应用程序可能导致法律责任或严重后果…...

Python人工智能项目报告

一、实践概述 1、实践计划和目的 在现代社会,计算机技术已成为支撑社会发展的核心力量,渗透到生活的各个领域,应关注人类福祉,确保自己的工作成果能够造福社会,同时维护安全、健康的自然环境,设计出具有包…...

PyQt6+pyqtgraph折线图绘制显示

1、实现效果 2、环境: 确认已经安装pyqtgraph的模块,如果没有安装,使用命令安装: pip install pyqtgraph 3、代码实现: 绘制折线函数: import sys import random from PySide6.QtWidgets import QAppl…...

1-golang_org_x_crypto_bcrypt测试 --go开源库测试

1.实例测试 package mainimport ("fmt""golang.org/x/crypto/bcrypt" )func main() {password : []byte("mysecretpassword")hashedPassword, err : bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)if err ! nil {fmt.Println(err)…...

C语言菜鸟入门·关键字·union的用法

目录 1. 简介 2. 访问成员 2.1 声明 2.2 赋值 3. 共用体的大小 4. 与typedef联合使用 5. 更多关键字 1. 简介 共用体(union)是一种数据结构,它允许在同一内存位置存储不同的数据类型,但每次只能存储其中一种类型的…...

leetcode hot100【LeetCode 238.除自身以外数组的乘积】java实现

LeetCode 238.除自身以外数组的乘积 题目描述 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 …...

【测试工具JMeter篇】JMeter性能测试入门级教程(一)出炉,测试君请各位收藏了!!!

一、前言 Apache JMeter是纯Java的开源软件,最初由Apache软件基金会的Stefano Mazzocchi开发,旨在加载测试功能行为和测量性能。可以使用JMeter进行性能测试,即针对重负载、多用户和并发流量测试Web应用程序。 我们选择JMeter原因 是否测试过…...

嵌入式驱动开发详解3(pinctrl和gpio子系统)

文章目录 前言pinctrl子系统pin引脚配置pinctrl驱动详解 gpio子系统gpio属性配置gpio子系统驱动gpio子系统API函数与gpio子系统相关的of函数 pinctrl和gpio子系统的使用设备树配置驱动层部分用户层部分 前言 如果不用pinctrl和gpio子系统的话,我们开发驱动时需要先…...

使用 OpenCV 进行视频中的行人检测

在计算机视觉领域,行人检测是一个重要的研究方向,它在视频监控、自动驾驶、人机交互等领域都有着广泛的应用。本文将介绍如何使用 OpenCV 库来实现视频中的行人检测。 环境准备 首先,我们需要安装 OpenCV 库。可以通过以下命令来安装&#…...

py文件转pyd文件的那些坑

当编译py脚本文件发给客户使用时,为了保密源代码防止反编译,可以将py文件转换为pyd文件,然后使用pyinstaller工具转换为exe应用程序,但有时测试发现,py文件可以正常运行,转换为pyd文件后却有各种错误&#…...

go interface(接口)使用

在 Go 语言中,接口(interface)是一种抽象类型,它定义了一组方法,但是不实现这些方法。接口指定了一个对象的行为,而不关心对象的具体实现。接口使得代码更加灵活和可扩展。 定义接口 接口使用 type 关键字…...

前端---HTML(一)

HTML_网络的三大基石和html普通文本标签 1.我们要访问网络,需不需要知道,网络上的东西在哪? 为什么我们写,www.baidu.com就能找到百度了呢? 我一拼ping www.baidu.com 就拼到了ip地址: [119.75.218.70]…...

unsloth vlm模型Qwen2-VL、Llama 3.2 Vision微调案例

T4卡15G显卡训练 参考: https://github.com/unslothai/unsloth 按自己显卡cuda版本安装 免费colab微调代码: Qwen2-VL: https://colab.research.google.com/drive/1whHb54GNZMrNxIsi2wm2EY_-Pvo2QyKh?usp=sharing from unsloth import FastVisionModel # NEW instead …...

云网络基础- TCP/IP 协议

文章目录 典型服务模式TCP/IP 协议设置和查看IPIP地址的分类:IP地址组成: 网络位主机位组成克隆:产生一台新的虚拟机win2008 典型服务模式 • C/S,Client/Server架构 – 由服务器提供资源或某种功能 – 客户机使用资源或功能 TCP/IP 协议 • TCP/IP是最广泛支持的通信协议集合…...

【数据分享】2001-2023年我国30米分辨率冬小麦种植分布栅格数据(免费获取)

小麦、玉米、水稻等各类农作物的种植分布数据在农业、环境、国土等很多专业都经常用到! 本次给大家分享的是我国2001-2023年逐年的30米分辨率冬小麦种植分布栅格数据!数据格式为TIFF格式,数据坐标为GCS_WGS_1984。该数据包括我国11个省份的冬…...

前端预览pdf文件流

需求 后端接口返回pdf文件流,实现新窗口预览pdf。 解决方案 把后端返回的pdf文件流转为blob路径,利用浏览器直接预览。 具体实现步骤 1、引入axios import axios from axios;2、创建预览方法(具体使用时将axios的请求路径替换为你的后端…...

【Leetcode 每日一题】146. LRU 缓存(c++)

146. LRU 缓存 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值&#x…...

Neural Magic 发布 LLM Compressor:提升大模型推理效率的新工具

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

QT简易项目 数据库可视化界面 数据库编程SQLITE QT5.12.3环境 C++实现

案例需求&#xff1a; 完成数据库插入&#xff0c;删除&#xff0c;修改&#xff0c;查看操作。 分为 插入&#xff0c;删除&#xff0c;修改&#xff0c;查看&#xff0c;查询 几个模块。 代码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget…...

为什么DDoS防御很贵?

分布式拒绝服务攻击&#xff08;DDoS攻击&#xff09;是一种常见的网络安全威胁&#xff0c;通过大量恶意流量使目标服务器无法提供正常服务。DDoS防御是一项复杂且昂贵的服务&#xff0c;本文将详细探讨为什么DDoS防御如此昂贵&#xff0c;并提供一些实用的代码示例和解决方案…...

Spring Boot3远程调用工具RestClient

Spring Boot3.2之后web模块提供了一个新的远程调用工具RestClient&#xff0c;它的使用比RestTemplate方便&#xff0c;开箱即用&#xff0c;不需要单独注入到容器之中&#xff0c;友好的rest风格调用。下面简单的介绍一下该工具的使用。 一、写几个rest风格测试接口 RestCont…...

C51相关实验

C51相关实验 LED //功能&#xff1a;1.让开发板的LED全亮&#xff0c;2,点亮某一个LED,3.让LED3以5Hz的频率闪动#include "reg52.h"#define LED P2 sbit led1 LED^1;void main(void) {LED 0xff;//LED全灭led1 0;while(1)//保持应用程序不退出{} }LED 输出端是高…...

云服务器部署springboot项目、云服务器配置JDK、Tomcat

目录 环境准备&#xff1a;JDK、Tomcat 将两个文件上传刀usr/java目录下并解压&#xff1a; 改java相关文件里的参数 执行以下命令&#xff0c;打开 profile 文件 执行以下命令&#xff0c;读取环境变量&#xff1a; 查看JDK是否安装成功&#xff1a; 改Tomcat参数 项目…...

STM32 USART串口发送

单片机学习&#xff01; 目录 前言 一、串口发送配置步骤 二、详细步骤 2.1 RCC开启USART和GPIO时钟 2.2 GPIO初始化 2.3 配置USART 2.4 开启USART 2.5 初始化总代码 三、发送数据函数设计 3.1 发送一个字节数据函数 3.2 发送一个数组函数 3.3 发送字符串函数 3.4…...

二叉树的深度搜索专题一>计算布尔二叉树的值

题目&#xff1a; 题目解析&#xff1a; 算法解析&#xff1a; 代码&#xff1a; public boolean evaluateTree(TreeNode root) {if(root.left null && root.right null) return root.val 1 ? true : false;boolean leftTree evaluateTree(root.left);boolean…...

Web day01 html css

目录 1.html: 常用标签&#xff1a; 1.video img p br b u i s &#xff1a; 2.布局标签&#xff1a;div span 3.表单标签&#xff1a; 4.表单标签项&#xff1a; 5.表格标签: 2.css: 1.使用样式: 2.选择器&#xff1a; 3.样式&#xff1a; 1.color&#xff1a; 2. …...

PHP 超级全局变量

超级全局变量是指在php任意脚本下都可以使用 PHP 超级全局变量列表: $GLOBALS&#xff1a;是PHP的一个超级全局变量组&#xff0c;在一个PHP脚本的全部作用域中都可以访问。 $_SERVER&#xff1a;$_SERVER 是一个PHP内置的超级全局变量,它是一个包含了诸如头信息(header)、路…...

题目:素数列

思路&#xff1a; 注意审题&#xff0c;题目中的等差素数列指公差相同且每一个元素都是素数的数列&#xff0c;并不是说是所有素数中一段连续且插值相同的数列&#xff0c;它可以是离散的。 因此&#xff0c;只需要暴力的遍历每一个素数&#xff0c;并找以其开头的所有可能等差…...

机器学习系列-决策树

文章目录 1. 决策树原理决策树的构建流程 2. 案例步骤 1&#xff1a;计算当前节点的熵步骤 2&#xff1a;对每个特征计算分裂后的熵(1) 按“天气”分裂数据集(2) 计算分裂后的加权熵 步骤 3&#xff1a;计算分裂依据信息增益信息增益率GINI系数&#xff08;二叉树&#xff09; …...

H3C OSPF 多区域实验

目录 前言 实验拓扑 实验需求 实验解析 路由器配置 测试 前言 此篇文章为 OSPF多区域试验&#xff0c;建议先食用OSPF单区域实验&#xff0c;理解实验原理 学习基本配置&#xff0c;再来使用此篇&#xff0c;效果更佳&#xff01;&#xff08;当然如果你已经了解原理与基…...

【Python】 深入理解Python的单元测试:用unittest和pytest进行测试驱动开发

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 单元测试是现代软件开发中的重要组成部分,通过验证代码的功能性、准确性和稳定性,提升代码质量和开发效率。本文章深入介绍Python中两种主流单元测试框架:unittest和pytest,并结合测试驱动开发(TDD)…...

微信小程序中使用iconfont的详细教程

我们知道微信小程序对包体积有很严格的要求&#xff0c;最大不超过2M&#xff0c;而图片资源对包体检有至关重要的影响&#xff0c;所以使用自定义的图标字体来代替大量图标图片也是提高小程序性能的重要手段&#xff0c;总的来说在微信小程序中使用 IconFont&#xff08;图标字…...

【小白学机器学习33】 大数定律python的 pandas.Dataframe 和 pandas.Series基础内容

目录 0 总结 0.1pd.Dataframe有一个比较麻烦琐碎的地方&#xff0c;就是引号 和括号 0.2 pd.Dataframe关于括号的原则 0.3 分清楚几个数据类型和对应的方法的范围 0.4 几个数据结构的构造关系 list → np.array(list) → pd.Series(np.array)/pd.Dataframe 1 python 里…...

hue 4.11容器化部署,已结合Hive与Hadoop

配合《Hue 部署过程中的报错处理》食用更佳 官方配置说明页面&#xff1a; https://docs.gethue.com/administrator/configuration/connectors/ 官方配置hue.ini页面 https://github.com/cloudera/hue/blob/master/desktop/conf.dist/hue.ini docker部署 注意&#xff1a; …...

“软件定义汽车”时代 | 产线海量数据刷写解决方案

一 背景 从起初汽车概念问世时期的“机械定义汽车”&#xff0c;到电力出现后的“电器定义汽车”&#xff0c;再到电子科技迅猛发展后的“电子定义汽车”&#xff0c;再到如今的“软件定义汽车”&#xff0c;可以看出&#xff0c;软件在车辆中扮演着越来越重要的角色。与此同时…...

DDoS对策是什么?详细解读DDoS攻击难以防御的原因与解决方案

近年来&#xff0c;DDoS&#xff08;分布式拒绝服务&#xff09;攻击的规模和频率不断增加。根据数据显示&#xff0c;2023年已观测到的最大攻击流量达到700Gbps&#xff0c;远远超出了许多企业的防御能力。DDoS攻击导致的网站性能问题如页面加载缓慢、频繁的504错误等现象&…...

【AI系统】Tensor Core 架构演进

自 Volta 架构时代起&#xff0c;英伟达的 GPU 架构已经明显地转向深度学习领域的优化和创新。2017 年&#xff0c;Volta 架构横空出世&#xff0c;其中引入的张量核心&#xff08;Tensor Core&#xff09;设计可谓划时代之作&#xff0c;这一设计专门针对深度学习计算进行了优…...

React前端框架基础知识详解

React 是由 Facebook 推出的一个用于构建用户界面的 JavaScript 库&#xff0c;现已成为前端开发中最流行的框架之一。React 的核心理念是通过组件化的方式构建用户界面&#xff0c;提升代码的可维护性和复用性。本文将为大家详细介绍 React 框架的基础知识&#xff0c;并带你快…...

Python学习——猜拳小游戏

import random player int(input(“请输入&#xff1a;剪刀 0&#xff0c;石头 1&#xff0c;布2”)) computer random.randint(0,2)# print(“玩家输入的是%d&#xff0c;电脑输入的是%d” %(player,computer)) 用于测试 if (player 0) and (computer 0) or (player 1) a…...

Spring:AOP通知类型

我们先来回顾下AOP通知: AOP通知描述了抽取的共性功能&#xff0c;根据共性功能抽取的位置不同&#xff0c;最终运行代码时要将其加入到合理的位置 通知具体要添加到切入点的哪里? 共提供了5种通知类型: 前置通知后置通知环绕通知(重点)返回后通知(了解)抛出异常后通知(了…...

【公益接口】不定时新增接口,仅供学习

文章日期&#xff1a;2024.11.24 使用工具&#xff1a;Python 文章类型&#xff1a;公益接口 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&#xff…...

php 导出excel 一个单元格 多张图片

public function dumpData(){error_reporting(0); // 禁止错误信息输出ini_set(display_errors, 0); // 不显示错误$limit $this->request->post(limit, 20, intval);$offset $this->request->post(offset, 0, intval);$page floor($offset / $limit) 1 ;$wh…...

Docker3:docker基础1

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…...

18. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--账本

这一篇我们来一起为账本功能编写代码。账本功能的代码很简单&#xff0c;就是一些简单的CURD操作。 一、需求 我们先来看一下需求&#xff1a; 编号需求说明1新增账本1. 账本名称不能和用户已有的账本名称重复2删除账本1. 存在收支记录的账本不能删除3修改账本1. 修改的账本…...

GPT1.0 和 GPT2.0 的联系与区别

随着自然语言处理技术的飞速发展&#xff0c;OpenAI 提出的 GPT 系列模型成为了生成式预训练模型的代表。作为 GPT 系列的两代代表&#xff0c;GPT-1 和 GPT-2 虽然在架构上有着继承关系&#xff0c;但在设计理念和性能上有显著的改进。本文将从模型架构、参数规模、训练数据和…...

在 Taro 中实现系统主题适配:亮/暗模式

目录 背景实现方案方案一&#xff1a;CSS 变量 prefers-color-scheme 媒体查询什么是 prefers-color-scheme&#xff1f;代码示例 方案二&#xff1a;通过 JavaScript 监听系统主题切换 背景 用Taro开发的微信小程序&#xff0c;需求是页面的UI主题想要跟随手机系统的主题适配…...

uni-app 界面TabBar中间大图标设置的两种方法

一、前言 最近写基于uni-app 写app项目的时候&#xff0c;底部导航栏 中间有一个固定的大图标&#xff0c;并且没有激活状态。这里记录下实现方案。效果如下&#xff08;党组织这个图标&#xff09;&#xff1a; 方法一&#xff1a;midButton的使用 官方文档&#xff1a;ta…...

leetcode 无重复字符的最长子串

3. 无重复字符的最长子串 已解答 中等 相关标签 相关企业 提示 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 提示&#xff1a; 0 < s.length < 5 * 104s 由英文字母、数字、符号和空格组成 class Solution:def lengthOfLongest…...