NS3学习——tcpVegas算法代码详解(2)

NS3学习——tcpVegas算法代码详解(1)-CSDN博客

目录

4.TcpVegas类中成员函数

(5) CongestionStateSet函数

(6) IncreaseWindow函数

1.检查是否启用 Vgas

2.判断是否完成了一个“Vegas 周期”

2.1--if:判断RTT样本数量是否足够

2.2--else:RTT 样本 > 2

2.2.1 if--diff > m_gamma 并且处于慢启动阶段

2.2.2 else if-- diff < m_gamma 并且处于慢启动阶段

2.2.3 else-- 进入拥塞避免阶段

2.2.3.1 --if diff > m_beta

2.2.3.2 --else if diff < m_alpha 

2.2.3.3 --else  m_alpha < diff < m_beta

2.2.4 --更新慢开始阈值

 2.3 --重置RTT计数与最小RTT

3.慢启动阶段判断

(7) GetName函数

(8) GetSsThresh函数


4.TcpVegas类中成员函数

(5) CongestionStateSet函数

void
TcpVegas::CongestionStateSet (Ptr<TcpSocketState> tcb,const TcpSocketState::TcpCongState_t newState)
{NS_LOG_FUNCTION (this << tcb << newState);if (newState == TcpSocketState::CA_OPEN){EnableVegas (tcb);}else{DisableVegas ();}
}

函数作用:根据TCP连接的拥塞状态来启用或者禁用Vegas算法。

函数体:检查传入的newState 参数值是否为:TcpSocketState::CA_OPEN(拥塞避免阶段),若是,则启用Vegas算法,TCP使用该算法来调整拥塞窗口的值;若不是,则停止使用Vegas。

TcpVegas 算法通常在拥塞避免阶段启用,因为此时网络已稳定,Vegas 可以通过动态调整拥塞窗口来更好地利用网络带宽,并避免网络拥塞。

(6) IncreaseWindow函数

