深入解析Linux内核网络-拥塞控制系列(二)

上篇文章:深入解析Linux内核网络-拥塞控制系列(一)对Linux内核网络中网络拥塞框架的框架进行了分析。本次针对具体的Cubic拥塞控制算法进行简单分析在进行代码的梳理前,同样还是先来看一下相关概念、原理:

在上一篇文章中也提到了拥塞控制算法的分类,Cubic属于基于丢包反馈的拥塞控制算法,即将丢包视为发生了网络拥塞的标志。在众多讲解Cubic拥塞控制算法的技术博文中,都会先谈及BIC拥塞控制算法。之所以涉及BIC拥塞控制算法,因为Cubic是在BIC拥塞控制算法上进一步衍生出来的。本文先将BIC进行介绍。

BIC拥塞控制算法的核心思想是通过二分搜索的思想来找到当前链路适合的拥塞窗口大小。如下图所示是BIC拥塞控制算法的拥塞窗口图像。

显然当链路在网络上因为排队而发生丢包时,链路的最佳拥塞窗口肯定是小于丢包时的拥塞窗口的,那么把丢包时的拥塞窗口大小记为 。发生丢包后,BIC使用乘法因子 缩小网络拥塞窗口的大小,并记录缩小前的拥塞窗口值为 ,随后进入上图中类似二分法的探索阶段,即每收到一个ACK的时候,便将窗口设置到 和 in的中点,拥塞窗口增长到这个中间值且没有出现丢包的话,就说明网络还可以容纳更多的数据包。那么将当前这个中值设为新的最小值,按照二分法重新计算下一个增长点,一直持续到接近Wmax。如上图所示每一个RTT轮次进行一次拥塞窗口的增加。由于二分法探索的性质,当远离 时上升较快,接近 时上升缓慢。越接近 附近,拥塞窗口的增长速度越慢,意味着在发生一次拥塞丢包缩小拥塞窗口后,拥塞窗口的增长可以更快地探索到 ,而且可以在 附近停留较多的时间。但上述描述的BIC拥塞控制算法存在不公平性问题,具体地,拥塞控制窗口的大小与RTT的大小强相关(每一个RTT轮次进行一次类似二分法的拥塞窗口探索)。例如当网络中存在两个TCP连接,其中一个TCP连接的RTT为20ms,另一个是30ms,那么RTT=20ms的TCP连接拥有更高的拥塞窗口增长率。

为缓解BIC的RTT不公平性问题,提出Cubic拥塞控制算法,Cubic的解决方法比较直接,将类似二分法的拥塞窗口函数设计成了如下1.1的三次函数 。

函数大致图像如下图所示,需要值得关注的是,函数的横坐标是时间T,而不是RTT。具体地,函数表达式如1.1所示。函数中的C是一个常数,作为调节因子,t是最近一次检测到丢包后经过的时间(如果假设丢包后进入一个新的拥塞窗口探索轮次,那么t就是当前轮次的持续时间,也是自上次窗口减少到当前的时间)。K的取值如1.2所示,其中 是乘法减少因子, 是最近一次发生网络拥塞时的拥塞窗口值,K代表1.1函数在没有丢包的条件下,从当前拥塞窗口增长到 所要花费的时间。从1.1公式里也可以看出来,拥塞窗口的变化不再是由RTT强相关。在拥塞避免阶段接收到ACK时,Cubic在下一个RTT使用公式1.1计算拥塞窗口(W(t+RTT))作为Target。

(1.1)

(1.2)

以上,将BIC到Cubic的基本原理进行了概览,下面将切入到Linux内核源码中看Cubic拥塞控制算法的相关实现。在分析代码流程前,这里先把Cubic拥塞控制算法涉及的核心变量贴出来:

struct bictcp {
 u32 cnt;  /* increase cwnd by 1 after ACKs */
 u32 last_max_cwnd; /* last maximum snd_cwnd */
 u32 last_cwnd; /* the last snd_cwnd */
 u32 last_time; /* time when updated last_cwnd */
 u32 bic_origin_point;/* origin point of bic function */
 u32 bic_K;  /* time to origin point
       from the beginning of the current epoch */
 u32 delay_min; /* min delay (msec << 3) */
 u32 epoch_start; /* beginning of an epoch */
 u32 ack_cnt; /* number of acks */
 u32 tcp_cwnd; /* estimated tcp cwnd */
 u16 unused;
 u8 sample_cnt; /* number of samples to decide curr_rtt */
 u8 found;  /* the exit point is found? */
 u32 round_start; /* beginning of each round */
 u32 end_seq; /* end_seq of the round */
 u32 last_ack; /* last time when the ACK spacing is close */
 u32 curr_rtt; /* the minimum rtt of current round */
}

