Android下载器之限速篇

0x01 前言

终于考完了马原完成了大学的最后一门考试,能够愉快的写代码了~今天就来记录一下个人下载器中是如何实现限速的。java

0x02 限速的对象

市面上常见的专业下载器都会提供下载/上传限速这个功能,那限速到底是限的哪个速度呢?git

咱们都知道HTTP/HTTPS的传输层协议是TCP,TCP是基于字节流的。字节流在Java中对应的就是InputStreamOutputStream。基于TCP的传输层下载/上传的步骤通常是github

  • 创建TCP链接,对应Java的Socket.connect()。这期间会经历三次握手,是一个耗时过程,可使用链接复用优化(OKHttp已经作了链接池缓存)。
  • 链接成功后就能够根据须要收发数据,利用Socket获取到链接的InputStreamOutputStream操做便可。
  • 关闭链接。

如下载为例,首先链接步骤是不须要限速的,由于链接部分一般是很快的,而且每每都想要链接更加快速。因此,咱们限速的对象确定就是从socket的InputStream这一步骤。缓存

0x03 如何“限速”?

首先,这里的限速并非真的限制读写数据流每时每刻的速度。这里要分清高中物理学过的两个概念——瞬时速度平均速度网络

s(t)t时刻下载的字节,v为下载速度则有:多线程

v_{瞬时} = \frac{\mathrm{d}s(t)}{\mathrm{d}t}
v_{平均} = \frac{s(t_2) - s(t_1)}{t_2 - t_1}

下载的瞬时速度和不少东西有关:硬件、网络情况、系统等,s(t)是咱们没法控制的。 可是有一个东西咱们是,那就是\Delta{t} = s(t_2) - s(t_1)socket

咱们下载的时候每每是在一个循环中不断的从网络读取数据到内存。咱们能够在read以前记录时间t_1read操做以后记录时间t_2。而后假设咱们想要将下载的速度限制为v_{expected},那么,咱们读取这段\Delta{s}字节的数据指望耗时为:post

t_{expected} = \frac{\Delta{s}}{v_{expected}}

优化

\Delta{t} = t_{expected} - (t_2 - t_1)

则当\Delta{t} > 0时,说明当前下载速度快,咱们就能够调用Thread.sleep()这段时间,使得在宏观上下载的平均速度在咱们的限制条件内。spa

另外,若是是多线程多任务,实际上平均分配速度便可。即任务间平均分配总速度,任务内线程平均分配任务得到的速度;\Delta{t} \leq 0时是符合限制的。

0x04 代码实现

// 链接、获取输入流
...

int readSize;
long start;

do {
    start = System.nanoTime();
    readSize = inputStream.read(buffer, 0, buffer.length);
    // targetBps是这个任务分配到的速度,targetBps < 0表示不限速
    if (readSize > 0 && targetBps > 0) {
        // downloadThreadCount是下载线程数
        long sleepDurarion = (long) (readSize * 1000.0 / (targetBps / Math.max(downloadThreadCount, 1)) - (System.nanoTime() - start) / 1000_000.0);
        
        if (sleepDuration > 0) {
            try {
                Thread.sleep(sleepDuration);
            } catch (InterruptedException e) {
                // do nothing
            }
        }
    }
} while (readSize > 0);
复制代码

0x05 其余

上面主要是如下载为例,事实上任何I/O流的操做均可以经过这种方式在宏观上“限速”。

由于这种方法限制的是平均速度,因此若是装有网速监测软件,可能会看到波动的网络速度变化。

实现了限速功能的下载器项目传送门,支持多任务、多线程、多进程、断点续传、速度限制等等...

其余相关文章: