美文网首页网络编程UnityUnity
Unity发送HTTP请求和文件下载

Unity发送HTTP请求和文件下载

作者: Aodota | 来源:发表于2016-12-07 21:42 被阅读5806次

Unity发送HTTP请求和文件下载

本类库封装基于ulua框架LuaFramework

1. 使用的类库

HttpWebRequest
HttpWebResponse
LuaFramework

2. 实现了哪些功能

  • 发送GET、POST请求
  • HTTP请求异步化
  • 支持Lua回调
  • 支持Host,采用Proxy的实现方式
  • 支持将HTTP请求内容保存为文件
  • HTTP下载文件,支持断电续传

3. HTTP实现思路

  • HTTPRequest 发起HTTP请求,异步回调返回HTTPResponse
    HTTPRequest源码
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Collections.Generic;
using LuaFramework;
using UnityEngine;
/// <summary>
/// Http请求
/// </summary>
public class HTTPRequest
{
    private string url;
    private int timeout;
    private Action<HTTPResponse> callback;
    private HttpWebRequest request;
    private string method;
    private string contentType;
    private KeyValuePair<string, int> proxy;
    protected int range = -1;
    // post内容
    private StringBuilder postBuilder;
    /// <summary>
    /// 错误代码
    /// </summary>
    public const int ERR_EXCEPTION = -1;
    /// <summary>
    /// 构造函数, 构造GET请求
    /// </summary>
    /// <param name="url">url地址</param>
    /// <param name="timeout">超时时间</param>
    /// <param name="callback">回调函数</param>
    public HTTPRequest (string url, string method, int timeout, Action<HTTPResponse> callback)
    {
        this.url = url;
        this.timeout = timeout;
        this.callback = callback;
        this.method = method.ToUpper();
    }
    /// <summary>
    /// 设置Post内容
    /// </summary>
    /// <param name="data">内容</param>
    public void SetPostData(string data) {
        if (postBuilder == null) {
            postBuilder = new StringBuilder (data.Length);
        }
        if (postBuilder.Length > 0) {
            postBuilder.Append ("&");
        }
        postBuilder.Append (data);
    }
    /// <summary>
    /// 添加Post内容
    /// </summary>
    /// <param name="key">key值</param>
    /// <param name="value">value值</param>
    public void AddPostData(string key, string value) {
        if (postBuilder == null) {
            postBuilder = new StringBuilder ();
        }
        if (postBuilder.Length > 0) {
            postBuilder.Append ("&");
        }
        postBuilder.Append (key).Append ("=").Append (UrlEncode (value));
    }
    /// <summary>
    /// 设置代理
    /// </summary>
    /// <param name="ip">ip地址</param>
    /// <param name="port">端口号</param>
    public void SetProxy(string ip, int port) {
        this.proxy = new KeyValuePair<string, int> (ip, port);
    }
    /// <summary>
    /// 设置ContentType
    /// </summary>
    /// <value>ContentType value</value>
    public string ContentType {
        set {
            this.contentType = value;
        }
    }
    /// <summary>
    /// 发动请求
    /// </summary>
    public void Start() {
        Debug.Log ("Handle Http Request Start");
        this.request = WebRequest.Create (url) as HttpWebRequest;
        this.request.Timeout = timeout;
        this.request.Method = method;
        if (this.proxy.Key != null) {
            this.request.Proxy = new WebProxy(this.proxy.Key, this.proxy.Value);
        }
        if (this.contentType != null) {
            this.request.ContentType = this.contentType;
        }
        if (this.range != -1) {
            this.request.AddRange (this.range);
        }
        // POST写POST内容
        if (method.Equals ("POST")) {
            WritePostData ();
        }
        try {
            AsyncCallback callback = new AsyncCallback (OnResponse);
            this.request.BeginGetResponse (callback, null);
        } catch (Exception e) {
            CallBack (ERR_EXCEPTION, e.Message);
            if (request != null) {
                request.Abort ();
            }
        }
    }
    /// <summary>
    /// 处理读取Response
    /// </summary>
    /// <param name="result">异步回到result</param>
    protected void OnResponse(IAsyncResult result) {
        //Debug.Log ("Handle Http Response");
        HttpWebResponse response = null;
        try {
            // 获取Response
            response = request.EndGetResponse (result) as HttpWebResponse;
            if (response.StatusCode == HttpStatusCode.OK) {
                if ("HEAD".Equals(method)) {
                    // HEAD请求
                    long contentLength = response.ContentLength;
                    CallBack((int)response.StatusCode, contentLength + "");
                    return;
                }
                // 读取请求内容
                Stream responseStream = response.GetResponseStream();
                byte[] buff = new byte[2048];
                MemoryStream ms = new MemoryStream();
                int len = -1;
                while ((len = responseStream.Read(buff, 0, buff.Length)) > 0) {
                    ms.Write(buff, 0, len);
                }
                // 清理操作
                responseStream.Close();
                response.Close();
                request.Abort();
                // 调用回调
                CallBack ((int)response.StatusCode, ms.ToArray());
            } else {
                CallBack ((int)response.StatusCode, "");
            }
        } catch (Exception e) {
            CallBack (ERR_EXCEPTION, e.Message);
            if (request != null) {
                request.Abort ();
            }
            if (response != null) {
                response.Close ();
            }
        }
    }
    /// <summary>
    /// 回调
    /// </summary>
    /// <param name="code">编码</param>
    /// <param name="content">内容</param>
    private void CallBack(int code, string content) {
        Debug.LogFormat ("Handle Http Callback, code:{0}", code);
        if (callback != null) {
            HTTPResponse response = new HTTPResponse ();
            response.StatusCode = code;
            response.Error = content;
            callback (response);
        }
    }
    /// <summary>
    /// 回调
    /// </summary>
    /// <param name="code">编码</param>
    /// <param name="content">内容</param>
    private void CallBack(int code, byte[] content) {
        Debug.LogFormat ("Handle Http Callback, code:{0}", code);
        if (callback != null) {
            HTTPResponse response = new HTTPResponse (content);
            response.StatusCode = code;
            callback (response);
        }
    }
    /// <summary>
    /// 写Post内容
    /// </summary>
    private void WritePostData() {
        if (null == postBuilder || postBuilder.Length <= 0) {
            return;
        }
        byte[] bytes = Encoding.UTF8.GetBytes (postBuilder.ToString ());
        Stream stream = request.GetRequestStream ();
        stream.Write (bytes, 0, bytes.Length);
        stream.Close ();
    }
    /// <summary>
    /// URLEncode
    /// </summary>
    /// <returns>encode value</returns>
    /// <param name="value">要encode的值</param>
    private string UrlEncode(string value) {
        StringBuilder sb = new StringBuilder();
        byte[] byStr = System.Text.Encoding.UTF8.GetBytes(value);
        for (int i = 0; i < byStr.Length; i++)
        {
            sb.Append(@"%" + Convert.ToString(byStr[i], 16));
        }
        return (sb.ToString());
    }
}

