U3D客户端框架之实现基于UnityWebRequest的Http服务 实现HttpCallBackArgs参数类、HttpRoutine访问器、HttpManager管理器

简介: Unity3D 在2018版本中弃用了WWW请求,使用UnityWebRequest 进行网络请求,这个方法是为了满足今天的 HTTP 通信的需求,而且诞生的新类,相对于WWW这个方法,会更灵活一些,但是用起来却很不方便。

1 Http服务 访问器设计思路


Unity3D 在2018版本中弃用了WWW请求,使用UnityWebRequest 进行网络请求,这个方法是为了满足今天的 HTTP 通信的需求,而且诞生的新类,相对于WWW这个方法,会更灵活一些,但是用起来却很不方便。


所以我将UnityWebRequest封装了一下。封装的目的有两个:1.封装后访问Http用着方便;2.在框架层上隔离原生API和具体业务,即使后续API变更也不会影响到业务逻辑,避免业务受影响。


f6163a07944372471e8690b777b2638e.png


2 代码实现


Get和Post的区别可以看 引用模块中 Get和Post对比的链接,那篇文章中详细讲解了Get和Post的异同和使用场景。


HttpCallBackArgs:Http请求的回调数据,包装了是否有错、返回值、数据Bytes数组;说明一下HttpCallBackArgs继承EventArgs,是为了准守规范,让看代码的人一看到这个类型,就知道这是一个事件类型,使用的时候直接吧EventArgs转换成具体的事件参数类即可。


HttpCallBackArgs.cs 代码实现


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    //http请求的回调数据
    public class HttpCallBackArgs : EventArgs
    {
        //是否有错(是否发生了错误)
        public bool HasError;
        //返回值 
        public string Value;
        //字节数据 
        public byte[] Data;
    }
}


HttpRoutine:Http访问Url的轮询器,核心代码的所在文件;内部实现了GetUrl、PostUrl,状态监测、回调处理、对失败情况下的重试逻辑


HttpRoutine.cs 代码实现


