Node + js實現大檔案分片上傳基本原理及實踐(一)

龍恩0707發表於2019-07-25

閱讀目錄

一:什麼是分片上傳?

分片上傳是把一個大的檔案分成若干塊,一塊一塊的傳輸。這樣做的好處可以減少重新上傳的開銷。比如:
如果我們上傳的檔案是一個很大的檔案,那麼上傳的時間應該會比較久,再加上網路不穩定各種因素的影響,很容易導致傳輸中斷,使用者除了重新上傳檔案外沒有其他的辦法,但是我們可以使用分片上傳來解決這個問題。通過分片上傳技術,如果網路傳輸中斷,我們重新選擇檔案只需要傳剩餘的分片。而不需要重傳整個檔案,大大減少了重傳的開銷。

如下圖是一個大檔案分成很多小片段:

但是我們要如何選擇一個合適的分片呢?

因此我們要考慮如下幾個事情:

1. 分片越小,那麼請求肯定越多,開銷就越大。因此不能設定太小。
2. 分片越大,靈活度就少了。
3. 伺服器端都會有個固定大小的接收Buffer。分片的大小最好是這個值的整數倍。

因此,綜合考慮到推薦分片的大小是2M-5M. 具體分片的大小需要根據檔案的大小來確定,如果檔案太大,建議分片的大小是5M,如果檔案相對較小,那麼建議分片的大小是2M。

實現檔案分片上傳的步驟如下:

1. 先對檔案進行md5加密。使用md5加密的優點是:可以對檔案進行唯一標識,同樣可以為後臺進行檔案完整性校驗進行比對。
2. 拿到md5值以後,伺服器端查詢下該檔案是否已經上傳過,如果已經上傳過的話,就不用重新再上傳。
3. 對大檔案進行分片。比如一個100M的檔案,我們一個分片是5M的話,那麼這個檔案可以分20次上傳。
4. 向後臺請求介面,介面裡的資料就是我們已經上傳過的檔案塊。(注意:為什麼要發這個請求?就是為了能續傳,比如我們使用百度網盤對吧,網盤裡面有續傳功能,當一個檔案傳到一半的時候,突然想下班不想上傳了,那麼伺服器就應該記住我之前上傳過的檔案塊,當我開啟電腦重新上傳的時候,那麼它應該跳過我之前已經上傳的檔案塊。再上傳後續的塊)。
5. 開始對未上傳過的檔案塊進行上傳。(這個是第二個請求,會把所有的分片合併,然後上傳請求)。
6. 上傳成功後,伺服器會進行檔案合併。最後完成。

二:理解Blob物件中的slice方法對檔案進行分割及其他知識點

在編寫程式碼之前,我們需要了解一些基本的知識點,然後在瞭解基礎知識點之上,我們再去實踐我們的大檔案分片上傳這麼的一個demo。首先我們來看下我們的Blob物件,如下程式碼所示:

var b = new Blob();
console.log(b);

如下所示:

如上圖我們可以看到,我們的Blob物件自身有 size 和 type兩個屬性,及它的原型上有 slice() 方法。我們可以通過該方法來切割我們的二進位制的Blob物件。

2. 學習 blob.slice 方法

blob.slice(startByte, endByte) 是Blob物件中的一個方法,File物件它是繼承Blob物件的,因此File物件也有該slice方法的。

引數:
startByte: 表示檔案起始讀取的Byte位元組數。
endByte: 表示結束讀取的位元組數。

返回值:var b = new Blob(startByte, endByte); 該方法的返回值仍然是一個Blob型別。

我們可以使用 blob.slice() 方法對二進位制的Blob物件進行切割,但是該方法也是有瀏覽器相容性的,因此我們可以封裝一個方法:如下所示:

function blobSlice(blob, startByte, endByte) {
  if (blob.slice) {
    return blob.slice(startByte, endByte);
  }
  // 相容firefox
  if (blob.mozSlice) {
    return blob.mozSlice(startByte, endByte);
  }
  // 相容webkit
  if (blob.webkitSlice) {
    return blob.webkitSlice(startByte, endByte);
  }
  return null;
}

3. 理解 async/await 的使用

