上篇文章提到了向服务器请求部分数据,已达到多线程下载的目的。
这里我们看看如何实现多线程写入文件。先看示例代码:
String url="";//待下载文件网络地址
String path="";//待下载文件本地地址
int startpos,endpos;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
//request.Timeout = Timeout.Infinite;
request.Timeout = 30000;
request.AddRange(startpos,endpos); //采用 http 协议的 Range 头,下载部分数据
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream sReader = response.GetResponseStream())
{
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{// 打开下载文件,共享写,注意每个线程写的时候从规划好的位置开始,不然会覆盖
while (len > 0)
{
byte[] buffer = new byte[10240];
int read = sReader.Read(buffer, 0, buffer.Length);
if (read <= 0)
{
break;
}
fs.Seek(startpos, SeekOrigin.Begin);//开始写的位置
fs.Write(buffer, 0, read);
fs.Flush();
UpdateStartPos(id, read);//更新待下载文件已下载数据量,id为当前下载文件标志
len -= read;
}
}
}
}
这里我们看看这几个点:
1.request.AddRange(start,end)。添加head头,请求部分数据。
2.FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))。打开文件流,共享写,实现多线程写入文件。但是要注意,多线程写入的时候,规划好每个线程写入的其实位置和request添加Range头的位置对应,避免写入文件覆盖数据。fs.Seek(start,SeekOrigin.Begin)
3.那么如何知道这个起始位置呢?
迅雷之前的版本在下载的时候会创建一个下载文件对应的cfg文件,这个cfg文件就是下载文件下载过程中的状态文件,例如当前下载线程数,每个线程起始位置,当前每个线程下载量和总下载量等。下载的同时会实时写入这个配置文件,更新下载状态。在下次启动时,加载这个cfg文件达到上次下载的结束状态,以便开始下载。
这里我们为了方便写入文件同时更新下载进度,我们以10k为单位下载数据写入文件,同时写入配置文件。注意代码中:UpdateStartPos(id, read);//更新待下载文件已下载数据量,id为当前下载文件标志。
这样就实现了多线程写入,断点功能。
这里注意,UpdateStartPos(id, read) 方法,在更新配置文件的时候会有cfg文件锁定的问题。
4. ReaderWriterLock 锁
更新cfg配置文件的时候采用读写锁。在.Net Framework 4 中建议采用ReaderWriterLockSlim。
对于所有新的开发建议使用 ReaderWriterLockSlim。 ReaderWriterLockSlim 类似于 ReaderWriterLock,但简化了递归规则以及升级和降级锁定状态的规则。 ReaderWriterLockSlim 可避免多种潜在的死锁情况。 此外,ReaderWriterLockSlim 的性能明显优于 ReaderWriterLock。
private ReaderWriterLockSlim _mutex = new ReaderWriterLockSlim();
public readonly int ReaderWriterLockTimeout = 30000;
public void UpdateStartPos(int taskid, int increment)
{
if(_mutex.TryEnterWriteLock(ReaderWriterLockTimeout)){
try
{
// 更新配置文件,可以是xml txt 甚至数据库,但尽可能是个短时间操作
}
finally
{
_mutex.ExitWriteLock();
}
}
}