mirror of
https://github.com/Alex-Rachel/TEngine.git
synced 2025-08-14 16:51:28 +00:00
提交网络模块TCP 超时重连机制
提交网络模块TCP 超时重连机制
This commit is contained in:
568
TEngineHotUpdate/src/TEngineCore/Net/GameClient.cs
Normal file
568
TEngineHotUpdate/src/TEngineCore/Net/GameClient.cs
Normal file
@@ -0,0 +1,568 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TEngineProto;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TEngineCore.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端状态
|
||||
/// </summary>
|
||||
public enum GameClientStatus
|
||||
{
|
||||
StatusInit, //初始化
|
||||
StatusReconnect, //重新连接
|
||||
StatusClose, //断开连接
|
||||
StatusConnect, //连接中
|
||||
StatusEnter, //Login登录成功
|
||||
}
|
||||
|
||||
public delegate void CsMsgDelegate(MainPack mainPack);
|
||||
|
||||
public class GameClient:TSingleton<GameClient>
|
||||
{
|
||||
|
||||
#region Propriety
|
||||
|
||||
#region TimeOutCheck
|
||||
private const int CHECK_TIMEOUT_PERFRAME = 10;
|
||||
const int MAX_MSG_HANDLE = 256;
|
||||
UInt32 m_dwLastCheckIndex = 0;
|
||||
CsMsgDelegate[] m_aMsgHandles = new CsMsgDelegate[MAX_MSG_HANDLE];
|
||||
float[] m_fMsgRegTime = new float[MAX_MSG_HANDLE];
|
||||
private float m_timeout = 15;
|
||||
#endregion
|
||||
|
||||
private string m_lastHost = null;
|
||||
private int m_lastPort = 0;
|
||||
private GameClientStatus m_status = GameClientStatus.StatusInit;
|
||||
/// <summary>
|
||||
/// GameClient状态
|
||||
/// </summary>
|
||||
public GameClientStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_status = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最新连接错误的时间
|
||||
/// </summary>
|
||||
private float m_lastLogDisconnectErrTime = 0f;
|
||||
/// <summary>
|
||||
/// 最新的错误码
|
||||
/// </summary>
|
||||
private int m_lastNetErrCode = 0;
|
||||
/// <summary>
|
||||
/// 最近一次心跳的时间
|
||||
/// </summary>
|
||||
private float m_lastHbTime = 0f;
|
||||
/// <summary>
|
||||
/// 心跳间隔
|
||||
/// </summary>
|
||||
private const float m_heartBeatDurTime = 15;
|
||||
/// <summary>
|
||||
/// 连续心跳超时
|
||||
/// </summary>
|
||||
private int m_heatBeatTimeoutNum = 0;
|
||||
private int m_ping = -1;
|
||||
public bool IsEntered
|
||||
{
|
||||
get { return m_status == GameClientStatus.StatusEnter; }
|
||||
}
|
||||
public bool IsNetworkOkAndLogined
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_status == GameClientStatus.StatusEnter;
|
||||
}
|
||||
}
|
||||
|
||||
public int LastNetErrCode
|
||||
{
|
||||
get { return m_lastNetErrCode; }
|
||||
}
|
||||
|
||||
private ClientConnectWatcher m_connectWatcher;
|
||||
|
||||
private void ResetParam()
|
||||
{
|
||||
m_lastLogDisconnectErrTime = 0f;
|
||||
m_heatBeatTimeoutNum = 0;
|
||||
m_lastHbTime = 0f;
|
||||
m_ping = -1;
|
||||
m_lastNetErrCode = 0;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private TcpConnection m_connect;
|
||||
|
||||
public GameClient()
|
||||
{
|
||||
m_connect = new TcpConnection(this);
|
||||
m_connectWatcher = new ClientConnectWatcher(this);
|
||||
}
|
||||
|
||||
public bool Connect(string host, int port, bool reconnect = false)
|
||||
{
|
||||
ResetParam();
|
||||
if (!reconnect)
|
||||
{
|
||||
SetWatchReconnect(false);
|
||||
}
|
||||
//GameEventMgr.Instance.Send(ShowWaitingUI);
|
||||
m_lastHost = host;
|
||||
m_lastPort = port;
|
||||
Status = reconnect ? GameClientStatus.StatusReconnect : GameClientStatus.StatusInit;
|
||||
TLogger.LogWarning("Start connect server {0}:{1} Reconnect:{2}", host, port, reconnect);
|
||||
return m_connect.Connect(host, port);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
m_connect.Close();
|
||||
m_status = GameClientStatus.StatusInit;
|
||||
}
|
||||
|
||||
#region 发送网络消息
|
||||
/// <summary>
|
||||
/// 发送消息包
|
||||
/// </summary>
|
||||
/// <param name="reqPkg"></param>
|
||||
/// <returns></returns>
|
||||
public bool SendCsMsg(MainPack reqPkg)
|
||||
{
|
||||
if (!CheckPack(reqPkg))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return DoSendData(reqPkg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息包并注册回调
|
||||
/// </summary>
|
||||
/// <param name="pack"></param>
|
||||
/// <param name="resHandler"></param>
|
||||
/// <param name="needShowWaitUI"></param>
|
||||
/// <returns></returns>
|
||||
public bool SendCsMsg(MainPack pack, CsMsgDelegate resHandler = null, bool needShowWaitUI = true)
|
||||
{
|
||||
if (!CheckPack(pack))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ret = DoSendData(pack);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
TLogger.LogError("SendCSMsg Error");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resHandler != null)
|
||||
{
|
||||
RegTimeOutHandle((uint)pack.Actioncode, resHandler);
|
||||
RegActionHandle((int)pack.Actioncode, resHandler);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DoSendData(MainPack reqPkg)
|
||||
{
|
||||
var sendRet = m_connect.SendCsMsg(reqPkg);
|
||||
|
||||
return sendRet;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 网络消息回调,非主线程
|
||||
Dictionary<int, List<CsMsgDelegate>> m_mapCmdHandle = new Dictionary<int, List<CsMsgDelegate>>();
|
||||
/// <summary>
|
||||
/// 委托缓存堆栈
|
||||
/// </summary>
|
||||
private Queue<List<CsMsgDelegate>> cachelistHandle = new Queue<List<CsMsgDelegate>>();
|
||||
|
||||
/// <summary>
|
||||
/// 消息包缓存堆栈
|
||||
/// </summary>
|
||||
private Queue<MainPack> queuepPacks = new Queue<MainPack>();
|
||||
|
||||
/// <summary>
|
||||
/// 网络消息回调,非主线程
|
||||
/// </summary>
|
||||
/// <param name="pack"></param>
|
||||
public void HandleResponse(MainPack pack)
|
||||
{
|
||||
lock (cachelistHandle)
|
||||
{
|
||||
List<CsMsgDelegate> listHandle;
|
||||
|
||||
if (m_mapCmdHandle.TryGetValue((int)pack.Actioncode, out listHandle))
|
||||
{
|
||||
cachelistHandle.Enqueue(listHandle);
|
||||
|
||||
queuepPacks.Enqueue(pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Udp网络消息回调,非主线程
|
||||
/// </summary>
|
||||
/// <param name="pack"></param>
|
||||
private void UdpHandleResponse(MainPack pack)
|
||||
{
|
||||
//Debug.Log(pack);
|
||||
List<CsMsgDelegate> listHandle;
|
||||
|
||||
if (m_mapCmdHandle.TryGetValue((int)pack.Actioncode, out listHandle))
|
||||
{
|
||||
foreach (CsMsgDelegate handle in listHandle)
|
||||
{
|
||||
handle(pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 注册网络消息回调
|
||||
/// <summary>
|
||||
/// 注册静态消息
|
||||
/// </summary>
|
||||
/// <param name="iCmdID"></param>
|
||||
/// <param name="msgDelegate"></param>
|
||||
public void RegActionHandle(int actionId, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
List<CsMsgDelegate> listHandle;
|
||||
if (!m_mapCmdHandle.TryGetValue(actionId, out listHandle))
|
||||
{
|
||||
listHandle = new List<CsMsgDelegate>();
|
||||
m_mapCmdHandle[actionId] = listHandle;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
if (listHandle.Contains(msgDelegate))
|
||||
{
|
||||
Debug.LogFormat("-------------repeat RegCmdHandle ActionCode:{0}-----------", (ActionCode)actionId);
|
||||
}
|
||||
listHandle.Add(msgDelegate);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 注册Udp静态消息
|
||||
/// </summary>
|
||||
/// <param name="iCmdID"></param>
|
||||
/// <param name="msgDelegate"></param>
|
||||
public void UdpRegActionHandle(int actionId, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
List<CsMsgDelegate> listHandle;
|
||||
if (!m_mapCmdHandle.TryGetValue(actionId, out listHandle))
|
||||
{
|
||||
listHandle = new List<CsMsgDelegate>();
|
||||
m_mapCmdHandle[actionId] = listHandle;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
if (listHandle.Contains(msgDelegate))
|
||||
{
|
||||
Debug.LogFormat("-------------repeat RegCmdHandle ActionCode:{0}-----------", (ActionCode)actionId);
|
||||
}
|
||||
listHandle.Add(msgDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除消息处理函数
|
||||
/// </summary>
|
||||
/// <param name="cmdId"></param>
|
||||
/// <param name="msgDelegate"></param>
|
||||
public void RmvCmdHandle(int actionId, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
List<CsMsgDelegate> listHandle;
|
||||
if (!m_mapCmdHandle.TryGetValue(actionId, out listHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
listHandle.Remove(msgDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckPack(MainPack pack)
|
||||
{
|
||||
if (pack == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pack.Actioncode == ActionCode.ActionNone)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pack.Requestcode == RequestCode.RequestNone)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 心跳处理
|
||||
protected bool CheckHeatBeatTimeout()
|
||||
{
|
||||
if (m_heatBeatTimeoutNum >= 2)
|
||||
{
|
||||
Shutdown();
|
||||
m_heatBeatTimeoutNum = 0;
|
||||
Status = GameClientStatus.StatusClose;
|
||||
TLogger.LogError("heat beat detect timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TickHeartBeat()
|
||||
{
|
||||
if (Status != GameClientStatus.StatusEnter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nowTime = GameTime.realtimeSinceStartup;
|
||||
if (m_lastHbTime + m_heartBeatDurTime < nowTime)
|
||||
{
|
||||
m_lastHbTime = nowTime;
|
||||
MainPack pack = new MainPack
|
||||
{
|
||||
Actioncode = ActionCode.HeartBeat
|
||||
};
|
||||
GameClient.Instance.SendCsMsg(pack, HandleHeatBeatRes);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleHeatBeatRes(MainPack mainPack)
|
||||
{
|
||||
if (mainPack.Returncode != ReturnCode.Success)
|
||||
{
|
||||
//如果是超时了,则标记最近收到包的次数
|
||||
if (mainPack.Returncode == ReturnCode.MsgTimeOut)
|
||||
{
|
||||
m_heatBeatTimeoutNum++;
|
||||
TLogger.LogError("heat beat timeout: {0}", m_heatBeatTimeoutNum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float diffTime = GameTime.realtimeSinceStartup - mainPack.HeatEchoTime;
|
||||
m_ping = (int)(diffTime * 1000);
|
||||
m_heatBeatTimeoutNum = 0;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 清理所有的网络消息
|
||||
/// </summary>
|
||||
public void CleanAllNetMsg()
|
||||
{
|
||||
m_mapCmdHandle.Clear();
|
||||
}
|
||||
|
||||
public void Reconnect()
|
||||
{
|
||||
m_connectWatcher.OnReConnect();
|
||||
Connect(m_lastHost, m_lastPort, true);
|
||||
}
|
||||
|
||||
public void OnUpdate()
|
||||
{
|
||||
HandleCsMsgOnUpdate();
|
||||
CheckCsMsgTimeOut();
|
||||
TickHeartBeat();
|
||||
CheckHeatBeatTimeout();
|
||||
m_connectWatcher.Update();
|
||||
}
|
||||
|
||||
#region 超时检测
|
||||
|
||||
private readonly MainPack _timeOutPack = new MainPack { Returncode = ReturnCode.MsgTimeOut };
|
||||
private void CheckCsMsgTimeOut()
|
||||
{
|
||||
float nowTime = GameTime.time;
|
||||
for (int i = 0; i < CHECK_TIMEOUT_PERFRAME; i++)
|
||||
{
|
||||
m_dwLastCheckIndex = (m_dwLastCheckIndex + 1) % MAX_MSG_HANDLE;
|
||||
if (m_aMsgHandles[m_dwLastCheckIndex] != null)
|
||||
{
|
||||
if (m_fMsgRegTime[m_dwLastCheckIndex] + m_timeout < nowTime)
|
||||
{
|
||||
TLogger.LogError("msg timeout, resCmdID[{0}]", m_aMsgHandles[m_dwLastCheckIndex]);
|
||||
|
||||
NotifyTimeout(m_aMsgHandles[m_dwLastCheckIndex]);
|
||||
|
||||
RmvCheckCsMsg((int)m_dwLastCheckIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RmvCheckCsMsg(int index)
|
||||
{
|
||||
m_aMsgHandles[index] = null;
|
||||
m_fMsgRegTime[index] = 0;
|
||||
}
|
||||
|
||||
private void RegTimeOutHandle(uint actionCode, CsMsgDelegate resHandler)
|
||||
{
|
||||
uint hashIndex = actionCode % MAX_MSG_HANDLE;
|
||||
m_aMsgHandles[hashIndex] = resHandler;
|
||||
m_fMsgRegTime[hashIndex] = GameTime.time;
|
||||
}
|
||||
|
||||
protected void NotifyTimeout(CsMsgDelegate msgHandler)
|
||||
{
|
||||
msgHandler(_timeOutPack);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HandleCsMsgOnUpdate()
|
||||
{
|
||||
if (cachelistHandle.Count <= 0 || queuepPacks.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
foreach (CsMsgDelegate handle in cachelistHandle.Dequeue())
|
||||
{
|
||||
handle(queuepPacks.Peek());
|
||||
}
|
||||
queuepPacks.Dequeue();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TLogger.LogError(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override void Active()
|
||||
{
|
||||
base.Active();
|
||||
}
|
||||
|
||||
public override void Release()
|
||||
{
|
||||
base.Release();
|
||||
}
|
||||
|
||||
public bool IsStatusCanSendMsg()
|
||||
{
|
||||
if (m_status == GameClientStatus.StatusEnter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float nowTime = GameTime.time;
|
||||
if (m_lastLogDisconnectErrTime + 5 < nowTime)
|
||||
{
|
||||
TLogger.LogError("GameClient not connected, send msg failed");
|
||||
m_lastLogDisconnectErrTime = nowTime;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否需要监控网络重连
|
||||
/// </summary>
|
||||
/// <param name="needWatch"></param>
|
||||
public void SetWatchReconnect(bool needWatch)
|
||||
{
|
||||
m_connectWatcher.Enable = needWatch;
|
||||
}
|
||||
|
||||
#region Ping
|
||||
/// <summary>
|
||||
/// ping值
|
||||
/// </summary>
|
||||
public int Ping
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPingValid())
|
||||
{
|
||||
return m_ping / 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPingValid()
|
||||
{
|
||||
if (IsNetworkOkAndLogined)
|
||||
{
|
||||
return m_ping >= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GetNetworkType
|
||||
public static CsNetworkType GetNetworkType()
|
||||
{
|
||||
CsNetworkType csNetType = CsNetworkType.CSNETWORK_UNKNOWN;
|
||||
NetworkReachability reachability = Application.internetReachability;
|
||||
switch (reachability)
|
||||
{
|
||||
case NetworkReachability.NotReachable:
|
||||
break;
|
||||
case NetworkReachability.ReachableViaLocalAreaNetwork:
|
||||
csNetType = CsNetworkType.CSNETWORK_WIFI;
|
||||
break;
|
||||
case NetworkReachability.ReachableViaCarrierDataNetwork:
|
||||
csNetType = CsNetworkType.CSNETWORK_3G;
|
||||
break;
|
||||
}
|
||||
return csNetType;
|
||||
}
|
||||
|
||||
public enum CsNetworkType
|
||||
{
|
||||
CSNETWORK_UNKNOWN = 0, /*未知类型*/
|
||||
CSNETWORK_WIFI = 1, /*Wifi类型*/
|
||||
CSNETWORK_3G = 2, /*3G类型*/
|
||||
CSNETWORK_2G = 3 /*2G类型*/
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user