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流的操做均可以經過這種方式在宏觀上「限速」。

由於這種方法限制的是平均速度,因此若是裝有網速監測軟件,可能會看到波動的網絡速度變化。

實現了限速功能的下載器項目傳送門,支持多任務、多線程、多進程、斷點續傳、速度限制等等...

其餘相關文章:

相關文章
相關標籤/搜索