如上,结构体struct bictcp包含了上述讲到的Cubic拥塞控制算法的计算公式所涉及到的数值。struct bictcp作为拥塞控制中的核心结构体,嵌入在struct inet_connection_sock中。[提示:关于网络协议栈相关的核心结构体,以及之间的关系、在拥塞控制的相关源码中会看到如下转换:

struct sock *sk;
struct tcp_sock *tp = tcp_sk(sk);
struct bictcp *ca = inet_csk_ca(sk);

回到struct bictcp结构体上,内核源码的注释较清楚,这里就重点介绍四个重要的结构体成员。

第一个是epoch_start,当发生丢包后,进行拥塞窗口的乘法减小,即cwnd = *Wmax,同时也会重置epoch_start = 0。随即会进入一个探索拥塞窗口的新阶段,称该阶段为一个新的轮次,通过判断epoch_start是否被重置为0,若被重置,接下来会设置epoch_start = current_time,意味着正式进入一个新的拥塞窗口探索轮次,理解这个轮次是非常重要的。可以认为ecpoch_start的作用是为每个新轮次的打时间戳。

第二个是cnt,为Cubic算法中的极为重要的变量,cubic的核心函数的最终目的就是计算出cnt值,用来控制在拥塞避免状态阶段,何时才能增大拥塞窗口,具体实现是通过与struct tcp_sock中的snd_cwnd_cnt(snd_cwnd_cnt表示当前的拥塞窗口中已经发送,即经过对方ACK确认的数据段的个数)进行比较,决定是否增大拥塞窗口大小,可以认为cnt是增加一个单位cwnd需要的ACK数量。两者通过比较,共同完成拥塞窗口的增长控制。

第三个是bic_origin_point,代表新的链路饱和点,取MAX(last_max_cwnd,snd_cwnd),即取上一次丢包时的拥塞窗口大小与当前拥塞窗口的最大值。

第四个是bic_K,对应到公式中的K,源码中也有注释:time to origin point from the begining of the current epoch。表示在当前轮次内,假设没有丢包的情况下,从 *Wmax增长到链路饱和点bic_origin_point所要花费的时间,即bic_k决定了 *Wmax在没有进一步丢包的情况下到达bic_origin_point的时间。bic_K只会在进入每个新的轮次开始时进行计算。如下图所示,将bic_origin_point、epoch_start、bic_k描述在Cubic算法函数图像中。

在上一篇文章深入解析Linux内核网络-拥塞控制系列(一)中提到了拥塞控制算法的框架,Cubic算法实现的接口如下所示:

static struct tcp_congestion_ops cubictcp __read_mostly = {
 .init  = bictcp_init,
 .ssthresh = bictcp_recalc_ssthresh,
 .cong_avoid = bictcp_cong_avoid,
 .set_state = bictcp_state,
 .undo_cwnd = tcp_reno_undo_cwnd,
 .cwnd_event = bictcp_cwnd_event,
 .pkts_acked     = bictcp_acked,
 .owner  = THIS_MODULE,
 .name  = "cubic",
};

先着重看一下cong_avoid接口的实现,如下所示为该接口的实现,其中参数acked代表当前新的已确认的数据包。tcp_is_cwnd_limited函数是用于判断TCP连接是否受到拥塞窗口的限制,即检查发出去,但是还、有收到ACK的包是否达到了拥塞窗口的上限,可以暂且先不关注这个函数。重点看tcp_slow_start和bictcp_update、tcp_cong_avoid_ai函数。

static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
 struct tcp_sock *tp = tcp_sk(sk);
 struct bictcp *ca = inet_csk_ca(sk);

 if (!tcp_is_cwnd_limited(sk))
  return;

 if (tcp_in_slow_start(tp)) {
  ......
  acked = tcp_slow_start(tp, acked);
  if (!acked)
   return;
 }
 bictcp_update(ca, tp->snd_cwnd, acked); /*计算一个ca->cnt出来 */
 tcp_cong_avoid_ai(tp, ca->cnt, acked);  /* 通过 计算的ca->cnt 进行拥塞控制 控制窗口cwnd的增长 */
}

tcp_in_slow_start函数通过判断是否处于慢启动区域,如下所示,通过比较当前的拥塞窗口大小与慢启动门限值,进而判断是否处于慢启动阶段。