在我很早之前,我已經對async/await 的使用和優勢做了講解,有興趣瞭解該知識點的,可以看我之前這篇文章.
因此我們現在來看下如下demo列子:

const hashFile2 = function(file) {
  return new Promise(function(resolve, reject) {
    console.log(111); 
  })
};
window.onload = async() => {
  const hash = await hashFile2();
}

如上程式碼,如果我們直接重新整理頁面,就可以在控制檯中輸出 111 這個的字元。為什麼我現在要講解這個呢,因為待會我們的demo會使用到該知識點,所以提前講解下理解下該知識。

4. 理解 FileReader.readAsArrayBuffer()方法

該方法會按位元組讀取檔案內容,並轉換為 ArrayBuffer 物件。readAsArrayBuffer方法讀取檔案後,會在記憶體中建立一個 ArrayBuffer物件(二進位制緩衝區),會將二進位制資料存放在其中。通過此方式,我們就可以直接在網路中傳輸二進位制內容。
其語法結構:

FileReader.readAsArrayBuffer(Blob|File);

Blob|File 必須引數,引數是Blob或File物件。

如下程式碼演示:

<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset=" utf-8">
<title>readAsArrayBuffer測試</title>
</head>
<body>
<input type="file" id="file"/>
<script>
  window.onload = function () {
    var input = document.getElementById("file");
    input.onchange = function () {
      var file = this.files[0];
      if (file) {
        //讀取本地檔案,以gbk編碼方式輸出
        var reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.onload = function () {
          console.log(this.result);
          console.log(new Blob([this.result]))
        }
      }
    }
  }
</script>
</body>
</html>

如果我們現在上傳的是文字檔案的話,就會列印如下資訊,如下所示:

三. 使用 spark-md5 生成 md5檔案

 瞭解spark-md5,請看npm官網

下面我們來理解下 上傳檔案如何來得到 md5 的值。上傳檔案簡單的如下demo, 程式碼所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>檔案上傳</title>
    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script>
</head>
<body>
  <h1>大檔案上傳測試</h1>
  <div>
    <h3>自定義上傳檔案</h3>
    <input id="file" type="file" name="avatar"/>
    <div>
      <input id="submitBtn" type="button" value="提交">
    </div>
  </div>
  <script type="text/javascript">
    $(function() {
      const submitBtn = $('#submitBtn');
      submitBtn.on('click', async () => {
        var fileDom = $('#file')[0]; 
        // 獲取到的files為一個File物件陣列,如果允許多選的時候,檔案為多個
        const files = fileDom.files;
        const file = files[0]; // 獲取第一個檔案,因為檔案是一個陣列
        if (!file) {
          alert('沒有獲取檔案');
          return;
        }
        var fileSize = file.size; // 檔案大小
        var chunkSize = 2 * 1024 * 1024; // 切片的大小
        var chunks = Math.ceil(fileSize / chunkSize); // 獲取切片的個數
        var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
        var spark = new SparkMD5.ArrayBuffer();
        var reader = new FileReader();
        var currentChunk = 0;

        reader.onload = function(e) {
          const result = e.target.result;
          spark.append(result);
          currentChunk++;
          if (currentChunk < chunks) {
            loadNext();
            console.log(`第${currentChunk}分片解析完成,開始解析${currentChunk + 1}分片`);
          } else {
            const md5 = spark.end();
            console.log('解析完成');
            console.log(md5);
          }
        };
        function loadNext() {
          var start = currentChunk * chunkSize;
          var end = start + chunkSize > file.size ? file.size : (start + chunkSize);
          reader.readAsArrayBuffer(blobSlice.call(file, start, end));
        };
        loadNext();
      });
    });
  </script>
</body>
</html>

