上传——断点续传之实践篇
单线程普通上传
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:分片大小
说明:可以看到,此方法与上传方法比,少了很多参数。因为它只是通知服务器已经结束,不携带文件数据。
由于多线程,最后一个分片,不一定最后传完,所以服务器无法判断上传是否结束。针对这个问题的解决方案,客户端多调用一个接口,通知服务器,我传输完毕,你可以合并分片文件。
以上是我的断点续传的实践,项目已经结束,所以做个总结,供大家参考。