学Unity的猫之文件上传下载续传(十四)
14.1 猫后爪的秘密
我:“皮皮,经常看到你举着后爪像天线一样,是在发信号吗?”皮皮慌张地收起后爪:“你能收到我发的信号?”
我:“哈哈哈,不能,但是我很好奇你们发信号的原理。”
皮皮:“这是喵星机密,不能告诉你!”
我:“那你们可以收发文件吗?”
皮皮:“不能,我们猫体内没有文件系统,所以不能收发文件。”
我:“那我今天教你实现文件传输吧。”
14.2 文件上传下载
文件上传文件下载(支持断点续传)
本工程以上传到
Github
,感兴趣的同学可以下载下来学习。Github
地址:https://github.com/linxinfa/UnityWebRequestDemo
14.3 UnityWebRequest与WWW
在Unity
中,我们可以用系统的WWW
或者HttpWebRequest
来实现文件的下载。 因为WWW
不存在设置timeout
属性,因此当我们网络不好请求超时的时候,无法简单的做出判断。 当网络极差的时候,游戏下载将会停止(即一直在等待yield return www
)当时间较长时网络恢复将无法继续下载,也没有提示,需要重启才能重新下载。Unity
早在5.4
版本的时候就出了新的API UnityWebRequest
用于替代WWW
。 有些较大的文件下载需要断点续传的功能(即下载了一部分突然中断下载后,再次下载直接从上次下载的地方继续下载,而不是重新下载),就需要使用HttpWebRequest
或UnityWebRequest
。
注:
WWW
在UnityEngine
命名空间下HttpWebRequest
在System.Net
命名空间下UnityWebRequest
在UnityEngine.Networking
命名空间下
14.4 UnityWebRequest常用方法与属性介绍
14.4.1 概述
UnityWebRequest
支持与上传,下载及断点续传功能。
UnityWebRequest
由三个元素组成: 1 UploadHandler
处理数据将数据上传到服务器的对象; 2 DownloadHandler
从服务器下载数据的对象; 3 UnityWebRequest
负责与HTTP
通信并管理上面两个对象。
常用方法
| 方法 | 作用 |
| - | - |
| SendWebRequest() | 开始与远程服务器通信。在调用此方法之后,有必要的话UnityWebRequest
将执行DNS
解析,将HTTP
请求发送到目标URL的远程服务器并处理服务器的响应。 |
| Get(url) | 创建一个HTTP
为传入URL
的UnityWebRequest
对象 |
| Post(url) | 向Web
服务器发送表单信息 |
| Put(url) | 将数据上传到 Web
服务器 |
| Abort() | 直接结束联网 |
| Head() | 创建一个为传输HTTP
头请求的 UnityWebRequest
对象 |
| GetResponseHeader() | 返回一个字典,内容为在最新的HTTP
响应中收到的所有响应头 |
14.4.2 构造函数
public UnityWebRequest();
public UnityWebRequest(Uri uri);
public UnityWebRequest(Uri uri,string method);
public UnityWebRequest(Uri uri,string method,Networking.DownloadHandler downloadHandler,
Networking.UploadHandler uploadHandler);
**参数:**解释一下参数的含义:
| 参数 | 含义 |
| - | - |
| method
| 相当于方法名,只有GET, POST, PUT, HEAD
四种,默认为GET
,一旦调用SendWebRequest()
,就无法更改 |
| downloadHandler
| 下载数据的委托方法 |
| uploadHandler
| 上传数据的委托方法 |
例
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class ChinarWebRequest : MonoBehaviour
{
void Start()
{
StartCoroutine(SendRequest());
}
IEnumerator SendRequest()
{
Uri uri = new Uri("http://www.baidu.com");
UnityWebRequest uwr = new UnityWebRequest(uri);
uwr.timeout = 5;
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError(uwr.error);
}
else
{
Debug.Log("请求成功");
}
}
}
14.4.3 Get方法
Get
方法,创建一个 UnityWebReqest
对象,参数传入URL
。
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class ChinarWebRequest : MonoBehaviour
{
void Start()
{
StartCoroutine(SendRequest());
}
IEnumerator SendRequest()
{
UnityWebRequest uwr = UnityWebRequest.Get("http://www.baidu.com");
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.Log(uwr.error);
}
else
{
Debug.Log("Get:请求成功");
}
}
}
14.4.4 Post方法
Post
方法将一个表上传到远程的服务器,一般来说我们登陆某个网站的时候会用到这个方法,我们的账号密码会以一个表单的形式传过去。
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class ChinarWebRequest : MonoBehaviour
{
void Start()
{
StartCoroutine(Post());
}
IEnumerator Post()
{
WWWForm form = new WWWForm();
form.AddField("key", "value");
form.AddField("name", "Chinar");
UnityWebRequest webRequest = UnityWebRequest.Post("http://www.baidu.com", form);
yield return webRequest.SendWebRequest();
if (webRequest.isHttpError || webRequest.isNetworkError)
{
Debug.Log(webRequest.error);
}
else
{
Debug.Log("发送成功");
}
}
}
14.4.5 Put方法
Put
方法将数据发送到远程的服务器。
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class ChinarWebRequest : MonoBehaviour
{
void Start()
{
StartCoroutine(Upload());
}
IEnumerator Upload()
{
byte[] myData = System.Text.Encoding.UTF8.GetBytes("Chinar的测试数据");
using (UnityWebRequest uwr = UnityWebRequest.Put("http://www.baidu.com", myData))
{
yield return uwr.SendWebRequest();
if (uwr.isNetworkError || uwr.isHttpError)
{
Debug.Log(uwr.error);
}
else
{
Debug.Log("上传成功!");
}
}
}
}
14.4.6 Abort方法
Abort
方法会尽快结束联网,可以随时调用此方法。 如果 UnityWebRequest
尚未完成,那么 UnityWebRequest
将尽快停止上传或下载数据。 中止的 UnityWebRequests
被认为遇到了系统错误。isNetworkError
或isHttpError
属性将返回true
,error
属性将为“User Aborted”
。
14.4.7 Head方法
Head
方法与Get
方法用法一致,都是传入一个URL
。
IEnumerator SendRequest1()
{
UnityWebRequest uwr = UnityWebRequest.Head("http://www.chinar.xin/chinarweb/WebRequest/Get/00-效果.mp4");
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.Log(uwr.error);
}
else
{
Debug.Log("Head:请求成功");
}
}
14.4.8 GetResponseHeader方法
GetResponseHeader
方法可以用来获取请求文件的长度 传入参数 "Content-Length"
字符串,表示获取文件内容长度。
IEnumerator SendRequest1()
{
UnityWebRequest uwr = UnityWebRequest.Head("http://www.chinar.xin/chinarweb/WebRequest/Get/00-效果.mp4");
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.Log(uwr.error);
}
else
{
long totalLength = long.Parse(huwr.GetResponseHeader("Content-Length"));
Debug.Log("totalLength");
}
}
14.2.9 常用属性
| 属性 | 类型 | 含义 |
| - | - | - |
| timeout
| int
| 等待时间(秒)超过此数值是 UnityWebReqest
的尝试连接将终止 |
| isHttpError
| bool
| HTTP
响应出现出现错误 |
| isNetworkError
| bool
| 系统出现错误 |
| error
| string
| 描述 UnityWebRequest
对象在处理HTTP
请求或响应时遇到的任何系统错误 |
| downloadProgress
| float
| 表示从服务器下载数据的进度 |
| uploadProgress
| float
| 表示从服务器上传数据的进度 |
| isDone
| bool
| 是否完成与远程服务器的通信 |
14.5 案例讲解
14.5.1 本地文件读取
要将本地文件上传到web
服务器,需要先以字节流的方式读取本地文件。 封装读取接口:
private byte[] ReadLocalFile(string filePath)
{
byte[] data = null;
using (FileStream fs = File.OpenRead(filePath))
{
int index = 0;
long len = fs.Length;
data = new byte[len];
int offset = data.Length > 1024 ? 1024 : data.Length;
while (index < len)
{
int readByteCnt = fs.Read(data, index, offset);
index += readByteCnt;
long leftByteCnt = len - index;
offset = leftByteCnt > offset ? offset : (int)leftByteCnt;
}
Debug.Log("Read Done");
}
return data;
}
14.5.2 上传文件
客户端部分,通过UnityWebRequest.Post
将字节流上传给web
服务器。
IEnumerator UploadFile(string url, string fileName, byte[] data)
{
WWWForm form = new WWWForm();
form.AddField("desc", "test upload file");
form.AddBinaryData("file_data", data, fileName, "application/x-gzip");
UnityWebRequest request = UnityWebRequest.Post(url, form);
var result = request.SendWebRequest();
if (request.isNetworkError)
{
Debug.LogError(request.error);
}
while (!result.isDone)
{
yield return null;
progressSlider.value = request.uploadProgress;
progressLbl.text = Math.Floor(request.uploadProgress * 100) + "%";
}
Debug.Log("finish upload, http return msg: \n" + request.downloadHandler.text);
}
服务端部分,使用python
搭建一个简单的web
服务器,解析file_data
字段的filename
和value
,然后写入到服务器本地。
import cgi
import mimetypes
import posixpath
import shutil
import urllib.parse
import os
import re
from http.server import BaseHTTPRequestHandler, HTTPServer
host = '127.0.0.1'
port = 8988
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD':'POST',
'CONTENT_TYPE':self.headers['Content-Type'],
}
)
self.send_response(200)
self.end_headers()
desc = form['desc'].value
filename = form['file_data'].filename
filevalue = form['file_data'].value
filesize = len(filevalue)
with open(filename, 'wb') as f:
f.write(filevalue)
msg = 'upload success, file: %s, size: %d, desc: %s'%(filename, filesize, desc)
print(msg)
self.wfile.write(msg.encode("utf-8"))
return
if '__main__' == __name__ :
sever = HTTPServer((host, port), SimpleHTTPRequestHandler)
print("我是web服务器,地址:http://localhost:" + str(port))
sever.serve_forever()
14.5.3 下载文件
客户端部分,先通过UnityWebRequest.Head
获取文件大小,然后判断本地文件是否是续传,如果续传则将游标移动到最后,通过HttpWebRequest
的AddRange
跟web
服务器请求数据偏移,得到数据返回后写入本地文件中。
皮皮:“微信搜索公众号 [爱上游戏开发],回复 “资料”,免费领取 200G 学习资料!”
IEnumerator DownloadFile(string url, string fileName)
{
Debug.Log("DownloadFile");
UnityWebRequest huwr = UnityWebRequest.Head(url + "/" + fileName);
yield return huwr.SendWebRequest();
if (huwr.isNetworkError || huwr.isHttpError)
{
Debug.Log(huwr.error);
yield break;
}
long totalLength = long.Parse(huwr.GetResponseHeader("Content-Length"));
Debug.Log("totalLength: " + totalLength);
#if UNITY_EDITOR
var filePath = Application.streamingAssetsPath + "/" + fileName;
#else
var filePath = Application.persistentDataPath + "/" + fileName;
#endif
using (m_localFileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
{
long nowFileLength = m_localFileStream.Length;
Debug.Log(m_localFileStream.Length);
if (nowFileLength < totalLength)
{
m_localFileStream.Seek(nowFileLength, SeekOrigin.Begin);
Debug.Log("seek : " + nowFileLength);
}
else
{
Debug.Log("dont need download");
yield break;
}
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url + "/" + fileName);
request.AddRange(nowFileLength);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
m_serverResponseStream = response.GetResponseStream();
Debug.Log("responseStream.Length: " + m_serverResponseStream.Length);
int readSize = 0;
while (true)
{
readSize = m_serverResponseStream.Read(m_downloadBuffer, 0, m_downloadBuffer.Length);
if (readSize > 0)
{
m_localFileStream.Write(m_downloadBuffer, 0, readSize);
nowFileLength += readSize;
progressSlider.value = (float)nowFileLength / totalLength;
progressLbl.text = Math.Floor((float)nowFileLength / totalLength * 100) + "%";
}
else
{
progressSlider.value = 1;
progressLbl.text = 100 + "%";
m_serverResponseStream.Close();
m_serverResponseStream.Dispose();
break;
}
yield return null;
}
Debug.Log("download Done");
}
}
服务端部分,解析headers
的Range
字段,判断是否需要数据偏移,如果需要,则将游标移动到对应位置,计算剩余文件大小,塞到hearders
的Content-Length
字段中,然后把文件字节流返回给客户端。
import cgi
import mimetypes
import posixpath
import shutil
import urllib.parse
import os
import re
from http.server import BaseHTTPRequestHandler, HTTPServer
host = '127.0.0.1'
port = 8988
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
if not mimetypes.inited:
mimetypes.init()
extensions_map = mimetypes.types_map.copy()
extensions_map.update({
'': 'application/octet-stream',
'.py': 'text/plain',
'.c': 'text/plain',
'.h': 'text/plain',
})
def do_GET(self):
f = self.send_head()
if f:
self.copyfile(f, self.wfile)
f.close()
def do_HEAD(self):
f = self.send_head()
if f:
f.close()
def send_head(self):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
if not self.path.endswith('/'):
self.send_response(301)
self.send_header("Location", self.path + "/")
self.end_headers()
return None
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
ctype = self.guess_type(path)
offset = 0
try:
f = open(path, 'rb')
content_ranges = self.headers['Range']
if None != content_ranges:
match = re.match('bytes=(\d+)-(\d*)', content_ranges)
if match:
offset = int(match.group(1))
f.seek(offset)
print('f.seek ' + str(offset))
except IOError:
self.send_error(404, "File not found")
return None
self.send_response(200)
self.send_header("Content-type", ctype)
fs = os.fstat(f.fileno())
realsize = fs.st_size
self.send_header("Content-Length", str(realsize - offset))
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
self.end_headers()
return f
def guess_type(self, path):
base, ext = posixpath.splitext(path)
if ext in self.extensions_map:
return self.extensions_map[ext]
ext = ext.lower()
if ext in self.extensions_map:
return self.extensions_map[ext]
else:
return self.extensions_map['']
def translate_path(self, path):
path = path.split('?', 1)[0]
path = path.split('#', 1)[0]
path = posixpath.normpath(urllib.parse.unquote(path))
words = path.split('/')
words = [_f for _f in words if _f]
path = os.getcwd()
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir):
continue
path = os.path.join(path, word)
return path
def copyfile(self, source, outputfile):
shutil.copyfileobj(source, outputfile)
if '__main__' == __name__ :
sever = HTTPServer((host, port), SimpleHTTPRequestHandler)
print("我是web服务器,地址:http://localhost:" + str(port))
sever.serve_forever()
完成。 如果有什么疑问,欢迎留言或私信。
-- END --
* Unity之Image & Raw Image
- unity shader预备知识
- 根据国家规定法规,请进行实名制认证!
- 对方申请添加您为好友!
公众号后台回复「资料」获取超多学习福利
>>> 点击进入技术讨论群 <<< ▽想深入了解么?
长按/扫码关注我吧↑↑↑
觉得不错就点个在看吧!
标题:学Unity的猫之文件上传下载续传(十四)
作者:shirlnGame
地址:https://mmzsblog.cn/articles/2021/01/22/1611321171996.html
-----------------------------
如未加特殊说明,此网站文章均为原创。
网站转载须在文章起始位置标注作者及原文连接,否则保留追究法律责任的权利。
公众号转载请联系网站首页的微信号申请白名单!
