KCP源码解析系列(五)拥塞控制

news2024/11/27 0:49:41

流量控制和拥塞控制的区别

如果你约了你的朋友见面聊一件事,有两种沟通方式:

  • 第一种是一次只说一句话,然后等待你的朋友回应“收到”,确认他在听之后,再说下一句话,如此反复,直到事情说完。
  • 第二种是一次性把所有话说完,然后再等朋友点头确认他都听到了。
    理想情况下,第二种方式是最好的,因为这样来回沟通的次数较少。然而,在实际过程中,还需要考虑以下几个因素:
  • 对方的理解能力。一次说太多,可能对方无法完全理解和接收。
  • 聊天环境。如果环境较为嘈杂,比如在KTV里,那么一次性说太多可能会导致对方听不清,从而产生困惑和尴尬。
    第一个因素涉及到流量控制(Flow Control),主要考虑接收方的信息处理能力。第二个因素涉及到拥塞控制(Congestion Control),主要考虑网络或沟通环境能承受的信息量。

拥塞控制的方式

  1. 慢开始(Slow-start)、拥塞避免(Congestion Avoidance)

  2. 快重传(Fast Restrangsmit)、快恢复(Fast Recovery)

下图可以表示整个拥塞控制的流程,但这是TCP的,KCP原理一样,但增速有差异。这个图初一看很复杂,但理解起来很简单。
在这里插入图片描述

拥塞控制的思路

  1. 当主机开始发送数据时,如果立即将较大的发送窗口的全部数据字节都注入到网络中,那么由于不清楚网络的情况,有可能引其网络拥塞

  2. 比较好的方法是试探一下,即从小到大逐渐增大发送端的拥塞控制窗口数值。这就叫慢开始。虽然开始的慢,但增速快。通常在刚刚开始发送报文段时可先将拥塞窗口cwnd设置为一个最大报文段的MSS的数值。在每收到一个对新报文段确认后,将拥塞窗口增加一个MSS的数值。

  3. 需要设定一个ssthresh,传输门限值,这是一个动态调整的阈值,根据cwnd和ssthresh的比较,采用不同的策略。

1)当cwnd < ssthresh时,使用慢开始算法

2)当cwnd > ssthresh时,停止使用慢开始算法而改用拥塞避免算法

3)当cwnd = ssthresh时 即可以使用慢开始算法,也可以使用拥塞避免算法。

  1. 随着cwnd逐步的增大,发送速度也可能会逐步增大,网络就可能发生拥塞,具体就是发生丢包。此时就会触发快重传。快重传很简单,就是比如你发送了1,2,3,4,5这几个包,然后收到了1,3,4,5的ack,2被连续跳过了三次,就直接把2重新发一次。

  2. 当发生快重传时,说明网络环境不太好,这是可以快速降低ssthresh,避免网络进一步发生堵塞,这个就叫快恢复。

KCP源码中的拥塞控制

慢开始和拥塞避免

 //当发送窗口的snd_una右移,说明可以更新拥塞窗口了
    if (_itimediff(kcp->snd_una, prev_una) > 0) {
        // 如果拥塞窗口小于远端窗口,表示还有发送空间
        if (kcp->cwnd < kcp->rmt_wnd) {
            
            IUINT32 mss = kcp->mss;
            // 慢开始的处理
            if (kcp->cwnd < kcp->ssthresh) {
                kcp->cwnd++;
                kcp->incr += mss;// 拥塞窗口增加量加上一个 MSS,增速较快。
            } else {// 拥塞避免阶段
                // 保证 incr 不小于 MSS
                if (kcp->incr < mss) {
                    kcp->incr = mss;
                }
                //这里的 /kcp->incr 表示增量会随着incr的增长而放缓, + (mss / 16)这部分是一个调节增加量,避免增长过于缓慢。
                kcp->incr += (mss * mss) / kcp->incr + (mss / 16);
                //这部分代码是为了更新cwnd
                if ((kcp->cwnd + 1) * mss <= kcp->incr) {
#if 1
                    kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0) ? mss : 1);
#else
                    kcp->cwnd++;
#endif
                }
            }
            //拥塞窗口>远端窗口,以远端窗口为准
            if (kcp->cwnd > kcp->rmt_wnd) {
                kcp->cwnd = kcp->rmt_wnd;
                kcp->incr = kcp->rmt_wnd * mss;
            }
        }

