unity-多线程异步下载HttpWebRequest

unity-多线程异步下载HttpWebRequest


前篇

使用的是 .net 里面的网络库 HttpWebRequest, 应用场景是需要同时下载多个小文件时, 效果很明显, 例如: 散文件热更.

效果如下, 最终下载完大小是 31,715KB

aaa


代码

  • csharp 代码, 里面包含 unity 和 tolua 相关, 自行去掉即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Text;
    using LuaInterface;
    using UnityEngine;

    public enum EMDErr : int {
    CreateRequest = -10001,
    GetResponse = -10002,
    NullResponse = -10003,
    ReadStream = -10004,
    }

    public class MultiDownObj {
    public string url;
    public string path;
    public LuaFunction luaFn;

    // 透传参数
    [NoToLua]
    public bool isDone = false;
    [NoToLua]
    public int code;
    [NoToLua]
    public HttpWebRequest httpReq;
    [NoToLua]
    public HttpWebResponse httpRsp;
    [NoToLua]
    public byte[] buffer;
    [NoToLua]
    public Stream rspStream;
    [NoToLua]
    public FileStream fs;
    }

    // 多线程异步下载
    public class MultiDownMgr : MonoBehaviour {
    private static MultiDownMgr _instance;
    public static MultiDownMgr Instance {
    get { return _instance; }
    }

    private const int BufferSize = 1024;

    private AsyncCallback rspCb = null;
    private AsyncCallback readCb = null;

    private List<MultiDownObj> objList = new List<MultiDownObj>();

    public int timeout = 10000; // 10s 超时
    public bool keepAlive = true;
    public int connectLimit {
    set { ServicePointManager.DefaultConnectionLimit = value; } // 并发线程数量
    }

    void Awake() {
    _instance = this;

    rspCb = new AsyncCallback(ResponseCb);
    readCb = new AsyncCallback(ReadDataCb);

    connectLimit = 32; // 默认 32 个并发
    }

    // lua 接口
    public void Request(string url, string path, LuaFunction fn) {
    // LogUtil.D("--- Request, url: {0}, path: {1}", url, path);
    MultiDownObj mdObj = new MultiDownObj();
    mdObj.url = url;
    mdObj.path = path;
    mdObj.luaFn = fn;
    RequestObj(mdObj);
    }

    void RequestObj(MultiDownObj mdObj) {
    objList.Add(mdObj);
    try {
    mdObj.httpReq = WebRequest.Create(mdObj.url) as HttpWebRequest;
    mdObj.httpReq.Method = "GET";
    mdObj.httpReq.Timeout = timeout;
    mdObj.httpReq.KeepAlive = keepAlive; // 设置为 false 会导致中断下载, 报错: Remote prematurely closed connection.
    mdObj.httpReq.BeginGetResponse(rspCb, mdObj);
    } catch (System.Exception ex) {
    Close(mdObj);
    mdObj.code = (int) EMDErr.CreateRequest;
    mdObj.path = ex.Message;
    mdObj.isDone = true;
    }
    }

    void ResponseCb(IAsyncResult ar) {
    MultiDownObj mdObj = ar.AsyncState as MultiDownObj;
    try {
    HttpWebResponse response = mdObj.httpReq.EndGetResponse(ar) as HttpWebResponse;
    if (response == null) {
    Close(mdObj);
    mdObj.code = (int) EMDErr.NullResponse;
    mdObj.isDone = true;
    return;
    }

    mdObj.httpRsp = response;
    mdObj.code = (int) response.StatusCode;
    mdObj.rspStream = response.GetResponseStream();
    if (response.StatusCode != HttpStatusCode.OK) {
    Close(mdObj);
    mdObj.isDone = true;
    return;
    }

    // 创建父目录
    string dirPath = System.IO.Path.GetDirectoryName(mdObj.path);
    if (!Utils.IsDirectoryExist(dirPath)) {
    Utils.CreateDirectory(dirPath);
    }

    mdObj.fs = new FileStream(mdObj.path, FileMode.Create);
    mdObj.buffer = new byte[BufferSize];
    mdObj.rspStream.BeginRead(mdObj.buffer, 0, BufferSize, readCb, mdObj);
    } catch (System.Exception ex) {
    Close(mdObj);
    mdObj.code = (int) EMDErr.GetResponse;
    mdObj.path = ex.Message;
    mdObj.isDone = true;
    }
    }

    void ReadDataCb(IAsyncResult ar) {
    MultiDownObj mdObj = ar.AsyncState as MultiDownObj;
    try {
    int read = mdObj.rspStream.EndRead(ar);
    if (read > 0) {
    mdObj.fs.Write(mdObj.buffer, 0, read);
    mdObj.fs.Flush();
    mdObj.rspStream.BeginRead(mdObj.buffer, 0, BufferSize, readCb, mdObj);
    } else {
    Close(mdObj);
    mdObj.isDone = true;
    }
    } catch (System.Exception ex) {
    Close(mdObj);
    mdObj.code = (int) EMDErr.ReadStream;
    mdObj.path = ex.Message;
    mdObj.isDone = true;
    return;
    }

    }

    // 释放资源
    void Close(MultiDownObj mdObj) {
    if (mdObj == null)
    return;

    if (mdObj.fs != null) {
    mdObj.fs.Close();
    mdObj.fs = null;
    }

    if (mdObj.rspStream != null) {
    mdObj.rspStream.Close();
    mdObj.rspStream = null;
    }

    if (mdObj.httpRsp != null) {
    mdObj.httpRsp.Close();
    mdObj.httpRsp = null;
    }

    if (mdObj.httpReq != null) {
    mdObj.httpReq.Abort();
    mdObj.httpReq = null;
    }

    mdObj.buffer = null;
    }

    public void StopDown(string url) {
    for (int i = 0; i < objList.Count; ++i) {
    if (objList[i].url == url) {
    objList.RemoveAt(i);
    i -= 1;
    }
    }
    }

    public void StopAll() {
    objList.Clear();
    }

    private void Update() {
    for (int i = 0; i < objList.Count; ++i) {
    MultiDownObj mdObj = objList[i];
    if (mdObj.isDone) {
    // LogUtil.D("--- mdObj done, code: {0}, path: {1}", mdObj.code, mdObj.path);
    if (mdObj.luaFn != null) {
    mdObj.luaFn.Call(mdObj.code, mdObj.url, mdObj.path);
    mdObj.luaFn.Dispose();
    mdObj.luaFn = null;
    }

    objList.RemoveAt(i);
    i -= 1;
    }
    }
    }
    }
  • lua 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function gDebugCustom.MultiDown()
    local function downFn(code, url, path)
    gLog("--- code: {0}, url: {1}, path: {2}", code, url, path)
    end

    for i=1,5 do
    local url = "https://www.aaa.com/download/game.apk"
    local path = gTool.PathJoin(Application.persistentDataPath, string.formatExt("apks/aaa-{0}.apk", i))
    MultiDownMgr.Instance:Request(url, path, downFn);
    end
    end