using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using YouYou;
namespace Myh
{
    //Http发送数据的回调委托 
    public delegate void HttpSendDataCallBack(HttpCallBackArgs args);
    //Http访问器
    public class HttpRoutine
    {
        //Http请求回调
        private HttpSendDataCallBack m_CallBack;
        //Http请求回调数据
        private HttpCallBackArgs m_CallBackArgs;
        //是否繁忙
        public bool IsBusy
        {
            get;
            private set;
        }
        //当前重试次数(尝试重新访问次数?)
        public int m_CurrRetry = 0;
        //URL
        private string m_Url;
        //是否是Get
        private bool m_IsGetData = false;
        //是否是Post
        private bool m_IsPost = false;
        //发送的数据
        private Dictionary<string, object> m_Dic;
        public HttpRoutine()
        {
            m_CallBackArgs = new HttpCallBackArgs();
        }
        #region SendData 发送Web数据
        public void SendData(string url, HttpSendDataCallBack cb, bool isPost = false,
                             bool isGetData = false, Dictionary<string, object> dic = null)
        {
            if (IsBusy)
                return;
            m_Url = url;
            m_CallBack = cb;
            m_IsPost = isPost;
            m_IsGetData = isGetData;
            m_Dic = dic;
            SendData();
        }
        private void SendData()
        {
            //不是post模式
            if (!m_IsPost)
            {
                GetUrl(m_Url);
            }
            else
            {
                //把数据存到字典里
                if (m_Dic != null)
                {
                    //设备唯一Id
                    m_Dic["deviceIdentifier"] = DeviceUtil.DeviceIdentifier;
                    //设备型号
                    m_Dic["deviceModel"] = DeviceUtil.DeviceModel;
                    //服务器时间 TODO:
                    //还没有和服务器同步时间,暂时先用本地时间
                    long t = DateTime.Now.Ticks;
                    //用当前服务器时间 和 设备id 算一个md5出来 作为本地请求的签名(签名具有时效性,超时无效)
                    string md5 = string.Format("{0}:{1}",t,DeviceUtil.DeviceIdentifier);
                    m_Dic["sign"] = EncryptUtil.Md5(md5);
                    //时间戳
                    m_Dic["t"] = t;
                }
                string json = string.Empty;
                if (m_Dic != null)
                {
                    json = JsonMapper.ToJson(m_Dic);
                    //不是get的方式
                    if (!m_IsGetData)
                    {
#if DEBUG_LOG_PROTO && DEBUG_MODEL
                        GameEntry.Log(LogCategory.Proto, "<color=#ffa200>发送消息:</color><color=#FFFB80>" + m_Url + "</color>");
                        GameEntry.Log(LogCategory.Proto, "<color=#ffdeb3>==>>" + json + "</color>");
#endif 
                    }
                    GameEntry.Pool.EnqueueClassObject(m_Dic);
                }
                PostUrl(m_Url,json);
            }
        }
        #endregion
        #region GetUrl Get请求
        //Get请求
        private void GetUrl(string url)
        {
            UnityWebRequest request = UnityWebRequest.Get(url);
            YouYou.GameEntry.Instance.StartCoroutine(Request(request));
        }
        #endregion
        #region PostUrl Post请求
        //Post请求
        private void PostUrl(string url, string json)
        {
            //定义一个表单
            WWWForm form = new WWWForm();
            //给表单添加值
            form.AddField("json",json);
            //把url 和表单传入进去
            UnityWebRequest request = UnityWebRequest.Post(url,form);
            GameEntry.Instance.StartCoroutine(Request(request));
        }
        #endregion
        #region Request 请求服务器
        /*
         * 功能:请求Web服务器
         * request:UnityWebRequest请求的实体
         */
        private IEnumerator Request(UnityWebRequest request)
        {
            //阻塞方法  和目标服务器建立连接,返回结果后才继续下一步
            yield return request.SendWebRequest();
            //如果有错误,重试与目标服务器连接(通信)
            if (request.isNetworkError || request.isHttpError)
            {
                //报错了进行重试
                if (m_CurrRetry > 0)
                {
                    //过一段时间后再重新判断
                    yield return new WaitForSeconds(GameEntry.Http.RetryInterval);
                }
                //重试次数+1
                ++m_CurrRetry;
                //如果<=配置的重试次数
                if (m_CurrRetry <= GameEntry.Http.Retry)
                {
#if DEBUG_LOG_PROTO && DEBUG_MODEL
                    //通过宏开关,决定要不要打印log
                    GameEntry.Log(LogCategory.Proto, "<color=#00eaff>请求URL:</color> <color=#00ff9c>{0}失败 当前重试次数{1}</color>", m_Url, m_CurrRetry);
#endif
                    //调用SendData,重新发送数据
                    SendData();
                    //结束本次携程
                    yield break;
                }
                //超过次数了,状态设置成有错误
                IsBusy = false;
                if (null != m_CallBack)
                {
                    m_CallBackArgs.HasError = true;
                    m_CallBackArgs.Value = request.error;
                    //不是GetData 方式的话 打印一个log
                    if (!m_IsGetData)
                    {
#if DEBUG_LOG_PROTO && DEBUG_MODEL
                        GameEntry.Log(LogCategory.Proto, "<color=#00eaff>接收消息:</color> <color=#00ff9c>" + request.url + "</color>");
                        GameEntry.Log(LogCategory.Proto, "<color=#c5e1dc>==>>" + JsonUtility.ToJson(m_CallBackArgs) + "</color>");
#endif
                    }
                    m_CallBack(m_CallBackArgs);
                }
            }
            //与主机建立连接
            else
            {
                IsBusy = false;
                if(null!=m_CallBack)
                {
                    m_CallBackArgs.HasError = false;
                    m_CallBackArgs.Value = request.downloadHandler.text;
                    if (!m_IsGetData)
                    {
#if DEBUG_LOG_PROTO && DEBUG_MODEL
                        GameEntry.Log(LogCategory.Proto, "<color=#00eaff>接收消息:</color> <color=#00ff9c>" + request.url + "</color>");
                        GameEntry.Log(LogCategory.Proto, "<color=#c5e1dc>==>>" + JsonUtility.ToJson(m_CallBackArgs) + "</color>");
#endif
                    }
                    m_CallBackArgs.Data = request.downloadHandler.data;
                    m_CallBack(m_CallBackArgs);
                }
            }
            //重试完毕,或者下载完毕
            m_CurrRetry = 0;
            m_Url = null;
            if (null != m_Dic)
            {
                m_Dic.Clear();
                m_Dic = null;
            }
            m_CallBackArgs.Data = null;
            request.Dispose();
            request = null;
            //把Http访问器回池
            GameEntry.Pool.EnqueueClassObject(this);
        }
        #endregion
    }
}


HttpManager:Http服务器的管理类,记录了账号服务器的Url,通过HttpManager开启HttpRoutine。


HttpManager.cs 代码实现


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    public class HttpManager : ManagerBase, IDisposable
    {
        //正式服 账号服务器url
        private string m_WebAccountUrl;
        //测试服 账号服务器url
        private string m_TestWebAccountUrl;
        //是否是测试环境
        private bool m_IsTest;
        //真实账号服务器Url
        public string RealWebAccountUrl
        {
            get
            {
                return m_IsTest ? m_TestWebAccountUrl : m_WebAccountUrl;
            }
        }
        //连接失败后重试次数
        public int Retry
        {
            get;
            private set;
        }
        //连接失败后重试间隔(单位:秒)
        public float RetryInterval
        {
            get;
            private set;
        }
        public override void Init()
        {
            //TODO:这些应该都要从设置里读取,可是现在没有,到时候回来改
            m_WebAccountUrl = "";
            m_TestWebAccountUrl = "";
            m_IsTest = true;
            Retry = 5;
            RetryInterval = 2f;
        }
        public void SendData(string url, HttpSendDataCallBack cb, bool isPost = false, 
                             bool isGetData = false, Dictionary<string, object> dic = null)
        {
            //从类对象池里,获取http访问器
            HttpRoutine httpRoutine = YouYou.GameEntry.Pool.DequeueClassObject<HttpRoutine>();
            httpRoutine.SendData(url, cb, isPost, isGetData, dic);
        }
        public void Dispose()
        {
        }
    }
}


