最近用 HttpWebRequest 做了个文件下载, HTTP 可以提供一些头信息的返回, 不过本身不带MD5之类的文件效验信息, 在做下载前可以跟本地文件进行一个对比, 如果跟本地文件相同的话就不需要下载了.
HTTP的头返回中有两个有用信息 : ContentLength(文件大小) 和 "Last-Modified"(上次操作文件的时间).
ContentLength : 如果跟本地文件的长度不一样, 那就是不同了, 可以直接下载.
Last-Modified : 文件上次操作的时间, 这个不是很有用, 比如说有两个版本的文件 [ABC] 的1.0和2.0版本, 它们的长度相同, 然后放到服务器上的时间也相同, 它们的 Last-Modified 是一样的, 这样如果有版本控制需要按顺序下载的话, 1.0的先下载下来了, 本地文件的FileInfo中不管是 LastAccessTime 还是 CreationTime 都是本地写入文件的时间, 然后在更新2.0版本的时候时间对比就失效了...
所以上面两个属性的对比都是不靠谱的, 只能作为辅助, 我们最需要的是文件的MD5, 之前想的是写一个接口能够计算并返回文件MD5, 然后用PHP之类的配置很简单就能用的话, 不管哪种系统都能方便用起来了不是, 可是我们想要最简洁的方案, 能不配环境就不配, 能不写代码就不写, 只需添加一个版本MD5文件就行了, 包含该版本所有文件的MD5信息, 跟Unity的AssetBundleManifest一样, 当版本不同的时候就先下载这个MD5列表, 然后跟本地文件对比, 不同的就下载即可.
-- 代码 --
public static async Task<FileUpdateInfo> CheckFileNeedUpdate(string url, string localPath, int minGapSec = 10, string md5 = null, CatchExceptionEnum catchExceptionEnum = CatchExceptionEnum.None) { var info = new FileUpdateInfo() { needUpdate = false, url = url, fileLength = 0, acceptRanges = false, remoteMD5 = md5 }; // check MD5 if(string.IsNullOrEmpty(info.remoteMD5) == false) { info.localMD5 = GetMD5HashFromFile(localPath); if(string.IsNullOrEmpty(info.localMD5) == false && string.Equals(info.localMD5, info.remoteMD5, StringComparison.OrdinalIgnoreCase)) { return info; } } HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); try { request.ServicePoint.Expect100Continue = false;//解决第二次请求失败问题 request.Method = "HEAD"; var response = await request.GetResponseAsync() as HttpWebResponse; if(response.StatusCode == HttpStatusCode.OK) { info.fileLength = response.ContentLength; // get length var acceptRanges = response.Headers.Get(AcceptRanges); // get can use ranged info.acceptRanges = acceptRanges.Equals("bytes", StringComparison.OrdinalIgnoreCase); FileInfo fileInfo = new FileInfo(localPath); if(fileInfo.Exists == false || fileInfo.Length != response.ContentLength) { info.needUpdate = true; return info; } var date = response.Headers.Get(LastModified); var serverFileWriteTime = System.DateTime.Parse(date); var span = serverFileWriteTime - fileInfo.CreationTime; if(span.TotalSeconds >= minGapSec) { info.needUpdate = true; return info; } // last check MD5 if(string.IsNullOrEmpty(info.remoteMD5)) { info.needUpdate = true; return info; } if(string.Equals(string.IsNullOrEmpty(info.localMD5) ? (info.localMD5 = GetMD5HashFromFile(localPath)) : info.localMD5, info.remoteMD5, StringComparison.OrdinalIgnoreCase) == false) { info.needUpdate = true; return info; } } } catch(System.Exception ex) { if((catchExceptionEnum & CatchExceptionEnum.ShowCached) != 0) { Common.Debug.LogError(ex.Message); } if((catchExceptionEnum & CatchExceptionEnum.Catch) != 0) { throw ex; } } finally { request.Abort(); } return info; }
这里还是有几个注意点的, request.ServicePoint.Expect100Continue = false; 和 request.Abort(); 才能保证发起多次请求不被服务器卡住, 如果不加的话只能从服务器下载一个文件, 然后之后的请求都没有响应了, 很危险.
首先是MD5对比, 如果传入了服务器上的文件的MD5, 跟本地的对比, 如果文件一致的话就不用下载了.
然后是获取HEAD信息, 因为我使用的是分段下载的方式, 需要获得服务器是否支持分段传输, 头信息中有个 "Accept-Ranges" 信息, 如果类型是bytes就是支持分段传输了, 断点续传就是基于它的.
之后的长度对比和文件操作时间对比, 也能算是文件对比了.
最后的MD5对比, 证明确实需要更新文件...
就这样如果是需要更新的文件, 会返回得到新文件的长度信息, 可以按照需求进行下载了.
Unity 的 WWW.LoadFromCacheOrDownload() 是怎样实现远程文件比对的呢, 好奇