上传——断点续传之实践篇(1)
在上一篇中,主要介绍了客户端的断点续传的处理,这一篇,主要补充下服务端的断点续传。
服务端单线程断点续传
1、获取上次传输的断点
var filePath = Path.Combine(rootFolderPath, document.WellId.ToString(), query.FileId + ".temp");
if (System.IO.File.Exists(filePath))
{
var length = new FileInfo(filePath).Length;
if (query.TotalSize > length)
{
response.Results = length;
}
}
2、单线程写入
1 private WebApiResponse WriteToFile(string rootFilePath, Guid wellId, string fileId, string ext, IHttpFile file, bool isNotChunk, bool isLastChunk)
2 {
3 WebApiResponse response = new WebApiResponse();
4
5 try
6 {
7 var folderPath = Path.Combine(rootFilePath, wellId.ToString());
8
9 if (Directory.Exists(folderPath) == false)
10 {
11 Directory.CreateDirectory(folderPath);
12 }
13 if (isNotChunk)
14 {
15 var filePath = Path.Combine(folderPath, fileId + ext);
16
17 if (System.IO.File.Exists(filePath))
18 {
19 System.IO.File.Delete(filePath);
20 }
21
22 using var stream = new FileStream(filePath, FileMode.CreateNew);
23 file.InputStream.WriteTo(stream);
24
25 }
26 else
27 {
28 //附加到临时文件
29 var filePath = Path.Combine(folderPath, fileId + ".temp");
30 using var stream = new FileStream(filePath, FileMode.Append);
31 file.InputStream.WriteTo(stream);
32 stream?.Close();
33
34 if (isLastChunk)
35 {
36 //最后一个分片,更新文件名
37
38 var tempFilePath = Path.Combine(folderPath, fileId + ".temp");
39
40 var targetFilePath = Path.Combine(folderPath, fileId + ext);
41
42 System.IO.File.Move(tempFilePath, targetFilePath, true);
43 }
44 }
45 }
46 catch (Exception ex)
47 {
48 return WebApiResponse.Fail("文件上传中出错:" + ex.Message);
49 }
50
51 return response;
52 }
服务端多线程断点续传
1、获取上次已经上传的分片
1 //获取临时文件夹中文件数,减去1,防止分片不完整
2 var mergeDirPath = Path.Combine(rootFolderPath, document.WellId.ToString(), "Temp");
3
4 if (Directory.Exists(mergeDirPath))
5 {
6 DirectoryInfo di = new DirectoryInfo(mergeDirPath);
7
8 //删除掉一些fileId对应不上的文件
9
10 var tempfiles = di.GetFiles();
11
12 for (int i = 0; i < tempfiles.Length; i++)
13 {
14 if (!tempfiles[i].Name.Contains(query.FileId))
15 {
16 tempfiles[i].Delete();
17 }
18 }
19
20 var files = di.GetFiles().OrderBy(f => f.CreationTime).ToList();
21
22 List<int> seqs = new List<int>();
23
24 foreach (var item in files)
25 {
26 //去掉小于片区的文件
27 if (item.Length != query.ChunkSize) continue;
28 var pName = Path.GetFileNameWithoutExtension(item.Name);
29
30 var s = pName.Last().ToString();
31
32 seqs.Add(int.Parse(s));
33 }
34
35 response.Results = seqs;
36 }
2、多线程写入
1 private WebApiResponse MulThreadWriteToFile(string rootFilePath, Guid wellId, string fileId, IHttpFile file, long chunkNumber)
2 {
3 WebApiResponse response = new WebApiResponse();
4
5 try
6 {
7 var folderPath = Path.Combine(rootFilePath, wellId.ToString(), "Temp");
8
9 if (Directory.Exists(folderPath) == false)
10 {
11 Directory.CreateDirectory(folderPath);
12 }
13
14 var filePath = Path.Combine(folderPath, fileId + chunkNumber + ".temp");
15
16 if (System.IO.File.Exists(filePath))
17 {
18 System.IO.File.Delete(filePath);
19 }
20
21 using var stream = new FileStream(filePath, FileMode.CreateNew);
22 file.InputStream.WriteTo(stream);
23 stream?.Close();
24 }
25 catch (Exception ex)
26 {
27 WebApiResponse.Fail("文件上传中出错:" + ex.Message);
28 }
29
30 return response;
31 }
3、多线程完成后通知
DirectoryInfo di = new DirectoryInfo(mergeDirPath);
var files = di.GetFiles().OrderBy(f => f.Name).ToList();
foreach (var item in files)
{
using var stream = new FileStream(filePath, FileMode.Append);
using var fs = item.OpenRead();
fs.WriteTo(stream);
fs?.Close();
stream?.Close();
}
客户端多线程上传
NeedAddFiles为要上传的文件集合,按分页的思想,每一页的列表上传,分给一个task,页内是一个一个文件上传的,多页是并发上传的。
while (true)
{
var sources = NeedAddFiles.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
if (sources.Count == 0) break;
pageIndex++;
var task = Task.Run(function: async () =>
{
foreach (var item in sources)
{
await FileUpload(item.Key, item.Value);
await Task.Delay(5);
}
});
tasks.Add(task);
}
//等待所有线程完成
await Task.WhenAll(tasks).ContinueWith(t =>
{
Console.WriteLine("多线程上传任务已完成");
});
Console.WriteLine("等待线程已完成");