天天看點

使用Go實作并發切片上傳和下載下傳檔案

1.實作目的

主要目的是用來熟悉go語言,通過該項目可以熟悉到的go知識點:

(1)go語言文法;

(2)go的goroutine使用方式;

(3)go通道chan的使用

(4)等待所有goroutine結束的同步信号使用;

(5)go的結構體定義和方法使用;

2.實作的功能點

(1)支援批量上傳下載下傳檔案,并進行md5值校驗;

(2)支援檢視檔案清單;

(3)支援斷點續傳和下載下傳檔案;

(4)支援大檔案切片上傳和大檔案切片下載下傳;

(5)支援分片失敗重傳和失敗重下載下傳;

(6)支援控制每個檔案上傳和下載下傳的最大goroutine數量;

(7)服務端根據配置檔案讀取儲存檔案目錄、綁定IP和端口号資訊等。

3.實作設計

3.1.總體上傳下載下傳檔案結構圖

使用Go實作并發切片上傳和下載下傳檔案

3.2.上傳檔案流程圖

使用Go實作并發切片上傳和下載下傳檔案

流程概述:

(1)上傳檔案時首先判斷檔案是否大于1M,如果小于1M則沒必要進行分片,直接整個檔案通過HTTP請求發送到Server端進行儲存;

(2)如果檔案大于1M,則首先判斷該檔案是不是上傳了一部分的檔案,這個通過查找目前目錄下有沒有對應中繼資料檔案來判斷,如果沒有則是全新要上傳的檔案,否則是需要斷點續傳的檔案;

(3)如果是全新要上傳的檔案,則會先對該檔案進行計算,比如該檔案能被切分成多個個分片,生成上傳uuid,作為唯一上傳辨別,并把這些資料儲存到本地檔案(建立一個隐藏檔案,字尾加上.uploading);

(4)同時還需向服務端先發送一個請求,讓其先在儲存目錄建立一個uuid的目錄,用來待會儲存分片檔案使用,同時服務端也會生成一個中繼資料檔案;

(5)如果是需要斷點續傳的檔案,則需要請求服務端擷取該檔案還缺少哪些檔案分片沒有上傳(服務端隻需從儲存目錄中已儲存的序号結合檔案中繼資料即可識别到哪些序号還沒上傳),并将這些切片序号發送回給用戶端,假設有1,5,6,7,沒發送,則服務端隻需要發送1,5,-1即可辨別,1分片和5到最後一個分片都沒上傳成功;

(6)在上傳前會先啟動一個goroutine,專門用來重傳失敗分片的,它會不斷的從RetryChannel通道中讀分片資料,如果沒有則阻塞,如果有則重傳該分片,如果再失敗則再發送到通道中,可以看做當隊列使用;

(7)開始讀檔案,如果是續傳的,還可以根據續傳情況進行偏移量偏移,跳過指定的切片段,讀時每次隻讀1M,然後判斷該切片是否已被上傳過,如果上傳過則無須再上傳,可直接跳過,否則就建立一個goroutine進行異步上傳;

(8)當所有goroutine都運作完成表示切片都上傳完成了則發送請求告訴服務端切片已上傳完成,服務端也會把檔案狀态置為active。

3.3.下載下傳檔案流程圖

使用Go實作并發切片上傳和下載下傳檔案

流程概述:實作思路跟上傳相似,這裡不再概述。

3.4.核心的設計點

(1)檔案分片

将大檔案進行分片,定義分片規格為1M,比如有5M大小的檔案,那麼就會分成檔案名為0,1,2,3,4,5這6個檔案,上傳到服務端後,服務端會建立一個uuid的檔案夾用來儲存這5個分片檔案,并且會記錄一個中繼資料檔案,裡面儲存着該中繼資料檔案對應哪個目錄,檔案切片大小、檔案大小和檔案md5等原資訊。對于小于1M的普通檔案則不進行切片處理,就是正常的一個檔案,是以我程式裡規定了檔案名為.slice結尾的檔案則為切片檔案,否則為普通檔案。

(2)斷點續傳和斷點續載

