• unity-多线程异步下载HttpWebRequest



    title: unity-多线程异步下载HttpWebRequest
    categories: Unity3d
    tags: [unity, 多线程, 异步, 下载]
    date: 2022-07-02 14:52:38
    comments: false
    mathjax: true
    toc: true

    unity-多线程异步下载HttpWebRequest


    前篇

    • 官方 - https://docs.microsoft.com/zh-cn/dotnet/framework/network-programming/making-asynchronous-requests

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

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

    aaa


    代码

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

      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;
                  }
              }
          }
      }
      
      • 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
    • lua 测试代码

      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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

  • 相关阅读:
    通俗易懂的Docker (一篇文章就让你玩转)
    @zabbix监控设备网络设备温度Temp及告警配置 (sensor)
    【刷题之路 | Java & Python】两数之和(暴力枚举&哈希表)
    张益唐与黎曼猜想
    网络安全——XSS跨站脚本攻击
    [C/C++]天天酷跑超详细教程-中篇
    基于ZYNQ的PCIE高速数据采集卡的设计(二)总体设计与上位机
    【Push Kit】模拟服务端发送消息至客户端,测试消息发送功能(华为推送服务)
    JVM虚拟机字节码执行引擎——类文件和类加载之前必看
    Netty6-快速入门HTTP服务
  • 原文地址:https://blog.csdn.net/yangxuan0261/article/details/125573130