HttpHelper.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. using CSharpUtil;
  2. using GameFramework.Network;
  3. using Newtonsoft.Json;
  4. using Newtonsoft.Json.Linq;
  5. using System;
  6. using System.Collections.Concurrent;
  7. using System.Net;
  8. using System.Net.Http;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using UnityEngine;
  12. using UnityGameFramework.Runtime;
  13. /// <summary>
  14. /// 异步版本HTTP请求.
  15. /// author: gwang
  16. /// version:
  17. /// 2.0.0 队列逻辑修改, 不再用任务等待. 2022.9.7
  18. /// 1.0.0 task等待异步版. 2022.8.15
  19. /// </summary>
  20. public class HttpHelper : MonoSingleton<HttpHelper>
  21. {
  22. HttpClient m_client = null;
  23. // 发送队列
  24. private ConcurrentQueue<NetPacket> mPacketQueue = new ConcurrentQueue<NetPacket>();
  25. /// <summary>
  26. /// 接收队列
  27. /// </summary>
  28. private ConcurrentDictionary<int, NetPacket> mRecvQueue = new ConcurrentDictionary<int, NetPacket>();
  29. private volatile int mRecvPackSN = 0;
  30. private volatile int mDN = -1;
  31. private string m_url = Config_URL.Server_URL; // 服务器地址
  32. private HttpHelper()
  33. {
  34. #if UNITY_EDITOR
  35. bool useProxy = false;
  36. if (useProxy)
  37. {
  38. var msghandler = new HttpClientHandler();
  39. msghandler.UseProxy = true;
  40. msghandler.Proxy = new WebProxy("127.0.0.1", 8888);
  41. m_client = new HttpClient(msghandler);
  42. }
  43. else
  44. {
  45. #endif
  46. m_client = new HttpClient();
  47. #if UNITY_EDITOR
  48. }
  49. #endif
  50. m_client.MaxResponseContentBufferSize = 256000000; // 最大返回的字节数
  51. m_client.Timeout = TimeSpan.FromMilliseconds(2500); // 超时时间2.5秒
  52. }
  53. // 回调方法
  54. public delegate void OnNetBack(object eventObj);
  55. private string Get(string url)
  56. {
  57. try
  58. {
  59. var responseString = m_client.GetStringAsync(url);
  60. return responseString.Result;
  61. }
  62. catch (Exception ex)
  63. {
  64. return null;
  65. }
  66. }
  67. private string Post(string url, string strJson) => PostAsync(url, strJson).Result; // post同步请求方法
  68. private async Task<string> PostAsync(string url, string strJson) // post异步请求方法
  69. {
  70. try
  71. {
  72. byte[] data = CompressUtil.Deflate(GlobalConfig.Encoding.GetBytes(strJson)); // 自定义编码(deflate+utf8)
  73. HttpContent content = new ByteArrayContent(data);
  74. HttpResponseMessage res = await m_client.PostAsync(url, content); // 由HttpClient发出异步Post请求
  75. if (res.StatusCode == HttpStatusCode.OK)
  76. {
  77. var bytes = res.Content.ReadAsByteArrayAsync().Result;
  78. string decode = CompressUtil.InFlate(bytes, GlobalConfig.Encoding); // 自定义解码(defalte+utf8)
  79. return decode;
  80. }
  81. else
  82. {
  83. if (res.StatusCode == HttpStatusCode.RequestTimeout) // 请求超时
  84. {
  85. LogHelper.LogWarning("HTTP Request time out!");
  86. }
  87. var resp = new RespVo() { err = (int)HttpStatusCode.RequestTimeout, tag = new JObject() };
  88. resp.tag["errmsg"] = "请求超时.请检查网络环境!";
  89. return JsonConvert.SerializeObject(resp);
  90. }
  91. }
  92. catch (Exception ex)
  93. {
  94. Debug.LogException(ex);
  95. var resp = new RespVo() { err = (int)HttpStatusCode.SeeOther, tag = new JObject() };
  96. resp.tag["errmsg"] = "网络失败! 请检查网络环境!";// + ex.Message;
  97. return JsonConvert.SerializeObject(resp);
  98. }
  99. }
  100. /// <summary>
  101. /// 异步发送请求
  102. /// </summary>
  103. /// <typeparam name="T">回调函数参数类型</typeparam>
  104. /// <param name="param">请求参数</param>
  105. /// <param name="callback">回调函数</param>
  106. public async void SendMsg<T>(ReqVo param, Action<T> callback)
  107. {
  108. await Task.Run(async () =>
  109. {
  110. var netPacket = NetPacket.CreateNew<T>(param, callback);
  111. var ctx = JsonConvert.SerializeObject(netPacket.request);
  112. if (GlobalConfig.GAME_COMM_DEBUG)
  113. {
  114. var msg = ctx.Length > 1024 ? $"{ctx[..150]}...{ctx[^30..]}" : ctx;
  115. LogHelper.Log($"[SEND:]{msg}");
  116. }
  117. string res = await this.PostAsync(m_url, ctx);
  118. netPacket.tsResp = DateTimeOffset.Now;
  119. if (res == null)
  120. {
  121. netPacket.response = new RespVo() { err = ErrCode.net_other };
  122. }
  123. else
  124. {
  125. if (GlobalConfig.GAME_COMM_DEBUG)
  126. {
  127. string format = res;
  128. var msg = format.Length > 1024 ? format[..150] + $"...({format.Length / 1024:.0kb})..." + format[^30..] : format; // 约简日志长度
  129. var sp = (netPacket.tsResp - netPacket.tsRequest).TotalMilliseconds; // 计算往返耗时
  130. LogHelper.Log($"[RECV:]({sp:.00}ms){msg}");
  131. }
  132. RespVo data = JsonConvert.DeserializeObject<RespVo>(@res); // 返回的json数据解析成结构体
  133. netPacket.response = data;
  134. }
  135. if (!mRecvQueue.TryAdd(netPacket.request.SN, netPacket))
  136. {
  137. LogHelper.LogWarning($"收到重复响应包!{netPacket.request.SN}");
  138. }
  139. //LogHelper.Log($"放入{netPacket.request.SN}");
  140. });
  141. }
  142. private void Update()
  143. {
  144. while (mRecvQueue.TryRemove(mRecvPackSN, out var netPacket))
  145. {
  146. RaiseResult(netPacket);
  147. mRecvPackSN++;
  148. //LogHelper.Log($"放出 SN: {netPacket.request.SN}");
  149. // 处理失败, 再处理
  150. }
  151. }
  152. /// <summary>
  153. /// 发送返回结果事件
  154. /// </summary>
  155. /// <param name="packet"></param>
  156. /// <exception cref="UnityException"></exception>
  157. private void RaiseResult(NetPacket packet)
  158. {
  159. if (packet.errNum == 0)
  160. {
  161. var resp = packet.response;
  162. if (resp.err == ErrCode.succeed)
  163. {
  164. if (!(packet.request.cmd == CmdCode.cmd_user_loginuserinfo || packet.request.cmd == CmdCode.cmd_user_registerNewRole)
  165. && resp.result.TryGetValue("store", out var store))
  166. {
  167. if (mDN <= resp.DN)
  168. {
  169. mDN = resp.DN;
  170. UserProxy.Instance.player.InitFromStore((JObject)store);
  171. }
  172. else
  173. {
  174. LogHelper.LogWarning("DN版本低, 丢弃数据.");
  175. }
  176. }
  177. packet.handler?.Invoke(); // 先执行结果回调
  178. EventComponent eventCmpt = GameEntry.GetComponent<EventComponent>();
  179. //if (null != eventCmpt)
  180. //{
  181. resp.events.ForEach(ev =>
  182. {
  183. switch (ev.name)
  184. {
  185. case Enum_EventType.TaskCardFinished: // 任务卡完成dwddd
  186. eventCmpt?.FireNow(this, new TaskCardEventFinish(ev.arg1, ev.arg2));
  187. break;
  188. case Enum_EventType.TaskCardActived: // 任务卡激活
  189. eventCmpt?.FireNow(this, new TaskCardEventAtive(ev.arg1, ev.arg2));
  190. break;
  191. case Enum_EventType.TaskCardReward: // 任务卡领取奖励
  192. eventCmpt?.FireNow(this, new TaskCardEventReward(ev.arg1, ev.arg2));
  193. break;
  194. case Enum_EventType.MissionStepComplete: // 任务卡-任务步骤完成
  195. eventCmpt?.FireNow(this, new TaskEventStepFinish(ev.arg1, ev.arg2));
  196. break;
  197. case Enum_EventType.MissionStepProcess: // 任务卡 - 进度更新
  198. eventCmpt?.FireNow(this, new TaskEventStepProcess(ev.arg1, ev.arg2));
  199. break;
  200. case Enum_EventType.AddItem: // 获得道具
  201. eventCmpt?.FireNow(this, new User_AddItemEvent(ev.arg1, ev.arg2));
  202. break;
  203. case Enum_EventType.RemoveItem: // 移除道具
  204. break;
  205. case Enum_EventType.AddTaskItem: // 添加任务卡
  206. eventCmpt?.FireNow(this, new TaskCardEventAdd(ev.arg1, ev?.arg2 ?? null));
  207. break;
  208. case Enum_EventType.RemoveTaskItem: // 移除任务卡
  209. break;
  210. case Enum_EventType.StartPlot: // 开启剧情对话
  211. eventCmpt?.FireNow(this, new StartPlotScene(Convert.ToInt32(ev.arg1), Convert.ToInt32(ev.arg2)));
  212. break;
  213. case Enum_EventType.NpcDialog: // 开启NPC对话
  214. eventCmpt?.FireNow(this, new StartPlotNPC(Convert.ToInt32(ev.arg1), Convert.ToInt32(ev.arg2)));
  215. break;
  216. case Enum_EventType.UnlockBuild:
  217. UserProxy.Instance.player.PrivateState.unlockedBuild.Add(Convert.ToInt32(ev.arg1)); // 同步已解锁建筑id
  218. BuildManager.UnlockBuild(Convert.ToInt32(ev.arg1));
  219. eventCmpt?.FireNow(this, new BuildUnlockEvent(ev.arg1, ev.arg2));
  220. break;
  221. case Enum_EventType.UnlockMap:
  222. eventCmpt?.FireNow(this, new MapUnlockEvent(ev.arg1, ev.arg2));
  223. break;
  224. case Enum_EventType.UserLvlUP:
  225. eventCmpt?.FireNow(this, new UserLvlUpEvent(ev.arg1, ev.arg2));
  226. break;
  227. case Enum_EventType.HeroLvlUp:
  228. eventCmpt?.FireNow(this, new HeroLvlUpEvent(ev.arg1, ev.arg2));
  229. eventCmpt?.FireNow(this, new HeroHPMPRefreshEvent(ev.name));
  230. break;
  231. case Enum_EventType.HeroTupo:
  232. eventCmpt?.FireNow(this, new HeroTupoEvent(ev.arg1, ev.arg2));
  233. eventCmpt?.FireNow(this, new HeroHPMPRefreshEvent(ev.name));
  234. break;
  235. case Enum_EventType.WeaponLvUp:
  236. eventCmpt?.FireNow(this, new WuqiLvlUpEvent());
  237. eventCmpt?.FireNow(this, new HeroHPMPRefreshEvent(ev.name));
  238. break;
  239. case Enum_EventType.WeaponTuPo:
  240. eventCmpt?.FireNow(this, new WuqiTupoEvent());
  241. eventCmpt?.FireNow(this, new HeroHPMPRefreshEvent(ev.name));
  242. break;
  243. case Enum_EventType.YanLingLvUp:
  244. eventCmpt?.FireNow(this, new YanLingLvlUpEvent());
  245. eventCmpt?.FireNow(this, new HeroHPMPRefreshEvent(ev.name));
  246. break;
  247. case Enum_EventType.YanLingTuPo:
  248. eventCmpt?.FireNow(this, new YanLingTupoEvent());
  249. eventCmpt?.FireNow(this, new HeroHPMPRefreshEvent(ev.name));
  250. break;
  251. case Enum_EventType.PaySuccess:
  252. eventCmpt?.FireNow(this, new PaySuccessEvent());
  253. break;
  254. }
  255. });
  256. //}
  257. }
  258. else
  259. {
  260. var errNo = resp.err;
  261. var msg = (resp.tag ?? new JObject()).TryGetValue("errmsg", out var msgJ) ? msgJ.ToString() : "";
  262. if (errNo == 3510) // 糊了一个补丁, 任务卡未找到是因为重复发送领取奖励导致的,但是流程上不好处理,-gwang 2021年3月4日17:22:55
  263. {
  264. }
  265. else if (errNo == 1021)
  266. {
  267. UI_CueDialog.Instance().Open("消息超时,或者本地设备时间与现实时间差距较大!", "错误", E_DialogType.OneButton, () =>
  268. {
  269. LogHelper.Log("Exiting");
  270. Application.Quit();
  271. });
  272. }
  273. if (GameConfigData.IsReady)
  274. { // 错误处理模块
  275. if (GameConfigData.Ins.errmsg.ContainsKey(resp.err))
  276. {
  277. var errInfo = GameConfigData.Ins.errmsg[resp.err];
  278. if (null != errInfo)
  279. {
  280. msg = string.IsNullOrEmpty(msg) ? errInfo.msg : msg;
  281. if (errInfo.type == 1) // 错误类型为重启
  282. {
  283. UI_CueDialog.Instance().Open(msg, "错误", E_DialogType.OneButton, () =>
  284. {
  285. LogHelper.Log("Exiting");
  286. Application.Quit();
  287. });
  288. }
  289. else if (errInfo.type == 0) // 错误类型为警告
  290. {
  291. UI_TipsWindow.InitFloatWaringDialog("警告:" + msg);
  292. }
  293. }
  294. }
  295. }
  296. else // 错误信息表尚未初始化
  297. {
  298. LogHelper.Log($"Err[{errNo}]:{msg}.");
  299. if (msg.Length > 0)
  300. {
  301. UI_CueDialog.Instance().Open(msg, "错误", E_DialogType.OneButton, () =>
  302. {
  303. LogHelper.Log("Exiting");
  304. Application.Quit();
  305. });
  306. }
  307. // UI_TipsWindow.InitFloatWaringDialog(errInfo.type == 1 ? "错误:" : "警告:"+ msg);
  308. }
  309. }
  310. }
  311. else
  312. {
  313. UI_CueDialog.Instance().Open("连接不到游戏服务器, 请检查网络.", "网络故障", E_DialogType.OneButton, ExitGame);
  314. throw new UnityException("网络模块故障, 需要应用上层逻辑处理或重置网络.");
  315. }
  316. }
  317. /// <summary>
  318. /// 退出游戏
  319. /// </summary>
  320. private void ExitGame()
  321. {
  322. Application.Quit();
  323. }
  324. private class NetPacket
  325. {
  326. // 包体编号
  327. public int id = 0;
  328. // 请求内容
  329. public ReqVo request = null;
  330. // 返回回调
  331. public Action handler = null;
  332. // 返回内容
  333. public RespVo response = null;
  334. // 发送时间
  335. public DateTimeOffset tsRequest = DateTimeOffset.MinValue;
  336. // 返回时间
  337. public DateTimeOffset tsResp = DateTimeOffset.MinValue;
  338. // 设定的超时时间(秒)
  339. public int tsTimeout = 0;
  340. // 错误编号
  341. public int errNum = 0;
  342. // 错误信息
  343. public string errMsg = string.Empty;
  344. public static NetPacket CreateNew<T>(ReqVo req, Action<T> callback)
  345. {
  346. var obj = new NetPacket();
  347. obj.tsTimeout = GlobalConfig.Net_Connect_TimeOut_sec;
  348. obj.id = objId++;
  349. obj.request = req;
  350. obj.handler = () =>
  351. {
  352. if (null != obj.response)
  353. {
  354. if (obj.response is T t) // RespVo(某些消息需要直接拿Resp做处理)
  355. {
  356. callback?.Invoke(t);
  357. }
  358. else
  359. {
  360. callback?.Invoke(obj.response.result.ToObject<T>());
  361. }
  362. }
  363. };
  364. obj.tsRequest = DateTimeOffset.Now;
  365. return obj;
  366. }
  367. private static volatile int objId = 0;
  368. private NetPacket()
  369. {
  370. }
  371. }
  372. }