一、前言
网上有许多的多线程断点续传操作,但总是写的很云里雾里,或者写的比较坑长。由于这几个月要负责公司的在线升级项目,所以正好顺便写了一下
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Threading.Tasks; 5 6 namespace TestCenter 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 string LocalSavePath = @"E:\Test\TestFile\Local\1.msi"; //本地目标文件路径 13 14 FileInfo SeverFilePath = new FileInfo(@"E:\Test\TestFile\Server\1.msi"); //服务器待文件路径 15 long FileLength = SeverFilePath.Length; //待下载文件大小 16 17 18 Console.WriteLine("Start Configuration"); 19 int PackCount = 0; //初始化数据包个数 20 21 long PackSize = 1024000; //数据包大小 22 23 if (FileLength % PackSize > 0) 24 { 25 PackCount = (int)(FileLength / PackSize) + 1; 26 } 27 28 else 29 { 30 PackCount = (int)(FileLength / PackSize); 31 } 32 33 34 Console.WriteLine("Start Recieve"); 35 var tasks = new Task[PackCount]; //多线程任务 36 37 for (int index = 0; index < PackCount; index++) 38 { 39 40 41 int Threadindex = index; //这步很关键,在Task()里的绝对不能直接使用index 42 var task = new Task(() => 43 { 44 string tempfilepath = @"E:\Test\TestFile\Temp\" + "QS_" + Threadindex + "_" + PackCount; //临时文件路径 45 46 using (FileStream tempstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write)) 47 { 48 int length = (int)Math.Min(PackSize, FileLength - Threadindex * PackSize); 49 50 var bytes = GetFile(Threadindex*PackCount, length); 51 52 tempstream.Write(bytes, 0, length); 53 tempstream.Flush(); 54 tempstream.Close(); 55 tempstream.Dispose(); 56 } 57 }); 58 tasks[Threadindex] = task; 59 task.Start(); 60 } 61 62 Task.WaitAll(tasks); //等待所有线程完成 63 Console.WriteLine("Recieve End"); 64 65 66 //检测有哪些数据包未下载 67 Console.WriteLine("Start Compare"); 68 DirectoryInfo TempDir = new DirectoryInfo(@"E:\Test\TestFile\temp"); //临时文件夹路径 69 List<string> Comparefiles = new List<string>(); 70 71 for (int i = 0; i < PackCount; i++) 72 { 73 bool hasfile = false; 74 foreach (FileInfo Tempfile in TempDir.GetFiles()) 75 { 76 if (Tempfile.Name.Split('_')[1] == i.ToString()) 77 { 78 hasfile = true; 79 break; 80 } 81 } 82 if (hasfile == false) 83 { 84 Comparefiles.Add(i.ToString()); 85 } 86 } 87 88 //最后补上这些缺失的文件 89 if (Comparefiles.Count > 0) 90 { 91 foreach (string com_index in Comparefiles) 92 { 93 string tempfilepath = @"E:\Test\TestFile\Temp\" + "QS_" + com_index+ "_" + PackCount; 94 using (FileStream Compstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write)) 95 { 96 int length = (int)Math.Min(PackSize, FileLength - Convert.ToInt32(com_index) * PackSize); 97 var bytes = GetFile(Convert.ToInt32(com_index)*PackCount, length); 98 Compstream.Write(bytes, 0, length); 99 Compstream.Flush(); 100 Compstream.Close(); 101 Compstream.Dispose(); 102 } 103 } 104 105 } 106 Console.WriteLine("Compare End"); 107 108 109 //准备将临时文件融合并写到1.msi中 110 Console.WriteLine("Start Write"); 111 using (FileStream writestream = new FileStream(LocalSavePath, FileMode.Create, FileAccess.Write, FileShare.Write)) 112 { 113 foreach (FileInfo Tempfile in TempDir.GetFiles()) 114 { 115 using (FileStream readTempStream = new FileStream(Tempfile.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 116 { 117 long onefileLength = Tempfile.Length; 118 byte[] buffer = new byte[Convert.ToInt32(onefileLength)]; 119 readTempStream.Read(buffer, 0, Convert.ToInt32(onefileLength)); 120 writestream.Write(buffer, 0, Convert.ToInt32(onefileLength)); 121 } 122 } 123 writestream.Flush(); 124 writestream.Close(); 125 writestream.Dispose(); 126 } 127 Console.WriteLine("Write End"); 128 129 130 131 //删除临时文件 132 Console.WriteLine("Start Delete Temp Files"); 133 foreach (FileInfo Tempfile in TempDir.GetFiles()) 134 { 135 Tempfile.Delete(); 136 } 137 Console.WriteLine("Delete Success"); 138 Console.ReadKey(); 139 } 140 141 142 //这个方法可以放到Remoting或者WCF服务中去,然后本地调用该方法即可实现多线程断点续传 143 public static byte[] GetFile(int start, int length) 144 { 145 string SeverFilePath = @"E:\Test\TestFile\Server\1.msi"; 146 using (FileStream ServerStream = new FileStream(SeverFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024*80, true)) 147 { 148 byte[] buffer = new byte[length]; 149 ServerStream.Position = start; 150 //ServerStream.Seek(start, SeekOrigin.Begin); 151 ServerStream.Read(buffer, 0, length); 152 return buffer; 153 } 154 } 155 } 156 }
二、讨论
1)需要注意的是第44行,不能直接使用index变量在Task()里进行操作,而是要将它赋给Threadindex,让Threadindex在Task()里,不然会直接报错,为什么呢?
链接:http://bbs.csdn.net/topics/390769774
2)70至108行代码可以在外面再套一层while循环,循环检测临时文件是否下完整了,然后再定义一个检测最大上限,超过这个上限就放弃本次更新,当用户的网络恢复正常后下次再做更新操作。所以说放临时文件的文件夹最好要包含版本信息,不会把2.0.0的临时文件和1.0.0的临时文件搞混。
3) FileStream.Position 与 FileStream.Seek(long offset, SeekOrigin seekorigin) 的作用都是获取流的指针位置,当文件路径使用绝对路径时使用Position;相对路径时使用Seek方法
链接:https://stackoverflow.com/questions/7238929/stream-seek0-seekorigin-begin-or-position-0