HTTPResponse源码

using System;
using System.IO;
using System.Text;
using UnityEngine;
using LuaFramework;
/// <summary>
/// HTTP返回内容
/// </summary>
public class HTTPResponse
{
    // 状态码
    private int statusCode;
    // 响应字节
    private byte[] responseBytes;
    // 错误内容
    private string error;
    /// <summary>
    /// 默认构造函数
    /// </summary>
    public HTTPResponse ()
    {
    }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="content">响应内容</param>
    public HTTPResponse (byte[] content)
    {
        this.responseBytes = content;
    }
    /// <summary>
    /// 获取响应内容
    /// </summary>
    /// <returns>响应文本内容</returns>
    public string GetResponseText() {
        if (null == this.responseBytes) {
            return null;
        }
        return Encoding.UTF8.GetString (this.responseBytes);
    }
    /// <summary>
    /// 将响应内容存储到文件
    /// </summary>
    /// <param name="fileName">文件名称</param>
    public void SaveResponseToFile(string fileName) {
        if (null == this.responseBytes) {
            return;
        }
        // FIXME 路径跨平台问题
        string path = Path.Combine (Application.dataPath +"/StreamingAssets", fileName);
        FileStream fs = new FileStream (path, FileMode.Create);
        BinaryWriter writer = new BinaryWriter (fs);
        writer.Write (this.responseBytes);
        writer.Flush ();
        writer.Close ();
        fs.Close ();
    }
    /// <summary>
    /// 获取状态码
    /// </summary>
    /// <value>状态码</value>
    public int StatusCode {
        set { 
            this.statusCode = value;
        }
        get {
            return this.statusCode;
        }
    }
    /// <summary>
    /// 获取错误消息
    /// </summary>
    /// <value>错误消息</value>
    public string Error {
        set {
            this.error = value;
        }
        get {
            return this.error;
        }
    }
}

NetworkManager中封装了一个发送HTTP请求的方法

