上传——断点续传之实践篇

单线程普通上传

1、用流打开文件

 var item = new FileInfo(filePath);
 FileStream stream = item.OpenRead();

 

2、读取到字节

var fs=stream;

var transeBytesSize = fs.Length;

var data = new byte[(int)transeBytesSize];

BinaryReader bReader = new BinaryReader(fs);

bReader.Read(data, 0, (int)transeBytesSize);

 

3、调用服务端上传接口

var uploadResult = await WebApi.UploadByTrunk(doc, data, md5, fileLength, transeBytesSize, chunkSize);

此方法是一个异步的上传方法,后面会多次用到,接口参数说明:

doc:文件相关的信息

data:传输的字节

md5:文件的md5

fileLength:文件大小

transeBytesSize:当前要传输的字节大小

chunkSize:分片大小

单线程断点续传

1、获取上次传输的断点位置,通过服务端接口获取

 var startResult = await WebApi.GetLastUploadSize(doc.Id, fileLength, fileId);

 var startPoint = startResult.Results;

接口参数说明:

doc.Id 文档Id

fileLength:文件长度

fileId:文件Id,guid类型

 

2、定位流的当前位置

定位流的目的是跳过已经已经上传的字节,startPoint就是断点所在,所以跳过startPoint个字节,再上传。

if (startPoint >= 0 && startPoint <= fileLength - 1)
{
   fs.Seek(startPoint, SeekOrigin.Current);
}

3、分片传输

   int i = 0;

   var totalChunks = leftChunkSize % chunkSize == 0 ? leftChunkSize / chunkSize : leftChunkSize / chunkSize + 1;

   for (; startPoint <= fileLength - 1; startPoint += transeBytesSize)
   {
     var leftChunkSize = fileLength - startPoint;
     var transeBytesSize = leftChunkSize > chunkSize ? chunkSize : leftChunkSize;

     var data = new byte[(int)transeBytesSize];
     bReader.Read(data, 0, (int)transeBytesSize);
     i++;
     var uploadResult = await WebApi.UploadByTrunk(doc, data, md5, fileLength, transeBytesSize, chunkSize, mulThreadEnable, totalChunks, i);                    
   }

上传接口参数补充,参考普通上传中的接口:

mulThreadEnable:是否启用多线程

totalChunks:总分片数

i:当前分片序号,表示第几个分片,传输当前片序号的目的,在于让服务器知道上传是否结束。服务器知道后,可以按顺序合并分片文件。

     

多线程断点续传   

1、获取上次传输的分片数,通过服务端接口获取

 var lastChunks = await WebApi.GetLastChunks(doc.Id, fileId, chunkSize);

 if (lastChunks != null)
 {
     hasChunks = lastChunks.Results;
 }

 

2、准备好分片数据

 List<Task<WebApiResponse>> tasks = new List<Task<WebApiResponse>>();

 int i = 0;

 Dictionary<int, byte[]> datas = new Dictionary<int, byte[]>();
 Dictionary<int, long> transeBytesSizeDic = new Dictionary<int, long>();


 for (; startPoint <= fileLength - 1; startPoint += transeBytesSize)
 {
   i++;

   leftChunkSize = fileLength - startPoint;

   transeBytesSize = leftChunkSize > chunkSize ? chunkSize : leftChunkSize;

   var data = new byte[(int)transeBytesSize];

   bReader.Read(data, 0, (int)transeBytesSize);

   if (hasChunks != null && hasChunks.Count > 0 && hasChunks.Contains(i)) continue;

   datas.Add(i, data);

   transeBytesSizeDic.Add(i, transeBytesSize);

 }   

3、上传分片数据

 for (int j = 1; j <= totalChunks; j++)
 {
    int k = j;
    if (!datas.ContainsKey(k)) continue;   //跳过已经上传的分片
    var task = WebApi.UploadByTrunk(doc, datas[k], md5, fileLength, transeBytesSizeDic[k], chunkSize, mulThreadEnable, datas.Count, k);
    tasks.Add(task);
  }      

 

4、等待任务完成后,发送结束标识

Task.WaitAll(tasks.ToArray());     

 foreach (var item in tasks)
 {

    var allResult = item.Result;

    //处理上传后的结果,如判断成功与否等                                                        

    //此处为实际的业务逻辑                  

  }

  var sendResult = await WebApi.SendFinish(doc, md5, totalChunks, fileLength, chunkSize); 

接口参数说明:

doc:文件相关的信息

md5:文件的md5

fileLength:文件大小

totalChunks:总分片大小

chunkSize:分片大小

说明:可以看到,此方法与上传方法比,少了很多参数。因为它只是通知服务器已经结束,不携带文件数据。

由于多线程,最后一个分片,不一定最后传完,所以服务器无法判断上传是否结束。针对这个问题的解决方案,客户端多调用一个接口,通知服务器,我传输完毕,你可以合并分片文件。

以上是我的断点续传的实践,项目已经结束,所以做个总结,供大家参考。