快重传

kcp协议在接收ACK时,会更新segments的fastack计数,如果达到阈值就立即重传

// 更新快速重传计数 fastack,sn为当前收到的包的
void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) {
    struct IQUEUEHEAD *p, *next;

    // 如果 sn 在发送队列的范围之外,则不进行处理
    if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0)
        return;

    // 遍历发送缓冲区,根据传入的 sn 更新各 segment 的 fastack 计数
    for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) {
        IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node);
        next = p->next;

        // 如果sn小于seg->sn,跳出循环,后面的seg->sn只会更大
        if (_itimediff(sn, seg->sn) < 0) {
            break;    
        }
        // 如果sn大于seg->sn,则增加其 fastack 计数
        else if (sn != seg->sn) {
            seg->fastack++;
        }
    }
}

在每次调用 ikcp_flush 函数时,会检测 segments 的 fastack 是否超过阈值 fastresend,如果超过,则立即进行重传:

void ikcp_flush(ikcpcb *kcp) {
    struct IQUEUEHEAD *p;
    //遍历发送缓冲区
    for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) {
        IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node);
        int needsend = 0;
        //判断是否达到快速重传的条件
        if (segment->fastack >= kcp->fastresend) {
           // segment->xmit未超过最大传输次数或者未设置传输限制
            if ((int)segment->xmit <= kcp->fastlimit || kcp->fastlimit <= 0) {
                needsend = 1;
                segment->xmit++;
                segment->fastack = 0;
                segment->resendts = current + segment->rto;
                change++;
            }
        }
        //如果需要重传
        if (needsend) {
            segment->ts = current;
            segment->wnd = seg.wnd;
            segment->una = kcp->rcv_nxt;
            size = (int)(ptr - buffer);
            need = IKCP_OVERHEAD + segment->len;
             // 判断当前缓冲区大小是否超过最大传输单元 (MTU),如果超过则先发送现有数据
            if (size + need > (int)kcp->mtu) {
                // 发送现有数据
                ikcp_output(kcp, buffer, size);
                // 重置缓冲区指针
                ptr = buffer;
            }
            ptr = ikcp_encode_seg(ptr, segment);
            if (segment->len > 0) {
                memcpy(ptr, segment->data, segment->len);
                ptr += segment->len;
            }
            // 检查传输次数是否超过死链接限制 (dead_link)
            if (segment->xmit >= kcp->dead_link) {
                // 标记连接状态为死链接
                kcp->state = -1;
            }
        }
    }
    // 其他处理...
}d

快速恢复

//change:快速重传的情况说明网络存在短时压力,但没有明显的丢包情况,采用温和策略(增量减半)以快速恢复,始终保持一定传输速率。
//lost:数据包明确丢失表明网络拥塞加剧,需要大幅度减少传输量(cwnd 设为 1)消除拥塞压力。这种情况下,确保数据继续传输但速率极低,避免进一步拥堵。

//是否触发了快重传
if (change) {
    //占用的窗口大小
    IUINT32 inflight = kcp->snd_nxt - kcp->snd_una;
    //慢启动阈值减半
    kcp->ssthresh = inflight / 2;
    if (kcp->ssthresh < IKCP_THRESH_MIN) kcp->ssthresh = IKCP_THRESH_MIN;
    //重设cwnd
    kcp->cwnd = kcp->ssthresh + kcp->fastresend;
    kcp->incr = kcp->cwnd * kcp->mss;
}
//是否触发了丢失
if (lost) {
  // 将 ssthresh 初始值为当前 cwnd 一半,确保下一次启动控制合理。
    kcp->ssthresh = kcp->cwnd / 2;
    if (kcp->ssthresh < IKCP_THRESH_MIN) kcp->ssthresh = IKCP_THRESH_MIN;
   // 设置拥塞窗口 cwnd 为 1
    kcp->cwnd = 1;
    kcp->incr = kcp->mss;
}

