提交网络模块TCP 超时重连机制

提交网络模块TCP 超时重连机制
This commit is contained in:
ALEXTANG
2022-05-23 17:28:32 +08:00
parent fad0796857
commit c57769605c
17 changed files with 3405 additions and 14 deletions

View 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
}
}