using Assets.MyGame.Scripts.Net.WebResponses; using Best.WebSockets; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using static Assets.MyGame.Scripts.Net.WebResponses.ChineseChess.ChineseChessGameClassList; namespace Assets.MyGame.Scripts.Net { public partial class WebSocketManager : MonoSingleton { public void OnMessage(WebSocket web, string msg, SocketType type) { NetLog.Log($"[WebSocket] {type} 收到消息: {msg}"); // ⭐ 优化:用 char 分割避免 Split 产生 string[] 数组 GC // 单条消息通常只有 1-2 个分隔段,直接手动解析 int start = 0; int sep; while ((sep = msg.IndexOf('\u001e', start)) >= 0) { ProcessSingleMessage(msg, start, sep - start, type); start = sep + 1; } // 处理最后一段(或没有分隔符的整条消息) if (start < msg.Length) ProcessSingleMessage(msg, start, msg.Length - start, type); } private void ProcessSingleMessage(string msg, int start, int length, SocketType type) { // 跳过空白段 if (length <= 0) return; // 快速检查 "{}"(握手包),避免 Substring + Trim if (length == 2 && msg[start] == '{' && msg[start + 1] == '}') { OnHandshakeCompleted(type); return; } // 提取子串(此处必须分配,后续反序列化需要) string item = msg.Substring(start, length); // ⭐ 优化:用 JsonTextReader 只读 type/target 做路由,跳过 arguments 反序列化 // 原代码:JsonConvert.DeserializeObject>(item) // 会反序列化整个 arguments 数组(包含鱼数据等),但这些数据在路由阶段完全不需要, // 具体处理方法内部会重新反序列化。这能消除预解析阶段 90%+ 的 GC。 var v = ParseHeader(item); if (v == null) return; #region 消息处理 switch (type) { case SocketType.login: LoginMSG(item, v); break; case SocketType.room: case SocketType.game: FishMSG(item, v); break; case SocketType.chess: ChineseChess(item, v); break; case SocketType.customer: CustomerMSG(item, v); break; default: break; } #endregion } private static void ChineseChess(string msg, WebSocketResponseBase v) { var roomMainPanel = UIManager.Instance.Get(UIPanel.MainGamePanelZGXQ); switch ((MessageType)v.type) { case MessageType.Invocation: switch (v.target) { case "GetRoomList": UIManager.Instance.ShowAsync(UIPanel.MainGamePanelZGXQ, msg); Screen.orientation = ScreenOrientation.Portrait; break; case "BeginGame": { var va = JsonConvert.DeserializeObject>(msg.ToString()).arguments[0]; roomMainPanel.subUIManager.CloseAll(); roomMainPanel.StartGame(msg); roomMainPanel.UpdateUserData(va); } break; case "CreateRoomNotice": { var va = JsonConvert.DeserializeObject>(msg.ToString()).arguments[0]; var panel = roomMainPanel.subUIManager.Get(UIPanel.ZGXQDistrict); if (panel != null) { panel.UpdataRoom(va); } } break; case "JoinRoomNotice": { var va = JsonConvert.DeserializeObject>(msg.ToString()).arguments[0]; var panel = roomMainPanel.subUIManager.Get(UIPanel.ZGXQDistrict); if (panel != null) { panel.UpdataRoom(va); } } break; //所有人 case "GoldConfigRoomNotice": roomMainPanel.subUIManager.CloseAll(); roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQGameExplain, msg); break; // 房主开始游戏 → 通知红方配金币 case "GoldConfigRequired": roomMainPanel.OnGoldConfigRequired(msg); break; //单发给触发人 case "GoldConfigSuccess": break; case "ReadyNotice": { // ReadyNotice arguments[0] 是用户ID字符串,不是房间对象 var userId = JsonConvert.DeserializeObject>(msg).arguments[0]; // 标记该玩家已准备 if (roomMainPanel.roomData != null && roomMainPanel.roomData.users != null) { var u = roomMainPanel.roomData.users.Find(x => x.userId == userId); if (u != null) u.isReady = true; // 只有双方都准备好才进入游戏 if (roomMainPanel.roomData.users.Count >= 2 && roomMainPanel.roomData.users.TrueForAll(x => x.isReady)) { roomMainPanel.InitRoomData(); } } } break; case "GameOperation": { var va = JsonConvert.DeserializeObject>>(msg).arguments[0]; roomMainPanel.IsShowHint(va["data"].ToString(), Convert.ToInt32(va["type"])); } break; case "EndGameSuccess": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; if (va == null || va == "") { roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQHe); } else if (va == GameManager.Instance.user.id) { roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin); } else { roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQDefeated); } } break; case "ExitRoomSuccess": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; if (GameManager.Instance.user.id == va) { WebSocketSendList.GetRoomListChineseChess(1); } else { for (int i = 0; i < roomMainPanel.roomData.users.Count; i++) { var item = roomMainPanel.roomData.users[i]; if (item.userId != va) { roomMainPanel.roomData.users.Remove(item); roomMainPanel.UpdateUserData(roomMainPanel.roomData); i--; } } } } break; case "ExitRoomNotice": { { var va = JsonConvert.DeserializeObject>(msg.ToString()).arguments[0]; var panel = roomMainPanel.subUIManager.Get(UIPanel.ZGXQDistrict); if (panel != null) { panel.UpdataRoom(va); } } } break; case "Disconnect": roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin); break; case "GameError": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; if (va == "只有代理才能配置密码") { UIManager.Instance.Get(UIPanel.MainGamePanelZGXQ).subUIManager.Get(UIPanel.ZGXQCreateRoom).ShowTiShi(); } else UIManager.Instance.ShowBoardError(va); } break; // ======== 新增缺失事件 ======== case "JoinRoom": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; // 调用者自身加入成功 if (roomMainPanel != null) roomMainPanel.UpdateUserData(va); } break; case "Reconnect": { var va = JsonConvert.DeserializeObject>>(msg).arguments[0]; Debug.Log($"[Chess] 重连: userId={va["userId"]}, userName={va["userName"]}"); } break; case "AllBeginTime": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log("[Chess] 双方已准备,开始计时"); } break; case "GameEnd": { var va = JsonConvert.DeserializeObject>>(msg).arguments[0]; string winUserId = va["winUserId"]?.ToString(); if (string.IsNullOrEmpty(winUserId)) roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQHe); else if (winUserId == GameManager.Instance.user.BaseInfo.number) roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin); else roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQDefeated); } break; case "EndGameRequestSent": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log("[Chess] 结束请求已发送: " + va); } break; case "OpponentRequestEnd": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log("[Chess] 对手请求结束: " + va); } break; case "RestartGameSuccess": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; if (roomMainPanel != null) roomMainPanel.StartGame(msg); } break; case "RestartRoomNotice": { var va = JsonConvert.DeserializeObject>(msg).arguments[0]; var panel = roomMainPanel?.subUIManager.Get(UIPanel.ZGXQDistrict); if (panel != null) panel.UpdataRoom(va); } break; case "SyncGameData": { var va = JsonConvert.DeserializeObject>>(msg).arguments[0]; string data = va["data"]?.ToString(); Debug.Log("[Chess] 同步数据: " + data); } break; default: break; } break; case MessageType.StreamItem: break; case MessageType.Completion: break; case MessageType.StreamInvocation: break; case MessageType.CancelInvocation: break; case MessageType.Ping: break; case MessageType.Close: break; default: break; } } private static void CustomerMSG(string msg, WebSocketResponseBase v) { switch ((MessageType)v.type) { case MessageType.Invocation: switch (v.target) { // 发起会话回调(用户端):服务端返回 {isOk, msg, conversation} case "OnConversationStarted": { var result = JsonConvert.DeserializeObject>(msg).arguments[0]; WebSocketReponseList.Customer_OnConversationStarted(result); } break; // 消息接收回调(用户端 & 客服端) case "OnMessageReceived": { var result = JsonConvert.DeserializeObject>(msg).arguments[0]; WebSocketReponseList.Customer_OnMessageReceived(result); } break; // 新会话通知(客服端) case "OnNewConversation": { var result = JsonConvert.DeserializeObject>(msg).arguments[0]; WebSocketReponseList.Customer_OnNewConversation(result); } break; default: break; } break; case MessageType.StreamItem: break; case MessageType.Completion: break; case MessageType.StreamInvocation: break; case MessageType.CancelInvocation: break; case MessageType.Ping: break; case MessageType.Close: break; default: break; } } /// /// LoginHub 消息处理(Broadcast / AssetUpdated / BackpackUpdated / NewEmail / KillAnnouncement) /// private static void LoginMSG(string msg, WebSocketResponseBase v) { switch ((MessageType)v.type) { case MessageType.Invocation: switch (v.target) { case "Broadcast": WebSocketReponseList.LoginBroadcast(msg); break; case "AssetUpdated": WebSocketReponseList.AssetUpdated(msg); break; case "BackpackUpdated": WebSocketReponseList.BackpackUpdated(msg); break; case "NewEmail": WebSocketReponseList.NewEmail(msg); break; case "KillAnnouncement": WebSocketReponseList.KillAnnouncement(msg); break; case "LevelChanged": WebSocketReponseList.LevelChanged(msg); break; case "PendingRoomNotice": WebSocketReponseList.PendingRoomNotice(msg); break; case "AccountKicked": WebSocketReponseList.AccountKicked(msg); break; case "ChangeConfig": WebSocketReponseList.ChangeConfig(msg); break; case "KickToMainPage": WebSocketReponseList.KickToMainPage(msg); break; default: break; } break; } } private static void FishMSG(string msg, WebSocketResponseBase v) { switch ((MessageType)v.type) { case MessageType.Invocation: switch (v.target) { case "GetRoomListResult": //UIManager.Instance.Get(UIPanel.SelectRoomPenel); WebSocketReponseList.GetRoomListResult(msg); break; case "RoomJoinNotice": WebSocketReponseList.RoomJoinNotice(msg); break; case "RoomInfo": WebSocketReponseList.RoomInfo(msg); break; case "RoomFireNotice": WebSocketReponseList.RoomFireNotice(msg); break; case "RoomKillNotice": WebSocketReponseList.RoomKillNotice(msg); break; case "SyncPlayerGold": WebSocketReponseList.SyncPlayerGold(msg); break; case "ChangeGunMultipleNotice": WebSocketReponseList.ChangeGunMultipleNotice(msg); break; case "GetRoomInfoResult": WebSocketReponseList.GetRoomInfoResult(msg); break; case "SyncGameData": WebSocketReponseList.SyncGameDataResult(msg); break; case "RoomLeaveNotice": WebSocketReponseList.LeaveRoomResult(msg); //WebSocketManager.Instance.Close(SocketType.game); break; case "ChangeControllerUser": { // ⭐ 房主控制权移交:仅维护 roomOwnerSit,不再设置 fangzhu // (鱼创建已由服务端 OnFishSpawn 统一下发,客户端无需房主出鱼) var userId = JsonConvert.DeserializeObject>(msg).arguments[0]; if (userId == GameManager.Instance.user.id) { if (SitPosData.localPlayerSit != null) SitPosData.roomOwnerSit = SitPosData.localPlayerSit; } else if (userId != null && userId.StartsWith("bot_")) { // 房主是人机:由本地首个非人机玩家代行房主显示职责 SitPosData.TransferRoomOwner(); } } break; case "Exception": { var ex = JsonConvert.DeserializeObject>(msg).arguments[0]; if (ex == null) break; // 根据错误码分类处理 switch (ex.code) { case 1001: // 房间已满 → 弹窗提示并跳转房间列表 UIManager.Instance.ShowTiShi(ex.message, () => { switch (GameManager.Instance.gameData.currGameType) { case GameType.JQQD: break; case GameType.defaultPanel: UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelDefault, null, (panel) => { WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1); }); break; case GameType.FLZY: UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelFLZY, null, (panel) => { WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1); }); break; case GameType.YYSC: UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelYYSC, null, (panel) => { WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1); }); break; case GameType.ZGXQ: UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelZGXQ, null, (panel) => { WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1); }); break; case GameType.XJMH: UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelXJMH, null, (panel) => { WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1); }); break; case GameType.HHWK: UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelHHWK, null, (panel) => { WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1); }); break; default: break; } }); break; // 资产/状态类错误 → 弹窗提示,玩家需明确知晓 case 1002: // 金币不足 UIManager.Instance.ShowBoardError(ex.message); break; case 1003: // 房间不存在 case 1011: // 用户不存在 case 1013: // 道具数量不足 case 1014: // 钻石不足 UIManager.Instance.ShowTiShi(ex.message); break; // 参数/配置/技术性错误 → Toast 轻提示 case 1004: // 玩家不在房间 case 1005: // 签名错误 case 1006: // 请求已过期 case 1007: // 鱼配置不存在 case 1008: // 子弹参数无效 case 1009: // 金币参数超出范围 case 1012: // 道具配置不存在 case 1015: // 炮倍参数无效 default: // 0=未知错误 UIManager.Instance.ShowBoardError(ex.message); break; } } break; // ======== 新增 FishGameHub 缺失事件 ======== case "BindSuccess": { var msgText = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log("[FishGameHub] BindSuccess: " + msgText); } break; case "SendPlayerInfoResult": { var gold = JsonConvert.DeserializeObject>(msg).arguments[0]; // 发送玩家信息结果,gold 为当前金币数(可为 null) if (gold.HasValue) WebSocketReponseList.SyncPlayerGold(JsonConvert.DeserializeObject>(msg).arguments[0].ToString()); } break; case "RoomDisconnectNotice": { var userIdDisc = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log("[FishGameHub] 玩家断线: " + userIdDisc); } break; //case "ChangeGunMultipleNotice": // WebSocketReponseList.ChangeGunMultipleNotice(msg); // break; case "UsePropNotice": { var data = JsonConvert.DeserializeObject>>(msg).arguments[0]; var userId = data["userId"]?.ToString(); string propNumber = data["propNumber"]?.ToString(); Debug.Log($"[FishGameHub] 使用道具: userId={userId}, prop={propNumber}"); var panel = CreateFishManager.Instance.currGameType; if (panel != null) panel.OnUsePropNotice(userId, propNumber); } break; case "KillNotice": // 全局击杀公告(真人击杀/机器人模拟) WebSocketReponseList.KillNotice(msg); break; case "OnFishExpired": { var fishIds = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log($"[FishGameHub] 鱼过期: {string.Join(",", fishIds)}"); } break; case "OnFishList": // 鱼列表刷新 WebSocketReponseList.OnFishList(msg); break; // ======== FishGameHub / FishRoomHub 补充事件 ======== case "BotLeaveRoomNotice": // 机器人退出房间,数据 { userId } WebSocketReponseList.BotLeaveRoomNotice(msg); break; case "HostDisconnect": // 房主断线,其他玩家可接受机器人控制权 WebSocketReponseList.HostDisconnectNotice(msg); break; case "OnSeatChange": // 座位变更 { type, userId, position, showWaiting? } WebSocketReponseList.OnSeatChange(msg); break; // ======== 新协议事件 ======== case "OnFishSpawn": // ⭐ 服务端出鱼事件(破坏性替代 createFish 帧同步) // type: "init"=首次出鱼 / "spawn"=定时出鱼 / "sync"=重连或取消挂机同步 WebSocketReponseList.OnFishSpawn(msg); break; case "FishFrozen": // ⭐ 冰冻道具(40003)生效:鱼存活时间延长 15 秒,客户端写死 15s 倒计时 WebSocketReponseList.FishFrozen(msg); break; case "PlayerAfkNotice": // 玩家挂机通知 { userId } WebSocketReponseList.PlayerAfkNotice(msg); break; case "PlayerCancelAfkNotice": // 玩家取消挂机通知 { userId } WebSocketReponseList.PlayerCancelAfkNotice(msg); break; case "RoomChange": { // FishRoomHub 房间变化通知(人数/状态变更),更新房间列表 Debug.Log("[FishRoomHub] 房间变化通知"); WebSocketReponseList.RoomChangeNotice(msg); } break; default: break; } break; case MessageType.StreamItem: break; case MessageType.Completion: break; case MessageType.StreamInvocation: break; case MessageType.CancelInvocation: break; case MessageType.Ping: break; case MessageType.Close: break; default: break; } } /// /// 轻量解析:用 JsonTextReader 只读 type 和 target 字段做路由, /// 跳过 arguments 数组反序列化(节省 90%+ 预解析 GC)。 /// arguments 在具体处理方法内部按需反序列化。 /// private static WebSocketResponseBase ParseHeader(string item) { if (string.IsNullOrEmpty(item)) return null; try { using (var sr = new StringReader(item)) using (var reader = new JsonTextReader(sr)) { var result = new WebSocketResponseBase(); while (reader.Read()) { if (reader.TokenType == JsonToken.PropertyName) { string propName = (string)reader.Value; // 读到 arguments 就停止,不再解析后续(节省大量 GC) if (propName == "arguments") break; reader.Read(); // 读值 if (propName == "type") { result.type = Convert.ToInt32(reader.Value); } else if (propName == "target") { result.target = reader.Value as string; } // invocationId 等字段跳过 } } return result; } } catch { return null; } } } }