没有大小限制的文件上传下载代码(仅供参考)

没有大小限制的文件上传下载代码,其实也不能完全这样讲,毕竟传输文件与网络协议以及文件系统有关系。如果不考虑这些,只看代码的实现,它确实是个好办法。以上传为例,基本思路是,分段传输数据到文件,每次传输时,告知文件名或标识、文件起始偏移、数据块即可。这样做,似乎还能够实现多线程同时传输数据,也许会有一些麻烦,存储竞争文件的写入锁问题。

像我们常用的文件下载工具,通常包含一个原文件同名的空白文件或者与原文件同名同大小的随机数据文件,与一个下载配置文件。这样实现有两种好处。其一,防止文件名被其他进程占用;其二,如果同时给定了文件大小,那么,在尝试创建此文件时是先占用掉磁盘空间的,同时也可以捕获IO异常,比如,可访问异常、权限异常、空间不足异常等等。

当然,作为一个简单的实现,下面代码会判断磁盘可用空间的大小,才会进行下载。

还有一个需要考虑的,下载文件与Windows资源管理器的冲突。这个是我意外发现,比如一个扩展名为.mpg的视频文件,当我们使用Windows定位在这个文件夹时,Windows资源管理器进程会在我们释放文件锁时立即占用,读取该视频文件的缩略图、视频长度、等其他流信息。那么,在我们下一段数据传输来时,需要重新打开文件,自然会出现“文件被其他进程占用”的异常。因此,未传输完成的文件的扩展名建议使用一个未知名称或者.tmp,Windows资源管理器进程(explorer.exe)就不会抢占文件了。

获得磁盘可用空间大小的代码:

/// <summary> /// 获得磁盘的可用剩余空间 /// </summary> /// <param name="path"></param> /// <param name="driveName"></param> /// <param name="driveVolume"></param> /// <returns></returns> public long GetDiskAvailableFreeSpace(string path, out string driveName, out string driveVolume) { driveName = ""; driveVolume = ""; string name = Program.CombineMovieAndTVPath(path); if (!FileUtility.IsValidAbsolutePath(name)) { return -1; //非绝对路径 } long r = -1; try { DriveInfo di = new DriveInfo(name[0].ToString()); driveName = di.Name; driveVolume = di.VolumeLabel; r = di.AvailableFreeSpace; #if DEBUG string dddd = string.Format("{0}: [{1}]\nAvilableFreeSpace: {2}({3})", di.Name, di.VolumeLabel, di.AvailableFreeSpace.FormatFileSize(), di.AvailableFreeSpace); Shell.OutputWithoutLog(dddd, ConsoleColor.DarkYellow); #endif } catch { } return r; }

上传文件的演示代码,客户端需要多次调用此操作:

/// <summary> /// 上传文件 /// </summary> /// <param name="filename">文件名,为空或者不存在,则创建新文件</param> /// <param name="buffer">数据</param> /// <param name="offset">文件偏移位置</param> /// <returns>文件全名</returns> public string UploadChunk(string filename, byte[] buffer, long offset) { /* 注意:上传文件的文件名称扩展名应该不被Windows系统识别的能够生成缩略图和详细信息的文件 * 它会导致打开文件流冲突,产生“另一个进程正在使用文件”的异常。 * 建议:将扩展名更名,在上传完毕后,调用重命名命令,改回原扩展名。 */ //文件全名 string fileName = filename; //文件名为空,在临时目录中创建文件 if (string.IsNullOrEmpty(filename)) { fileName = CreateTempFile(); if (fileName.Length == 0) return ""; //return 临时文件名创建失败 } else if (!FileUtility.IsValidAbsolutePath(fileName)) //检查文件是否为全路径名 { fileName = GetPathName(fileName); //得到文件全名 } FileStream fs = null; try { fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); fs.Seek(offset, SeekOrigin.Begin); fs.Write(buffer, 0, buffer.Length); } catch (Exception ex) { fileName = ""; Shell.Output2("ERROR: " + ex.Message); } finally { if (fs != null) { fs.Close(); } } return fileName; //返回文件全名 }

下载文件代码,客户端需要多次调用此操作:

/// <summary> /// 下载文件 /// </summary> /// <param name="filename">文件名</param> /// <param name="offset">偏移</param> /// <param name="bufferSize">要下载的数据大小</param> /// <returns>下载数据块</returns> public byte[] DownloadChunk(string filename, long offset, int bufferSize) { string pathFilename = Program.CombineMovieAndTVPath(filename); if (!File.Exists(pathFilename)) return new byte[0]; //return 文件不存在 //获得文件大小 long fileSize = 0; try { fileSize = new FileInfo(pathFilename).Length; } catch { fileSize = -1; } if (fileSize < 0) return new byte[0]; //return 无法获取文件大小 if (offset > fileSize) return new byte[0]; //retrun 偏移超出文件范围 //读取文件 byte[] buffer; int bytesRead; try { using (FileStream fs = new FileStream(pathFilename, FileMode.Open, FileAccess.Read, FileShare.Read)) { fs.Seek(offset, SeekOrigin.Begin); buffer = new byte[bufferSize]; bytesRead = fs.Read(buffer, 0, bufferSize); } if (bytesRead != bufferSize) { byte[] trimmedBuffer = new byte[bytesRead]; Array.Copy(buffer, trimmedBuffer, bytesRead); return trimmedBuffer; //return 返回最后的数据 } else { return buffer; //return 返回数据 } } catch { //读取文件出错 } return new byte[0]; //return 读取文件出错 }

那么客户端配合上传文件的参考代码:

