unity http multithread download

base http protocal  & Loom 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using UnityEngine;
using System.IO;
using System.Threading;


public class HttpDownloadItem
{
const int MBYTES = 1024 * 1024;


public enum State
{
INITIAL,
DOWNLOADING,
ERROR,
COMPLETE
}


public string m_URL;
public string m_StoragePath;
public State m_State;
public long m_FileLength;


int m_DownloadedBytesCount;
long m_ThreadsPreDownloadBytes;
FileStream m_FileStream;


object m_WriteLock;
bool m_Disposed;
long m_StartIndex;
int m_ThreadsNum;
AutoResetEvent m_AutoReset = new AutoResetEvent(false);


public HttpDownloadItem(string url, string storagePath)
{
m_URL = url;
m_StoragePath = storagePath;
m_State = State.INITIAL;
m_WriteLock = new object();
}


public void Reset()
{
m_State = State.INITIAL;
}


public void Start()
{
m_State = State.DOWNLOADING;
Loom.RunAsync(() => {
InitDownloadLength();
StartDownload();
});
}


public void Dispose()
{
m_Disposed = true;
if (m_FileStream != null)
m_FileStream.Close();
}


void InitDownloadLength()
{
try
{
var request = WebRequest.GetHttpWebRequest(m_URL);
request.Timeout = 10000;
request.SendChunked = false;
request.Method = WebRequestMethods.Http.Head;
var response = request.GetResponse();
m_FileLength = response.ContentLength;
m_FileStream = File.Open(m_StoragePath, FileMode.OpenOrCreate, FileAccess.Write);
m_FileStream.SetLength(0);
response.Close();
request.Abort();
}
catch (Exception e)
{
m_State = State.ERROR;
MainThreadLog(e.ToString());
m_FileStream.Close();
throw e;
}
}


void StartDownload()
{
while (m_FileLength - m_ThreadsPreDownloadBytes > 0)
{
if (m_Disposed)
break;


if (Loom.Current.IsThreadsMax())
{
if (m_ThreadsNum > 0)
m_AutoReset.WaitOne();


continue;
}


long length = 0;
var remain = m_FileLength - m_ThreadsPreDownloadBytes;
if (remain > MBYTES)
{
m_ThreadsPreDownloadBytes += MBYTES;
length = MBYTES;
}
else
{
m_ThreadsPreDownloadBytes += remain;
length = remain;
}


var start = m_StartIndex;
Interlocked.Increment(ref m_ThreadsNum);
Loom.RunAsync(() => Down((int)start, (int)length));
m_StartIndex += length;
}


while (m_ThreadsNum > 0)
continue;


CompleteDownload();
}


void Down(int startIndex, int length, int retryTime = 0)
{
if (retryTime > 0)
MainThreadLog("下载线程重试 : " + retryTime.ToString());


if (retryTime == 3)
{
m_State = State.ERROR;
throw new Exception("one thread download three times fail.");
}


try
{
if (m_Disposed)
return;


var request = WebRequest.GetHttpWebRequest(m_URL);
request.Timeout = 10000;
request.AddRange(startIndex, startIndex + length - 1);
request.ServicePoint.ConnectionLimit = int.MaxValue;
request.SendChunked = false;
request.Method = WebRequestMethods.Http.Get;
var response = request.GetResponse();
var stream = response.GetResponseStream();
stream.ReadTimeout = 10000;
var buffer = new byte[MBYTES];
var count = 0;
var totalCount = 0;
while ((count = stream.Read(buffer, 0, buffer.Length)) > 0)
{
lock (m_WriteLock)
{
m_FileStream.Seek(startIndex + totalCount, SeekOrigin.Begin);
m_FileStream.Write(buffer, 0, count);
m_DownloadedBytesCount += count;
}


UnityEngine.Debug.LogError((float)m_DownloadedBytesCount / (MBYTES));
totalCount += count;
if (totalCount >= length)
break;
}


stream.Close();
response.Close();
request.Abort();
}
catch (Exception e)
{
MainThreadLog(e.ToString());
Down(startIndex, length, retryTime + 1);
}
finally
{
m_AutoReset.Set();
Interlocked.Decrement(ref m_ThreadsNum);
}
}


void CompleteDownload()
{
try
{
m_FileStream.Flush();


if (m_DownloadedBytesCount == m_FileLength)
{
m_State = State.COMPLETE;
UnityEngine.Debug.Log("Download OK : " + m_URL);
}
else
{
UnityEngine.Debug.Log("Download ERROR : " + m_URL);
m_State = State.ERROR;
}
}
finally
{
Dispose();
}
}


void MainThreadLog(string s)
{
Loom.QueueOnMainThread(() => {
UnityEngine.Debug.LogError(string.Format("download exception : {0} \n URL : {1}", s, m_URL));
});
}
}


public class HttpDownload
{
Dictionary<string, string> m_URLAndPaths;
Queue<HttpDownloadItem> m_DownloadQueue;
Action m_OnComplete;
Action m_OnError;
bool m_Disposed;


public HttpDownload(Dictionary<string, string> urlAndPaths, Action onComplete, Action onError)
{
m_URLAndPaths = urlAndPaths;
m_DownloadQueue = new Queue<HttpDownloadItem>();
foreach (var pair in urlAndPaths)
{
var item = new HttpDownloadItem(pair.Key, pair.Value);
m_DownloadQueue.Enqueue(item);
}


m_OnComplete = onComplete;
m_OnError = onError;
}


~HttpDownload()
{
Dispose();
}


public void Dispose()
{
m_Disposed = true;
m_URLAndPaths = null;
}


public void Retry()
{
if (m_DownloadQueue.Count <= 0)
return;


var item = m_DownloadQueue.Peek();
item.Reset();
Run();
}


public void Run()
{
Loom.RunAsync(() => {
while (true)
{
// 队列为空,结束下载
if (m_DownloadQueue.Count == 0)
{
Loom.QueueOnMainThread(() => {
if (m_OnComplete != null)
m_OnComplete();
});
break;
}


if (m_Disposed)
{
foreach (var t in m_DownloadQueue)
t.Dispose();


break;
}


var item = m_DownloadQueue.Peek();
switch (item.m_State)
{
case HttpDownloadItem.State.INITIAL:
item.Start();
break;
case HttpDownloadItem.State.COMPLETE:
m_DownloadQueue.Dequeue();
break;
case HttpDownloadItem.State.ERROR:
Loom.QueueOnMainThread(()=> {
if (m_OnError != null)
m_OnError();
});
Debug.LogError("One Download Item Error");
return;
}
}
});
}
}

 

 

posted @ 2020-04-29 16:28  liuchong9123  阅读(347)  评论(0编辑  收藏  举报