如上程式碼,首先我在 input type = 'file' 這樣的會選擇一個檔案,然後點選進行上傳,先獲取檔案的大小,然後定義一個分片的大小預設為2兆,使用 var chunks = Math.ceil(fileSize / chunkSize); // 獲取切片的個數 方法獲取切片的個數。
如果 fileSize(檔案大小) 小於 chunkSize(2兆)的話,使用向上取整,因此為1個分片。同理如果除以的結果 是 1.2 這樣的,那麼就是2個分片了,依次類推.... 然後使用 SparkMD5.ArrayBuffer 方法了,詳情可以看官網(http://npm.taobao.org/package/spark-md5). 先初始化當前的 currentChunk 分片為0,然後 reader.onload = function(e) {} 方法,如果當前的分片數量小於 chunks 的數量的話,會繼續呼叫 loadNext()方法,該方法會讀取下一個分片,開始的位置計算方式是:var start = currentChunk * chunkSize; 
currentChunk 的含義是第二個分片(從0開始的,因此這裡它的值為1),結束的位置 計算方式為:
var end = start + chunkSize > file.size ? file.size : (start + chunkSize);
也就說,如果一個檔案的大小是2.1兆的話,一個分片是2兆的話,那麼它就最大分片的數量就是2片了,但是 currentChunk 預設從0開始的,因此第二個分片,該值就變成1了,因此 start的位置就是 var start = 1 * 2(兆)了,然後 var end = start + chunkSize > file.size ? file.size : (start + chunkSize);
 如果 start + chunkSize 大於 檔案的大小(file.size) 的話,那麼就直接去 file.size(檔案的大小),否則的話,結束位置就是 start + chunkSize 了。最後我們使用
blobSlice 進行切割,就切割到第二個分片的大小了,blobSlice.call(file, start, end),這樣的方法。然後把切割的檔案讀取到記憶體中去,使用 reader.readAsArrayBuffer() 將buffer讀取到記憶體中去了。繼續會呼叫 onload 該方法,直到 進入else 語句內,那麼 const md5 = spark.end(); 就生成了一個md5檔案了。如上程式碼,如果我現在上傳一個大檔案的話,在控制檯中就會列印如下資訊了:如下圖所示:

四. 使用koa+js實現大檔案分片上傳實踐

注:根據網上demo來講解的

先看下整個專案的架構如下:

|---- 專案根目錄
| |--- app.js           # node 入口檔案
| |--- package.json      
| |--- node_modules     # 所有依賴的包
| |--- static           # 存放靜態資原始檔目錄
| | |--- js
| | | |--- index.js     # 檔案上傳的js    
| | |--- index.html
| |--- uploads          # 儲存上傳檔案後的目錄
| |--- utils            # 儲存公用的js函式
| | |--- dir.js 

 static/index.html 檔案程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>檔案上傳</title>
    <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script>
</head>
<body>
  <h1>大檔案上傳測試</h1>
  <div>
    <h3>自定義上傳檔案</h3>
    <input id="file" type="file" name="avatar"/>
    <div>
      <input id="submitBtn" type="button" value="提交">
    </div>
  </div>
  <script type="text/javascript" src="./js/index.js"></script>
</body>
</html>

執行頁面,效果如下所示:

static/js/index.js 程式碼如下:

$(document).ready(() => {
  const chunkSize = 2 * 1024 * 1024; // 每個chunk的大小,設定為2兆
  // 使用Blob.slice方法來對檔案進行分割。
  // 同時該方法在不同的瀏覽器使用方式不同。
  const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
  const hashFile = (file) => {
    return new Promise((resolve, reject) => { 
      const chunks = Math.ceil(file.size / chunkSize);
      let currentChunk = 0;
      const spark = new SparkMD5.ArrayBuffer();
      const fileReader = new FileReader();
      function loadNext() {
        const start = currentChunk * chunkSize;
        const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
      }
      fileReader.onload = e => {
        spark.append(e.target.result); // Append array buffer
        currentChunk += 1;
        if (currentChunk < chunks) {
          loadNext();
        } else {
          console.log('finished loading');
          const result = spark.end();
          // 如果單純的使用result 作為hash值的時候, 如果檔案內容相同,而名稱不同的時候
          // 想保留兩個檔案無法保留。所以把檔名稱加上。
          const sparkMd5 = new SparkMD5();
          sparkMd5.append(result);
          sparkMd5.append(file.name);
          const hexHash = sparkMd5.end();
          resolve(hexHash);
        }
      };
      fileReader.onerror = () => {
        console.warn('檔案讀取失敗!');
      };
      loadNext();
    }).catch(err => {
        console.log(err);
    });
  }
  const submitBtn = $('#submitBtn');
  submitBtn.on('click', async () => {
    const fileDom = $('#file')[0];
    // 獲取到的files為一個File物件陣列,如果允許多選的時候,檔案為多個
    const files = fileDom.files;
    const file = files[0];
    if (!file) {
      alert('沒有獲取檔案');
      return;
    }
    const blockCount = Math.ceil(file.size / chunkSize); // 分片總數
    const axiosPromiseArray = []; // axiosPromise陣列
    const hash = await hashFile(file); //檔案 hash 
    // 獲取檔案hash之後,如果需要做斷點續傳,可以根據hash值去後臺進行校驗。
    // 看看是否已經上傳過該檔案,並且是否已經傳送完成以及已經上傳的切片。
    console.log(hash);
    
    for (let i = 0; i < blockCount; i++) {
      const start = i * chunkSize;
      const end = Math.min(file.size, start + chunkSize);
      // 構建表單
      const form = new FormData();
      form.append('file', blobSlice.call(file, start, end));
      form.append('name', file.name);
      form.append('total', blockCount);
      form.append('index', i);
      form.append('size', file.size);
      form.append('hash', hash);
      // ajax提交 分片,此時 content-type 為 multipart/form-data
      const axiosOptions = {
        onUploadProgress: e => {
          // 處理上傳的進度
          console.log(blockCount, i, e, file);
        },
      };
      // 加入到 Promise 陣列中
      axiosPromiseArray.push(axios.post('/file/upload', form, axiosOptions));
    }
    // 所有分片上傳後,請求合併分片檔案
    await axios.all(axiosPromiseArray).then(() => {
      // 合併chunks
      const data = {
        size: file.size,
        name: file.name,
        total: blockCount,
        hash
      };
      axios.post('/file/merge_chunks', data).then(res => {
        console.log('上傳成功');
        console.log(res.data, file);
        alert('上傳成功');
      }).catch(err => {
        console.log(err);
      });
    });
  });
})

如上程式碼,和我們上面生成md5程式碼很類似,從新增到formData下面的程式碼不一樣了,我們可以來簡單的分析下,看下程式碼的具體含義:

const blockCount = Math.ceil(file.size / chunkSize); // 分片總數 

上面的程式碼的含義是獲取分片的總數,我們之前講解過,然後使用 for迴圈遍歷分片,依次把對應的分片新增到 formData資料裡面去,如下所示程式碼:

const axiosPromiseArray = [];
const blockCount = Math.ceil(file.size / chunkSize); // 分片總數

for (let i = 0; i < blockCount; i++) {
  const start = i * chunkSize;
  const end = Math.min(file.size, start + chunkSize);
  // 構建表單
  const form = new FormData();
  form.append('file', blobSlice.call(file, start, end));
  form.append('name', file.name);
  form.append('total', blockCount);
  form.append('index', i);
  form.append('size', file.size);
  form.append('hash', hash);
  // ajax提交 分片,此時 content-type 為 multipart/form-data
  const axiosOptions = {
    onUploadProgress: e => {
      // 處理上傳的進度
      console.log(blockCount, i, e, file);
    },
  };
  // 加入到 Promise 陣列中
  axiosPromiseArray.push(axios.post('/file/upload', form, axiosOptions));
}
// 所有分片上傳後,請求合併分片檔案
await axios.all(axiosPromiseArray).then(() => {
  // 合併chunks
  const data = {
    size: file.size,
    name: file.name,
    total: blockCount,
    hash
  };
  axios.post('/file/merge_chunks', data).then(res => {
    console.log('上傳成功');
    console.log(res.data, file);
    alert('上傳成功');
  }).catch(err => {
    console.log(err);
  });
});

如上程式碼,迴圈分片的總數,然後依次實列化formData資料,依次放入到formData實列中,然後分別使用 '/file/upload' 請求資料,最後把所有請求成功的資料放入到 axiosPromiseArray 陣列中,當所有的分片上傳完成後,我們會使用 await axios.all(axiosPromiseArray).then(() => {}) 方法,最後我們會使用 '/file/merge_chunks' 方法來合併檔案。

下面我們來看看 app.js 伺服器端的程式碼,如下所示:

const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const multer = require('koa-multer');
const serve = require('koa-static');
const path = require('path');
const fs = require('fs-extra');
const koaBody = require('koa-body');
const { mkdirsSync } = require('./utils/dir');
const uploadPath = path.join(__dirname, 'uploads');
const uploadTempPath = path.join(uploadPath, 'temp');
const upload = multer({ dest: uploadTempPath });
const router = new Router();
app.use(koaBody());
/**
 * single(fieldname)
 * Accept a single file with the name fieldname. The single file will be stored in req.file.
 */
router.post('/file/upload', upload.single('file'), async (ctx, next) => {
  console.log('file upload...')
  // 根據檔案hash建立資料夾,把預設上傳的檔案移動當前hash資料夾下。方便後續檔案合併。
  const {
    name,
    total,
    index,
    size,
    hash
  } = ctx.req.body;

  const chunksPath = path.join(uploadPath, hash, '/');
  if(!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);
  fs.renameSync(ctx.req.file.path, chunksPath + hash + '-' + index);
  ctx.status = 200;
  ctx.res.end('Success');
})

router.post('/file/merge_chunks', async (ctx, next) => {
  const {    
    size, 
    name, 
    total, 
    hash
  } = ctx.request.body;
  // 根據hash值,獲取分片檔案。
  // 建立儲存檔案
  // 合併
  const chunksPath = path.join(uploadPath, hash, '/');
  const filePath = path.join(uploadPath, name);
  // 讀取所有的chunks 檔名存放在陣列中
  const chunks = fs.readdirSync(chunksPath);
  // 建立儲存檔案
  fs.writeFileSync(filePath, ''); 
  if(chunks.length !== total || chunks.length === 0) {
    ctx.status = 200;
    ctx.res.end('切片檔案數量不符合');
    return;
  }
  for (let i = 0; i < total; i++) {
    // 追加寫入到檔案中
    fs.appendFileSync(filePath, fs.readFileSync(chunksPath + hash + '-' +i));
    // 刪除本次使用的chunk    
    fs.unlinkSync(chunksPath + hash + '-' +i);
  }
  fs.rmdirSync(chunksPath);
  // 檔案合併成功,可以把檔案資訊進行入庫。
  ctx.status = 200;
  ctx.res.end('合併成功');
})
app.use(router.routes());
app.use(router.allowedMethods());
app.use(serve(__dirname + '/static'));
app.listen(9000, () => {
  console.log('服務9000埠已經啟動了');
});

如上程式碼:分別引入 koa, koa-router, koa-multer, koa-static, path, fs-extra, koa-body 依賴包。

koa-multer 的作用是為了處理上傳檔案的外掛。

utils/dir.js 程式碼如下(該程式碼的作用是判斷是否有這個目錄,有這個目錄的話,直接返回true,否則的話,建立該目錄):

const path = require('path');
const fs = require('fs-extra');
const mkdirsSync = (dirname) => {
  if(fs.existsSync(dirname)) {
    return true;
  } else {
    if (mkdirsSync(path.dirname(dirname))) {
      fs.mkdirSync(dirname);
      return true;
    }
  }
}
module.exports = {
  mkdirsSync
};

1. /file/upload 請求程式碼如下:

router.post('/file/upload', upload.single('file'), async (ctx, next) => {
  console.log('file upload...')
  // 根據檔案hash建立資料夾,把預設上傳的檔案移動當前hash資料夾下。方便後續檔案合併。
  const {
    name,
    total,
    index,
    size,
    hash
  } = ctx.req.body;

  const chunksPath = path.join(uploadPath, hash, '/');
  if(!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);
  fs.renameSync(ctx.req.file.path, chunksPath + hash + '-' + index);
  ctx.status = 200;
  ctx.res.end('Success');
})

如上程式碼,會處理 '/file/upload' 這個請求,upload.single('file'), 的含義是:接受一個檔名稱欄位名。
單一檔案將儲存在req.file中,這是 koa-multer 外掛的用法,具體可以看 koa-multer官網(https://www.npmjs.com/package/koa-multer)。 獲取到檔案後,請求成功回撥,然後會在專案中的根目錄下建立一個 uploads 這個目錄,如下程式碼可以看到:

const uploadPath = path.join(__dirname, 'uploads');
const chunksPath = path.join(uploadPath, hash, '/');
if(!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);

最後上傳完成後,我們可以在我們的專案中可以看到我們所有的檔案都在我們本地了,如下所示:

我們也可以在我們的網路中看到如下很多 '/file/upload' 的請求,如下可以看到很多請求,說明我們的請求是分片上傳的,如下所示:

2. '/file/merge_chunks'

最後所有的分片請求上傳成功後,我們會呼叫 '/file/merge_chunks' 這個請求來合併所有的檔案,根據我們的hash值,來獲取檔案分片。

如下程式碼:

// 根據hash值,獲取分片檔案。
// 建立儲存檔案
// 合併
const chunksPath = path.join(uploadPath, hash, '/');
const filePath = path.join(uploadPath, name);
// 讀取所有的chunks 檔名存放在陣列中
const chunks = fs.readdirSync(chunksPath);
// 建立儲存檔案
fs.writeFileSync(filePath, ''); 
if(chunks.length !== total || chunks.length === 0) {
  ctx.status = 200;
  ctx.res.end('切片檔案數量不符合');
  return;
}
for (let i = 0; i < total; i++) {
  // 追加寫入到檔案中
  fs.appendFileSync(filePath, fs.readFileSync(chunksPath + hash + '-' +i));
  // 刪除本次使用的chunk    
  fs.unlinkSync(chunksPath + hash + '-' +i);
}
fs.rmdirSync(chunksPath);
// 檔案合併成功,可以把檔案資訊進行入庫。
ctx.status = 200;
ctx.res.end('合併成功');

如上程式碼,會迴圈分片的總數,然後把所有的分片寫入到我們的filePath目錄中,如這句程式碼:

fs.appendFileSync(filePath, fs.readFileSync(chunksPath + hash + '-' +i));

其中 filePath 的獲取 是這句程式碼:const filePath = path.join(uploadPath, name); 也就是說在我們專案的根目錄下的uploads資料夾下,這麼做的原因是為了防止網路突然斷開或伺服器突然異常的情況下,檔案上傳到一半的時候,我們本地會儲存一部分已經上傳的檔案,如果我們繼續上傳的時候,我們會跳過哪些已經上傳後的檔案,繼續上傳未上傳的檔案。這是為了斷點續傳做好準備的,下次我會分析下如何實現斷點續傳的原理了。如果我們把上面這兩句程式碼註釋掉,如下所示:

// 刪除本次使用的chunk    
fs.unlinkSync(chunksPath + hash + '-' +i);
fs.rmdirSync(chunksPath);

我們就可以看到我們專案本地會有 uploads 會有很多分片檔案了,如下所示:

當我們這個檔案上傳完成後,如上程式碼,我們會把它刪除掉,因此如果我們不把該程式碼註釋掉的話,是看不到效果的。

如果我們繼續上傳另外一個檔案後,會在我們專案的根目錄下生成第二個檔案,如下所示:

如上就是我們整個分片上傳的基本原理,我們還沒有做斷點續傳了,下次有空我們來分析下斷點續傳的基本原理,斷點續傳的原理,無非就是說在我們上傳的過程中,如果網路中斷或伺服器中斷的情況下,我們需要把檔案儲存到本地,然後當網路恢復的時候,我們繼續上傳,那麼繼續上傳的時候,我們會比較上傳的hash值是否在我本地的hash值是否相同,如果相同的話,直接跳過該分片上傳,繼續下一個分片上傳,依次類推來進行判斷,雖然使用這種方式來進行比對的情況下,會需要一點時間,但是相對於我們重新上傳消耗的時間來講,這些時間不算什麼的。下次有空我們來分析下斷點續傳的基本原理哦。分片上傳原理基本分析到這裡哦。
github原始碼檢視

相關文章