static inline bool tcp_in_slow_start(const struct tcp_sock *tp)
{
 return tp->snd_cwnd < tp->snd_ssthresh;
}

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linuxc/c++高级开发【直播公开课】

零声白金VIP体验卡:零声白金VIP体验卡(含基础架构/高性能存储/golang/QT/音视频/Linux内核)

若处于慢启动阶段,则按照如下算法对拥塞窗口进行增加。那么该函数返回的acked如何理解?acked:刚刚被确认的数据包数量,且还未用来更新到拥塞窗口的剩余大小。如果在tcp_slow_start的慢启动流程中,acked值最终消耗变为0,那么说明刚刚确认的数据包均用在了慢启动环节的拥塞窗口增长上。当前还未退出慢启动阶段。

u32 tcp_slow_start(struct tcp_sock *tp, u32 acked)
{
   //在慢启动阶段,cwnd最多增加到慢启动门限值
 u32 cwnd = min(tp->snd_cwnd + acked, tp->snd_ssthresh);
   //acked用于更新cwnd,acked已使用cwnd-tp->snd_cwnd,更新acked
 acked -= cwnd - tp->snd_cwnd;
    //snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this */
 tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp);

 return acked;
}

若在慢启动阶段返回的acked不为0,说明在慢启动阶段,刚刚确认的数据包已经足够使得拥塞窗口大小超过慢启动门限值,足以退出慢启动,剩余的acked用于拥塞避免阶段的增长。此时进入bictcp_update函数中,该函数也是Cubic的核心函数,函数的第一个参数是存放的Cubic拥塞控制算法相关变量信息的结构体,第二个参数是当前的拥塞窗口大小,第三个参数是在上面也介绍过:acked:刚刚被确认的数据包数量,且还未用来更新到拥塞窗口的剩余大小。这里要注意第三个参数acked可能是由慢启动环节中还未消耗掉的剩余量,也有可能是拥塞避免阶段的。

算法的实现过程中涉及到很多巧妙的设计,这里暂且不去关注这些细节,我们重点来看cubic三次函数的计算过程:

static inline void bictcp_update(struct bictcp *ca, u32 cwnd, u32 acked)
{
 u32 delta, bic_target, max_cnt;
 u64 offs, t;

 ca->ack_cnt += acked; /* count the number of ACKed packets */
    ......
 ca->last_cwnd = cwnd;   /* 更新上一次的拥塞窗口值 */
 ca->last_time = tcp_jiffies32; /* 最后一次更新last_cwnd的时间 */

 if (ca->epoch_start == 0) {  /*开始一个新的epoch */
  ca->epoch_start = tcp_jiffies32; /* record beginning */
  ca->ack_cnt = acked;   /* start counting */
  ca->tcp_cwnd = cwnd;   /* syn with cubic */

  if (ca->last_max_cwnd <= cwnd) {
   ca->bic_K = 0;
   ca->bic_origin_point = cwnd;
  } else {
   /* Compute new K based on
    * (wmax-cwnd) * (srtt>>3 / HZ) / c * 2^(3*bictcp_HZ)
    */
   ca->bic_K = cubic_root(cube_factor
            * (ca->last_max_cwnd - cwnd));
   ca->bic_origin_point = ca->last_max_cwnd;
  }
 }

 t = (s32)(tcp_jiffies32 - ca->epoch_start); /*当前时间到epoch_start的时间*/
 t += msecs_to_jiffies(ca->delay_min >> 3);  /* + ca->delay_min  预测下一个rtt时间内的cwnd */
 /* change the unit from HZ to bictcp_HZ */
 t <<= BICTCP_HZ;
 do_div(t, HZ);

 if (t < ca->bic_K)  /* t - K */
  offs = ca->bic_K - t;
 else
  offs = t - ca->bic_K;

 /* c/rtt * (t-K)^3 */
 delta = (cube_rtt_scale * offs * offs * offs) >> (10+3*BICTCP_HZ);
 if (t < ca->bic_K)                            /* below origin  */
  bic_target = ca->bic_origin_point - delta;
 else                                          /* above origin* /
  bic_target = ca->bic_origin_point + delta;

 /* cubic function - calc bictcp_cnt*/
 if (bic_target > cwnd) {
  ca->cnt = cwnd / (bic_target - cwnd);
 } else {
  ca->cnt = 100 * cwnd;              /* very small increment*/
 }

 /*
  * The initial growth of cubic function may be too conservative
  * when the available bandwidth is still unknown.
  */
 if (ca->last_max_cwnd == 0 && ca->cnt > 20)
  ca->cnt = 20; /* increase cwnd 5% per RTT */
    ......
}

