1 Http服务 访问器设计思路
Unity3D 在2018版本中弃用了WWW请求,使用UnityWebRequest 进行网络请求,这个方法是为了满足今天的 HTTP 通信的需求,而且诞生的新类,相对于WWW这个方法,会更灵活一些,但是用起来却很不方便。
所以我将UnityWebRequest封装了一下。封装的目的有两个:1.封装后访问Http用着方便;2.在框架层上隔离原生API和具体业务,即使后续API变更也不会影响到业务逻辑,避免业务受影响。
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博客