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,這是擁塞控制中切換模式的關鍵值。