Unity開発(b)マルチスレッドHTTPファイルダウンロードマネージャー(下)



Unity Development Multi Threaded Http File Download Manager



ファイルのダウンロードプロセス

ファイルのダウンロードについては、正確性と効率性を確保するために、次のプロセスを設計しました

開始ファイル長のダウンロードファイルのダウンロードMD5チェックサム完了した解凍ファイルのダウンロード

この記事にはunzip filesは含まれていませんが、独自の読者を修正する需要を刺激するためです



ネットワーク選択

ヒント:なぜ選択したのですかHttpWebRequest

UnityはWWW with UnityWebRequestネットワークソリューションを提供しますが、どちらもコルーチン(またはメインプロセスの単一性)に基づいており、次に実行するスレッドには適用されません。したがって、ネイティブC#HttpWebRequestオンラインHttpWebRequest解析のみが可能であり、詳細には説明されていません。



スレッド内のダウンロードプロセス全体が直接使用されるためHttpWebRequest同期インターフェース(ブロッキング)、結果が返されるのを待ちます。

HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest HttpWebResponse respone = (HttpWebResponse)request.GetResponse() Stream ns = respone.GetResponseStream() int readSize = ns.Read(bytes, 0, oneReadLen) // read the first data

同期インターフェース(ブロッキング)を使用します。必ずタイムアウトメカニズムを設定してください。そうしないと、デフォルトのタイムアウトメカニズムで長時間または効果が得られません。

HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest request.Timeout = TimeOutWait request.ReadWriteTimeout = ReadWriteTimeOut

HTTPダウンロードロジック

HTTPにはさまざまな方法があります。たとえば、Thunderのダウンロードでは、4 MBの大きなファイルを細かく分割し、別のスレッド(p2p)で対応するブロックをダウンロードします。次に、すべてのブロックのダウンロードが完了し、チェックして、大きなファイル全体をダウンロードします。ダウンロードが完了しました。
ここに含まれるシナリオはシングルパス(CDN)にダウンロードされ、複雑なロジックがダウンロードを続行するための機能である必要はありません。
FIGのダウンロードロジックは次のとおりです。



はい しない 最大 十分でない 開始一時ファイルが存在します固定長のダウンロード一時ファイルの長さ0を作成します一時ファイルの書き込みテール一時ファイルの長さ一時ファイルの名前を変更します公式ドキュメントの名前ですダウンロードが完了しました

ヒント:ファイル全体をダウンロードしてから、ファイル全体を書き直してみませんか?

多くの暴力的なゲームの慣習であるファイル全体を直接書き直しますが、多くの問題を引き起こします

  1. 総会でのメモリを大量に消費するファイル
  2. ファイルが大きいほど、失敗の可能性が高くなります。
  3. プロセス/スレッドの偶発的な死亡、ただしファイルのサイズが半分しかない(ファイルが破損している)
  4. ネットワークトラフィックの浪費、再ダウンロードの必要性を再開

ヒント:固定長のダウンロードはすべて?

各ファイルブロックサイズのファイルシステムは、一般的に4KB(異なるファイルシステムは同じではない場合があります)であり、最大の効率を達成できるように、4KBの整数倍であることが好ましい。ここで定義const int oneReadLen = 16 * 1024、妥協的なアプローチであり、読者はオンデマンドで独自に定義できます。

ヒント:HTTPはどのように達成するのですか?

ダウンロードスレッドがファイルを再ダウンロードするときに、一時ファイルで対応するものがあるかどうかを判断し、一時ファイルの長さを取得してから、現在の長さからダウンロードして、HTTPを実現できます。

一部の読者は、IO書き込みファイル書き込みの半分の方法、ファイルの名前変更の失敗、ネットワークの切断方法を尋ねる場合があります。

do while

例外処理-ダウンロードロジックループ