/// <summary>
/// 创建一个HTTP请求
/// </summary>
/// <param name="url">URL.</param>
/// <param name="callback">Callback</param>
public HTTPRequest CreateHTTPRequest(string url, string method, LuaFunction callback) {
    HTTPRequest client = new HTTPRequest(url, method, 5000, (HTTPResponse response)=>{
   if (null != callback) {
           callbackEvents.Enqueue(() => {
               callback.Call(response);
           });
       }
    });
    return client;
}

Lua中使用范例

    local request = networkMgr:CreateHTTPRequest("http://www.baidu.com", "GET", function(response)
        response:GetResponseText()
    end
    )
    # 支持HOST模式
    #request:SetProxy("42.62.46.224", 80)
    request:Start()

4. 文件下载实现思路

这里主要借鉴了Unity技术博客 - 客户端断点续传这篇文章的思想
支持了Lua回调

HTTPDownLoad源码

using System;
using System.Net;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using LuaFramework;
using UnityEngine;
/// <summary>
/// 文件下载
/// </summary>
public class HTTPDownLoad
{
    // 下载进度
    public float Progress {
        get;
        private set;
    }
    // 状态 0 正在下载 1 下载完成 -1 下载出错
    public int Status {
        get;
        private set;
    }
    // 错误信息
    public string Error {
        get;
        set;
    }
    // 总长度
    public long TotalLength {
        get;
        private set;
    }
    // 保存路径
    private string savePath;
    // url地址
    private string url;
    // 超时时间
    private int timeout;
    // 子线程
    private Thread thread;
    // 子线程是否停止标志
    private bool isStop;
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="url">url地址</param>
    /// <param name="timeout">超时时间</param>
    /// <param name="callback">回调函数</param>
    public HTTPDownLoad (string url, string savePath, int timeout)
    {
        this.savePath = savePath;
        this.url = url;
        this.timeout = timeout;
    }
    /// <summary>
    /// 开启下载
    /// </summary>
    public void DownLoad() {
        // 开启线程下载
        thread = new Thread (StartDownLoad);
        thread.IsBackground = true;
        thread.Start ();
    }
    /// <summary>
    /// 开始下载
    /// </summary>
    private void StartDownLoad() {
        try {
            // 构建文件流
            FileStream fs = new FileStream (this.savePath, FileMode.OpenOrCreate, FileAccess.Write);
            // 文件当前长度
            long fileLength = fs.Length;
            // 文件总长度
            TotalLength = GetDownLoadFileSize ();
            Debug.LogFormat("fileLen:{0}", TotalLength);
            if (fileLength < TotalLength) {
                // 没有下载完成
                fs.Seek (fileLength, SeekOrigin.Begin);
                // 发送请求开始下载
                HttpWebRequest request = WebRequest.Create (this.url) as HttpWebRequest;
                request.AddRange ((int)fileLength);
                request.Timeout = this.timeout;
                // 读取文件内容
                Stream stream = request.GetResponse().GetResponseStream();
                if (stream.CanTimeout) {
                    stream.ReadTimeout = this.timeout;
                }
                byte[] buff = new byte[4096];
                int len = -1;
                while ((len = stream.Read (buff, 0, buff.Length)) > 0) {
                    if (isStop) {
                        break;
                    }
                    fs.Write (buff, 0, len);
                    fileLength += len;
                    Progress = fileLength * 1.0f / TotalLength;
                } 
                stream.Close ();
                stream.Dispose ();
            } else {
                Progress = 1;
            }
            fs.Close ();
            fs.Dispose ();
            // 标记下载完成
            if (Progress == 1) {
                Status = 1;
            }
        } catch (Exception e) {
            Error = e.Message;
            Status = -1;
        }
    }
    /// <summary>
    /// 获取下载的文件大小
    /// </summary>
    /// <returns>文件大小</returns>
    public long GetDownLoadFileSize() {
        HttpWebRequest request = WebRequest.Create (this.url) as HttpWebRequest;
        request.Method = "HEAD";
        request.Timeout = this.timeout;
        HttpWebResponse response = request.GetResponse () as HttpWebResponse;
        return response.ContentLength;
    }
    /// <summary>
    /// 关闭下载
    /// </summary>
    public void Close() {
        this.isStop = true;
    }
}

NetworkManager中封装了一个文件下载的方法

/// <summary>
/// 下载文件
/// </summary>
/// <param name="url">url</param>
/// <param name="savePath">保存地址</param>
/// <param name="callback">callback</param>
public HTTPDownLoad DownLoadFile(string url, string savePath, LuaFunction callback) {
    // 一次只允许下载一个文件
    if (downFileEvent != null) {
        callback.Call (-1, 0, "其他文件正在下载");
        return null;
    }
    // 下载文件
    HTTPDownLoad download = new HTTPDownLoad (url, savePath, 5000);
    download.DownLoad ();
    // 回调函数
    downFileEvent = delegate(){
        callback.Call (download.Status, download.Progress, download.Error);
        if (download.Status != 0) {
            downFileEvent = null;
        }
    };
    return download;
}

