//#define UseWebClient using UnityEngine; using System.Collections; using System.Net; using System.IO; using System.ComponentModel; using System; using System.Threading; public class RemoteFile { static bool m_ProcessErroring = false; public static bool processErroring { get { return m_ProcessErroring; } } public static int maxDownLoadTask = 48; public static int MaxConnectLimit = 48; static int gDownloadIsRunningCount; public static int DownloadIsRunningCount { get { return gDownloadIsRunningCount; } } public static int gStartTickcount = 0; static long gTotalDownloadSize = 0L; //已下载的字节数 static object lockObj = new object(); public static long TotalDownloadedSize { get { return System.Threading.Interlocked.Read(ref gTotalDownloadSize); } set { gTotalDownloadSize = value; } } static float downloadSpeedRef = 0f; //字节/秒 static long downloadSizeRef = 0L; public static string DownloadSpeed { get { float speed = downloadSpeedRef; if (RemoteFile.gStartTickcount != 0) { float second = Mathf.Abs(System.Environment.TickCount - RemoteFile.gStartTickcount) / 1000f; if (second > 1f || (downloadSpeedRef <= 0.1f && TotalDownloadedSize > 0)) { if (second > 0f) { var delta = TotalDownloadedSize - downloadSizeRef; downloadSizeRef = TotalDownloadedSize; speed = (delta / second + downloadSpeedRef) * 0.5f; downloadSpeedRef = speed; RemoteFile.gStartTickcount = System.Environment.TickCount; } } } if (speed > 1048576f) { return StringUtility.Contact((speed / 1048576f).ToString("f1"), " M/S"); } else if (speed > 1024f) { return StringUtility.Contact((speed / 1024f).ToString("f1"), " KB/S"); } else { return StringUtility.Contact(speed.ToString("f1"), " B/S"); } } } public AssetVersion assetVersion { get; private set; } protected string mRemoteFile; protected string localFile; bool speedLimit = false; protected string mLocalFileTemp; //临时文件 protected System.DateTime mRemoteLastModified; protected System.DateTime mLocalLastModified; protected long mRemoteFileSize = 0; public static long TotalRemoteFileSize = 0L; const int bufferSize = 8192; byte[] buff; HttpWebRequest headRequest; HttpWebResponse headResponse; HttpWebRequest fileRequest = null; HttpWebResponse fileResponse = null; Action onCompleted; protected bool mHadError = false; public bool HaveError { get { return mHadError; } } bool m_Done = false; public bool done { get { return m_Done; } private set { m_Done = value; if (value) { state = State.Stoped; if (onCompleted != null) { onCompleted(!HaveError, assetVersion); onCompleted = null; } } } } State state = State.Wait; int read_Stream_startTickcount = 0; //请求操作开始,用于超时判断 int timeOut = 5000; //超时时间 public static void Prepare() { gDownloadIsRunningCount = 0; gStartTickcount = System.Environment.TickCount; TotalDownloadedSize = 0L; downloadSpeedRef = 0f; downloadSizeRef = 0L; } public void Init(string remoteFile, string _localFile, AssetVersion _assetVersion, bool _speedLimit = false) { mRemoteFile = remoteFile; localFile = _localFile; assetVersion = _assetVersion; this.speedLimit = _speedLimit; } public void Begin(Action _onCompleted) { onCompleted = _onCompleted; SnxxzGame.Instance.StartCoroutine(Co_DownloadRemoteFile()); } bool stop = false; public void Stop() { if (stop) { return; } stop = true; try { if (headRequest != null) { headRequest.Abort(); headRequest = null; } if (headResponse != null) { headResponse.Close(); headResponse = null; } if (fileRequest != null) { fileRequest.Abort(); fileRequest = null; } if (fileResponse != null) { fileResponse.Close(); fileResponse = null; } if (fs != null) { fs.Flush(); fs.Close(); fs = null; } if (inStream != null) { inStream.Close(); inStream = null; } } catch (Exception ex) { Debug.Log(ex); } finally { onCompleted = null; mHadError = false; if (state == State.Working) { gDownloadIsRunningCount--; } } } private void OnDispose() { } void MakeSureDirectory(string filePath) { string dir = Path.GetDirectoryName(filePath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } } bool Move(string sourceFile, string destFile) { bool ret = true; #if !UNITY_WEBPLAYER try { if (speedLimit) { ThreadPool.QueueUserWorkItem((object _obj) => { File.Move(sourceFile, destFile); }); } else { File.Move(sourceFile, destFile); } } catch (Exception ex) { DebugEx.LogError(ex.Message); ret = false; } #endif return ret; } IEnumerator Co_DownloadRemoteFile() { done = false; while (gDownloadIsRunningCount >= maxDownLoadTask) { if (stop) { yield break; } //超过最大任务数时,先等待 yield return null; } while (assetVersion != null && assetVersion.extersion == ".manifest" && !AssetVersionUtility.GetAssetVersion(assetVersion.relativePath.Replace(".manifest", "")).localValid) { if (stop) { yield break; } yield return null; } state = State.Working; gDownloadIsRunningCount++; mHadError = false; fileWriteState = FileWriteState.None; mLocalFileTemp = localFile + ".tmp"; //先下载为临时文件 MakeSureDirectory(mLocalFileTemp); //确保文件写入目录存在 mLocalLastModified = DateTime.MinValue; long localFileSize = 0L; #if !UNITY_WEBPLAYER mLocalLastModified = File.GetLastWriteTime(mLocalFileTemp); #endif headRequest = (HttpWebRequest)System.Net.WebRequest.Create(mRemoteFile); if (headRequest.ServicePoint.ConnectionLimit < RemoteFile.MaxConnectLimit) { headRequest.ServicePoint.ConnectionLimit = RemoteFile.MaxConnectLimit; } headRequest.Method = "HEAD"; // Only the header info, not full file! headRequest.ServicePoint.Expect100Continue = false; headRequest.Timeout = 3000; headRequest.Proxy = null; headRequest.KeepAlive = false; bool isAcceptRange = true; bool headRequestOk = false; //是否支持断点续传 int tick1 = 0; try { headRequest.BeginGetResponse( //改为异步的方法 (x) => { try { headResponse = (x.AsyncState as HttpWebRequest).EndGetResponse(x) as HttpWebResponse; mRemoteLastModified = headResponse.LastModified; mRemoteFileSize = headResponse.ContentLength; if (headResponse.Headers["Accept-Ranges"] != null) { string s = headResponse.Headers["Accept-Ranges"]; if (s == "none") { isAcceptRange = false; } } System.Threading.Interlocked.Add(ref RemoteFile.TotalRemoteFileSize, mRemoteFileSize); headRequestOk = true; } catch (Exception ex) { DebugEx.LogWarning("ERROR: " + ex); mHadError = true; } finally { if (headResponse != null) { headResponse.Close(); headResponse = null; } if (headRequest != null) { headRequest.Abort(); headRequest = null; } } }, headRequest); tick1 = System.Environment.TickCount; } catch (WebException webEx) { DebugEx.LogWarning("Request File Head ERROR: " + mRemoteFile + ""); DebugEx.LogWarning("ERROR: " + webEx); mHadError = true; gDownloadIsRunningCount--; done = true; yield break; } catch (System.Exception e) { DebugEx.LogWarning("Request File Head ERROR: " + mRemoteFile + ""); DebugEx.LogWarning("ERROR: " + e); mHadError = true; gDownloadIsRunningCount--; done = true; yield break; } while (!headRequestOk && !mHadError) { if (stop) { yield break; } if (processErroring) { mHadError = true; break; } float dur = System.Environment.TickCount - tick1; if (dur > timeOut) { DebugEx.LogWarningFormat("获取远程文件{0} 信息超时!", mRemoteFile); mHadError = true; break; } yield return null; } if (mHadError) { DebugEx.LogWarningFormat("获取远程文件{0} 信息失败!", mRemoteFile); if (headRequest != null) { headRequest.Abort(); headRequest = null; } done = true; gDownloadIsRunningCount--; yield break; } //判断是否有已经下载部分的临时文件 if (File.Exists(mLocalFileTemp)) { // This will not work in web player! //判断是否断点续传, 依据临时文件是否存在,以及修改时间是否小于服务器文件时间 #if !UNITY_WEBPLAYER localFileSize = (File.Exists(mLocalFileTemp)) ? (new FileInfo(mLocalFileTemp)).Length : 0L; #endif bool outDated = IsOutdated; if (localFileSize == mRemoteFileSize && !outDated) { gDownloadIsRunningCount--; mHadError = !Move(mLocalFileTemp, localFile);//把临时文件改名为正式文件 done = true; yield break; // We already have the file, early out } else if (localFileSize > mRemoteFileSize || outDated) { if (!outDated) DebugEx.LogWarning("Local file is larger than remote file, but not outdated. PANIC!"); if (outDated) { DebugEx.LogWarning(mLocalFileTemp + " Local file is outdated, deleting"); } try { if (File.Exists(mLocalFileTemp)) File.Delete(mLocalFileTemp); } catch (System.Exception e) { DebugEx.LogWarning("Could not delete local file"); DebugEx.LogError(e); } while (File.Exists(mLocalFileTemp)) { if (stop) { yield break; } yield return null; } localFileSize = 0; } } if (mHadError) { gDownloadIsRunningCount--; done = true; yield break; } if (File.Exists(localFile)) { try { if (File.Exists(localFile)) File.Delete(localFile); } catch (System.Exception e) { DebugEx.LogWarning("Could not delete local file"); DebugEx.LogWarning(e); } while (File.Exists(localFile)) { if (stop) { yield break; } yield return null; } } #if UseWebClient //|| UNITY_IOS using (WebClient client = new WebClient()) { client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted); client.DownloadFileAsync(new Uri(mRemoteFile), mLocalFileTemp); } while (!done) { yield return null; } mHadError = !Move(mLocalFileTemp, localFile);//把临时文件改名为正式文件 #else try { fileRequest = (HttpWebRequest)HttpWebRequest.Create(mRemoteFile); if (fileRequest.ServicePoint.ConnectionLimit < RemoteFile.MaxConnectLimit) { fileRequest.ServicePoint.ConnectionLimit = RemoteFile.MaxConnectLimit; } fileRequest.ServicePoint.Expect100Continue = false; fileRequest.Timeout = 3000; fileRequest.Proxy = null; fileRequest.KeepAlive = false; if (localFileSize != 0L && isAcceptRange) { fileRequest.AddRange((int)localFileSize, (int)mRemoteFileSize - 1); } #if !UNITY_WEBPLAYER fileRequest.Method = WebRequestMethods.Http.Get; #endif fileRequest.BeginGetResponse(AsynchCallback, fileRequest); tick1 = System.Environment.TickCount; } catch (System.Exception ex) { DebugEx.LogWarning("BeginGetResponse exception: " + ex.Message); DebugEx.LogWarning(ex); if (fileRequest != null) { fileRequest.Abort(); fileRequest = null; } mHadError = true; done = true; gDownloadIsRunningCount--; yield break; } while (fileResponse == null && !mHadError) { // Wait for asynch to finish if (stop) { yield break; } if (processErroring) { mHadError = true; break; } float dur = System.Environment.TickCount - tick1; if (dur > timeOut) { DebugEx.LogWarningFormat("下载远程文件{0} 超时!", mRemoteFile); mHadError = true; break; } yield return null; } if (mHadError) { DebugEx.LogWarningFormat("[RemoteFile] 远程文件{0} 下载失败! ", localFile); if (fileRequest != null) { fileRequest.Abort(); fileRequest = null; } if (fileResponse != null) { fileResponse.Close(); fileResponse = null; } done = true; gDownloadIsRunningCount--; yield break; } try { inStream = fileResponse.GetResponseStream(); fs = new FileStream(mLocalFileTemp, (localFileSize > 0) ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.ReadWrite); if (buff == null) { buff = new byte[bufferSize]; } fileWriteState = FileWriteState.Writting; inStream.BeginRead(buff, 0, bufferSize, ReadDataCallback, null); read_Stream_startTickcount = System.Environment.TickCount; } catch (Exception ex) { DebugEx.LogWarning("ERROR: " + mRemoteFile + ""); DebugEx.LogWarning(ex); if (inStream != null) { inStream.Close(); inStream = null; } if (fs != null) { fs.Close(); fs = null; } if (fileResponse != null) { fileResponse.Close(); fileResponse = null; } mHadError = true; fileWriteState = FileWriteState.Error; } while (fileWriteState == FileWriteState.Writting) { if (stop) { yield break; } if (processErroring) { fileWriteState = FileWriteState.Error; break; } if (downloadSpeedRef == 0) { int dura = System.Environment.TickCount - read_Stream_startTickcount; if (dura > timeOut) { fileWriteState = FileWriteState.Timeout; DebugEx.LogWarningFormat("[RemoteFile] 远程文件{0} 读取超时{1}!", mRemoteFile, dura); break; } } yield return null; } if (fileRequest != null) { fileRequest.Abort(); fileRequest = null; } if (fileWriteState == FileWriteState.Error || fileWriteState == FileWriteState.Timeout) { DebugEx.LogWarningFormat("[RemoteFile] 远程文件{0} 下载失败! ", localFile); if (fileResponse != null) { fileResponse.Close(); fileResponse = null; } gDownloadIsRunningCount--; mHadError = true; done = true; yield break; } try { FileInfo localTempFileInfo = new FileInfo(mLocalFileTemp); if (localTempFileInfo.Exists) { //临时文件存在,需要判断大小是否一致 //判断临时文件和远程文件size是否一致 if (localTempFileInfo.Length != mRemoteFileSize && mRemoteFileSize != 0L) { mHadError = true; DebugEx.LogError(string.Format(localFile + " 下载完成后, 但是大小{0} 和远程文件不一致 {1}", localTempFileInfo.Length, mRemoteFileSize)); } else { //大小一致 mHadError = !Move(mLocalFileTemp, localFile);//把临时文件改名为正式文件 } } else { //临时文件不存在 mHadError = true; } #endif } catch (Exception ex) { DebugEx.LogError(ex); mHadError = true; } yield return null; gDownloadIsRunningCount--; done = true; } bool IsOutdated { get { if (File.Exists(mLocalFileTemp)) return mRemoteLastModified > mLocalLastModified; return false; } } enum FileWriteState { None, Writting, Completed, Error, Timeout, } Stream inStream; FileStream fs; FileWriteState fileWriteState = FileWriteState.None; //下载文件写入状态 void ReadDataCallback(IAsyncResult ar) { if (stop) { return; } try { if (inStream != null) { int read = inStream.EndRead(ar); lock (lockObj) { gTotalDownloadSize += read; } if (read > 0) { fs.BeginWrite(buff, 0, read, WriteDataCallBack, fs); } else { if (fs != null) { fs.Close(); fs = null; } if (inStream != null) { inStream.Close(); inStream = null; } if (fileResponse != null) { fileResponse.Close(); fileResponse = null; } fileWriteState = FileWriteState.Completed; } } } catch (Exception ex) { if (!processErroring) { DebugEx.LogWarning(ex); DebugEx.LogWarning("ReadDataCallback 异常信息: " + ex.Message); } if (fs != null) { fs.Close(); fs = null; } if (inStream != null) { inStream.Close(); inStream = null; } fileWriteState = FileWriteState.Error; } } void WriteDataCallBack(IAsyncResult _asyncResult) { fs.Flush(); inStream.BeginRead(buff, 0, bufferSize, new AsyncCallback(ReadDataCallback), null); read_Stream_startTickcount = System.Environment.TickCount; } #if UseWebClient //|| UNITY_IOS protected void DownloadCompleted(System.Object sender, AsyncCompletedEventArgs e) { done = true; } #else // Throwind an exception here will not propogate to unity! protected void AsynchCallback(IAsyncResult result) { try { if (result == null) { DebugEx.LogError("Asynch result is null!"); mHadError = true; } HttpWebRequest webRequest = (HttpWebRequest)result.AsyncState; if (webRequest == null) { DebugEx.LogError("Could not cast to web request"); mHadError = true; } fileResponse = webRequest.EndGetResponse(result) as HttpWebResponse; if (fileResponse == null) { DebugEx.LogError("Asynch response is null!"); mHadError = true; } } catch (Exception ex) { mHadError = true; DebugEx.LogWarning(ex); DebugEx.LogWarning("[RemoteFile] AsynchCallback 异常: " + ex.Message); } } #endif public enum State { Wait, Working, Stoped, } }