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);
计算公式如下:
计算实际拥塞窗口与目标窗口的差值:
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,这是拥塞控制中切换模式的关键值。