Darwin流媒体服务器中拥塞控制是在RTPBandwidthTracker类中定义并实现的。
1.在服务器启调用 RTPSession::Play给客户端播送音视频数据时,会调用RTPBandwidthTracker::SetWindowSize给拥塞窗口fCongestionWindow,fSlowStartThreshold设定初值。
void RTPBandwidthTracker::SetWindowSize(SInt32 clientWindowSize)
{
//
// Currently we only allow this info to be set once
if (fClientWindow > 0)
return;
// call SetWindowSize once the clients buffer size is known
// since this occurs before the stream starts to send
fClientWindow = clientWindowSize;
fLastCongestionAdjust = 0;
#if RTP_PACKET_RESENDER_DEBUGGING
//€ test to see what happens w/o slow start at beginning
//if ( initSlowStart )
// qtss_printf( "ack list initializing with slow start.\n" );
//else
// qtss_printf( "ack list initializing at full speed.\n" );
#endif
if (fUseSlowStart)
{
fSlowStartThreshold = clientWindowSize * 3 / 4;
//
// This is a change to the standard TCP slow start algorithm. What
// we found was that on high bitrate high latency networks (a DSL connection, perhaps),
// it took just too long for the ACKs to come in and for the window size to
// grow enough. So we cheat a bit.
fCongestionWindow = clientWindowSize / 2;
//fCongestionWindow = kMaximumSegmentSize;
}
else
{
fSlowStartThreshold = clientWindowSize;
fCongestionWindow = clientWindowSize;
}
if (fSlowStartThreshold < kMaximumSegmentSize)
fSlowStartThreshold = kMaximumSegmentSize;
}
2.RTPStream::ReliableRTPWrite在发送数据前会调用fResender.IsFlowControlled判断窗口是否已满,如果已满则延迟发包,代码如下
if (fResender.IsFlowControlled())
{
err = QTSS_WouldBlock;
}else
{
//
// Assign a lifetime to the packet using the current delay of the packet and
// the time until this packet becomes stale.
fBytesSentThisInterval += inLen;
fResender.AddPacket(inBuffer, inLen, (SInt32)(fDropAllPacketsForThisStreamDelay - curPacketDelay));
(void)fSockets->GetSocketA()->SendTo(fRemoteAddr, fRemoteRTPPort, inBuffer, inLen);
}
//fBytesInList是已经发送的数据大小,fCongestionWindow代表当前拥塞窗口的大小
const Bool16 IsFlowControlled() { return ((SInt32)fBytesInList >= fCongestionWindow); }
3.已送数据fBytesInList大小更新:
3.1 RTPStream::ReliableRTPWrite调用RTPPacketResender::AddPacket发包时会将发送包的大小累加到fBytesInList上。
void RTPPacketResender::AddPacket(void * inRTPPacket, UInt32 packetSize, SInt32 ageLimit)
{
......
fBandwidthTracker->FillWindow(packetSize);
......
}
void FillWindow(UInt32 inNumBytes)
{
fBytesInList += inNumBytes; fIsRetransmitting = false;
}
3.2 RTPPacketResender::AckPacket收到确认包时会调用fBandwidthTracker->EmptyWindow(theEntry->fPacketSize),fBytesInList可用的大小变大。
RTPPacketResender::ResendDueEntries()删除过期的包也会调用fBandwidthTracker->EmptyWindow;RTPPacketResender::RemovePacket在移除不被使用的包时,也
会调用。
void RTPBandwidthTracker::EmptyWindow(UInt32 bytesIncreased, Bool16 updateBytesInList)
{
if (bytesIncreased == 0)
return;
Assert(fClientWindow > 0 && fCongestionWindow > 0);
if (fBytesInList < bytesIncreased)
bytesIncreased = fBytesInList;
if (updateBytesInList)
fBytesInList -= bytesIncreased;
// this assert hits
Assert(fBytesInList < ((UInt32)fClientWindow + 2000)); //mainly just to catch fBytesInList wrapping below 0
// update the congestion window by the number of bytes just acknowledged.
if (fCongestionWindow >= fSlowStartThreshold)
{
// when we hit the slow start threshold, only increase the
// window for each window full of acks.
fCongestionWindow += bytesIncreased * bytesIncreased / fCongestionWindow;
}
else
//
// This is a change to the standard TCP slow start algorithm. What
// we found was that on high bitrate high latency networks (a DSL connection, perhaps),
// it took just too long for the ACKs to come in and for the window size to
// grow enough. So we cheat a bit.
fCongestionWindow += bytesIncreased;
if (fCongestionWindow > fClientWindow)
fCongestionWindow = fClientWindow;
// qtss_printf("Window = %d, %d left\n", fCongestionWindow, fCongestionWindow-fBytesInList);
}
拥塞避免:为了防止fCongestionWindow增加过快而导致网络拥塞,所以需要设置一个慢开始门限fSlowStartThreshold状态变量。
当fCongestionWindow >= fSlowStartThreshold,使用拥塞控制算法,停用慢启动算法,fCongestionWindow缓慢的增长,比慢启动要慢的多;
当fCongestionWindow < fSlowStartThreshold,使用慢启动算法。
当需要重传时(调用RTPPacketResender::ResendDueEntries),说明网络出现拥塞,就要把慢启动开始门限(fSlowStartThreshold)设置为设置为发送窗口的3/4,fCongestionWindow(拥塞窗口)设置为原来的1/2,然后在使用慢启动算法,这样做的目的能迅速的减少主机向网络中传输数据,使发生拥塞的路由器能够把队列中堆积的分组处理完毕,代码如下:
RTPPacketResender::ResendDueEntries--->RTPBandwidthTracker::AdjustWindowForRetransmit
void RTPBandwidthTracker::AdjustWindowForRetransmit()
{
// this assert hits
Assert(fBytesInList < ((UInt32)fClientWindow + 2000)); //mainly just to catch fBytesInList wrapping below 0
// slow start says that we should reduce the new ss threshold to 1/2
// of where started getting errors ( the current congestion window size )
// so, we get a burst of re-tx becuase our RTO was mis-estimated
// it doesn't seem like we should lower the threshold for each one.
// it seems like we should just lower it when we first enter
// the re-transmit "state"
// if ( !fIsRetransmitting )
// fSlowStartThreshold = fCongestionWindow/2;
// make sure that it is at least 1 packet
if (fSlowStartThreshold < kMaximumSegmentSize)
fSlowStartThreshold = kMaximumSegmentSize;
// start the full window segemnt counter over again.
fSlowStartByteCount = 0;
// tcp resets to one (max segment size) mss, but i'm experimenting a bit
// with not being so brutal.
//curAckList->fCongestionWindow = kMaximumSegmentSize;
// fCongestionWindow = kMaximumSegmentSize;
// fCongestionWindow = fCongestionWindow / 2; // half the congestion window size
SInt64 theTime = OS::Milliseconds();
if (theTime - fLastCongestionAdjust > 250) //调整周期为250
{
fSlowStartThreshold = fCongestionWindow * 3 / 4; //慢开始门限设为拥塞窗口的3/4
fCongestionWindow = fCongestionWindow / 2; //拥塞窗口设为原来的1/2
fLastCongestionAdjust = theTime;
}
/*
if ( fSlowStartThreshold < fCongestionWindow )
fCongestionWindow = fSlowStartThreshold/2;
else
fCongestionWindow = fCongestionWindow /2;
*/
if (fCongestionWindow < kMaximumSegmentSize)
fCongestionWindow = kMaximumSegmentSize;
// qtss_printf("Congestion window now %d\n", fCongestionWindow);
fIsRetransmitting = true;
}