3 代码测试


Get访问网页会把网页内的Html代码读出来;Getzip会把zip下载下来。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Myh;
using YouYou;
using UnityEngine;
public class TestHttp : ITest
{
    private void TestGetWebUrl()
    {
        GameEntry.Http.SendData("https://www.baidu.com", (HttpCallBackArgs args) =>
        {
            GameEntry.Log(LogCategory.Normal, "httpCallbackArgs hasError:{0} Value:{1} data str:{2}",
                args.HasError, args.Value, Encoding.UTF8.GetString(args.Data));
        });
    }
    private void TestGetDownloadUrl()
    {
        //从web站点上下载eee.zip文件
        GameEntry.Http.SendData("https://www.xxx.com/s/eee.zip", (HttpCallBackArgs args) =>
        {
            GameEntry.Log(LogCategory.Normal, "httpCallbackArgs hasError:{0} Value:{1} data str:{2}",
                args.HasError, args.Value, Encoding.UTF8.GetString(args.Data));
        });
    }
    public void OnTestStart()
    {
    }
    public void OnTestUpdate()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            TestGetWebUrl();
        }
        else if (Input.GetKeyDown(KeyCode.E))
        {
            TestGetDownloadUrl();
        }
    }
}


4 引用


Get和Post对比:HTTP请求中Get和Post的区别是什么?_天才小熊猫oo的博客-CSDN博客

相关文章
|
8月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
428 61
|
8月前
|
C# 图形学 开发者
Unity开发中使用UnityWebRequest从HTTP服务器下载资源。
总之,UnityWebRequest就是游戏开发者手中的万能钓鱼竿,既可以获取文本数据,也能钓上图片资源,甚至是那声音的涟漪。使用UnityWebRequest的时候,你需要精心准备,比如确定URL、配置请求类型和头信息;发起请求;巧妙处理钓获的数据;还需要机智面对网络波澜,处理各种可能出现的错误。按照这样的过程,数据的钓取将会是一次既轻松愉快也效率高效的编程钓鱼之旅。
396 18
|
8月前
|
应用服务中间件 网络安全 数据安全/隐私保护
网关服务器配置指南:实现自动DHCP地址分配、HTTP服务和SSH无密码登录。
哇哈哈,道具都准备好了,咱们的魔术秀就要开始了。现在,你的网关服务器已经魔法满满,自动分配IP,提供网页服务,SSH登录如入无人之境。而整个世界,只会知道效果,不会知道是你在幕后操控一切。这就是真正的数字世界魔法师,随手拈来,手到擒来。
401 14
|
6月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
10月前
|
中间件 Go
Golang | Gin:net/http与Gin启动web服务的简单比较
总的来说,`net/http`和 `Gin`都是优秀的库,它们各有优缺点。你应该根据你的需求和经验来选择最适合你的工具。希望这个比较可以帮助你做出决策。
512 35
|
11月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1067 29
|
11月前
|
关系型数据库 MySQL PHP
源码编译安装LAMP(HTTP服务,MYSQL ,PHP,以及bbs论坛)
通过以上步骤,你可以成功地在一台Linux服务器上从源码编译并安装LAMP环境,并配置一个BBS论坛(Discuz!)。这些步骤涵盖了从安装依赖、下载源代码、配置编译到安装完成的所有细节。每个命令的解释确保了过程的透明度,使即使是非专业人士也能够理解整个流程。
326 18
|
JSON 数据格式
.net HTTP请求类封装
`HttpRequestHelper` 是一个用于简化 HTTP 请求的辅助类,支持发送 GET 和 POST 请求。它使用 `HttpClient` 发起请求,并通过 `Newtonsoft.Json` 处理 JSON 数据。示例展示了如何使用该类发送请求并处理响应。注意事项包括:简单的错误处理、需安装 `Newtonsoft.Json` 依赖,以及建议重用 `HttpClient` 实例以优化性能。
400 2
|
Web App开发 前端开发
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
最近在线上往hbase导数据,因为hbase写入能力比较强,没有太在意写的问题。让业务方进行历史数据的导入操作,中间发现一个问题,写入速度太快,并且业务数据集中到其中一个region,这个region无法split掉,处于不可用状态。
1509 0
|
Web App开发 监控 前端开发
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
Hbase依赖的datanode日志中如果出现如下报错信息:DataXceiverjava.io.EOFException: INFO org.apache.hadoop.hdfs.server.datanode.DataNode: Exception in receiveBlock for block  解决办法:Hbase侧配置的dfs.socket.timeout值过小,与DataNode侧配置的 dfs.socket.timeout的配置不一致,将hbase和datanode的该配置调成大并一致。
938 0