UDT源码剖析(九)之CCC
这是整个UDT的算法核心:拥塞控制。我也没太搞明白,不过还是打算列出来,理解的不争取的地方,还请多多指正哈~
CCC
class CCC
{
virtual void init() {} //在UDT connection建立起来的时候被调用
virtual void close() {} //在UDT connection关闭的时候被调用
virtual void onACK(int32_t) {} //在收到ACK的时候被调用
virtual void onLoss(const int32_t*, int) {} //一个丢失的报文被收到时调用
virtual void onTimeout() {} //超时的时候被调用
virtual void onPktSent(const CPacket*) {} //发送数据的时候被调用
virtual void onPktReceived(const CPacket*) {} //收到数据的时候被调用
virtual void processCustomMsg(const CPacket*) {} //在收到用户定义的packet时被调用
protected:
void setACKTimer(int msINT); //设置周期性确认和ACK周期
void setACKInterval(int pktINT); //设置基于分组的确认以及发送ACK的分组数量
void setRTO(int usRTO); //设置RTO
void sendCustomMsg(CPacket& pkt) const; //发送用户定义的control packet
const CPerfMon* getPerfInfo(); //检索性能信息
void setUserParam(const char* param, int size); //设置用户定义的参数
private:
void setMSS(int mss); //设置MSS
void setMaxCWndSize(int cwnd); //设置拥塞窗口
void setBandwidth(int bw); //设置带宽
void setSndCurrSeqNo(int32_t seqno); //设置正确的seqno
void setRcvRate(int rcvrate); //设置recv rate
void setRTT(int rtt); //设置RTT
protected:
const int32_t& m_iSYNInterval; //SYN (SYN间隔数),每10ms增加计数
double m_dPktSndPeriod; //packet发送周期 ms
double m_dCWndSize; //拥塞窗口大小,使用packet计数
int m_iBandwidth; //估计的带宽,使用每秒发送的packet计数
double m_dMaxCWndSize; //最大的拥塞窗口,packet计数
int m_iMSS; //最大的包大小
int32_t m_iSndCurrSeqNo; //当前最大的还没有发送的编号
int m_iRcvRate; //接收端的数据包的到达速率,使用每秒接受的packet计数
int m_iRTT; //RTT,使用ms计数
char* m_pcParam; //用户提供的参数
int m_iPSize; //用户提供的参数大小
private:
UDTSOCKET m_UDT; //UDT的索引
int m_iACKPeriod; //周期性定时器发送ACK,毫秒计数
int m_iACKInterval; //多少个packet发送一个ACK,使用packets计数
bool m_bUserDefinedRTO; //RTO是自己算还是通过用户定义
int m_iRTO; //RTO,ms计数
CPerfMon m_PerfInfo; //统计信息
};
- 初始化:
CCC::CCC()
CCC::CCC():
m_iSYNInterval(CUDT::m_iSYNInterval), /*每10ms增加一次*/
m_dPktSndPeriod(1.0), /*每1ms发送一个packet*/
m_dCWndSize(16.0), /*拥塞窗口的大小为16个packets*/
m_iBandwidth(), /*估计的带宽暂且不提*/
m_dMaxCWndSize(), /*最大的拥塞窗口也不提*/
m_iMSS(), /*MSS的值也不提*/
m_iSndCurrSeqNo(), /*当前最大的还没有发送的编号*/
m_iRcvRate(), /*接收端的接受速率*/
m_iRTT(), /*RTT*/
m_pcParam(NULL), /*假设用户没有提供参数*/
m_iPSize(0),
m_UDT(), /*UDT的索引也没有提供*/
m_iACKPeriod(0), /*周期性发送ACK数值也没有提供*/
m_iACKInterval(0), /*多少packets发送一个ACK也没有提供*/
m_bUserDefinedRTO(false), /*默认计算RTO,不由用户提供*/
m_iRTO(-1), /*RTO默认为-1*/
m_PerfInfo() /*全局统计变量默认不提供*/
{
}
- 销毁:
CCC::~CCC()
CCC::~CCC()
{
delete [] m_pcParam; //删除用户提供的参数就行了
}
- 发送用户定义的packets:
void CCC::sendCustomMsg(CPacket& pkt) const
void CCC::sendCustomMsg(CPacket& pkt) const
{
CUDT* u = CUDT::getUDTHandle(m_UDT);
if (NULL != u)
{
pkt.m_iID = u->m_PeerID;
u->m_pSndQueue->sendto(u->m_pPeerAddr, pkt);
}
}
- 向CUDT实例填充统计信息:
const CPerfMon* CCC::getPerfInfo()
const CPerfMon* CCC::getPerfInfo()
{
try
{
CUDT* u = CUDT::getUDTHandle(m_UDT);
if (NULL != u)
u->sample(&m_PerfInfo, false);
}
catch (...)
{
return NULL;
}
return &m_PerfInfo;
}
- 设置系列函数:
void CCC::setACKTimer(int msINT) //设置周期性确认和ACK周期(最大为10ms,不可能超过10ms)
{
m_iACKPeriod = msINT > m_iSYNInterval ? m_iSYNInterval : msINT;
}
void CCC::setACKInterval(int pktINT) //设置多少个packets发送一个ACK
{
m_iACKInterval = pktINT;
}
void CCC::setRTO(int usRTO) //使用用户提供的RTO估算
{
m_bUserDefinedRTO = true;
m_iRTO = usRTO;
}
void CCC::setMSS(int mss) //设置MSS
{
m_iMSS = mss;
}
void CCC::setBandwidth(int bw) //设置带宽
{
m_iBandwidth = bw;
}
void CCC::setSndCurrSeqNo(int32_t seqno) //设置当前的seqno
{
m_iSndCurrSeqNo = seqno;
}
void CCC::setRcvRate(int rcvrate) //设置接收rate
{
m_iRcvRate = rcvrate;
}
void CCC::setMaxCWndSize(int cwnd) //设置最大的拥塞窗口的数值
{
m_dMaxCWndSize = cwnd;
}
void CCC::setRTT(int rtt) //设置RTT
{
m_iRTT = rtt;
}
void CCC::setUserParam(const char* param, int size) //设置用户参数
{
delete [] m_pcParam;
m_pcParam = new char[size];
memcpy(m_pcParam, param, size);
m_iPSize = size;
}
CCCFactory
class CCCVirtualFactory
{
public:
virtual ~CCCVirtualFactory() {}
virtual CCC* create() = 0;
virtual CCCVirtualFactory* clone() = 0;
};
template <class T>
class CCCFactory: public CCCVirtualFactory
{
public:
virtual ~CCCFactory() {}
virtual CCC* create() {return new T;}
virtual CCCVirtualFactory* clone() {return new CCCFactory<T>;}
};
CUDTCC
class CUDTCC: public CCC
{
public:
CUDTCC();
public:
virtual void init(); //继承的函数
virtual void onACK(int32_t);
virtual void onLoss(const int32_t*, int);
virtual void onTimeout();
private:
int m_iRCInterval; //UDT速率控制间隔
uint64_t m_LastRCTime; //最后的速率增加时间
bool m_bSlowStart; //是否处于慢启动阶段
int32_t m_iLastAck; //最后收到的ACKed seqno
bool m_bLoss; //是否从上次增加速率之后发生的丢失事件
int32_t m_iLastDecSeq; //最大的packet seqno,自从最后一次减少速率
double m_dLastDecPeriod; //最后一次减少速率时发送packet的速率
int m_iNAKCount; //收到NAK包的计数
int m_iDecRandom; //发生随机的阀值下降
int m_iAvgNAKNum; //在拥塞期间收到的平均的NAK数值
int m_iDecCount; //拥塞期间减少的数量
};
- 初始化:
CUDTCC::CUDTCC()
CUDTCC::CUDTCC():
m_iRCInterval(),
m_LastRCTime(),
m_bSlowStart(),
m_iLastAck(),
m_bLoss(),
m_iLastDecSeq(),
m_dLastDecPeriod(),
m_iNAKCount(),
m_iDecRandom(),
m_iAvgNAKNum(),
m_iDecCount()
{
}
- 使用时初始化:
void CUDTCC::init()
void CUDTCC::init()
{
m_iRCInterval = m_iSYNInterval; //UDT的速率控制间隔定义为10ms
m_LastRCTime = CTimer::getTime(); //最后的速率增加时间定义为现在
setACKTimer(m_iRCInterval); //设置周期性确认的ACK为10ms
m_bSlowStart = true; //启动时处于慢启动阶段
m_iLastAck = m_iSndCurrSeqNo; //最后一次收到的ACK是当前还没有发送的编号
m_bLoss = false; //刚开始没有出现loss事件
m_iLastDecSeq = CSeqNo::decseq(m_iLastAck); //最大发送seqno为最大的需要发送的seqno-1
m_dLastDecPeriod = 1; //刚开始的速率为10ms/1 packet
m_iAvgNAKNum = 0; //收到的NAK计数为0
m_iNAKCount = 0;
m_iDecRandom = 1; //随机的阀值下降为0
m_dCWndSize = 16; //拥塞窗口的大小为16
m_dPktSndPeriod = 1; //packet的发送时间周期为10ms/1 packet
}
- 收到一个ACK时:
void CUDTCC::onACK(int32_t ack)
void CUDTCC::onACK(int32_t ack)
{
int64_t B = 0;
double inc = 0;
// Note: 1/24/2012
// 最小增加参数从“1.0 / m_iMSS”增加到0.01,因为原稿太小,导致发送速率在长时间内保持在低水平
const double min_inc = 0.01;
uint64_t currtime = CTimer::getTime(); //获取当前的时间
if (currtime - m_LastRCTime < (uint64_t)m_iRCInterval) //如果收到的ACK的时间<10ms,视为正常,不进行处理
return;
m_LastRCTime = currtime; //否则的话,就超时了哇
if (m_bSlowStart) //如果此时位于慢启动阶段
{
m_dCWndSize += CSeqNo::seqlen(m_iLastAck, ack);//收到了几个ACK就增加几个拥塞窗口(packets计数),和TCP的策略一样
m_iLastAck = ack; //更新上一次收到的ACK
if (m_dCWndSize > m_dMaxCWndSize) //是不是可以把这个当做慢启动门限来翻译啊?
{ //如果现在的拥塞窗口大于满启动门限
m_bSlowStart = false; //关闭慢启动
if (m_iRcvRate > 0) //如果接受方的速率>0
m_dPktSndPeriod = 1000000.0 / m_iRcvRate;//将发送周期与接受周期(速率)相对应
else//否则,减小发送速率,具体=(拥塞窗口packets/(RTT+10ms))
m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval);
}
}
else//处于拥塞避免阶段,拥塞窗口的增加程度变小
m_dCWndSize = m_iRcvRate / 1000000.0 * (m_iRTT + m_iRCInterval) + 16;
// During Slow Start, no rate increase
if (m_bSlowStart) //如果处于慢启动阶段,增加完拥塞窗口就可以返回了
return;
//此后的处理,处于拥塞避免阶段
if (m_bLoss) //上一次是否有丢失事件发生,此时已经离开了慢启动阶段
{ //上一次因为丢包而引起的重传而进入慢启动阶段的标志loss清除掉
m_bLoss = false;
return; //然后返回
}
//之后是对拥塞避免阶段的一些处理
B = (int64_t)(m_iBandwidth - 1000000.0 / m_dPktSndPeriod);//每秒估计发送的packets - 每秒实际发送的packets
//如果每秒的发送速率大于最后减少时的发送速率(比上一次发生拥塞时的速率还要高)&&
//每秒估计发送的packets/9 < 估计与实际的发送时间之差
if ((m_dPktSndPeriod > m_dLastDecPeriod) && ((m_iBandwidth / 9) < B))
B = m_iBandwidth / 9; //调整B为估计发送速率的1/9,控制B的数值
if (B <= 0)
inc = min_inc;//如果发送速率还没有达到之前一次的顶峰,inc=0.01
else
{
// inc = max(10 ^ ceil(log10( B * MSS * 8 ) * Beta / MSS, 1/MSS)
// Beta = 1.5 * 10^(-6)
//否则一顿蛇皮计算
inc = pow(10.0, ceil(log10(B * m_iMSS * 8.0))) * 0.0000015 / m_iMSS;
//确定无论如何计算,inc<=0.01
if (inc < min_inc)
inc = min_inc;
}
//调整当前的发送速率为:发送速率*10 / (发送速率*0.01+10)
//确保不会发送速率不会超过上一次发生拥塞时的速率,防止再次出现丢包事件
m_dPktSndPeriod = (m_dPktSndPeriod * m_iRCInterval) / (m_dPktSndPeriod * inc + m_iRCInterval);
}
- 在发生包丢失事件时被调用:
void CUDTCC::onLoss(const int32_t* losslist, int)
void CUDTCC::onLoss(const int32_t* losslist, int)
{
//Slow Start stopped, if it hasn't yet
if (m_bSlowStart) //如果处于慢启动状态
{
m_bSlowStart = false; //在慢启动阶段丢包,退出慢启动状态,免得拥塞窗口暴跌
if (m_iRcvRate > 0) //对方接收的速率
{
// Set the sending rate to the receiving rate.
m_dPktSndPeriod = 1000000.0 / m_iRcvRate; //调整发送速率
return;
}
// If no receiving rate is observed, we have to compute the sending
// rate according to the current window size, and decrease it
// using the method below.
m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval);//适当的降低发送速率,反正不会再次进入满启动,导致拥塞窗口暴跌
}
//没有在慢启动状态丢失packet或者对方的接受速率正常
m_bLoss = true; //将丢包设置为true
//比较之前还有丢包没有处理
if (CSeqNo::seqcmp(losslist[0] & 0x7FFFFFFF, m_iLastDecSeq) > 0)
{
m_dLastDecPeriod = m_dPktSndPeriod; //将上一次发送包的速率调增为现在的速率
m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.125);//增减发送速率??不应该降低吗??此时处于拥塞避免期间
//调整丢失包的NAK NUMBER
m_iAvgNAKNum = (int)ceil(m_iAvgNAKNum * 0.875 + m_iNAKCount * 0.125);
m_iNAKCount = 1; //设计拥塞期间的数据
m_iDecCount = 1;
m_iLastDecSeq = m_iSndCurrSeqNo; //调整丢失的包的ACK
// remove global synchronization using randomization
//使用随机化去除全局同步,没有搞懂他是想干啥
srand(m_iLastDecSeq);
m_iDecRandom = (int)ceil(m_iAvgNAKNum * (double(rand()) / RAND_MAX));
if (m_iDecRandom < 1)
m_iDecRandom = 1;
}
else if ((m_iDecCount ++ < 5) && (0 == (++ m_iNAKCount % m_iDecRandom)))
{
// 0.875^5 = 0.51, rate should not be decreased by more than half within a congestion period
m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.125); //反正就是根据接收方接收的速率,调整本地的发送速率
m_iLastDecSeq = m_iSndCurrSeqNo;
}
}
- 超时时被调用:
void CUDTCC::onTimeout()
//在超时的时候,进行计算
//在超时的时候处于慢启动阶段,但是依旧发生了丢包,那么增加速率再次降低
//在TCP中,若在慢启动阶段发生丢包时,会重新进入慢启动阶段,拥塞窗口调整为1,降低慢启动门限
void CUDTCC::onTimeout()
{
if (m_bSlowStart) //如果处于慢启动阶段,不过为啥要退出慢启动阶段??
{
m_bSlowStart = false; //退出慢启动阶段
if (m_iRcvRate > 0) //如果接收方的速率>0
m_dPktSndPeriod = 1000000.0 / m_iRcvRate; //调整发送方的速率与接受方相同
else //如果接收方的速率<0,计算发送方的速率为:拥塞窗口/(RTT+10),减低发送速率
m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval);
}
else
{
/*
m_dLastDecPeriod = m_dPktSndPeriod;
m_dPktSndPeriod = ceil(m_dPktSndPeriod * 2);
m_iLastDecSeq = m_iLastAck;
*/
}
}