int chuckSize = Configuration.System_TransferBufferSize; byte[] buffer = new byte[chuckSize]; long offset = 0; using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) { long fileSize = fs.Length; fs.Position = offset; int bytesRead; do { bytesRead = fs.Read(buffer, 0, chuckSize); if (bytesRead != buffer.Length) { chuckSize = bytesRead; byte[] trimmedBuffer = new byte[bytesRead]; Array.Copy(buffer, trimmedBuffer, bytesRead); buffer = trimmedBuffer; } if (buffer.Length == 0) break; //break do-while; to = ServicesProxy.UploadChunk(to, buffer, offset); if (to.Length == 0) //失败 跳过错误文件 { break; } if (bytesRead > 0) { if (worker.CancellationPending) //检查是否“取消”上传 { } } offset += bytesRead; //读取下一段数据 loaded += bytesRead; //总计读取多少数据,用来统计总进度 //报告上传进度 int percentComplete = (int)((float)offset / (float)fileSize * 100); int percentComplete2 = (int)((float)(loaded) / (float)max * 100); if (percentComplete > highestPercentageReached) highestPercentageReached = percentComplete; if (percentComplete2 > highestPercentageReached2) highestPercentageReached2 = percentComplete2; TimeSpan elapsedTime = stopwatch.Elapsed; //报告进度 worker.ReportProgress(0, new ReportProgressUserState() { percentComplete = percentComplete, percentComplete2 = percentComplete2, elapsedTime = elapsedTime }); } while (bytesRead > 0); //上传完毕 if (bytesRead <= 0) { //重命名文件 //... //处理成功和失败列表等 } }

配合下载文件的参考代码:

int highestPercentageReached = 0; //当前文件传输的完成度 int highestPercentageReached2 = 0; //整体进度 long loaded = 0, max = 0; _lastbps = 0; _lastLeftTime = TimeSpan.Zero; _lastSeconds = 0; foreach (FileInformation fi in p.files) { max += fi.Filesize; } FileDetails fd = FileDetailsHelper.GetFileDetails(p.lvTag.filmId); string path = fd.Path; List<string> loadedFiles = new List<string>(p.files.Length); List<string> faultFiles = new List<string>(p.files.Select(n => Path.Combine(path, n.Filename))); if (max > 0 && fd != default(FileDetails)) { DriveInfo di = null; long availableSize = 0; string msg = ""; try { di = new DriveInfo(p.outputDir[0].ToString()); availableSize = di.AvailableFreeSpace; } catch (Exception ex) { msg = "访问本地驱动器失败。\n\n" + ex.Message; } if (availableSize < max) { msg = string.Format( "本地驱动器 {0}({1}) 的可用空间不足。\n\n下载文件: {2}\n可用: {3}\n缺少: {4}", di.VolumeLabel, di.Name, max.FormatFileSize(), availableSize.FormatFileSize(), (max - availableSize).FormatFileSize()); e.Result = new UploadAndDownloadResult() { loadedFiles = loadedFiles.ToArray(), faultFiles = faultFiles.ToArray(), from = "", to = "", elapsedTime = TimeSpan.Zero, errMsg = msg, canceled = true, }; } else { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); foreach (FileInformation fi in p.files) { string filename = Path.Combine(path, fi.Filename); string localfilename = Path.Combine(p.outputDir, fi.Filename) + ".tmp"; if (worker.CancellationPending) { if (stopwatch.IsRunning) stopwatch.Stop(); e.Result = new UploadAndDownloadResult() { loadedFiles = loadedFiles.ToArray(), faultFiles = faultFiles.ToArray(), from = filename, to = localfilename, elapsedTime = stopwatch.Elapsed, canceled = true, }; break; } long offset = 0; long fileSize = fi.Filesize; FileStream fs = null; try { fs = new FileStream(localfilename, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); fs.Seek(offset, SeekOrigin.Begin); while (offset < fileSize) { if (worker.CancellationPending) { if (stopwatch.IsRunning) stopwatch.Stop(); e.Result = new UploadAndDownloadResult() { loadedFiles = loadedFiles.ToArray(), faultFiles = faultFiles.ToArray(), from = filename, to = localfilename, elapsedTime = stopwatch.Elapsed, canceled = true, }; //e.Cancel = true; break; } byte[] buffer = ServicesProxy.DownloadChunk(filename, offset, _BufferSize); if (buffer.Length == 0) break; //结束循环,认为返回0长度数据是文件读取失败或者读取完成 fs.Write(buffer, 0, buffer.Length); offset += buffer.Length; loaded += buffer.Length; int percentComplete = (int)((float)offset / (float)fileSize * 100); int percentComplete2 = (int)((float)(loaded) / (float)max * 100); if (percentComplete > highestPercentageReached) highestPercentageReached = percentComplete; if (percentComplete2 > highestPercentageReached2) highestPercentageReached2 = percentComplete2; TimeSpan elapsedTime = stopwatch.Elapsed; worker.ReportProgress(0, new ReportProgressUserState() { percentComplete = percentComplete, percentComplete2 = percentComplete2, elapsedTime = elapsedTime, }); } } catch { } finally { if (fs != null) { fs.Close(); } } if (offset == fileSize) //下载完毕 { string rename = localfilename.Substring(0, localfilename.Length - 4); bool renamefault = false; try { if (File.Exists(rename)) File.Delete(rename); //删除存在的文件 File.Move(localfilename, rename); } catch { renamefault = true; } if (renamefault) continue; //重命名或删除同名文件失败 loadedFiles.Add(filename); faultFiles.Remove(filename); e.Result = new UploadAndDownloadResult() { loadedFiles = loadedFiles.ToArray(), faultFiles = faultFiles.ToArray(), from = filename, to = rename, elapsedTime = stopwatch.Elapsed, }; } } if (stopwatch.IsRunning) stopwatch.Stop(); } } if (e.Result == null) { //没能下载 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值