在中文网站上没有找到一个对该算法好点的讲解和实现,而且大多雷同,算法的实现也是错误的,所以我在外网找了一篇,然后用google翻译了一下,大家将就着看看吧
问题陈述
考虑连接到网络的主机A。主机A以可变长度数据包的形式产生突发流量(如下所述),必须通过网络将其传输到接收器。
假设主机 A 发送:
-
前2秒为10 Mbps 数据。
-
接下来3秒的数据传输量为20 Mbps 。
-
接下来5秒没有数据。
-
接下来2秒的数据传输量为15 Mbps 。
这种流量性质称为突发流量,来自源进入网络。
突发流量是指突然出现的、意想不到的网络流量高峰和低谷。它是以短暂、不均匀的方式传输的数据。
它会对网络产生不利影响并可能导致网络拥塞。
如果许多主机连接到网络并且不断向网络发送突发流量,则可能会导致数据包丢失和意外延迟,从而降低服务质量,并且数据包可能会积压。这种意外的数据包延迟和数据包丢失被称为网络拥塞,或者简单地说,网络被拥塞。它会降低整个系统的性能。
在实践中,网络需要保证其客户和用户的QoS 。问题是设计一种算法,可以处理突发流量并使其平滑。这个问题可以通过多种方式解决,其中之一就是漏桶算法。
例子
让我们举个例子来了解网络期望什么样的流量来防止拥塞。
假设主机 A 发送:
- 前2秒为10 Mbps 数据。
- 接下来3秒的数据传输量为20 Mbps 。
- 接下来3秒没有数据。
- 接下来2秒的数据传输量为15 Mbps 。
现在假设我们将最大传输速率(每秒从主机到网络传输的位数)固定为5 Mbps。然后我们想要开发一种算法,假设数据可用,该算法将以统一的速率发送5 Mbps 的数据。
示例说明:
在上面的例子中,总共(10*2+20*3+15*2)=110( 1 0*2+2 0*3+1 5*2 )=1 1 0Mb 的数据在10秒内发送。这个流量是突发的。当我们应用该算法时,将以固定速率在22秒内发送5 Mbps 的数据。在这两种情况下,我们都会发送全部110 Mb 的数据。这样就可以保证网络流量的均匀性。
约束条件
- 数据包大小<=< =桶的泄漏率。
- 泄漏率<=< =网络到主机的专用带宽。
- 数据包一次到达一个。
流量整形
在继续讨论算法之前,让我们讨论一个重要的概念,称为流量整形。流量整形是一种控制发送到网络的流量的机制。它意味着调节流向网络的数据的平均速率以防止拥塞。它保持发送到网络的恒定流量模式。该恒定速率可以取决于网络带宽限制和主机发送的数据包的优先级。
建立连接后,主机或发送方可以与网络提供商协商此恒定速率,流量将以此速率发送到网络。每个网络保证每个主机都有专用带宽。
这可以用来设置这个限制。流量整形是防止网络拥塞的最常用技术之一。当网络知道预期的流量类型时,它可以更好地处理它。
漏桶算法是一种流量整形算法,用于通过平均发送到网络的数据速率将突发流量转换为平滑流量。
漏桶算法
漏桶算法是一种临时存储多个数据包的拥塞控制方法。这些数据包以发送者和网络之间决定的恒定速率发送到网络。该算法用于通过数据网络中的流量整形来实现拥塞控制。
为了理解该算法,让我们首先讨论漏桶的类比。
考虑一个底部有小孔的桶。现在想象一下,水以随机的间隔倒入桶中。在每个时间间隔,倒入桶中的水量是不固定的。现在,无论桶内有多少水,水都会以恒定的速度从孔中流出。为了更清楚起见,请考虑下图。
-
漏水的速度(称为漏水率)与桶内的水量无关。
-
如果水桶满了,倒出的水就会流失。
漏桶的相同思想可以应用于数据包。
- 考虑到,每个网络接口都有一个漏桶。
- 现在,当发送者想要传输数据包时,数据包被扔进桶中。这些数据包累积在网络接口处的存储桶中。
- 如果桶满了,报文就会被桶丢弃而丢失。
- 这个桶会以恒定的速度泄漏。这意味着数据包将以恒定速率传输到网络。该恒定速率称为泄漏率或平均速率。
- 这样,突发流量就被漏桶转化为平滑、固定的流量。
- 以不同的时间间隔排队和释放数据包有助于减少网络拥塞并提高整体性能。
该设计可以在主机操作系统内部进行模拟,特别是在传输层和网络层。
执行
漏桶算法可以使用FIFO(先进先出)队列来实现。最先到达桶中的数据包应首先传输。
- 队列充当存储桶或缓冲区来保存数据包。这是在主机操作系统中实现的。
- 来自主机的数据包在到达时被推入队列。
- 在某些时间间隔,数据包会根据泄漏率发送到网络。一般来说,这个间隔是一个时钟周期。时钟节拍是从物理时钟到处理器生成的中断。
- 该泄漏率是由网络预先确定的。网络将为每个主机保证一些专用带宽。该专用带宽可用于设置该泄漏率。
- 如果该队列已满,则到达的数据包将被丢弃。
伪代码
- 步骤 - 1:
初始化缓冲区大小和泄漏率。 - 步骤 - 2:
在时钟周期,将n初始化为泄漏率。 - 步骤3:
如果n大于队列前面的数据包的大小,则将该数据包发送到网络中。 - 步骤 - 4:
将n减少数据包的大小。 - 步骤5:
重复步骤3和步骤4,直到n小于队列前端数据包的大小或桶为空。在下一个时钟周期之前不能传输其他数据包。 - 步骤6:
转至步骤2。
例子
假设
缓冲区大小= 10000 Mb ,泄漏率= 1000 Mb。假设在某个时刻我们的存储桶为[200,500,400,500,100][ 2 0 0 ,5 0 0 ,4 0 0 ,5 0 0 ,1 0 0 ]。
现在,在每次迭代中,我们将传输数据包,以使每个数据包的大小总和不大于泄漏率。
- 迭代-1:
- 桶=[200,500,400,500,100][ 2 0 0 ,5 0 0 ,4 0 0 ,5 0 0 ,1 0 0 ] 我们将第一个和第二个数据包传输到网络中。我们不能将第三个作为总和来转移(200+500+400)( 2 0 0+5 0 0+4 0 0 )大于泄漏率。
- 迭代 - 2 :
- 桶=[400,500,100][ 4 0 0 ,5 0 0 ,1 0 0 ] 我们将在本次迭代中传输接下来的三个数据包,因为它们的总大小不大于泄漏率。 �=1000n=1 0 0 0。在队列的前面,我们有大小为400的数据包。自从400<=4 0 0< = n ,我们将发送该数据包并将n的值更新为(1000-400)=600( 1 0 0 0-4 0 0 )=6 0 0。
- 桶 =[500,100]=[ 5 0 0 ,1 0 0 ] 自从500<=�5 0 0< =n,我们将在队列的前面发送数据包。的价值�=(600-500)=100n=( 6 0 0-5 0 0 )=1 0 0。
- 桶=[100][ 1 0 0 ]。自从100<=�1 0 0< =n,我们将在队列的前面发送数据包。的价值�=(100-100)=0n=( 1 0 0-1 0 0 )=0。因此,在这次迭代之后,我们的桶将是空的。现在假设如果有一些新的数据包到达,它们将被推到队列的末尾。
C++ 中的实现
代码 :
#include <bits/stdc++.h>
#include <chrono>
#include <thread>
using namespace std;
class Packet {
// Packet class to simulate data packets.
int id, size;
public:
Packet(int id, int size) {
// Constructor to initialize packets.
this->id = id;
this->size = size;
}
int getSize() {
return this->size;
}
int getId() {
return this->id;
}
};
class LeakyBucket {
// Leaky Bucket class to simulate leaky bucket algorithm.
int leakRate, bufferSizeLimit, currBufferSize;
queue<Packet> buffer;
public:
LeakyBucket(int leakRate, int size) {
// Constructor to initialize leak rate, and maximum buffer size available.
this->leakRate = leakRate;
this->bufferSizeLimit = size;
this->currBufferSize = 0;
}
void addPacket(Packet newPacket) {
// Function to add new packets at the end of the buffer.
if(currBufferSize + newPacket.getSize() > bufferSizeLimit) {
// If the packet cannot fit in the buffer, then reject the packet.
cout << "Bucket is full. Packet rejected." << endl;
return ;
}
// Add packet to the buffer.
buffer.push(newPacket);
// Update current Buffer Size.
currBufferSize += newPacket.getSize();
// Print out the appropriate message.
cout << "Packet with id = " << newPacket.getId() << " added to bucket." << endl;
}
void transmit() {
// Function to transmit packets. Called at each clock tick.
if(buffer.size() == 0) {
// Check if there is a packet in the buffer.
cout << "No packets in the bucket." << endl;
return ;
}
// Initialize n to the leak rate.
int n = leakRate;
while(buffer.empty() == 0) {
Packet topPacket = buffer.front();
int topPacketSize = topPacket.getSize();
// Check if the packet can be transmitted or not.
if(topPacketSize > n) break;
// Reduce n by packet size that will be transmitted.
n = n - topPacketSize;
// Update the current buffer size.
currBufferSize -= topPacketSize;
// Remove packet from buffer.
buffer.pop();
/* 在正式代码中,可以把他做成一个异步任务 */
cout << "Packet with id = " << topPacket.getId() << " transmitted." << endl;
}
}
};
int main() {
LeakyBucket* bucket = new LeakyBucket(1000, 10000);
bucket->addPacket(Packet(1, 200));
bucket->addPacket(Packet(2, 500));
bucket->addPacket(Packet(3, 400));
bucket->addPacket(Packet(4, 500));
bucket->addPacket(Packet(5, 200));
while(true) {
bucket->transmit();
cout << "Waiting for next tick." << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
参考【https://www.scaler.com/topics/leaky-bucket-algorithm/】