上传——断点续传之实践篇(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("等待线程已完成");