using System.Net.Sockets; using GameBase; using GameProto; using TEngine; using CSPkg = GameProto.CSPkg; namespace GameLogic { public enum GameClientStatus { StatusInit, //初始化 StatusReconnect, //重新连接 StatusClose, //断开连接 StatusLogin, //登录中 StatusEnter, //AccountLogin成功,进入服务器了 } public enum CsMsgResult { NoError = 0, NetworkError = 1, InternalError = 2, MsgTimeOut = 3, PingTimeOut = 4, } //定义消息回报的回调接口 public delegate void CsMsgDelegate(CsMsgResult result, CSPkg msg); /// /// 统计网络协议的接口 /// public delegate void CsMsgStatDelegate(int cmdID, int pkgSize); public class GameClient : Singleton { private readonly INetworkChannel _channel; private GameClientStatus _status = GameClientStatus.StatusInit; private readonly MsgDispatcher _dispatcher; private readonly ClientConnectWatcher _connectWatcher; private float _lastLogDisconnectErrTime = 0f; private int _lastNetErrCode = 0; public int LastNetErrCode => _lastNetErrCode; public GameClientStatus Status { get => _status; set => _status = value; } public bool IsEntered => _status == GameClientStatus.StatusEnter; /// /// 连续心跳超时 /// private int _heatBeatTimeoutNum = 0; private int _ping = -1; private float NowTime => GameTime.unscaledTime; private string _lastHost = null; private int _lastPort = 0; public GameClient() { _connectWatcher = new ClientConnectWatcher(this); _dispatcher = new MsgDispatcher(); _dispatcher.SetTimeout(5f); GameEvent.AddEventListener(NetworkEvent.NetworkConnectedEvent,OnNetworkConnected); GameEvent.AddEventListener(NetworkEvent.NetworkClosedEvent,OnNetworkClosed); GameEvent.AddEventListener(NetworkEvent.NetworkErrorEvent,OnNetworkError); GameEvent.AddEventListener(NetworkEvent.NetworkCustomErrorEvent,OnNetworkCustomError); _channel = Network.Instance.CreateNetworkChannel("GameClient", ServiceType.Tcp, new NetworkChannelHelper()); } private void OnNetworkConnected(INetworkChannel channel, object userdata) { bool isReconnect = (_status == GameClientStatus.StatusReconnect); //准备登录 Status = GameClientStatus.StatusLogin; OnServerConnected(isReconnect); } private void OnNetworkClosed(INetworkChannel channel) { } private void OnNetworkError(INetworkChannel channel, NetworkErrorCode networkErrorCode, SocketError socketError, string errorMessage) { } private void OnNetworkCustomError(INetworkChannel channel, object userData) { } public void Connect(string host, int port, bool reconnect = false) { ResetParam(); if (!reconnect) { SetWatchReconnect(false); } if (reconnect) { // GameEvent.Get().ShowWaitUITip(WaitUISeq.LOGINWORLD_SEQID, G.R(TextDefine.ID_TIPS_RECONNECTING)); } else { // GameEvent.Get().ShowWaitUI(WaitUISeq.LOGINWORLD_SEQID); } _lastHost = host; _lastPort = port; Status = reconnect ? GameClientStatus.StatusReconnect : GameClientStatus.StatusInit; _channel.Connect(host, port); } public void Reconnect() { if (string.IsNullOrEmpty(_lastHost) || _lastPort <= 0) { // GameModule.UI.ShowTipMsg("Invalid reconnect param"); return; } _connectWatcher.OnReConnect(); Connect(_lastHost, _lastPort, true); } public void Shutdown() { _channel.Close(); _status = GameClientStatus.StatusInit; } public void OnServerConnected(bool isReconnect) { } public bool SendCsMsg(CSPkg reqPkg) { if (!IsStatusCanSendMsg(reqPkg.Head.MsgId)) { return false; } return DoSendData(reqPkg); } public bool IsStatusCanSendMsg(uint msgId) { bool canSend = false; if (_status == GameClientStatus.StatusLogin) { canSend = (msgId == (uint)CSMsgID.CsCmdActLoginReq); } if (_status == GameClientStatus.StatusEnter) { canSend = true; } if (!canSend) { float nowTime = NowTime; if (_lastLogDisconnectErrTime + 5 < nowTime) { Log.Error("GameClient not connected, send msg failed, msgId[{0}]", msgId); _lastLogDisconnectErrTime = nowTime; } //UISys.Mgr.ShowTipMsg(TextDefine.ID_ERR_NETWORKD_DISCONNECT); } return canSend; } public bool SendCsMsg(CSPkg reqPkg, uint resCmd, CsMsgDelegate resHandler = null, bool needShowWaitUI = true) { if (!IsStatusCanSendMsg(reqPkg.Head.MsgId)) { return false; } var ret = DoSendData(reqPkg); if (!ret) { if (resHandler != null) { resHandler(CsMsgResult.InternalError, null); } _dispatcher.NotifyCmdError(resCmd, CsMsgResult.InternalError); } else { //注册消息 if (resHandler != null) { _dispatcher.RegSeqHandle(reqPkg.Head.Echo, resCmd, resHandler); if (reqPkg.Head.Echo > 0 && IsWaitingCmd(resCmd) && needShowWaitUI) { // TODO // GameEvent.Get().ShowWaitUI(reqPkg.Head.Echo); } } } return ret; } private bool DoSendData(CSPkg reqPkg) { if (!IsIgnoreLog(reqPkg.Head.MsgId)) { Log.Debug("[c-s] CmdId[{0}]\n{1}", reqPkg.Head.MsgId, reqPkg.Body.ToString()); } var sendRet = _channel.Send(reqPkg); return sendRet; } private bool IsIgnoreLog(uint msgId) { bool ignoreLog = false; switch (msgId) { case (uint)CSMsgID.CsCmdHeatbeatReq: case (uint)CSMsgID.CsCmdHeatbeatRes: ignoreLog = true; break; } return ignoreLog; } public static bool IsWaitingCmd(uint msgId) { //心跳包不需要读条等待 if (msgId == (uint)CSMsgID.CsCmdHeatbeatRes) { return false; } return true; } private void ResetParam() { _lastLogDisconnectErrTime = 0f; _heatBeatTimeoutNum = 0; _lastHbTime = 0f; _ping = -1; _lastNetErrCode = 0; } public void OnUpdate() { _dispatcher.Update(); TickHeartBeat(); CheckHeatBeatTimeout(); _connectWatcher.Update(); } /// /// 注册静态消息 /// /// /// public void RegCmdHandle(int iCmdID, CsMsgDelegate msgDelegate) { _dispatcher.RegCmdHandle((uint)iCmdID, msgDelegate); } /// /// 移除消息处理函数 /// /// /// public void RmvCmdHandle(int cmdId, CsMsgDelegate msgDelegate) { _dispatcher.RmvCmdHandle((uint)cmdId, msgDelegate); } /// /// 设置加密密钥 /// /// public void SetEncryptKey(string key) { } /// /// 设置是否需要监控网络重连。 /// 登录成功后,开启监控,可以自动重连或者提示玩家重连。 /// /// public void SetWatchReconnect(bool needWatch) { _connectWatcher.Enable = needWatch; } public bool IsNetworkOkAndLogin() { return _status == GameClientStatus.StatusEnter; } #region 心跳处理 /// /// 最近一次心跳的时间 /// private float _lastHbTime = 0f; /// /// 心跳间隔 /// private readonly float _heartBeatDurTime = 5; /// /// 心跳超时的最大次数 /// private const int HeatBeatTimeoutMaxCount = 2; private bool CheckHeatBeatTimeout() { if (_heatBeatTimeoutNum >= HeatBeatTimeoutMaxCount) { //断开连接 Shutdown(); //准备重连 _heatBeatTimeoutNum = 0; Status = GameClientStatus.StatusClose; Log.Error("heat beat detect timeout"); return false; } return true; } void TickHeartBeat() { if (Status != GameClientStatus.StatusEnter) { return; } var nowTime = NowTime; if (_lastHbTime + _heartBeatDurTime < nowTime) { _lastHbTime = nowTime; CSPkg heatPkg = ProtobufUtility.BuildCsMsg((int)CSMsgID.CsCmdHeatbeatReq); heatPkg.Body.HeatBeatReq = new CSHeatBeatReq { HeatEchoTime = _lastHbTime }; SendCsMsg(heatPkg, (int)CSMsgID.CsCmdHeatbeatRes, HandleHeatBeatRes); } } void HandleHeatBeatRes(CsMsgResult result, CSPkg msg) { if (result != CsMsgResult.NoError) { //如果是超时了,则标记最近收到包的次数 if (result == CsMsgResult.MsgTimeOut) { _heatBeatTimeoutNum++; Log.Warning("heat beat timeout: {0}", _heatBeatTimeoutNum); } } else { var resBody = msg.Body.HeatBeatRes; float diffTime = NowTime - resBody.HeatEchoTime; _ping = (int)(diffTime * 1000); _heatBeatTimeoutNum = 0; } } #endregion #region Ping值 /// /// ping值 /// public int Ping { get { if (IsPingValid()) { return _ping / 4; } else { return 0; } } } public bool IsPingValid() { if (IsNetworkOkAndLogin()) { return _ping >= 0; } return false; } #endregion } }