Lua中使用范例

local path = "/Workspace/Unity/LuaFramework_UGUI/Assets/StreamingAssets/***.zip"
local downloadRequest = networkMgr:DownLoadFile("文件下载url", path, function(status, progress, error)
        print("status:" .. status .. ", progress:" .. progress)
        if error ~= nil then
        print("error:" .. error)
        end
end)

4. 交流与讨论

欢迎大家交流和讨论O(∩_∩)O~

相关文章

网友评论

  • a4a6a67c4eab:callbackEvents这个没有定义,麻烦说明一下子,
    Aodota:这个就是一个Queue<Action>,用于在主循环中调用
  • 小马哥nice:请问xlua适用吗?
    Aodota:@小马哥nice 我这是c#写的,应该很容易适配xlua。xlua没用过,所以未实践过
  • d64ba0ba3002:在Unity5.4中有错误,你们有遇到么
    System.Net.WebException: Error getting response stream (Write: The authentication or decryption has failed.): SendFailure ---> System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: Invalid certificate received from server. Error code: 0xffffffff800b010f
    at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.validateCertificates (Mono.Security.X509.X509CertificateCollection certificates) [0x00000] in <filename unknown>:0
    at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.ProcessAsTls1 () [0x00000] in <filename unknown>:0
    at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00000] in <filename unknown>:0
    at (wrapper remoting-invoke-with-check) Mono.Security.Protocol.Tls.Handshake.HandshakeMessage:Process ()
    at Mono.Security.Protocol.Tls.ClientRecordProtocol.ProcessHandshakeMessage (Mono.Security.Protocol.Tls.TlsStream handMsg) [0x00000] in <filename unknown>:0
    at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
    --- End of inner exception stack trace ---
    at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
    --- End of inner exception stack trace ---
    at System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
    at System.Net.HttpWebRequest.GetResponse () [0x00000] in <filename unknown>:0
    Aodota:@濄去嘞_1d4f 这个服务器支持很简单的,经过项目验证的
    5872ad950dd3:可能有些服务器并不支持断点
    大佬 这个经过项目检验了吗
    Aodota:@蓝猫_8666 你是https吧,https不支持代理,直接使用域名试试呢:blush:
  • Ming_8cde:你好,我是个纯新手,刚接触到这一块,看HTTP请求里的callbackEvents没有定义,这个是什么东西啊?能不能把修改后的NetworkManager发出来看一下
  • zimu2016:感谢楼主 最近刚好在弄这块。有个问题想请教一下,就是如果你做这个APP内下载的话,热更新的版本控制又是怎么实现的呢?
    Aodota:@zimu2016 你是指你们游戏不同用户之间资源不一致吗?是因为高清和低清画质切换导致?这种建议动更检查只做低清资源的校验动更,保证基本的游戏运行正常。对于高清资源,玩家是要主动在游戏内部下载的,可以采取高清资源自己校验高清资源版本号,拉差异包更新。这种放到游戏内部提醒玩家高清资源有更新,让玩家自己手动选择下载。不知道我描述的是不是你们碰到的问题
    zimu2016:@Aodota 恩,热更这一块主要是本地与服务器两处文件MD5 list的对比。但是我现在感到困惑的是,在APP内下载的话可能有些用户下载了,有些用户不想下载。我现在考虑是再添加一份list,和普通的那些文件做区分。不过这样的话就需要搭两个热更服务器了,若是以后有更多一小块一小块的APP内下载,这样似乎也不太合适,所以想请教一下您关于这方面有没有什么想法。叙述有些凌乱,请见谅。
    Aodota:@zimu2016 热更这块我们还没弄,大致思路有了,可以和你分享下。我们准备就是拿本地的资源版本和服务器版本对比,拉取不同版本之间的差异包,拉取之后解压到动更目录。更新本地的资源版本号
  • 6a7e49f16f00:不是最近的tolua ?
    Aodota: @Liaoer 客气,多多交流😀
    6a7e49f16f00:@Aodota 最近在学这个,请多多分享,感谢
    Aodota:@Liaoer 是最近的tolua

本文标题:Unity发送HTTP请求和文件下载

本文链接:https://www.haomeiwen.com/subject/kejjmttx.html