每次发生丢包时,epoch_start会重置为0,意味着将开始一个新的轮次、新的时段。丢包后调到bictcp_update函数时,设置epoch_start为当前时间,正式开始新的轮次,并开始更新 、K值。所以可以认为epoch_start是为丢包后的每个新轮次的开始打上时间戳。如下代码片段所示,若当前的拥塞控制窗口大于 ,那么更新当前链路的拥塞窗口的饱和点为 (即当前达到了新的饱和点),此时设置K为0(ca->bic_K),k=0代表当前拥塞窗口的大小就是饱和点,达到饱和点的时间是0。若当前拥塞窗口小于 ,更新当前链路的拥塞窗口饱和点为 ,并且计算出K,此时的K按照公式1.2进行计算当前拥塞窗口在没有丢包的情况下到达饱和点的时间。

if (ca->last_max_cwnd <= cwnd) {
   ca->bic_K = 0;
   ca->bic_origin_point = cwnd;
  } else {
   /* Compute new K based on
    * (wmax-cwnd) * (srtt>>3 / HZ) / c * 2^(3*bictcp_HZ)
    */
   ca->bic_K = cubic_root(cube_factor
            * (ca->last_max_cwnd - cwnd));
   ca->bic_origin_point = ca->last_max_cwnd;
  }
}

通过对这段代码的分析,再啰嗦一下对K的理解,K:time to origin point from the begining of the current epoch; orign point:Cubic函数的中心点,也是饱和点(bic_origin_point)对应的位置,故ca->bic_K代表的是从当前轮次开始对应的拥塞窗口,在没有进一步丢包的情况下,到达饱和点bic_origin_point所需要的时间(换句话就是:在当前轮次内,假设没有丢包的情况下,从当前拥塞窗口增长 *Wmax到链路饱和点bic_origin_point所要花费的时间)。下一步开始计算时间t,首先计算当前时间到当前轮次开始的时间,然后加下一个RTT的时间(由于下一个RTT时间是不确定的值,故在具体实现时是选择的rtt的平均值),t = 当前时间-进入当前轮次的时间+最小RTT。这里最终的t是一个预测时间。因为我们的目标就是要计算下个RTT时间的拥塞窗口大小,即保证在下一次ACK到达之前,有充足的cwnd配额可供持续发送数据。

 t = (s32)(tcp_jiffies32 - ca->epoch_start); /*当前时间到epoch_start的时间*/
 t += msecs_to_jiffies(ca->delay_min >> 3);  /* + ca->delay_min  

如下图所示,是时间t在cubic三次函数中的描述,蓝色括号代表当前轮次已经经过的时间,红色的是下一个rtt的时间。

上文提到的cubic计算公式,如下所示,t、K、Wmax、C均已知。

下面开始计算目标值,首先判断时间t,计算|t-K|的值,从而计算C*(t-K)^3;然后根据t与K的大小,即当前的t是否超过了到达饱和点的时间,对应的是Cubic函数图像的中心位置。计算最终的目标值,是预测的下个rtt拥塞窗口值,即保证下个时间段内有足够的cwnd配额可供持续发送数据。

if (t < ca->bic_K)  /* t - K */
  offs = ca->bic_K - t;
 else
  offs = t - ca->bic_K;

 /* c/rtt * (t-K)^3 */
 delta = (cube_rtt_scale * offs * offs * offs) >> (10+3*BICTCP_HZ);
 if (t < ca->bic_K)                            /* below origin  */
  bic_target = ca->bic_origin_point - delta;
 else                                          /* above origin* /
  bic_target = ca->bic_origin_point + delta;

最后根据当前拥塞窗口大小、目标值获取最终的cnt值,cnt值是cubic拥塞算法的核心,主要用来控制在拥塞避免状态时,什么时候才能增大拥塞窗口。具体取值时,按照cubic函数的形状:target与cwnd相差越多,增长越快,如果当前cwnd已经超出预期的target,应该做降速,所以此时取值cnt为100*cwnd。(关于cnt的定义在本文介绍cubic相关变量时有做特别说明)。

/* cubic function - calc bictcp_cnt*/
 if (bic_target > cwnd) {
  ca->cnt = cwnd / (bic_target - cwnd);
 } else {
  ca->cnt = 100 * cwnd;              /* very small increment*/
 }