首先是斷點續傳,在開始傳檔案時,我會建立一個隐藏檔案作為上傳中繼資料檔案,比如檔案名是file.txt,則我建立的中繼資料檔案名為.file.txt.uploading,裡面記錄着檔案中繼資料資訊,比如上傳UUID、檔案大小和檔案md5等資訊,如果使用者上傳完成,則起最後會被删除掉,表示整個檔案都上傳完成了,假設上傳過程中出現了中斷,則下次重新上傳該檔案時我檢測到該隐藏檔案就知道它是還未上傳完整的檔案,會先去服務端請求看缺少哪些分片資料,比如該檔案一共有1,2,3,4,5片,服務端響應回來說隻收到了1,3,5片,那麼待會我就隻需要把0,2,4片重傳一次即可。

斷點續載同理,也是需要在用戶端維護中繼資料,且通過查找已下載下傳的分片來找出未下載下傳的分片序号,然後隻需要重新下載下傳沒有的分片即可。

(3)失敗重傳

上傳器和下載下傳器的結構體定義我都會定義一個RetryChannel,這是一個分片結構體類型的通道,當分片上傳或下載下傳失敗時,會将分片發送到這個通道,在上傳或下載下傳開始時我都會啟動一個goroutine,專門負責從這個通道讀資料,讀到了就對這個分片進行重新上傳或下載下傳。

(4)并發上傳或下載下傳

并發使用了go的goroutine,并發機關以檔案切片為機關,同時通過通道(申請有數量限制的通道)的方式控制運作的goroutine的數量,同時采用go裡的同步信号量來控制是否所有goroutine都運作完成了。

3.5.項目代碼結構

用戶端目錄結構:

使用Go實作并發切片上傳和下載下傳檔案

上傳器結構體定義:

// Uploader 上傳器
type Uploader struct {
    common.FileMetadata             // 檔案中繼資料
    common.SliceSeq                 // 需要重傳的序号
    waitGoroutine   sync.WaitGroup  // 同步goroutine
    NewLoader       bool            // 是否是新建立的上傳器
    FilePath        string          // 上傳檔案路徑
    SliceBytes      int             // 切片大小
    RetryChannel    chan *FilePart  // 重傳channel通道
    MaxGtChannel    chan struct{}   // 限制上傳的goroutine的數量通道
    StartTime       int64           // 上傳開始時間
}      

下載下傳器結構體定義:

// Downloader 下載下傳器
type Downloader struct {
    common.FileMetadata                 // 檔案中繼資料
    common.SliceSeq                     // 需要重傳的序号
    waitGoroutine   sync.WaitGroup      // 同步goroutine
    DownloadDir     string              // 下載下傳檔案儲存目錄
    RetryChannel    chan int            // 重傳channel通道
    MaxGtChannel    chan struct{}       // 限制上傳的goroutine的數量通道
    StartTime       int64               // 下載下傳開始時間
}      

服務端目錄結構:

使用Go實作并發切片上傳和下載下傳檔案

檔案的傳輸采用的是HTTP協定,服務端的工作主要是起一個HTTP Server,然後監聽對應URL,綁定對應的響應方法,同時把接收到的檔案資料儲存到指定目錄下。

4.使用示例

使用方法可用–help檢視:

使用Go實作并發切片上傳和下載下傳檔案

上傳、列出和下載下傳使用方法示例(圖中的下載下傳路徑和檔案路徑可以自行修改,且確定服務端FtpServer目錄下的etc目錄下的config.json檔案裡指定的StoreDir指定目錄存在):

服務端先啟動:進入項目目錄,執行:go run main.go

上傳檔案示例:

用戶端運作:

使用Go實作并發切片上傳和下載下傳檔案

服務端響應:

使用Go實作并發切片上傳和下載下傳檔案

 列出檔案清單示例:

使用Go實作并發切片上傳和下載下傳檔案

下載下傳檔案示例:

使用Go實作并發切片上傳和下載下傳檔案

5.可改進點

當然,這個小項目可改進點還有很多,我這裡列出幾個我想到的:

(1)需重傳的序号計算算法還可以實作的更好點,比如還沒傳的,可以用1-3,5-9這樣來表示;

(2)目前的md5計算是計算整個檔案的,但其實可以給每個分片都賦予一個md5,這樣就不用再最後累計一邊整個檔案的md5,降低讀IO次數;

(3)下載下傳檔案時,其實可以開辟一個指定size的空洞檔案,然後接收到檔案分片可以按照偏移量寫到給新檔案中,避免了最後一步的合并過程的IO。

6.項目下載下傳位址

Client端項目github位址:https://github.com/luohaixiannz/FtpClient

Server端項目github位址:https://github.com/luohaixiannz/FtpServer