void
TcpVegas::IncreaseWindow (Ptr<TcpSocketState> tcb, uint32_t segmentsAcked)
{NS_LOG_FUNCTION (this << tcb << segmentsAcked);if (!m_doingVegasNow){NS_LOG_LOGIC ("Vegas is not turned on, we follow NewReno algorithm.");TcpNewReno::IncreaseWindow (tcb, segmentsAcked);return;}if (tcb->m_lastAckedSeq >= m_begSndNxt){ // A Vegas cycle has finished, we do Vegas cwnd adjustment every RTT.NS_LOG_LOGIC ("A Vegas cycle has finished, we adjust cwnd once per RTT.");m_begSndNxt = tcb->m_nextTxSequence;if (m_cntRtt <= 2){  // We do not have enough RTT samples, so we should behave like RenoNS_LOG_LOGIC ("We do not have enough RTT samples to do Vegas, so we behave like NewReno.");TcpNewReno::IncreaseWindow (tcb, segmentsAcked);}else //m_cntRtt > 2{NS_LOG_LOGIC ("We have enough RTT samples to perform Vegas calculations");uint32_t diff;uint32_t targetCwnd;uint32_t segCwnd = tcb->GetCwndInSegments ();double tmp = m_baseRtt.GetSeconds () / m_minRtt.GetSeconds ();targetCwnd = static_cast<uint32_t> (segCwnd * tmp);NS_LOG_DEBUG ("Calculated targetCwnd = " << targetCwnd);NS_ASSERT (segCwnd >= targetCwnd); // implies baseRtt <= minRttdiff = segCwnd - targetCwnd;NS_LOG_DEBUG ("Calculated diff = " << diff);if (diff > m_gamma && (tcb->m_cWnd < tcb->m_ssThresh)){NS_LOG_LOGIC ("We are going too fast. We need to slow down and ""change to linear increase/decrease mode.");segCwnd = std::min (segCwnd, targetCwnd + 1);tcb->m_cWnd = segCwnd * tcb->m_segmentSize;tcb->m_ssThresh = GetSsThresh (tcb, 0);NS_LOG_DEBUG ("Updated cwnd = " << tcb->m_cWnd <<" ssthresh=" << tcb->m_ssThresh);}else if (tcb->m_cWnd < tcb->m_ssThresh){     // Slow start modeNS_LOG_LOGIC ("We are in slow start and diff < m_gamma, so we ""follow NewReno slow start");TcpNewReno::SlowStart (tcb, segmentsAcked);}else //tcb m_cWnd > m_ssThresh{     // Linear increase/decrease modeNS_LOG_LOGIC ("We are in linear increase/decrease mode");if (diff > m_beta){NS_LOG_LOGIC ("We are going too fast, so we slow down by decrementing cwnd");segCwnd--;tcb->m_cWnd = segCwnd * tcb->m_segmentSize;tcb->m_ssThresh = GetSsThresh (tcb, 0);NS_LOG_DEBUG ("Updated cwnd = " << tcb->m_cWnd <<" ssthresh=" << tcb->m_ssThresh);}else if (diff < m_alpha){NS_LOG_LOGIC ("We are going too slow, so we speed up by incrementing cwnd");segCwnd++;tcb->m_cWnd = segCwnd * tcb->m_segmentSize;NS_LOG_DEBUG ("Updated cwnd = " << tcb->m_cWnd <<" ssthresh=" << tcb->m_ssThresh);}else //   m_alpha < diff < m_beta{NS_LOG_LOGIC ("We are sending at the right speed");}} //else  tcb m_cWnd > m_ssThreshtcb->m_ssThresh = std::max (tcb->m_ssThresh, 3 * tcb->m_cWnd / 4);NS_LOG_DEBUG ("Updated ssThresh = " << tcb->m_ssThresh);} // else m_cntRtt > 2m_cntRtt = 0;m_minRtt = Time::Max ();} //if tcb->m_lastAckedSeq >= m_begSndNxtelse if (tcb->m_cWnd < tcb->m_ssThresh)  //tcb->m_lastAckedSeq < m_begSndNxt{TcpNewReno::SlowStart (tcb, segmentsAcked);}
} //IncreaseWindow

函数体逻辑:

1.检查是否启用 Vgas

if (!m_doingVegasNow)
{// If Vegas is not on, we follow NewReno algorithmNS_LOG_LOGIC ("Vegas is not turned on, we follow NewReno algorithm.");TcpNewReno::IncreaseWindow (tcb, segmentsAcked);return;
}

如果 m_doingVegasNow 为 false,即 Vegas 算法没有启用,那么执行 NewReno 拥塞控制算法。如果 Vegas 启用,那么执行以下代码:

2.判断是否完成了一个“Vegas 周期”

if (tcb->m_lastAckedSeq >= m_begSndNxt)
{// A Vegas cycle has finished, we do Vegas cwnd adjustment every RTT.NS_LOG_LOGIC ("A Vegas cycle has finished, we adjust cwnd once per RTT.");m_begSndNxt = tcb->m_nextTxSequence;

如果 tcb->m_lastAckedSeq发送方成功接收到的最后一个已确认包的序列号)大于等于 m_begSndNxt( Vegas 周期开始时的发送序列号),则表示当前已经完成了一个 Vegas 周期,并且将 m_begSndNxt 更新为当前的 tcb->m_nextTxSequence,以便下次周期开始时使用新的序列号。执行以下代码:

补:在 Vegas 算法中,一个周期是指发送方根据当前 RTT(往返时延)计算并调整其拥塞窗口(cwnd)的过程。这个周期通常与 RTT 周期同步。

m_lastAckedSeq 的变化非常重要,它帮助判断一个周期是否已经完成。每当接收方成功确认一个数据包,m_lastAckedSeq 会更新,以便发送方能知道哪些数据包已经被接收并得到确认。

在每个周期开始时,m_begSndNxt 会被更新为 当前发送序列号,而这个序列号代表的是 下一个将要发送的数据包的起始字节序列号。
当接收到的 ACK 包的序列号大于等于 m_begSndNxt 时,说明当前周期的所有数据包已经被确认,当前周期结束。

每个周期(每个 RTT)执行一次 IncreaseWindow。

tcb->m_nextTxSequence 是当前即将发送的下一个数据包的序列号。将 m_begSndNxt 更新为 tcb->m_nextTxSequence 是为了确保下一个周期从正确的地方开始。

2.1--if:判断RTT样本数量是否足够
if (m_cntRtt <= 2)
{// We do not have enough RTT samples, so we should behave like RenoNS_LOG_LOGIC ("We do not have enough RTT samples to do Vegas, so we behave like NewReno.");TcpNewReno::IncreaseWindow (tcb, segmentsAcked);
}

Vegas 需要足够的 RTT 样本才能做出可靠的拥塞窗口调整。

如果 RTT 样本数少于 2(即 m_cntRtt <= 2),它会退回到 NewReno 行为,这时会使用一个简单的慢启动和拥塞避免机制。

如果 RTT 样本 > 2,算法就会根据 Vegas 的逻辑调整cwnd值,同时执行else中的代码:

2.2--else:RTT 样本 > 2
else
{NS_LOG_LOGIC ("We have enough RTT samples to perform Vegas calculations");

计算目标拥塞窗口:

uint32_t diff;
uint32_t targetCwnd;
uint32_t segCwnd = tcb->GetCwndInSegments ();double tmp = m_baseRtt.GetSeconds () / m_minRtt.GetSeconds ();
targetCwnd = static_cast<uint32_t> (segCwnd * tmp);
NS_LOG_DEBUG ("Calculated targetCwnd = " << targetCwnd);
NS_ASSERT (segCwnd >= targetCwnd); // implies baseRtt <= minRtt

Vegas 计算目标拥塞窗口(targetCwnd),首先获取当前拥塞窗口大小 segCwnd,然后根据 baseRtt(最小 RTT)和 minRtt(当前窗口内最小 RTT)来计算目标拥塞窗口。

如果 baseRtt 小于等于 minRtt,就可以安全计算目标窗口。

NS_ASSERT (segCwnd >= targetCwnd);

计算公式如下:  

targetCwnd=segCwnd*\frac{baseRtt}{minRtt}

计算实际拥塞窗口与目标窗口的差值:

diff = segCwnd - targetCwnd;
NS_LOG_DEBUG ("Calculated diff = " << diff);

 计算当前拥塞窗口与目标拥塞窗口之间的差值 diff。这个差值会决定是否需要调整拥塞窗口的大小。

2.2.1 if--diff > m_gamma 并且处于慢启动阶段

当前窗口的差值 diff 大于阈值 m_gamma,并且当前处于慢启动阶段(cwnd 小于 m_ssThresh)

if (diff > m_gamma && (tcb->m_cWnd < tcb->m_ssThresh))
{// We are going too fast. We need to slow down and change from// slow-start to linear increase/decrease mode by setting cwnd// to target cwnd. We add 1 because of the integer truncation.NS_LOG_LOGIC ("We are going too fast. We need to slow down and ""change to linear increase/decrease mode.");segCwnd = std::min (segCwnd, targetCwnd + 1);tcb->m_cWnd = segCwnd * tcb->m_segmentSize;tcb->m_ssThresh = GetSsThresh (tcb, 0);NS_LOG_DEBUG ("Updated cwnd = " << tcb->m_cWnd << " ssthresh=" << tcb->m_ssThresh);
}

m_alpha 和 m_beta 用于在正常的增速和减速中控制窗口的变化。m_gamma 是一个更大的阈值,通常用于判断网络是否发生了拥塞。

如果 diff > m_gamma,说明当前发送速率比目标速率快得多,且当前处于慢启动阶段。在慢启动阶段,cwnd 会急剧增长。如果在慢启动阶段的拥塞窗口已大于目标值,说明网络可能出现了拥塞的风险。

segCwnd = std::min(segCwnd, targetCwnd + 1):

调整当前cwnd,防止 segCwnd 超过目标窗口 targetCwnd,即避免发送方继续过快地发送数据。

由于 segCwnd 是以“段”为单位的(tcb->GetCwndInSegments()),加1的操作是为了避免整数截断。因为在计算过程中,通常会有一个小数部分,而加 1 可以保证计算结果向上取整,避免由于整数取整带来的问题。

比如,如果目标拥塞窗口是 targetCwnd = 5.4,由于取整的原因,segCwnd 可能被调整为 5,而加 1 后调整为 6。这样可以确保窗口不会太小,从而避免过早减速。

tcb->m_cWnd = segCwnd * tcb->m_segmentSize:

segCwnd 是拥塞窗口的大小(以段为单位)。
tcb->m_segmentSize 是每个 TCP 数据段的大小(字节数)。
segCwnd * tcb->m_segmentSize 得到的是字节级别的拥塞窗口大小,即实际可发送的数据量(以字节为单位)。通过这个公式,可以将段数(segCwnd)转换为字节数(tcb->m_cWnd),并调整发送窗口。

tcb->m_ssThresh = GetSsThresh(tcb, 0):

重新计算并设置新的慢启动阈值,用于控制从慢启动到拥塞避免阶段的过渡。

2.2.2 else if-- diff < m_gamma 并且处于慢启动阶段

当前的拥塞窗口小于慢启动阈值 m_ssThresh 并且 diff 小于 m_gamma

else if (tcb->m_cWnd < tcb->m_ssThresh)
{// Slow start modeNS_LOG_LOGIC ("We are in slow start and diff < m_gamma, so we ""follow NewReno slow start");TcpNewReno::SlowStart (tcb, segmentsAcked);
}

如果当前的拥塞窗口小于慢启动阈值 m_ssThresh,说明此时处于慢启动阶段。此时 diff 小于 m_gamma,表明网络没有拥塞,拥塞窗口仍然可以增长。

此时退回使用 NewReno 算法中的慢启动阶段,通过调用 TcpNewReno::SlowStart 来增加拥塞窗口。

2.2.3 else-- 进入拥塞避免阶段

当前的拥塞窗口大于慢启动阈值,tcb->m_cWnd 大于或等于 tcb->m_ssThresh

进入拥塞避免阶段,通过与目标 targetCwnd 的差值 diff 大小来选择是 增加窗口、减小窗口,还是 保持当前窗口。

else
{// Linear increase/decrease modeNS_LOG_LOGIC ("We are in linear increase/decrease mode");
2.2.3.1 --if diff > m_beta
if (diff > m_beta){// We are going too fast, so we slow downNS_LOG_LOGIC ("We are going too fast, so we slow down by decrementing cwnd");segCwnd--;tcb->m_cWnd = segCwnd * tcb->m_segmentSize;tcb->m_ssThresh = GetSsThresh (tcb, 0);NS_LOG_DEBUG ("Updated cwnd = " << tcb->m_cWnd << " ssthresh=" << tcb->m_ssThresh);}
  • 这表示当前发送速率太快,实际的发送速率(segCwnd)已经超过了目标速率 targetCwnd。
  • 为了避免拥塞,减小 segCwnd,即减小拥塞窗口,从而减慢发送速率。
  • 减小后的 segCwnd 通过 tcb->m_cWnd = segCwnd * tcb->m_segmentSize; 更新。
  • 同时, 通过 GetSsThresh(tcb, 0) 更新慢启动阈值 tcb->m_ssThresh。
2.2.3.2 --else if diff < m_alpha 
 else if (diff < m_alpha){// We are going too slow, so we speed upNS_LOG_LOGIC ("We are going too slow, so we speed up by incrementing cwnd");segCwnd++;tcb->m_cWnd = segCwnd * tcb->m_segmentSize;NS_LOG_DEBUG ("Updated cwnd = " << tcb->m_cWnd << " ssthresh=" << tcb->m_ssThresh);}
  • 这表示 当前发送速率太慢,实际的发送速率(segCwnd)低于目标速率 targetCwnd。
  • 为了加速数据传输,增加 segCwnd,即增大 拥塞窗口。
  • 增大的 segCwnd 同样通过 tcb->m_cWnd = segCwnd * tcb->m_segmentSize; 更新。
  • 在这种情况下,不需要调整慢启动阈值 tcb->m_ssThresh,因为它不会影响这一阶段的行为。
2.2.3.3 --else  m_alpha < diff < m_beta
else 
{NS_LOG_LOGIC ("We are sending at the right speed");
}
  • 这表示 当前发送速率合适,既不太快也不太慢,数据流量保持在理想状态。
  • 不需要对拥塞窗口做出调整,继续维持当前的速率。
2.2.4 --更新慢开始阈值
tcb->m_ssThresh = std::max (tcb->m_ssThresh, 3 * tcb->m_cWnd / 4);
NS_LOG_DEBUG ("Updated ssThresh = " << tcb->m_ssThresh);
  • 在(根据拥塞窗口和慢启动阈值大小比较)进行窗口调整之后,根据当前的拥塞窗口 tcb->m_cWnd 更新慢启动阈值(ssthresh)。
  • 计算公式 3 * tcb->m_cWnd / 4 是根据 Vegas 算法的设定,确保慢启动阈值不会过小。
  • 最终tcb->m_ssThresh 会被设置为 tcb->m_ssThresh 和 3 * tcb->m_cWnd / 4 中的较大值。这是为了确保慢启动阈值有足够的大小,避免在后续过程中频繁进入慢启动阶段。
 2.3 --重置RTT计数与最小RTT
m_cntRtt = 0;
m_minRtt = Time::Max ();
  • 由于每个周期结束都会重新进行 RTT 测量和窗口调整,所以需要重置 RTT计数(m_cntRtt)和最小RTT(m_minRtt)值。
  • m_cntRtt = 0:重置 RTT 样本计数器。
  • m_minRtt = Time::Max():将最小 RTT 重置为一个很大的值,确保下一周期开始时能够重新计算最小 RTT。

3.慢启动阶段判断

else if (tcb->m_cWnd < tcb->m_ssThresh)
{TcpNewReno::SlowStart(tcb, segmentsAcked);
}

在周期结束后,检查是否进入了慢启动阶段:

如果当前拥塞窗口 cwnd 小于慢启动阈值 ssThresh,则执行 NewReno 的慢启动算法,快速增长窗口。

注:如果tcb->m_lastAckedSeq < m_begSndNxt,表示当前 Vegas 周期没有结束,那么会进入 else if 判断,如果满足慢启动条件(tcb->m_cWnd < tcb->m_ssThresh),则会执行 NewReno 的慢启动算法。

为什么最后还要判断是否进入慢开始阶段?

如果 cwnd 小于 ssthresh,本应处于慢启动阶段,但由于没有判断 cwnd < ssthresh,程序会直接进入其他模式(如线性增加阶段)。这意味着即使 cwnd 还处于慢启动阶段,程序也会让它变得增长更慢。由于此时 cwnd 还较小,采用线性增长的方式会导致窗口增长太慢,无法迅速利用带宽,从而导致 网络利用率低,吞吐量上升的速度很慢,甚至不能充分利用网络的带宽。也就是说,可能会在不该进入线性增长阶段时就进入该阶段,从而导致 窗口增长速度过慢,降低网络利用率。

这个判断确保了在每个阶段执行适当的窗口调整策略,并帮助算法正确地处理不同网络状态下的拥塞控制。

(7) GetName函数

std::string
TcpVegas::GetName () const
{return "TcpVegas";
}

此函数主要用于标识 TCP 拥塞控制算法的类型,通过调用 GetName(),程序可以知道当前正在使用的是 TCP Vegas 算法。返回一个字符串,"TcpVegas"。

(8) GetSsThresh函数

uint32_t
TcpVegas::GetSsThresh (Ptr<const TcpSocketState> tcb,uint32_t bytesInFlight)
{NS_LOG_FUNCTION (this << tcb << bytesInFlight);return std::max (std::min (tcb->m_ssThresh.Get (), tcb->m_cWnd.Get () - tcb->m_segmentSize), 2 * tcb->m_segmentSize);
}} // namespace ns3

该函数的作用是计算和返回慢启动阈值(ssthresh)。

Ptr<const TcpSocketState>,指向当前连接的 TCP 套接字状态。TcpSocketState 中存储了关于当前 TCP 连接的许多信息,如拥塞窗口(m_cWnd)、慢启动阈值(m_ssThresh)等。
bytesInFlight:这通常表示当前已发送但尚未确认的数据量。这个参数在此函数中没有被直接使用。

tcb->m_ssThresh.Get():当前连接的慢启动阈值。
tcb->m_cWnd.Get() - tcb->m_segmentSize:计算拥塞窗口(cwnd)减去一个数据段的大小,表示如果当前 cwnd 足够大时,应该将 ssthresh 设置为接近这个值。
2 * tcb->m_segmentSize:这是 ssthresh 的最小值,表示即使拥塞窗口较小时,慢启动阈值也不会低于 2 * m_segmentSize。这个值是一个合理的下限,避免在拥塞窗口很小的时候,ssthresh 过小导致性能问题。

返回值:该函数通过 std::max() 和 std::min() 保证返回的 ssthresh 在合理的范围内:

std::min():确保 ssthresh 不会大于 cwnd - segmentSize,即不能超过当前拥塞窗口减去一个数据段的大小。
std::max():确保 ssthresh 不会小于 2 * segmentSize,即在任何情况下 ssthresh 至少为两个数据段大小。
最终返回值就是经过限制的 ssthresh,这是拥塞控制中切换模式的关键值。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 CentOS 8 系统上安装 Jenkins 的全过程

一、前言 我是一个前端开发&#xff0c;需要频繁将编写的前端系统打包更新到公司的linux服务器&#xff0c;觉得这种工作纯体力活&#xff0c;有时候太浪费时间&#xff0c;以前用过别人搭建的Jenkins可以很好的解决这个问题。 Jenkins 是一款流行的开源持续集成和持续交付&a…

Mac上Stable Diffusion的环境搭建(还算比较简单)

https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon AI兴起的速度是真的快&#xff0c;感觉不了解点相关的东西都要与时代脱节了&#xff0c;吓得我赶紧找个AIGC看看能不能实现我艺术家的人梦想&#xff08;绷不住了&#xff09; 我…

瑞吉外卖项目学习笔记(九)套餐列表分页查询、新增套餐、图片上传和下载

瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现 瑞吉外卖项目学习笔记(二)Swagger、logback、表单校验和参数打印功能的实现 瑞吉外卖项目学习笔记(三)过滤器实现登录校验、添加员工、分页查询员工信息 瑞吉外卖项目学习笔记(四)TableField(fill FieldFill.INSERT)公共字…

VMware Workstation虚拟机网络模式

做虚拟机和宿主机互ping实验时&#xff0c;除了要提前配置好网段、ip等信息&#xff0c;还要把宿主机、虚拟机的防火墙关闭&#xff01; 首先说一下VMware的几种虚拟交换机。 VMnet0&#xff1a;用于虚拟桥接网络下的虚拟交换机。 VMnet1&#xff1a;用于虚拟Host-Only网络下…

UDP传输层通信协议详解

引言 在计算机网络通信的广阔天地中&#xff0c;传输层协议扮演着至关重要的角色。它们负责在网络中的两个终端之间建立、管理和终止数据传输。在众多传输层协议中&#xff0c;UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;以其独特的特性和应…

Node.js 工具:在 Windows 11 中配置 Node.js 的详细步骤

一、概述 记录时间 [2024-12-25] 本文讲述如何在 Windows 11 中进行 Node.js 工具的安装和配置。 以下是详细的步骤和说明。 二、安装 Node.js 1. 官网下载 通过官网&#xff0c;下载 Node.js&#xff0c;上面有好几种下载方式&#xff0c;文中下载的是 zip 压缩包。 如图&…

Chrome被360导航篡改了怎么改回来?

一、Chrome被360导航篡改了怎么改回来&#xff1f; 查看是否被360主页锁定&#xff0c;地址栏输入chrome://version&#xff0c;看命令行end后面&#xff08;蓝色部分&#xff09;&#xff0c;是否有https://hao.360.com/?srclm&lsn31c42a959f 修改步骤 第一步&#xff1a…

Mysql 查询性能调优总结

一、查询分析性能的相关配置 1.1 配置显示查询性能的参数 在 MySQL 中&#xff0c;SHOW PROFILES 命令用于显示最近的查询性能概况&#xff0c;帮助你分析哪些查询比较耗时。 show profiles; 需要启用参数 profiling&#xff0c;才能使用上述功能&#xff0c;其相关参数设置…

python中使用selenium执行组合快捷键ctrl+v不生效问题

在执行ctrlv进行粘贴时&#xff0c;绑定一个页面上的元素对象&#xff08;无论元素对象是否是引用过期或者是粘贴的目标文本区&#xff0c;但前提需要粘贴的目标文本区获取焦点&#xff09;执行ctrlv后可以生效。执行粘贴组合快捷键&#xff08;ctrlv&#xff09;的示例代码 se…

C++模板:编译时模拟Duck Typing

C泛型与多态&#xff08;4&#xff09;: Duck Typing - 简书 James Whitcomb Riley在描述这种is-a的哲学时&#xff0c;使用了所谓的鸭子测试&#xff08;Duck Test&#xff09;: 当我看到一只鸟走路像鸭子&#xff0c;游泳像鸭子&#xff0c;叫声像鸭子&#xff0c;那我就把它…

【求职面试】驾照的种类

大型客车 A1 大型载客汽车 A3、B1、B2、C1、C2、C3、C4、M 牵引车 A2 重型、中型全挂、半挂汽车列车 B1、B2、C1、C2、C3、C4、M 城市公交车 A3 核载10人以上的城市公共汽车 C1、C2、C3、C4 中型客车 B1 中型载客汽车&#xff08;10人以上、19人以下&#xff09; C1、C2、C3…

PyQt实战——使用python提取JSON数据(十)

系类往期文章&#xff1a; PyQt5实战——多脚本集合包&#xff0c;前言与环境配置&#xff08;一&#xff09; PyQt5实战——多脚本集合包&#xff0c;UI以及工程布局&#xff08;二&#xff09; PyQt5实战——多脚本集合包&#xff0c;程序入口QMainWindow&#xff08;三&…

RAG实战:构建基于本地大模型的智能问答系统

RAG实战&#xff1a;构建基于本地大模型的智能问答系统 引言 在当今AI快速发展的时代&#xff0c;如何构建一个既智能又可靠的问答系统是一个重要课题。本文将介绍如何使用RAG&#xff08;检索增强生成&#xff09;技术&#xff0c;结合本地大模型&#xff0c;构建一个高效的智…

OAuth 2.0

简介 OAuth 是一种开放标准的授权协议或框架&#xff0c;它提供了一种安全的方式&#xff0c;使第三方应用程序能够访问用户在其他服务上的受保护资源&#xff0c;而无需共享用户的凭证&#xff08;如用户名和密码&#xff09;。OAuth 的核心思想是通过“授权令牌”来代替直接…

IntelliJ IDEA 远程调试

IntelliJ IDEA 远程调试 在平时开发 JAVA 程序时&#xff0c;在遇到比较棘手的 Bug 或者是线上线下结果不一致的情况下&#xff0c;我们会通过打 Log 或者 Debug 的方式去定位并解决问题&#xff0c;两种方式各有利弊&#xff0c;今天就简要介绍下如何通过远程 Debug 的情况下…

美国辅料查询之FDA批准药用辅料数据库(IID数据库)

药用辅料的性质很大程度上决定了制剂的性质&#xff0c;每一种新的药用辅料的问世&#xff0c;都会为制剂技术的发展带来新的机遇&#xff0c;每一种药用辅料都可能让制剂研发员开发出新剂型药物&#xff0c;所以在药物制剂研发过程中&#xff0c;药用辅料的信息调研是不可或缺…

YOLOv10目标检测-训练自己的数据

yolov10 https://github.com/THU-MIG/yolov10?tabreadme-ov-file 1. 数据集 模型的建立需要收集图片并且进行标注。YOLOv10标注的文件格式如下&#xff08;每张图片对应一个标签文件&#xff09;&#xff1a; 0 0.441753 0.815461 0.061021 0.042763 1 0.395895 0.759868 …

Redis学习(五)优惠券秒杀2——分布式锁

Redis学习&#xff08;五&#xff09;优惠券秒杀2 一、分布式锁-redission二、快速入门三、redission可重入锁原理四、redission锁的MutiLock原理 一、分布式锁-redission 基于setnx实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程…

Flink调优----资源配置调优与状态及Checkpoint调优

目录 第 1 章 资源配置调优 1.1 内存设置 1.1.1 TaskManager 内存模型 1、内存模型详解 2、案例分析 1.1.2 生产资源配置示例 1.2 合理利用 cpu 资源 1.2.1 使用 DefaultResourceCalculator 策略 1.2.2 使用 DominantResourceCalculator 策略 1.2.3 使用 DominantRes…

Docker怎么关闭容器开机自启,批量好几个容器一起操作?

环境&#xff1a; WSL2 docker v25 问题描述&#xff1a; Docker怎么关闭容器开机自启&#xff0c;批量好几个容器一起操作&#xff1f; 解决方案&#xff1a; 在 Docker 中&#xff0c;您可以使用多种方法来关闭容器并配置它们是否在系统启动时自动启动。以下是具体步骤和…