zoukankan      html  css  js  c++  java
  • 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;
          */
       }
    }
    
  • 相关阅读:
    运算符重载
    C++ 画星号图形——圆形(核心代码记录)
    C++ 画星号图形——空心三角形(星号左对齐)(核心代码介绍)
    C++ 画星号图形——空心三角形(星号居中对齐)(核心代码介绍)
    QMap迭代器
    QVector也是隐式数据共享的
    调试dump文件
    How does the compilation and linking process work?
    when to use reinterpret_cast
    构造类的时候,提供类型转换
  • 原文地址:https://www.cnblogs.com/ukernel/p/9191066.html
Copyright © 2011-2022 走看看