下载一个文件中断时,下次启动继续沿着上一次下载在很多游戏的更新中会用到
断点续传的核心代码API:
HttpWebRequest request = WebRequest.Create(tNowDownloadInfo.nNowDownloadURL) as HttpWebRequest;
request.ProtocolVersion = HttpVersion.Version10;
request.ServicePoint.ConnectionLimit = int.MaxValue;
//使用流操作文件
FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
//获取文件现在的长度
long tmpLoaclFileLength = fs.Length;
//获取下载文件的总长度
long tmpTotlaSize = GetLength(tNowDownloadInfo.nNowDownloadURL, tNowDownloadInfo.nDownloadTimeout);
//断点续传重要API,设置本地开始下载起始位置
fs.Seek(tmpLoaclFileLength, SeekOrigin.Begin);
如果使用带https头的则需要添加设置协议:
if (tNowDownloadInfo.nNowDownloadURL.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback = new
RemoteCertificateValidationCallback(ValidateServerCertificate);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
}
else
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
}
static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true; //接受
}
完整代码:
using UnityEngine;
using System.Collections;
using System.Threading;
using System.IO;
using System.Net;
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
public enum DownloadResult
{
/// <summary>
/// 当前下载的某一个文件已下载成功,不是指一个完全的下载任务已完成
/// </summary>
Success,
/// <summary>
/// 某一个下载文件下载失败
/// </summary>
Fail,
/// <summary>
/// 获取不到文件
/// </summary>
NotFindFile,
/// <summary>
/// 下载文件中
/// </summary>
Downloading,
/// <summary>
/// 完成一个下载任务
/// </summary>
AllDownloadFinished,
}
public class DownloadInfo
{
/// <summary>
/// 是否可以执行回调,用于解决线程之间的问题
/// </summary>
public volatile bool tIsCanCallback;
/// <summary>
/// 当前下载的任务文件大小总和
/// </summary>
public int nAllFileTotalSize = 1;
public DownloadResult tDownloadState;
public float tProgress = 0;
public bool tIsDone = false;
public DownlaodFileCallback tDownloadCallback;
public string[] tSavaeFullFilePath;
public string[] tDownlaodURL;
/// <summary>
/// 当前正在下载的文件地址
/// </summary>
public string nNowDownloadURL = "";
/// <summary>
/// 当前下载完成保存本地的地址
/// </summary>
public string nSavaeFullFilePath = "";
/// <summary>
/// 当前下载索引
/// </summary>
public int nDownloadIndex = 0;
/// <summary>
/// 每秒的下载速度
/// </summary>
public int nTimeDownSpeed = 0;
/// <summary>
/// 每隔多少时间执行回调一次
/// </summary>
public float nUpdateExeCallTime = 1f;
/// <summary>
/// 当前总下载的进度
/// </summary>
public long nLocalFileTotalSize = 0;
/// <summary>
/// 当前下载的文件总大小
/// </summary>
public int nNowTotalSize = 0;
public int nDownloadTimeout = 6000;
/// <summary>
/// 下载的数据是否保存到本地,如果需要设置的话请保证访问的文件小于2MB
/// </summary>
public bool nIsSavaeFile = true;
/// <summary>
/// 下载的数据,只有当nIsSavaeFile false的时候数据会存在该字段中
/// </summary>
public byte[] nToFileData;
}
public delegate void DownlaodFileCallback(DownloadInfo varInfo, DownloadResult varResultType);
/// <summary>
/// 通过http下载资源
/// </summary>
public class HttpDownload : MonoBehaviour
{
static HttpDownload Instance;
public static HttpDownload GetInstacne()
{
if (Instance == null)
{
GameObject tHttpDownLoad = new GameObject("HttpDownLoad");
Instance = tHttpDownLoad.AddComponent<HttpDownload>();
GameObject.DontDestroyOnLoad(tHttpDownLoad);
}
return Instance;
}
static readonly object LockObject = new object();
Thread tDownloadThread;
Queue<DownloadInfo> tDownloadList = new Queue<DownloadInfo>();
static DownloadInfo tNowDownloadInfo;
volatile bool tIsKillGame = false;
/// <summary>
/// 下载方法(断点续传)
/// </summary>
/// <param name="url">URL下载地址</param>
/// <param name="savePath">Save path保存路径</param>
/// <param name="callBack">Call back回调函数</param>
public void DownLoad(DownloadInfo varInfo)
{
tDownloadList.Enqueue(varInfo);
StartTask();
}
public void UpgradeVeresionFiles(DownloadInfo varInfo)
{
for (int i = 0; i < varInfo.tSavaeFullFilePath.Length; i++)
{
if (File.Exists(varInfo.tSavaeFullFilePath[i]))
{
File.Delete(varInfo.tSavaeFullFilePath[i]);
}
}
DownLoad(varInfo);
}
void StartTask()
{
if (tNowDownloadInfo == null)
{
tDownloadThread = new Thread(DownloadTask);
//开启子线程
tDownloadThread.IsBackground = true;
tDownloadThread.Start();
}
}
void DownloadTask()
{
while (true)
{
Thread.Sleep(1000);//执行网络下载之前先暂停1秒
if (tDownloadList.Count > 0)
{
tNowDownloadInfo = tDownloadList.Dequeue();
lock (LockObject)
{
if (!tNowDownloadInfo.nIsSavaeFile)
{
Stream tmpDownStream = null;
try
{
HttpWebRequest request = WebRequest.Create(tNowDownloadInfo.nNowDownloadURL) as HttpWebRequest;
request.ProtocolVersion = HttpVersion.Version10;
request.ServicePoint.ConnectionLimit = int.MaxValue;
request.Timeout = tNowDownloadInfo.nDownloadTimeout;
if (tNowDownloadInfo.nNowDownloadURL.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
}
else
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
}
tmpDownStream = request.GetResponse().GetResponseStream();
byte[] buffer = new byte[1024 * 2];
int tmpReadLength = tmpDownStream.Read(buffer, 0, buffer.Length);
List<byte> tmpCopyArray = new List<byte>();
while (tmpReadLength > 0)
{
byte[] tmp = new byte[tmpReadLength];
Array.Copy(buffer, tmp, tmpReadLength);
tmpCopyArray.AddRange(tmp);
tmpReadLength = tmpDownStream.Read(buffer, 0, buffer.Length);
}
tNowDownloadInfo.nToFileData = tmpCopyArray.ToArray();
if (tNowDownloadInfo.tDownloadCallback != null)
{
tNowDownloadInfo.tDownloadState = DownloadResult.Success;
tNowDownloadInfo.nDownloadIndex = 0;
tNowDownloadInfo.tIsCanCallback = true;
request = null;
}
}
catch (Exception e)
{
if (tNowDownloadInfo.tDownloadCallback != null)
{
tNowDownloadInfo.tDownloadState = DownloadResult.Fail;
tNowDownloadInfo.nDownloadIndex = 0;
tNowDownloadInfo.tIsCanCallback = true;
}
tmpDownStream.Close();
tmpDownStream.Dispose();
//Debug.LogError("Error:" + e.ToString());
}
finally
{
tmpDownStream.Close();
tmpDownStream.Dispose();
}
}
else
{
for (int i = 0; i < tNowDownloadInfo.tSavaeFullFilePath.Length; i++)
{
tNowDownloadInfo.nNowDownloadURL = tNowDownloadInfo.tDownlaodURL[i];
tNowDownloadInfo.nSavaeFullFilePath = tNowDownloadInfo.tSavaeFullFilePath[i];
if (tNowDownloadInfo.nNowDownloadURL.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
}
else
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
}
string tmpDir = Path.GetDirectoryName(tNowDownloadInfo.nSavaeFullFilePath);
//判断保存路径是否存在
if (!Directory.Exists(tmpDir))
{
Directory.CreateDirectory(tmpDir);
}
//这是要下载的文件名,比如从服务器下载a.zip到D盘,保存的文件名是test
string filePath = tNowDownloadInfo.nSavaeFullFilePath;
//使用流操作文件
FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
//获取文件现在的长度
long tmpLoaclFileLength = fs.Length;
//获取下载文件的总长度
long tmpTotlaSize = GetLength(tNowDownloadInfo.nNowDownloadURL, tNowDownloadInfo.nDownloadTimeout);
if (tmpTotlaSize == 0)
{
tNowDownloadInfo.tDownloadState = DownloadResult.NotFindFile;
tNowDownloadInfo.nDownloadIndex = i;
tNowDownloadInfo.tIsCanCallback = true;
if (fs != null)
{
fs.Close();
fs.Dispose();
}
continue;
}
tNowDownloadInfo.nNowTotalSize = (int)tmpTotlaSize;
float tmpProgress = 0;
//如果没下载完
if (tmpLoaclFileLength < tmpTotlaSize)
{
//断点续传重要API,设置本地开始下载起始位置
fs.Seek(tmpLoaclFileLength, SeekOrigin.Begin);
HttpWebRequest request = WebRequest.Create(tNowDownloadInfo.nNowDownloadURL) as HttpWebRequest;
request.ProtocolVersion = HttpVersion.Version10;
request.ServicePoint.ConnectionLimit = int.MaxValue;
request.Timeout = tNowDownloadInfo.nDownloadTimeout;
Stream tmpDownStream = null;
try
{
//断点续传核心,设置远程访问文件流的起始位置
request.AddRange(tmpLoaclFileLength,tmpTotlaSize);
tmpDownStream = request.GetResponse().GetResponseStream();
byte[] buffer = new byte[1024 * 1024 * 3];
//使用流读取内容到buffer中
//方法返回值代表读取的实际长度,并不是buffer有多大,stream就会读进去多少
int tmpReadLength = tmpDownStream.Read(buffer, 0, buffer.Length);
float time = 0;
int tmpDownloadSpeed = 0;
while (tmpReadLength > 0)
{
//如果Unity客户端关闭,停止下载
if (tIsKillGame)
{
tmpDownStream.Close();
tmpDownStream.Dispose();
fs.Close();
fs.Dispose();
return;
}
//将内容再写入本地文件中
fs.Write(buffer, 0, tmpReadLength);
//计算进度
tmpLoaclFileLength += tmpReadLength;
tmpProgress = (float)tmpLoaclFileLength / tmpTotlaSize;
//类似尾递归
tmpReadLength = tmpDownStream.Read(buffer, 0, buffer.Length);
tNowDownloadInfo.nLocalFileTotalSize = tmpLoaclFileLength;
time += Main.deltaTime;
Main.deltaTime = 0;
if (time >= tNowDownloadInfo.nUpdateExeCallTime)
{
tNowDownloadInfo.nTimeDownSpeed = tmpDownloadSpeed;
tNowDownloadInfo.tDownloadState = DownloadResult.Downloading;
tNowDownloadInfo.nDownloadIndex = i;
tNowDownloadInfo.tIsCanCallback = true;
time = 0;
tmpDownloadSpeed = 0;
}
tmpDownloadSpeed += tmpReadLength;
}
if (tmpDownStream != null)
{
tmpDownStream.Close();
tmpDownStream.Dispose();
}
}
catch (Exception e)
{
//Debug.Log(e.ToString());
tNowDownloadInfo.tDownloadState = DownloadResult.Fail;
tNowDownloadInfo.nDownloadIndex = i;
tNowDownloadInfo.tIsCanCallback = true;
if (fs != null)
{
fs.Close();
fs.Dispose();
}
continue;
}
finally
{
fs.Close();
fs.Dispose();
if (tmpDownStream != null)
{
tmpDownStream.Close();
tmpDownStream.Dispose();
}
}
}
else
{
tmpProgress = 1;
}
fs.Close();
fs.Dispose();
//如果下载完毕,执行回调
if (tmpProgress == 1)
{
if (i == tNowDownloadInfo.tDownlaodURL.Length - 1)
{
tNowDownloadInfo.tIsDone = true;
tNowDownloadInfo.tDownloadState = DownloadResult.AllDownloadFinished;
}
else
{
tNowDownloadInfo.tDownloadState = DownloadResult.Success;
}
tNowDownloadInfo.nDownloadIndex = i;
tNowDownloadInfo.tIsCanCallback = true;
}
}
}
}
}
Thread.Sleep(500);
}
}
/// <summary>
/// 检查回调
/// </summary>
void Update()
{
if (tNowDownloadInfo != null && tNowDownloadInfo.tIsCanCallback)
{
if (tNowDownloadInfo.tDownloadCallback != null)
{
try
{
tNowDownloadInfo.tIsCanCallback = false;
tNowDownloadInfo.tDownloadCallback(tNowDownloadInfo, tNowDownloadInfo.tDownloadState);
}
catch (Exception e)
{
tNowDownloadInfo.tIsCanCallback = false;
}
}
}
}
static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true; //总是接受
}
/// <summary>
/// 获取下载文件的大小
/// </summary>
/// <returns>The length.</returns>
/// <param name="url">URL.</param>
long GetLength(string url, int timeout)
{
try
{
HttpWebRequest requet = HttpWebRequest.Create(url) as HttpWebRequest;
requet.Method = "HEAD";
requet.Timeout = timeout;
HttpWebResponse response = requet.GetResponse() as HttpWebResponse;
long length = response.ContentLength;
response.Close();
return length;
}
catch (Exception)
{
return 0;
}
}
void OnApplicationQuit()
{
tIsKillGame = true;
}
}