bictcp_update函数最终要计算的就是上面的ca->cnt,当执行完bictcp_update后,顺序执行tcp_cong_avoid_ai函数,如下所示,函数第二个参数就是bictcp_update计算后的ca->cnt值。下面函数中tp->snd_cwnd_cnt是一个核心变量,文章前面也介绍过,代表当前的拥塞窗口中已经发生(经过对方ACK确认)的数据段的个数,该变量在下面函数中与ca->cnt进行对比,来决定是否增大拥塞窗口大小。

/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd (or alternative w),
 * for every packet that was ACKed.
 */
void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked)
{
    
    /*
    tp->snd_cwnd_cnt 表示在当前的拥塞窗口中已经发送(经过对方ack包确认)的数据段个数.
    ca->cnt = w :它是cubic拥塞算法的核心,主要用来控制拥塞避免状态的时候,什么时候才能增大拥塞窗口
    具体实现是通过比较cnt和snd_cwnd_cnt,来决定是否增大拥塞窗口
    */
    
    /* If credits accumulated at a higher w, apply them gently now. */
    /*当被确认的包的数量大于w时,将snd_cwnd_cnt清0,继续加大拥塞窗口值,继续probe Wmax*/
    if (tp->snd_cwnd_cnt >= w) {
        tp->snd_cwnd_cnt = 0;
        tp->snd_cwnd++;
    }
 
    tp->snd_cwnd_cnt += acked; //累计被确认的包
    if (tp->snd_cwnd_cnt >= w) {
        /*按比例增加拥塞窗口,并减少snd_cwnd_cnt*/
        u32 delta = tp->snd_cwnd_cnt / w;
 
        tp->snd_cwnd_cnt -= delta * w;
        tp->snd_cwnd += delta;
    }
    tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_cwnd_clamp);
}

至此,Cubic拥塞控制算法在Linux内核的核心计算过程分析结束,除了本文涉及到的分析要点外,Cubic拥塞控制算法还有很多其他的思想,例如混合慢启动、快速收敛、TCP友好型等,后面有机会也进行几次分析分享。本文若有描述不恰当、有误的地方,欢迎批评指正!

原文作者:技术简说

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑出现这些现象,说明你的固态硬盘要坏了

与传统机械硬盘&#xff08;HDD&#xff09;相比&#xff0c;固态硬盘&#xff08;SSD&#xff09;速度更快、更稳定、功耗更低。但固态硬盘并不是完美无瑕的&#xff0c;由于颗粒写入机制&#xff0c;可能会在七到十年的预期寿命之前出现故障。所以用户最好为最终故障做好准备…

vue3 自己写一个月的日历

效果图 代码 <template><div class"monthPage"><div class"calendar" v-loading"loading"><!-- 星期 --><div class"weekBox"><div v-for"(item, index) in dayArr" :key"index&q…

认识计算机的设备管理

在计算机系统中&#xff0c;除了处理器和内存之外&#xff0c;其他的大部分硬设备称为外部设备。它包括输入/输出设备&#xff0c;辅存设备及终端设备等。这些设备种类繁多&#xff0c;特性各异&#xff0c;操作方式的差异很大&#xff0c;从而使操作系统的设备管理变得十分繁杂…

数据仓库工具Hive

1. 请解释Hive是什么&#xff0c;它的主要用途是什么&#xff1f; Hive是一个基于Hadoop的数据仓库工具&#xff0c;主要用于处理和分析大规模结构化数据。它可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类似SQL的查询功能&#xff0c;将SQL语句转换为MapRedu…

使用 iperf 和 iftop 测试网络带宽

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

京东商品详情数据在数据分析行业中的重要性

京东商品详情数据在数据分析行业中具有重要作用。这些数据提供了丰富的信息&#xff0c;可以帮助企业了解市场趋势、消费者需求、产品表现以及运营策略等多个方面。 首先&#xff0c;京东商品详情数据可以为企业提供市场趋势分析的依据。通过观察商品的销售量、销售额、价格等…

Qt 6.5 类库实例大全:QObject

大家好&#xff0c;我是20YC小二&#xff01;福利时间&#xff1a;欢迎(wx)扫码关注&#xff0c;免费领取《C程序员入门必修第一课&#xff1a;C基础课程》在线视频教程&#xff0c;还有更多技术分享&#xff01;#下面进入今天内容# 1. QObject 介绍 QObject 是 Qt 库中最重要…