if (kcp->cwnd < 1) {
    kcp->cwnd = 1;
    kcp->incr = kcp->mss;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2063031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

身份证二要素验证接口如何用PHP进行调用

一、什么是身份证二要素验证接口&#xff1f; 身份证二要素验证接口又叫身份证实名认证、身份实名验证、身份证核验接口、实名核验接口等&#xff0c;该接口叫法非常多&#xff0c;但是他的入参都是一样的&#xff0c;核验姓名和身份证号码是否一致&#xff0c;并返回性别、籍…

2024年了,我依然建议去创建属于自己的Google App

大环境的确不好&#xff0c;Android&#xff0c;iOS程序员都白菜价了&#xff0c;每天充斥着我们耳边的都是裁员、降薪等不好的消息&#xff0c;是不是我们移动端开发人员的日子真的到头了呢&#xff1f;大约在一年前&#xff0c;其实我也意识到这个问题&#xff0c;想学习的话…

BlazeFace: Sub-millisecond Neural Face Detection on Mobile GPUs

Abstract 我们提出了BlazeFace&#xff0c;这是一种轻量级且性能优异的面部检测器&#xff0c;专为移动GPU推理而设计。它在旗舰设备上运行速度可达200到1000 FPS。这种超实时性能使其能够应用于任何增强现实管道中&#xff0c;作为任务特定模型的输入来准确识别面部感兴趣区域…

CST软件仿真案例:圆极化平板天线仿真02

本期继续完成一款圆极化Patch天线的仿真实例。读者可以完整的了解到怎么用CST微波工作室&#xff0c;完成对一款天线建模、设置到仿真分析的完整过程。 本期中&#xff0c;我们要设计的圆极化天线尺寸如下图所示&#xff1a; 本期内容是接着上期部分开始。首先先完成仿真实例0…

k8s部署redis一主两从三哨兵(集群内访问)

一、配置清单 1.基于K8s搭建部署1主2从3哨兵的Redis集群哨兵模式(集群内访问) 2.持久化数据选择用storageclass&#xff0c;动态创建pv存储&#xff0c;动态存储使用longhorn 创建redis配置文件 #注意内容中注释 apiVersion: v1 kind: Namespace metadata:name: prod--- ap…

C#学习第二节课 ,伤害计算

伤害计算 我一直好奇游戏的伤害计算是怎么计算并输出的,这第二节课利用学过的初级语法,Console.WriteLine,Console.ReadLine(),以及基础变量,int,string 和if 判断 组合,来实现打印一下伤害计算吧! 老规矩 先上结果图 代码区域 namespace hello01 {internal class Program …

秒杀商品超出限量购买修复方法

问题:秒杀商品购买会超出限量,多买 修复: 主要修改了以下文件,如果需要了文件全部在附件中,修改的基础版本是v2.3.2如果没有二开过,可以直接覆盖替换;如果二开了,请对比处理, ⚠️注意: 这样修改完以后,可能导致之前的秒杀都库存不足无法购买 ,需要重新添加 修改的记录,以供参…

开源低代码开发工具Lowcoder

Lowcoder_CN 是一个基于Web的低代码开发平台&#xff0c;旨在通过可视化界面和丰富的API&#xff0c;支持快速应用开发、企业内部工具构建以及教育用途。 以下是对Lowcoder_CN的详细介绍&#xff1a; 一、平台特点 开源与免费&#xff1a;Lowcoder_CN是开源的&#xff0c;允许…

【ACM出版 | IEEEACM院士、CCF杰出会员担任组委| 往届会后3个半月检索 】第三届人工智能与智能信息处理国际学术会议(AIIIP 2024)

第三届人工智能与智能信息处理国际学术会议&#xff08;AIIIP 2024&#xff09;将于2024年10月25日-27日在中国-天津举行。新一代人工智能理论的快速发展为信息处理技术的提供了新方法&#xff0c;促进了智能信息处理的发展与应用。智能信息处理是信号与信息领域一个前沿、热点…

颖通控股突击巨额分红6.31亿远超净利润,滞销产品三年累计1750万

《港湾商业观察》黄懿 7月18日&#xff0c;被外界称为香水巨头的颖通控股有限公司&#xff08;下称“颖通控股”&#xff09;向港交所主板提交上市申请&#xff0c;BNP Paribas、中信证券为其联席保荐人。颖通&#xff08;上海&#xff09;贸易有限公司为其国内运营主体。 据…

[星瞳科技]OpenMV如何进行wifi通信?

OpenMV官方扩展板采用ATWINC1500模组&#xff0c;可以传输图像。 详细资料&#xff1a;http://singtown.cc/product/openmv-wifi扩展板/ 如果想使用ESP8266&#xff0c;使用串口通信就可以。 wifi扫描 无线传输图像 无线传输小球坐标 import sensor import time import netw…

基于PHP+MySQL组合开发的DIY分销商城小程序源码系统 附带源代码包以及搭建部署教程

系统概述 随着消费者对购物便捷性、个性化需求的不断增长&#xff0c;传统的电商模式已难以满足市场多样化需求。分销商城小程序以其低门槛、易传播、高粘性等特点&#xff0c;成为众多商家转型升级的首选。本源码系统正是基于这一市场需求&#xff0c;利用PHP这一成熟稳定的后…

【C++】深度解析:用 C++ 模拟实现 priority_queue类,探索其底层实现细节(仿函数、容器适配器)

目录 ⭐前言 ✨堆 ✨容器适配器 ✨仿函数 ⭐priority_queue介绍 ⭐priority_queue参数介绍 ⭐priority_queue使用 ⭐priority_queue实现 ✨仿函数实现 ✨堆的向上调整和向下调整 ✨完整代码 ⭐前言 ✨堆 堆是一种特殊的树形数据结构&#xff0c;通常以二叉树的…

AVI视频损坏了怎么修复?轻松几步解决你的困扰

在数字化时代&#xff0c;视频已成为我们记录生活、分享经验和传递信息的重要方式。AVI作为一种常见的视频格式&#xff0c;因其无损质量的特点而受到广泛欢迎。然而&#xff0c;有时候我们可能会遇到AVI视频文件损坏的情况&#xff0c;导致无法正常播放。别担心&#xff0c;本…

探索paho-mqtt:Python世界的物联网通信桥梁

文章目录 **探索paho-mqtt&#xff1a;Python世界的物联网通信桥梁**第一部分&#xff1a;背景介绍第二部分&#xff1a;paho-mqtt概览第三部分&#xff1a;安装指南第四部分&#xff1a;基础函数使用第五部分&#xff1a;实际应用场景第六部分&#xff1a;常见问题与解决方案第…

VMware vSphere Replication 虚拟机备份及迁移实践

vSphere Replication 介绍 vSphere Replication 是适用于 vSphere 的基于 Hypervisor 管理程序的异步复制解决方案&#xff0c;是 VMware vCenter Server 的扩展&#xff0c;包含在vCenter Server Standard中&#xff0c;可为环境中的所有虚拟机提供灾难恢复和数据保护。 vSph…

上午 2019

信息系统规划方法_信息系统规划有哪些方法-CSDN博客 1. 关键成功因素法&#xff08;CSF&#xff09; 在现行系统中&#xff0c;总存在着多个变量影响系统目标的实现&#xff0c;其中若干个因素是关键的和主要的&#xff08;即关键成功因素&#xff09;。通过对关键成功因素的识…

SpingBoot 两种方式配置多数据源

第一种&#xff1a;使用与MyBaits-Plus师出同门的“dynamic-datasource-spring-boot-starter” 官网地址&#xff1a; 基础必读&#xff08;免费&#xff09; dynamic-datasource 看云 1&#xff1a;引入依赖 <!-- 苞米豆多数据源 --> <dependency><group…

DID测试套件

DID测试套件 介绍 名称 DID Test Suite 网址 https://github.com/w3c/did-test-suite 功能 用于验证DID实现是否符合W3C DID Core规范的一系列测试反映各DID方法&#xff08;如did:orb、did:key、did:web等&#xff09;的实现对DID Core规范的遵从程度确保不同DID方法、…

揭示 Vue 3 setup 函数的奥秘

setup 是 Vue 3 的新特性&#xff0c;也是组合式 API&#xff08;Composition API&#xff09;的核心。它提供了一种全新的方式设计组件的逻辑&#xff0c;便于复用同时也增加代码可读性。 1. 理解 setup 1、组合式 API 的入口 setup 是在组件实例创建之前执行的&#xff0c…