Files
hjha-server/GlobalSever/MQ/ServerHeart.cs
xiaoou e9616125ce feat: initial commit - HJHA game server full source
6 major server modules (PdkFriendServer/GlobalSever/ServerCore/GameModule/GameNetModule) +
game logic (GameFix/GameDAL/ServerData) +
network layer (NetWorkMessage) +
data layer (ObjectModel) +
utilities (MrWu/Core/Config/CloudAPI/dll) +
adapters (zyxAdapter/base)

.NET 8.0 C# solution, 16 projects, 958 source files
2026-07-07 12:02:15 +08:00

572 lines
18 KiB
C#

/*
* 作者:吴隆健
* 日期: 2019-04-15
* 时间: 15:27
*
*/
using System;
using System.Collections.Concurrent;
using Server.DB.Redis;
using Server.Data.Module;
using System.Collections.Generic;
using System.Threading;
using MrWu.Debug;
using System.Threading.Tasks;
using GameData;
using Server.Core;
using Server.MQ;
using StackExchange.Redis;
namespace Server
{
//发包就四种情况
/*
* 1.发所有
* 2.发某类模块的所有
* 3.发给指定的
* 4.发给某类模块的任意一个
* */
//拦截多开与检查断线
//提供的服务就是判断在不在线,
/// <summary>
/// 服务器生命检查
/// </summary>
public class ServerHeart : ServerNode
{
/// <summary>
///
/// </summary>
public static ServerHeart instanece {
get;
private set;
}
public bool IsSendHeart = false;
/// <summary>
///
/// </summary>
protected ServerHeart() { }
static ServerHeart() {
instanece = new ServerHeart();
}
/// <summary>
/// 模块类型
/// </summary>
public ModuleType myType {
get;
private set;
}
/// <summary>
/// 模块名称
/// </summary>
public string moduleName {
get;
private set;
}
/// <summary>
/// 未收到心跳的最长时间,超过这个事件认为是掉线
/// </summary>
private const int MaxNoGetHeart = 40;
/// <summary>
/// 1分钟不更新认为自己死亡
/// </summary>
private readonly TimeSpan heartTime = new TimeSpan(0, 0, MaxNoGetHeart);
/// <summary>
/// 1分钟不更新认为死亡 单位秒
/// </summary>
private const int dieTime = MaxNoGetHeart * 1000;
/// <summary>
/// 所有的服务器->这个数据
/// </summary>
private readonly ConcurrentDictionary<int, ClusterInfo> Servers = new ConcurrentDictionary<int, ClusterInfo>();
private string m_nodeHeartKey;
/// <summary>
/// 节点信息 key
/// </summary>
/// <returns></returns>
private string nodeHeartKey {
get
{
if (m_nodeHeartKey == null)
m_nodeHeartKey = GetNodeHeartKey(moduleName, nodeNum); // "Game:LockModule:Module." + moduleName + "." + nodeNum;
return m_nodeHeartKey;
}
}
public string GetNodeHeartKey(string moduleName,int nodeNum)
{
return $"Game:LockModule:Module.{moduleName}.{nodeNum}";
}
/// <summary>
/// 定时器 _ 线程定时器
/// </summary>
private Timer timer;
DistributedLock disLock = new DistributedLock(10);
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <param name="nodeNum"></param>
/// <returns></returns>
//启动 -> 因为操作同一个库,所以必须分布式锁 -> 从redis中获取到自己这个服务器的编号
public bool Start(ModuleType type, int nodeNum) {
this.myType = type;
this.moduleName = Enum.GetName(type.GetType(), type);
this.id = (int)type;
this.nodeNum = nodeNum;
//检查redis中的数据
bool start = disLock.LockRun(nodeHeartKey,() => {
if (redisManager.db.KeyExists(nodeHeartKey))
return false;
return Heart();
});
AddModule(id,moduleName, nodeNum);
if (start) {
//10秒一次
timer = new Timer(OnTime, null, 0, 10000);
}
return start;
}
/// <summary>
/// 正常关闭模块
/// </summary>
public void Close() {
timer.Dispose();
if (heartTimer != null)
heartTimer.Dispose();
disLock.LockRun(nodeHeartKey, () => {
if (redisManager.db.KeyExists(nodeHeartKey))
redisManager.db.KeyDelete(nodeHeartKey);
});
}
private List<NodeInfo> tmpdieNode = new List<NodeInfo>();
/// <summary>
/// 定时器
/// </summary>
private void OnTime(Object obj) {
//心跳
HeartAsync();
tmpdieNode.Clear();
//每10秒检测一次所有服务器是否掉线
try {
foreach (var ser in Servers.Values) {
foreach (var node in ser.nodes.Values) {
if (node.type == id && node.nodeNum == nodeNum) continue;
if (node.ExpireTime - TimeInfo.Instance.FrameTime < 15) //距离过期时间剩下15秒内 才去检查
{
if (!tmpdieNode.Contains(node)) {
tmpdieNode.Add(node);
}
}
}
}
} catch (Exception e) {
Debug.Error("D12E5112-817D-40C0-A8FB-CED9AC969945:" + e.ToString());
}
_ = CheckDie();
}
private async Task CheckDie()
{
if (tmpdieNode.Count <= 0)
{
return;
}
List<NodeInfo> checks = new List<NodeInfo>();
List<Task<TimeSpan?>> timeSpans = new List<Task<TimeSpan?>>();
IBatch batch = redisManager.db.CreateBatch();
for (int i = 0; i < tmpdieNode.Count; i++)
{
checks.Add(tmpdieNode[i]);
timeSpans.Add(batch.KeyTimeToLiveAsync(GetNodeHeartKey(tmpdieNode[i].ModuleName, tmpdieNode[i].nodeNum)));
}
batch.Execute();
await Task.WhenAll(timeSpans);
for (int i = 0; i < checks.Count; i++)
{
if (timeSpans[i].Result == null)
{
Debug.ImportantLog($"模块掉线:{checks[i].ModuleName}-{checks[i].nodeNum}");
checks[i].isDie = true;
RemoveDie(checks[i].type, checks[i].nodeNum);
GlobalDispatcher.Instance.DispatchNextFrame(GlobalMsgId.ModuleNodeCrash,checks[i].ModuleName);
}
else
{
checks[i].ExpireTime = TimeInfo.Instance.FrameTime + (long)timeSpans[i].Result.Value.TotalMilliseconds;
//Debug.Info($"过期时间:{checks[i].ModuleName} {checks[i].nodeNum} {checks[i].ExpireTime}");
}
}
}
/// <summary>
/// 添加模块
/// </summary>
/// <param name="moduleId"></param>
/// <param name="nodeNum"></param>
public void AddModule(int moduleId,string moduleName,int nodeNum)
{
try
{
ClusterInfo ci = Servers.GetOrAdd(moduleId, new ClusterInfo()
{
type = moduleId,
ModuleName = moduleName
});
NodeInfo node = ci.nodes.GetOrAdd(nodeNum, new NodeInfo()
{
type = moduleId,
ModuleName = moduleName,
nodeNum = nodeNum,
isDie = false
});
node.ExpireTime = TimeInfo.Instance.FrameTime + dieTime;
}
catch (Exception e)
{
Debug.Error("1AD0CEC8-1ACB-4CEC-AB11-EB86C8A63C75:" + e.ToString());
}
}
// /// <summary>
// /// 处理心跳
// /// </summary>
// /// <param name="moduleId"></param>
// /// <param name="nodeNum"></param>
// public void DoHeart(int moduleId, int nodeNum) {
// //心跳
// try
// {
// ClusterInfo ci = Servers.GetOrAdd(moduleId, new ClusterInfo()
// {
// type = moduleId
// });
//
// NodeInfo node = ci.nodes.GetOrAdd(nodeNum, new NodeInfo()
// {
// type = moduleId,
// nodeNum = nodeNum
// });
//
// node.LastActivityTime = TimeInfo.Instance.FrameTime;
// } catch (Exception e) {
// Debug.Error("1AD0CEC8-1ACB-4CEC-AB11-EB86C8A63C75:" + e.ToString());
// }
// }
// /// <summary>
// /// 来心跳
// /// </summary>
// /// <param name="moduleId"></param>
// /// <param name="nodeNum"></param>
// public Task DoHeartAsync(int moduleId, int nodeNum) {
// return Task.Run(
// () => DoHeart(moduleId, nodeNum)
// );
// }
/// <summary>
/// 某个模块是否死亡
/// </summary>
public Task<bool> IsDieAsync(int moduleId, int nodeNum) {
return Task.Run(() => IsDie(moduleId, nodeNum));
}
/// <summary>
/// 检查节点死否死亡
/// </summary>
/// <param name="moduleId"></param>
/// <param name="nodeNum"></param>
/// <returns></returns>
public bool IsDie(int moduleId, int nodeNum) {
bool result = true;
try {
if (Servers.TryGetValue(moduleId, out ClusterInfo ci))
{
if (ci.nodes.TryGetValue(nodeNum, out NodeInfo node))
{
result = node.isDie;
if (result)
RemoveDie(moduleId, nodeNum);
}
}
} catch (Exception e) {
Debug.Error("FC703541-D8AB-4641-9618-9CB0F090FFE4:" + e.ToString());
}
return result;
}
/// <summary>
/// 检查该模块是否没有一个在线
/// </summary>
/// <param name="moduleId"></param>
/// <returns></returns>
public bool IsDie(int moduleId) {
bool result = true;
try {
if (Servers.TryGetValue(moduleId, out ClusterInfo ci)) {
foreach (var node in ci.nodes.Values) {
if (!node.isDie) {
result = false;
break;
}
}
}
} catch (Exception e) {
Debug.Error("78A9B0FC-9EFF-4E53-B921-F5EF328BCF5F:" + e.ToString());
}
return result;
}
/// <summary>
/// 输出日志
/// </summary>
public string Log() {
string str = string.Empty;
str += id + "," + nodeNum + ":";
str += Environment.NewLine;
try {
foreach (var ser in Servers.Values) {
foreach (NodeInfo node in ser.nodes.Values) {
str += "在线的模块:" + node.type + "," + node.nodeNum +"," + Environment.NewLine;
}
}
} catch (Exception e) {
Debug.Error("E4BEB6EB-465A-428B-9C5D-85264A108429:" + e.ToString());
}
//str += Environment.NewLine;
return str;
}
/// <summary>
/// 移除死亡的模块
/// </summary>
/// <param name="moduleId"></param>
/// <param name="nodeNum"></param>
public Task RemoveDie(int moduleId, int nodeNum, bool force = false) {
//移除死掉的节点
return Task.Run(() => {
bool r = false;
try {
ClusterInfo ci = null;
if (Servers.TryGetValue(moduleId, out ci)) {
NodeInfo node = null;
if (ci.nodes.TryGetValue(nodeNum, out node)) {
if (node.isDie && (moduleId != this.id || nodeNum != this.nodeNum)) {
r = ci.nodes.TryRemove(nodeNum,out _);
} else if (force) {
r = ci.nodes.TryRemove(nodeNum,out _);
}
}
}
} catch (Exception e) {
Debug.Error("3D910EC1-3682-44B2-A767-4FC8517BFD2D:" + e.ToString());
}
if (r) {
GlobalDispatcher.Instance.DispatchNextFrame(GlobalMsgId.ModuleNodeDie,new ServerNode(moduleId,nodeNum));
}
});
}
/// <summary>
/// 获取所有的在线的节点
/// </summary>
/// <returns></returns>
public List<ServerNode> GetAllOnline() {
List<ServerNode> nodes = new List<ServerNode>();
try
{
foreach (var ser in Servers.Values)
{
foreach (var node in ser.nodes.Values)
{
if (!node.isDie)
nodes.Add(new ServerNode(node.type, node.nodeNum));
}
}
}
catch (Exception e)
{
Debug.Error("F95EF643-7315-48A6-8D07-1606E16102D3:" + e.ToString());
}
return nodes;
}
/// <summary>
/// 获取所有的在线节点
/// </summary>
/// <returns></returns>
public List<ServerNode> GetAllOnline(int moduleId) {
List<ServerNode> nodes = new List<ServerNode>();
try {
ClusterInfo ci = null;
if (Servers.TryGetValue(moduleId, out ci)) {
foreach (var node in ci.nodes.Values) {
if (!node.isDie)
nodes.Add(new ServerNode(node.type, node.nodeNum));
}
}
} catch (Exception e) {
Debug.Error("E1C6A85D-8F0C-47A9-8103-5AC3D7ACDF0B:" + e.ToString());
}
return nodes;
}
/// <summary>
/// 心跳
/// </summary>
/// <returns></returns>
private Task<bool> HeartAsync() {
return redisManager.db.StringSetAsync(nodeHeartKey, "online", heartTime);
}
private bool Heart()
{
return redisManager.db.StringSet(nodeHeartKey, "online", heartTime);
}
#region
/// <summary>
/// 发送心跳包定时器
/// </summary>
private Timer heartTimer;
/// <summary>
/// 12秒钟发送一次心跳包
/// </summary>
const int constheartTime = 12000;
/// <summary>
/// 心跳包
/// </summary>
private string heartPack;
/// <summary>
/// 开始发送
/// </summary>
/// <param name="heartPack"></param>
public void BeginHeart(string heartPack) {
this.heartPack = heartPack;
heartTimer = new Timer(SendHeart, null, 0, constheartTime);
}
/// <summary>
/// 更改心跳包
/// </summary>
public void ChangeHeart(string heartPack) {
this.heartPack = heartPack;
}
/// <summary>
/// 发送心跳包
/// </summary>
private void SendHeart(object state) {
if (!IsSendHeart)
{
return;
}
GlobalMQ.instance.DeliveryAll(GlobalMQ.CreateMessage(heartPack), true);
}
#endregion
/// <summary>
/// 集群信息
/// </summary>
private class ClusterInfo
{
/// <summary>
/// 模块id
/// </summary>
public int type;
/// <summary>
/// 模块名称
/// </summary>
public string ModuleName;
/// <summary>
/// 节点id
/// </summary>
public ConcurrentDictionary<int, NodeInfo> nodes = new ConcurrentDictionary<int, NodeInfo>();
}
/// <summary>
/// 节点信息
/// </summary>
private class NodeInfo
{
/// <summary>
/// 类型
/// </summary>
public int type;
/// <summary>
/// 模块名称
/// </summary>
public string ModuleName;
/// <summary>
/// 节点
/// </summary>
public int nodeNum;
/// <summary>
/// 过期的时间
/// </summary>
public long ExpireTime;
public bool isDie
{
get;
set;
}
}
}
}