RocketMq集成SpringBoot(待完善)

环境 jdk1.8, springboot2.7.3 Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.3</version><relativePath/> <!-- lookup parent from…

C++学习笔记:继承

继承 什么是继承?继承的写法基类和派生类的赋值转换继承中的作用域派生类的默认成员函数单继承,多继承,虚拟继承is-a 和 has-a 什么是继承? 继承是C语言面向对象的三大特性之一&#xff0c;是面向对象程序设计使代码可以复用的最重要的手段,基本都是在一个类的基础上为了增加…

一个简单的可视化的A星自动寻路

一个简单的应用场景&#xff0c;流程图连线 源码&#xff1a; addExample("A星路径查找", function () {return {template: <div><div ref"main"></div></div>,data() { return {}; },computed: {},methods: {},mounted() {var c…

2-3、运算符

语雀原文链接 文章目录 1、算术运算符2、关系运算符3、逻辑运算符4、赋值运算符5、移位运算符6、位运算符(二进制位进行运算)7、条件运算符:三目运算符8、运算符的优先级 1、算术运算符 &#xff1a;加法-&#xff1a;减法*&#xff1a;乘法/&#xff1a;除法取商%&#xff1…

logback日志框架使用

依赖引入 <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.1.7</version> </dependency> 使用logback日志框架只需要引入以上即可&#xff0c;(我们平时使用较多的Slf4j…

Fall in love with English

Fall in love with English 爱上英语 Hiding behind the loose dusty curtain, a teenager packed up his overcoat into the suitcase. 躲藏在布满尘土的松软的窗帘后边&#xff0c;一个年轻人打包他的外套到行李箱中。 He planned to leave home at dusk though there was th…

ssh免密登录及scp/rsync免密传输文件的方式

在通过ssh登录其它电脑或通过scp/rsync同其它电脑之间传输文件时&#xff0c;每次都需要输入密码&#xff0c;如下图所示&#xff1a;在windows10上通过ssh登录虚拟机&#xff0c;每次登录都需要输入密码&#xff1b;若端口默认为22,可省略通过-p指定 可通过将本机上的公钥key存…

熔池处理Tecplot 360 和CFD-Post做出一样的效果

熔池处理Tecplot 360 和CFD-Post做出一样的效果 效果展示详细讲述Tecplot 360实现过程分析实现过程第一步实现过程第二步界面美化注意点效果展示 详细讲述Tecplot 360实现过程 分析 这里主要是将体积分数大于0.5的区域抽取出来,然后显示温度场,所以这里主要考虑下面连个思考…

Agent相关工作调研

API Bank 要解决两个问题&#xff1a; 1)目前的LLM在使用工具方面的效果如何&#xff1f; 2) LLM使用工具还存在哪些障碍&#xff1f; 理想的效果&#xff1a;&#xff1a;通过访问全球工具存储库&#xff0c;LLM可以通过概述实现需求所需的所有步骤来帮助人们规划需求。随后…

你知道Java中的BigInteger类和BigDecimal类吗?

BigInteger和BigDecimal&#xff1a; 我们在学习JavaSE基础的时候学习过int和double&#xff0c;前者是整形&#xff0c;后者是双精度浮点数&#xff0c;但它们是有最大值的&#xff0c;也就是说&#xff0c;他两并不支持无限大的数字。 其范围如下所示&#xff1a; 因此对于…

图论-并查集

并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题.一些常见的用途有求连通子图,求最小生成树Kruskal算法和最近公共祖先(LCA)等. 并查集的基本操作主要有: .1.初始化 2.查询find 3.合并union 一般我们都会采用路径压缩 这样…

Java期末复习题之分支循环

点击返回标题->23年Java期末复习-CSDN博客 第1题. 编写一个模拟同时掷骰子的程序。要用Math.random()模拟产生两个骰子&#xff0c;将两个结果相加&#xff0c;相加的和等于7的可能性最大&#xff0c;等于2和12的可能性最小。程序模投掷3600次&#xff0c;判断求和的结果是否…

在IDEA中创建Maven项目时没有src文件、不自动配置文件

错误示例&#xff1a; 没有src文件&#xff0c;并且没有自动下载相关的配置文件 对我这中情况无效的解决办法&#xff1a; ①配置好下列图中圈出来的文件 ②在VM选项中输入&#xff1a;“-DarchetypeInternal” ③点击应用&#xff0c;再点击确定 ④还是不行 解决办法&#x…