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;
      */
   }
}

posted on 2018-06-16 18:31  ukernel  阅读(1223)  评论(0编辑  收藏  举报

导航