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.總體上傳下載文件結構圖
3.2.上傳文件流程圖
流程概述:
(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.下載文件流程圖
流程概述:實現思路跟上傳相似,這里不再概述。
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.項目代碼結構
客戶端目錄結構:
上傳器結構體定義:
// 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 // 下載開始時間 }
服務端目錄結構:
文件的傳輸采用的是HTTP協議,服務端的工作主要是起一個HTTP Server,然后監聽對應URL,綁定對應的響應方法,同時把接收到的文件數據保存到指定目錄下。
4.使用示例
使用方法可用–help查看:
上傳、列出和下載使用方法示例(圖中的下載路徑和文件路徑可以自行修改,且確保服務端FtpServer目錄下的etc目錄下的config.json文件里指定的StoreDir指定目錄存在):
服務端先啟動:進入項目目錄,執行:go run main.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