unity-多线程异步下载HttpWebRequest
unity-多线程异步下载HttpWebRequest
前篇
- 官方 - https://docs.microsoft.com/zh-cn/dotnet/framework/network-programming/making-asynchronous-requests
使用的是 .net 里面的网络库 HttpWebRequest, 应用场景是需要同时下载多个小文件时, 效果很明显, 例如: 散文件热更.
效果如下, 最终下载完大小是 31,715KB
代码
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
210using 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;
// 透传参数
[ ]
public bool isDone = false;
[ ]
public int code;
[ ]
public HttpWebRequest httpReq;
[ ]
public HttpWebResponse httpRsp;
[ ]
public byte[] buffer;
[ ]
public Stream rspStream;
[ ]
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
11function 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