我将在 Java 中实现一个(简单的)下载器应用程序作为个人练习。它将在不同的线程中运行多个作业,这样我将在执行期间始终同时下载几个文件。
我希望能够定义在所有下载作业之间共享的下载速率限制,但即使对于单个下载任务,我也不知道该怎么做。我该怎么做呢?我应该尝试实施哪些解决方案?
谢谢。
我将在 Java 中实现一个(简单的)下载器应用程序作为个人练习。它将在不同的线程中运行多个作业,这样我将在执行期间始终同时下载几个文件。
我希望能够定义在所有下载作业之间共享的下载速率限制,但即使对于单个下载任务,我也不知道该怎么做。我该怎么做呢?我应该尝试实施哪些解决方案?
谢谢。
这个问题是高水平的,所以我希望你不要期望低水平的答案。一般来说,您首先需要定义/决定您将使用哪些网络实用程序。例如,你只是要打开一个标准的 java Socket 吗?您将使用一些第三方网络库吗?您是否熟悉任何可用的选项?
在最一般的意义上,您可以通过您决定的网络库来控制带宽。应该是一个比较简单的公式。
您将拥有某种设置带宽限制的对象(称为套接字)。您将套接字上的带宽限制(通常)设置为总带宽/活动连接数。如果某些连接没有使用其全部分配的带宽,您可以持续优化此数字。当你到达那里时寻求关于该算法的帮助,如果你甚至关心......
等式的第二部分是,操作系统/网络库是否已经通过给它一个速率限制数字来控制带宽,或者您是否需要通过限制读/写速率来自己控制这个过程?这并不像看起来那么简单,因为操作系统可以拥有 TCP 套接字缓冲区,这些缓冲区将读取数据直到填满。假设您有一个用于入站流量的 2Mb 套接字缓冲区。如果您依赖远程端仅在 2Mb 缓冲区已满时停止发送数据,您将不得不等待 2Mb 数据传输,然后才有机会通过从队列中移除来限制速率,您将总是有一个巨大的突发在您可以限制速率之前,在每个套接字上。
那时,您开始谈论编写将在 tcp(或 UDP)上运行的协议,以便一方可以告诉另一方,“好的,发送更多数据”,或“等等,我的带宽限制已暂时达到”。长话短说,开始吧,一旦实施到位并想要改进它,就提出问题......
我将从管理所有下载的 DownloadManager 开始。
interface DownloadManager
{
public InputStream registerDownload(InputStream stream);
}
所有想要参与托管带宽的代码都会在开始读取之前向下载管理器注册它的流。在它的 registerDownload() 方法中,管理器将给定的输入流包装在一个ManagedBandwidthStream
.
public class ManagedBandwidthStream extends InputStream
{
private DownloadManagerImpl owner;
public ManagedBandwidthStream(
InputStream original,
DownloadManagerImpl owner
)
{
super(original);
this.owner = owner;
}
public int read(byte[] b, int offset, int length)
{
owner.read(this, b, offset, length);
}
// used by DownloadManager to actually read from the stream
int actuallyRead(byte[] b, int offset, int length)
{
super.read(b, offset, length);
}
// also override other read() methods to delegate to the read() above
}
该流确保所有对 read() 的调用都被定向回下载管理器。
class DownloadManagerImpl implements DownloadManager
{
public InputStream registerDownload(InputStream in)
{
return new ManagedDownloadStream(in);
}
void read(ManagedDownloadStream source, byte[] b, int offset, int len)
{
// all your streams now call this method.
// You can decide how much data to actually read.
int allowed = getAllowedDataRead(source, len);
int read = source.actuallyRead(b, offset, len);
recordBytesRead(read); // update counters for number of bytes read
}
}
那么您的带宽分配策略就是关于如何实现 getAllowedDataRead()。
限制带宽的一种简单方法是,保留一个计数器,记录在给定周期(例如 1 秒)内可以读取多少字节。每次调用 read 都会检查计数器并使用它来限制读取的实际字节数。计时器用于重置计数器。
在实践中,在多个流之间分配带宽可能会变得相当复杂,尤其是为了避免饥饿和促进公平,但这应该给你一个公平的开始。
这基本上就是大多数限制器的工作方式(就像wget
)