<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          徒手?jǐn)]了個多線程斷點續(xù)傳下載器,我從中學(xué)習(xí)到了這些知識

          共 5741字,需瀏覽 12分鐘

           ·

          2020-08-04 22:40

          2020年6月20日?上海張江

          感謝看客老爺點進(jìn)來了,周末閑來無事,想起同事強(qiáng)哥的那句話:“你有沒有玩過斷點續(xù)傳?” 當(dāng)時轉(zhuǎn)念一想,斷點續(xù)傳下載用的確實不少,具體細(xì)節(jié)嘛,真的沒有去思考過啊。這不,思考過后有了這篇文章。感謝強(qiáng)哥,讓我有了一篇可以水的文章,下面會用純 Java 無依賴實現(xiàn)一個簡單的多線程斷點續(xù)傳下載器。

          這篇文章到底有什么內(nèi)容呢?先簡單列舉一下,順便思考幾個問題。

          1. 斷點續(xù)傳的原理。

          2. 重啟續(xù)傳文件時,怎么保證文件的一致性?

          3. 同一個文件多線程下載如何實現(xiàn)?

          4. 網(wǎng)速帶寬固定,為什么多線程下載可以提速?

          多線程斷點續(xù)傳會用到哪些知識呢?上面已經(jīng)拋出了幾個問題,不妨思考一下。下面會針對上面的四個問題一一進(jìn)行解釋,現(xiàn)在大多數(shù)的服務(wù)都可以在線提供,下載使用的場景越來越少,不過這不妨礙我們對原理的探求。

          斷點續(xù)傳的原理

          想要了解斷點續(xù)傳是如何實現(xiàn)的,那么肯定是要了解一下 HTTP 協(xié)議了。HTTP 協(xié)議是互聯(lián)網(wǎng)上應(yīng)用最廣泛網(wǎng)絡(luò)傳輸協(xié)議之一,它基于 TCP/IP 通信協(xié)議來傳遞數(shù)據(jù)。所以斷點續(xù)傳的奧秘也就隱藏在這 HTTP 協(xié)議中了。

          我們都知道 HTTP 請求會有一個 Request headerResponse header ,就在這請求頭和響應(yīng)頭里,有一個和 Range 相關(guān)的參數(shù)。下面通過百度網(wǎng)盤的 pc 客戶端下載鏈接進(jìn)行測試。

          使用 cURL 查看 response header. 如果你想知道更多關(guān)于 cURL 的用法,可以看我之前的一篇文章 :進(jìn)來領(lǐng)略下cURL的獨門絕技。

          $?curl?-I?http://wppkg.baidupcs.com/issue/netdisk/yunguanjia/BaiduYunGuanjia_7.0.1.1.exe
          HTTP/1.1?200?OK
          Server:?JSP3/2.0.14
          Date:?Sat,?25?Jul?2020?13:41:55?GMT
          Content-Type:?application/x-msdownload
          Content-Length:?65804256
          Connection:?keep-alive
          ETag:?dcd0bfef7d90dbb3de50a26b875143fc
          Last-Modified:?Tue,?07?Jul?2020?13:19:46?GMT
          Expires:?Sat,?25?Jul?2020?14:05:19?GMT
          Age:?257796
          Accept-Ranges:?bytes
          Cache-Control:?max-age=259200
          Content-Disposition:?attachment;filename="BaiduYunGuanjia_7.0.1.1.exe"
          x-bs-client-ip:?MTgwLjc2LjIyLjU0
          x-bs-file-size:?65804256
          x-bs-request-id:?MTAuMTM0LjM0LjU2Ojg2NDM6NDM4MTUzMTE4NTU3ODc5MTIxNzoyMDIwLTA3LTA3IDIyOjAxOjE1
          x-bs-meta-crc32:?3545941535
          Content-MD5:?dcd0bfef7d90dbb3de50a26b875143fc
          superfile:?2
          Ohc-Response-Time:?1?0?0?0?0?0
          Access-Control-Allow-Origin:?*
          Access-Control-Allow-Methods:?GET,?PUT,?POST,?DELETE,?OPTIONS,?HEAD
          Ohc-Cache-HIT:?bj2pbs54?[2],?bjbgpcache54?[4]

          可以看到百度 pc 客戶端的 response header 信息有很多,我們只需要重點關(guān)注幾個。


          Content-Length:?65804256??//?請求的文件的大小,單位?byte
          Accept-Ranges:?bytes??????//?是否允許指定傳輸范圍,bytes:范圍請求的單位是?bytes?(字節(jié)),none:不支持任何范圍請求單位,
          Last-Modified:?Tue,?07?Jul?2020?13:19:46?GMT??//?服務(wù)端文件最后修改時間,可以用于校驗文件是否更改過
          x-bs-meta-crc32:?3545941535?//?crc32,可以用于校驗文件是否更改過
          ETag:?dcd0bfef7d90dbb3de50a26b875143fc?//Etag?標(biāo)簽,可以用于校驗文件是否更改過

          可見并不見得所有下載都支持?jǐn)帱c續(xù)傳,只有在 ?response header 中有 Accept-Ranges: bytes 字段時才可以斷點續(xù)傳。如果有這個信息,該怎么斷點續(xù)傳呢?其實只需要在 response header 中指定 Content-Range 值就可以了。

          Content-Range 使用格式有下面幾種。

          Content-Range:?=-/?//?size?為文件總大小,如果不知道可以用?*
          Content-Range:?=-/*??
          Content-Range:?=-
          Content-Range:?=*/

          舉例

          單位 bytes,從第 10 個 bytes 開始下載:Content-Range: bytes=10-.

          單位 bytes,從第 10 個 bytes 開始下載,下載到第100個 bytes:Content-Range: bytes=10-100.

          這就是斷點續(xù)傳實現(xiàn)的原理了,你可以能已經(jīng)發(fā)現(xiàn)了,Content-Range 的 start 和 end 已經(jīng)讓分段下載有了可能。

          怎么保證文件的一致性?

          這里要說的文件完整性有兩個方面,一個是下載階段的,一個是寫入階段的。

          因為我們要寫的下載器是支持?jǐn)帱c續(xù)傳的,那么在進(jìn)行續(xù)傳時,怎么確定文件自從我們上次下載時沒有進(jìn)行過更新呢?其實可以通過 response header 中的幾個屬性值進(jìn)行判斷。

          Last-Modified:?Tue,?07?Jul?2020?13:19:46?GMT??//?服務(wù)端文件最后修改時間,可以用于校驗文件是否更改過
          ETag:?dcd0bfef7d90dbb3de50a26b875143fc?//Etag?標(biāo)簽,可以用于校驗文件是否更改過
          x-bs-meta-crc32:?3545941535?//?crc32,可以用于校驗文件是否更改過

          Last-ModifiedETag 都可以用來檢驗文件是否更新過,根據(jù) HTTP 協(xié)議的規(guī)定,當(dāng)文件更新時,是會生成新的 ETag 值的,它類似于文件的指紋信息,而 Last-Modified 只是上次修改時間,有時可能并不能夠證明文件內(nèi)容被修改過。

          上面是下載階段的文件一致性校驗,那么在寫入階段呢?不管單線程還是多線程,由于要斷點續(xù)傳,在寫入時都要在指定位置進(jìn)行字符追加。在 Java 中有沒有好的實現(xiàn)方式?

          答案是一定的,使用 RandomAccessFile 類即可,RandomAccessFile 不同于其他的流操作。它可以在使用時指定讀寫模式,使用 seek 方法隨意的移動要操作的文件指針位置。很適合斷點續(xù)傳的寫入場景。

          比如在 test.txt 的位置 0 開始寫入字符 abc,在位置 100 開始寫入字符 ddd.

          try?(RandomAccessFile?rw?=?new?RandomAccessFile("test.txt",?"rw")){?//?rw?為讀寫模式
          ????rw.seek(0);?//?移動文件內(nèi)容指針位置
          ????rw.writeChars("abc");
          ????rw.seek(100);
          ????rw.writeChars("ddd");
          }

          斷點續(xù)傳的寫入就靠它了,在續(xù)傳時只需要移動文件內(nèi)容指針到要續(xù)傳的位置即可。

          seek 方法還有很多妙用,比如使用它你可以快速定位到已知的位置,進(jìn)行快速檢索;也可以在同一個文件的不同位置進(jìn)行并發(fā)讀寫。

          多線程下載如何實現(xiàn)?

          多線程下載必然要每個線程下載文件中的一部分,然后把每個線程下載到的文件內(nèi)容組裝成一個完整的文件,在這個過程中肯定是一個 byte 都不能出錯的,不然你組裝起來的文件是肯定運行不起來的。那么怎么實現(xiàn)下載文件的一部分呢?其實在斷點續(xù)傳的部分已經(jīng)介紹過了,還是 Content-Range 參數(shù),只要計算好每個部分要下載的 bytes 范圍就可以了。

          比如:單位 bytes,第二部分從第 10 個 bytes 開始下載,下載到第100個 bytes:Content-Range: bytes=10-100.

          網(wǎng)速帶寬固定,為什么多線程下載可以提速?

          這是一個比較有意思的問題了,最大網(wǎng)速是固定的,運營商給你 100Mbs 的網(wǎng)速,不管你怎么使用,速度最大也就是 100/8=12.5MB/S. 既然瓶頸在這里,為什么多線程下載可以提速呢?其實理論上來說,單線程下載就可以達(dá)到最大網(wǎng)速。但是往往事實是網(wǎng)絡(luò)不是那么通暢,十分擁堵,很難達(dá)到理想的最大速度。也就是說只有在網(wǎng)絡(luò)不那么通暢的時候,多線程下載才能提速。否則,單線程即可。不過最大速度永遠(yuǎn)都是網(wǎng)絡(luò)帶寬。

          那為什么多線程下載可以提速呢?HTTP 協(xié)議在傳輸時候是基于 TCP 協(xié)議傳輸數(shù)據(jù)的,為了弄明白這個問題需要了解一下 TCP 協(xié)議的擁塞控制機(jī)制。擁塞控制 是TCP 的一個避免網(wǎng)絡(luò)擁塞的算法,它是基于和性增長/乘性降低這樣的控制方法來控制擁塞的。

          簡單來說就是在 TCP 開始傳輸數(shù)據(jù)時,服務(wù)端會不斷的探測可用帶寬。在一個傳輸內(nèi)容段被成功接收后,會加倍傳輸兩倍段內(nèi)容,如果再次被成功接收,就繼續(xù)加倍,直到發(fā)生了丟包,這是這也被叫做慢啟動。當(dāng)達(dá)到慢啟動閥值(ssthresh)時,慢啟動算法就會轉(zhuǎn)換為線性增長的階段,每次只增加一個分段,放緩增加速度。我覺得其實慢啟動的加倍增速過程并不慢,只是一種叫法。

          但是當(dāng)發(fā)生了丟包,也就是檢測到擁塞時,發(fā)送方就會將發(fā)送段大小降低一個乘數(shù),比如二分之一,慢啟動閾值降為超時前擁塞窗口的一半大小、擁塞窗口會降為1個MSS,并且重新回到慢啟動階段。這時多線程的優(yōu)勢就體現(xiàn)出來了,因為你的多線程會讓這個速度減速沒有那么猛烈,畢竟這時可能有另一個線程正處在慢啟動的在最終加速階段,這樣總體的下載速度就優(yōu)于單線程了。

          多線程斷點續(xù)傳代碼實現(xiàn)

          基于上面的原理介紹,心里應(yīng)該有了具體的實現(xiàn)思路了。我們只需要使用多線程,結(jié)合 Content-Range 參數(shù)分段請求文件內(nèi)容保存到臨時文件,下載完畢后使用 RandomAccessFile 把下載的文件合并成一個文件即可。而在需要斷點續(xù)傳時,只需要讀取一下當(dāng)前臨時文件大小,然后調(diào)整 Content-Range ,就可以進(jìn)行續(xù)傳下載。

          代碼不多,下面是部分核心代碼,完整代碼可以直接點開文章最后的 Github 倉庫。

          1.Content-Range 請求指定文件的區(qū)間內(nèi)容。

          URL?httpUrl?=?new?URL(url);
          HttpURLConnection?httpConnection?=?(HttpURLConnection)httpUrl.openConnection();
          httpConnection.setRequestProperty("User-Agent",?"Mozilla/5.0?(Windows?NT?10.0;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/83.0.4103.116?Safari/537.36");
          httpConnection.setRequestProperty("RANGE",?"bytes="?+?start?+?"-"?+?end?+?"/*");
          InputStream?inputStream?=?httpConnection.getInputStream();

          2.獲取文件的 ETag.

          Map>?headerFields?=?httpConnection.getHeaderFields();
          List?eTagList?=?headerFields.get("ETag");
          System.out.println(eTagList.get(0));

          3.使用 RandomAccessFile 續(xù)傳寫入文件。

          RandomAccessFile?oSavedFile?=?new?RandomAccessFile(httpFileName,?"rw");
          oSavedFile.seek(localFileContentLength);?//?文件寫入開始位置指針移動到已經(jīng)下載位置
          byte[]?buffer?=?new?byte[1024?*?10];
          int?len?=?-1;
          while?((len?=?inputStream.read(buffer))?!=?-1)?{
          ????oSavedFile.write(buffer,?0,?len);
          }

          斷點續(xù)傳測試,下載一部分之后關(guān)閉程序再次啟動。

          img

          完整代碼已經(jīng)上傳到 github.com/niumoo/down-bit.

          參考

          [1] HTTP headers

          [2] Class RandomAccessFile

          [3] RandomAccessFile簡介與使用

          [4] 維基百科 - TCP擁塞控制

          [5] 維基百科 - 和性增長/乘性降低

          最后

          文章有幫助可以點個「在看」或「分享」,都是支持,我都喜歡!

          我是Guide哥,Java后端開發(fā),半個全棧,自由的少年。一個三觀比主角還正的技術(shù)人。我們下期再見!

          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  A级免费视频 | 欧美一级片 | 丁香五月婷婷色 | 欧美一级欧美三级在线观看 | 一级黄色在线观看 |