欢迎来到我的博客小站。  交流请加我微信好友: studyjava。  也欢迎关注同名公众号:Java学习之道

学Unity的猫之文件上传下载续传(十四)

  |   0 评论   |   0 浏览

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。 有些较大的文件下载需要断点续传的功能(即下载了一部分突然中断下载后,再次下载直接从上次下载的地方继续下载,而不是重新下载),就需要使用HttpWebRequestUnityWebRequest

注:WWWUnityEngine命名空间下HttpWebRequestSystem.Net命名空间下UnityWebRequestUnityEngine.Networking命名空间下

14.4 UnityWebRequest常用方法与属性介绍

14.4.1 概述

UnityWebRequest支持与上传,下载及断点续传功能。

UnityWebRequest由三个元素组成: 1 UploadHandler处理数据将数据上传到服务器的对象; 2 DownloadHandler从服务器下载数据的对象; 3 UnityWebRequest负责与HTTP通信并管理上面两个对象。

常用方法

| 方法 | 作用 |
| - | - |
| SendWebRequest() | 开始与远程服务器通信。在调用此方法之后,有必要的话UnityWebRequest将执行DNS解析,将HTTP请求发送到目标URL的远程服务器并处理服务器的响应。 |
| Get(url) | 创建一个HTTP为传入URLUnityWebRequest对象 |
| 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被认为遇到了系统错误。isNetworkErrorisHttpError属性将返回trueerror属性将为“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字段的filenamevalue,然后写入到服务器本地。

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获取文件大小,然后判断本地文件是否是续传,如果续传则将游标移动到最后,通过HttpWebRequestAddRangeweb服务器请求数据偏移,得到数据返回后写入本地文件中。

皮皮:“微信搜索公众号 [爱上游戏开发],回复 “资料”,免费领取 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");
        }
    }

服务端部分,解析headersRange字段,判断是否需要数据偏移,如果需要,则将游标移动到对应位置,计算剩余文件大小,塞到heardersContent-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
-----------------------------
如未加特殊说明,此网站文章均为原创。
网站转载须在文章起始位置标注作者及原文连接,否则保留追究法律责任的权利。
公众号转载请联系网站首页的微信号申请白名单!

个人微信公众号 ↓↓↓                 

微信搜一搜爱上游戏开发