我们在使用Blob服务的时候,免不了要上传大文件,采用一般方式(UploadFromStream)上传数据,如果由于网络或是其他因素导致传输中断,则整个传输前功尽弃。针对这种情况,我们可以使用Blob的PutBlock机制将大文件分块(即分成若干个block)传输,并且实现断点续传。在这里我们通过一个例子来看看如何实现分块传输以及断点续传。
首先我们来看一下两个方法的定义:
PutBlock,上传单个Block数据。
1: public void PutBlock (
2: string blockId,
3: Stream blockData,
4: string contentMD5
5: )
- blockId, 使用Base64编码格式,代表了唯一的block;
- blockData,block包含的数据流;
- contentMD5,用来验证block完整性的哈希值,可以为Null或是空串。
PutBlockList:根据blockId集合提交所有的block,并创建或更新blob文件。只有提交以后,通过PutBlock上传的块数据才能成为Blob文件的一部分。
1: public void PutBlockList (
2: IEnumerable<string> blockList,
3: BlobRequestOptions options
4: )
- blockList,blockId集合;
- options,给请求定义的额外的配置。
对于分块传输的实现步骤,描述如下:
- 首先确定每一个block的大小,Blob服务规定Block最大不能超过4MB;
- 然后通过FileStream的Read方法依次按量读取块数据,并依次调用PutBlock将块数据上传,每次上传时需要一个对应的blockId,上传成功以后将对应的blockId存储;
- 所有Block上传完成以后,调用PutBlockList提交将才上传的所有block,组成blob文件。(注意,对于未提交的Block数据,一个星期之内将被自动回收。)
分块上传搞定以后,到底如何实现断点续传呢?
我们来看,假如100M的数据,每个block 1M,当我们上传了50个block的时候突然网断了,在网络断开的那一刻,我们能确定,到底成功上传了多少份Block,以及成功上传的block对应的block Id。对于同一文件,大小是不变的,所以我们可以根据传输了多少份block来确定续传时该从文件的那个位置开始继续读取block数据,并继续传输剩余的block。并且最后调用PutBlockList来提交网断前后上传的所有Block Id,最终完成传输。
好了,废话不多说,上代码,朋友们一看就明白。
这里使用了windows form application来演示断点续传,如图所示,点击"End”可模拟传输中断,再点击"Start”可模拟续传。
例子中使用了BackGroundWorker组件实现后台操作上传数据,前台进度条刷新。传输成功的block Id存储在内存中(List<string>),实际情况可能大家要考虑使用其他更为安全的存储方式以保证能续传。
断点续传分块数据主要代码:
1 private void UploadBigFile(BackgroundWorker worker, DoWorkEventArgs e) 2 { 3 //bufferSize 40KB 4 byte[] bufferBytes = new byte[bufferSize]; 5 string fileName = Path.GetFileName(filePath); 6 7 CloudBlockBlob blob = GetBlobkBlob(fileName); 8 using (FileStream fileStream = File.OpenRead(filePath)) 9 { 10 //Get the total count of block 11 blockCount = (int)(fileStream.Length / bufferSize) + 1; 12 Int64 currentBlockSize = 0; 13 for (int i = blockIds.Count; i < blockCount; i++) 14 { 15 if (worker.WorkerSupportsCancellation && worker.CancellationPending) 16 { 17 return; 18 } 19 currentBlockSize = bufferSize; 20 if (i == blockCount - 1) 21 { 22 currentBlockSize = fileStream.Length - bufferSize * i; 23 bufferBytes = new byte[currentBlockSize]; 24 } 25 if (currentBlockSize == 0) break; 26 //Get the block data 27 fileStream.Read(bufferBytes, 0, Convert.ToInt32(currentBlockSize)); 28 using (MemoryStream memoryStream = new MemoryStream(bufferBytes)) 29 { 30 try 31 { 32 //Get Base64-encoded block Id 33 string blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())); 34 //Upload the block 35 blob.PutBlock(blockId, memoryStream, null); 36 //Cache the block Id 37 blockIds.Add(blockId); 38 worker.ReportProgress(i + 1); 39 } 40 catch (Exception) 41 { 42 43 } 44 } 45 } 46 } 47 //Commit all the blocks 48 blob.PutBlockList(blockIds); 49 isCanceled = false; 50 } 51 52 private CloudBlockBlob GetBlobkBlob(string fileName) 53 { 54 var storageAccount = CloudStorageAccount.Parse(storageConnectionString); 55 CloudBlobClient blobStorage = storageAccount.CreateCloudBlobClient(); 56 57 CloudBlobContainer container = blobStorage.GetContainerReference("mycontainer"); 58 container.CreateIfNotExists(); 59 60 return container.GetBlockBlobReference(fileName); 61 }
点击 这里 下载源码。