例外をダウンロードします。これはごく普通の現象であり、例外を処理することは常に厄介な問題でした。このホワイトペーパーのソリューションは、成功するまでサイクルダウンロードロジックを呼び出しています。これを実現するには、次の条件を満たす必要があります。

  1. The site can be restored --http
  2. Abnormal capture and processing
  3. Cycle start the download process
    ポイント1は、HTTPソリューションを使用して上記で明確に説明されています
    ポイント2、主にtry ... catch ...を使用して、最終的にインターセプトと論理的決定を行います。以下の特定のコードを参照してください。
    ポイント3、サイクルはダウンロードを開始し、 前の記事 論理
if(mac._state == DownloadMacState.Complete) { lock(_lock) { _completeList.Add(mac._downUnit) _runningList[Thread.CurrentThread] = null } } else if (mac._state == DownloadMacState.Error) { lock (_lock) {// download failed, is put repeatedly download queue, continue to the next download _readyList.Enqueue(mac) // fail to prevent frequent callbacks, only a certain number of callbacks if(mac.IsNeedErrorCall()) _errorList.Add(mac) } break } else { ThreadDebugLog.Log('Error DownloadMacState ' + mac._state + ' ' + mac._downUnit.name) break }

MD5チェックサム

MD5チェックサムは難しいことではありません。オンラインには多くの方法があります。ここでは少し最適化を行います。

while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0) { // calculate MD5 hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0) }

ファイルをフェッチし、メモリを節約します。

コード統合

using System using System.Collections.Generic using System.IO using System.Linq using System.Net using System.Text public enum DownloadMacState { None, ResetSize, Download, Md5, Complete, Error, } public class DownloadFileMac { const int oneReadLen = 16 * 1024 // a read length 16384 = 16 * kb const int Md5ReadLen = 16 * 1024 // a read length 16384 = 16 * kb const int ReadWriteTimeOut = 2 * 1000 // Timeout waiting time const int TimeOutWait = 5 * 1000 // Timeout waiting time public DownloadUnit _downUnit public int _curSize = 0 public int _allSize = 0 public DownloadMacState _state = DownloadMacState.None public int _tryCount = 0 // attempts public string _error = '' public DownloadFileMac(DownloadUnit downUnit) { _downUnit = downUnit } // fail to prevent frequent callbacks, only a certain number of callbacks public bool IsNeedErrorCall() _tryCount == 100) return true return false public void Run() { _tryCount++ _state = DownloadMacState.ResetSize if (!ResetSize()) return _state = DownloadMacState.Download if (!Download()) return _state = DownloadMacState.Md5 if (!CheckMd5()) // check fails, the next heavy { _state = DownloadMacState.Download if (!Download()) return _state = DownloadMacState.Md5 if (!CheckMd5()) return // failed twice, the file in question } _state = DownloadMacState.Complete } private bool ResetSize() { if(_downUnit.size <= 0) { _downUnit.size = GetWebFileSize(_downUnit.downUrl) if (_downUnit.size == 0) return false } _curSize = 0 _allSize = _downUnit.size return true } private bool CheckMd5() { if (string.IsNullOrEmpty(_downUnit.md5)) return true // do verification, the default success string md5 = GetMD5HashFromFile(_downUnit.savePath) if (md5 != _downUnit.md5) { File.Delete(_downUnit.savePath) ThreadDebugLog.Log('File MD5 checksum error:' + _downUnit.name) _state = DownloadMacState.Error _error = 'Check MD5 Error ' return false } return true } public bool Download() { // Open last download file long startPos = 0 string tempFile = _downUnit.savePath + '.temp' FileStream fs = null if (File.Exists(_downUnit.savePath)) { // file already exists, skip //ThreadDebugLog.Log('File is Exists ' + _downUnit.savePath) _curSize = _downUnit.size return true } else if (File.Exists(tempFile)) { fs = File.OpenWrite(tempFile) startPos = fs.Length fs.Seek(startPos, SeekOrigin.Current) // current file pointer movement stream // file has been downloaded, did not change the name, end if (startPos == _downUnit.size) { fs.Flush() fs.Close() fs = null if (File.Exists(_downUnit.savePath)) File.Delete(_downUnit.savePath) File.Move(tempFile, _downUnit.savePath) _curSize = (int)startPos return true } } else { string direName = Path.GetDirectoryName(tempFile) if (!Directory.Exists(direName)) Directory.CreateDirectory(direName) fs = new FileStream(tempFile, FileMode.Create) } // logic download HttpWebRequest request = null HttpWebResponse respone = null Stream ns = null try { request = WebRequest.Create(_downUnit.downUrl) as HttpWebRequest request.ReadWriteTimeout = ReadWriteTimeOut request.Timeout = TimeOutWait if (startPos > 0) request.AddRange((int)startPos) // set the Range value, HTTP // request to the server to get the server response data stream respone = (HttpWebResponse)request.GetResponse() ns = respone.GetResponseStream() ns.ReadTimeout = TimeOutWait long totalSize = respone.ContentLength long curSize = startPos if (curSize == totalSize) { fs.Flush() fs.Close() fs = null if (File.Exists(_downUnit.savePath)) File.Delete(_downUnit.savePath) File.Move(tempFile, _downUnit.savePath) _curSize = (int)curSize } else { byte[] bytes = new byte[oneReadLen] int readSize = ns.Read(bytes, 0, oneReadLen) // read the first data while (readSize > 0) { fs.Write(bytes, 0, readSize) // will download the data written to the temporary file curSize += readSize // determine whether the download is complete // the temp file download is complete, change official documents if (curSize == totalSize) { fs.Flush() fs.Close() fs = null if (File.Exists(_downUnit.savePath)) File.Delete(_downUnit.savePath) File.Move(tempFile, _downUnit.savePath) } // callback about _curSize = (int)curSize // Continue reading down readSize = ns.Read(bytes, 0, oneReadLen) } } } catch (Exception ex) { // failed to download, delete temporary files if (fs != null) { fs.Flush() fs.Close() fs = null } if (File.Exists(tempFile)) File.Delete(tempFile) if (File.Exists(_downUnit.savePath)) File.Delete(_downUnit.savePath) ThreadDebugLog.Log('Download error:' + ex.Message) _state = DownloadMacState.Error _error = 'Download Error ' + ex.Message } finally { if (fs != null) { fs.Flush() fs.Close() fs = null } if (ns != null) { ns.Close() ns = null } if (respone != null) { respone.Close() respone = null } if (request != null) { request.Abort() request = null } } if (_state == DownloadMacState.Error) return false return true } private int GetWebFileSize(string url) { HttpWebRequest request = null WebResponse respone = null int length = 0 try { request = WebRequest.Create(url) as HttpWebRequest request.Timeout = TimeOutWait request.ReadWriteTimeout = ReadWriteTimeOut // request to the server to get the server response data stream respone = request.GetResponse() length = (int)respone.ContentLength } catch (WebException e) { ThreadDebugLog.Log('Get file length error:' + e.Message) _state = DownloadMacState.Error _error = 'Request File Length Error ' + e.Message } finally { if (respone != null) { respone.Close() respone = null } if (request != null) { request.Abort() request = null } } return length } private string GetMD5HashFromFile(string fileName) { byte[] buffer = new byte[Md5ReadLen] int readLength = 0// read each length var output = new byte[Md5ReadLen] using (Stream inputStream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { using (System.Security.Cryptography.HashAlgorithm hashAlgorithm = new System.Security.Cryptography.MD5CryptoServiceProvider()) { while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0) { // calculate MD5 hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0) } // completion of the final calculation, you must call (due on a cycle has been completed for all operations, so when this method is called two parameters are 0) hashAlgorithm.TransformFinalBlock(buffer, 0, 0) byte[] retVal = hashAlgorithm.Hash System.Text.StringBuilder sb = new System.Text.StringBuilder(32) for (int i = 0 i < retVal.Length i++) { sb.Append(retVal[i].ToString('x2')) } hashAlgorithm.Clear() inputStream.Close() return sb.ToString() } } } }

ここまでMulti-threaded HTTP file download managerすべて終了しました。