You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

819 lines
27 KiB

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Sog.Log;
using Sog.IO;
namespace Sog
{
/*
* 服务器统一入口,不同的服务器逻辑加载不同的逻辑dll
* dll支持reload(hotfix)
* 不支持卸载,只能重复加载,每reload一次内存会变大一些,不要太频繁reload问题不大
* 测试结果:reload一次内存增加个几百k的样子
*/
public class ServerEntry
{
private ServerApp m_app;
private ServerType m_serverType;
private AppParam m_appParam;
private IScript m_scriptObj;
private IScript m_scriptObjReloaded;
private IShareCommand m_shareCommand;
private long m_lastReadShmCommandTime = 0;
private long m_serverStartTime = 0;
private Mutex m_procMutex;
//工作线程
private Thread m_workThread;
private bool m_serverCanExit = false;
private string m_serverCommand;
// reload测试模式, 每分钟reload一次
private int m_reloadedCount = 0;
private long m_needReloadTime = 0;
private int m_testReloadCount;
public ServerApp GetApp()
{
return m_app;
}
public ServerEntry(AppParam appParam)
{
m_appParam = appParam;
m_app = new ServerApp(m_appParam);
m_serverType = (ServerType)ServerIDUtils.GetServerType(m_app.ServerID);
//初始化日志等级,文件路径,文件名
TraceLog.SetLogPathName(m_appParam.ServerConfig.logpath, m_appParam.ServerConfig.logname);
SetLogReloadableParam();
//只运行模式
if (m_appParam.RunMode == AppRunMode.Run)
{
m_app.OnTick += this.OnTick;
}
m_testReloadCount = 0;
//gate 在使用异步模式的时候由于涉及到异步模式回掉dll里的函数,reload会出问题,所以不支持
if (m_serverType == ServerType.AccountGate ||
m_serverType == ServerType.GameGate ||
m_serverType == ServerType.VersionGate ||
m_serverType == ServerType.ChatGate ||
m_serverType == ServerType.Operation ||
m_serverType == ServerType.HttpProxy ||
m_serverType == ServerType.HttpProxyWorld ||
m_serverType == ServerType.HttpProxyPay)
{
m_testReloadCount = 0;
}
else
{
if(false == int.TryParse(m_app.GetCluster().GetAppParamByKey("testReload"), out m_testReloadCount))
{
m_testReloadCount = 0;
}
}
}
private void SetLogReloadableParam()
{
TraceLog.SetLogLevel(LogLevel.ParseFromString(m_appParam.ServerConfig.loglevel));
TraceLog.SetSkipLogMsgID(m_appParam.ServerConfig.skipLogMsgID);
//修改shift文件大小和数量
if(m_appParam.ServerConfig.logshiftfilesize > 0)
{
LogDefaultParam.ShiftFileSize = m_appParam.ServerConfig.logshiftfilesize;
}
if (m_appParam.ServerConfig.logshiftfilecount > 0)
{
LogDefaultParam.ShiftFileCount = m_appParam.ServerConfig.logshiftfilecount;
}
}
//初始化,读取cluster的配置文件,这个将会建立管道配置
private int Init()
{
int iRet = InitShmCommand();
if(iRet != 0)
{
return iRet;
}
iRet = m_app.GetCluster().InitAllChannel();
if(iRet != 0)
{
TraceLog.Error("ServerEntry.Init Cluster.InitAllChannel failed ret {0}", iRet);
return iRet;
}
//初始化时区相关信息
AppTime.InitTimeZone();
//注册所有消息类型
ProtoRegister.Instance.RegisterAllPacket();
LoadScriptObj();
InitScript();
m_serverStartTime = AppTime.GetNowSysMs();
return 0;
}
private string GetMutexName()
{
// "Global\"代表全局命名mutex, 不同的终端看到的是同一个, 否则, 不同终端都可以创建同名不同实例的mutex
return "Global\\sog_" + ServerIDUtils.IDToString(m_app.ServerID);
}
private bool CheckAppIsAlreadyRunning()
{
string mutexname = GetMutexName();
try
{
System.Threading.Mutex mutex;
bool bRet = System.Threading.Mutex.TryOpenExisting(mutexname, out mutex);
if (bRet)
{
return true;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
return false;
}
private void CreateMutex()
{
string mutexname = GetMutexName();
m_procMutex = new System.Threading.Mutex(true, mutexname);
}
static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception exception = e.ExceptionObject as Exception;
if (exception != null)
{
TraceLog.Error("UnhandledExceptionHandler be call");
TraceLog.Exception(exception);
Thread.Sleep(100);
}
}
private int InitShmCommand()
{
//加个预期之外的异常处理
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
if (m_appParam.RunMode == AppRunMode.None)
{
Console.WriteLine("server id {0} runmode init error", ServerIDUtils.IDToString(m_appParam.ServerID));
return 2;
}
//启动模式
if (m_appParam.RunMode == AppRunMode.Run)
{
if (CheckAppIsAlreadyRunning() == true)
{
string strError = string.Format("server id {0} name {1} already running,runmode Run,please check!"
, ServerIDUtils.IDToString(m_appParam.ServerID)
, m_appParam.ServerConfig.scriptTypeName);
Console.WriteLine(strError);
return 1;
}
CreateMutex();
TraceLog.Debug("==========================================================");
TraceLog.Debug("server id {0} starting at {1}", ServerIDUtils.IDToString(m_appParam.ServerID),DateTime.Now);
TraceLog.Debug("InitShmCommand server run in run mode,CreateShareCommand");
m_shareCommand = new ShareMemoryCommand(m_appParam.ServerID);
m_shareCommand.Create();
return 0;
}
//代理启动模式
else if(m_appParam.RunMode == AppRunMode.Start)
{
if (CheckAppIsAlreadyRunning() == true)
{
string strError = string.Format("server id {0} name {1} already running,runmode Start,please check!"
, ServerIDUtils.IDToString(m_appParam.ServerID)
, m_appParam.ServerConfig.scriptTypeName);
Console.WriteLine(strError);
return 1;
}
StringBuilder sb = new StringBuilder();
for(int i=0; i< m_appParam.Args.Length; i++)
{
if(m_appParam.Args[i] == "start")
{
continue;
}
sb.Append(m_appParam.Args[i]);
sb.Append(' ');
}
sb.Append("run");
string param = sb.ToString();
//防止进程访问log冲突,先关闭自己的log
TraceLog.ForceCloseLogFile();
System.Diagnostics.Process thisProcess = System.Diagnostics.Process.GetCurrentProcess();
string filename = "SogLoader";
if(OSUtils.IsWindows())
{
filename += ".exe";
}
string currentDir = System.IO.Directory.GetCurrentDirectory();
filename = currentDir + "/" + filename;
ProcessStartInfo startInfo = new ProcessStartInfo(filename, param);
startInfo.CreateNoWindow = true;
startInfo.WorkingDirectory = currentDir;
//startInfo.UseShellExecute = true;
//开启子进程,自己退出
using (Process subProcess = new Process())
{
subProcess.StartInfo = startInfo;
subProcess.EnableRaisingEvents = true;
subProcess.Start();
// AttachToProcess(subProcess.Id);
Thread.Sleep(10);
}
return 9998;
}
//hotfixcheck 模式
else if (m_appParam.RunMode == AppRunMode.HotfixCheck)
{
string errfile = "./" + ServerIDUtils.IDToString(m_appParam.ServerID) + "_hotfixcheckerror";
//先删除错误文件
try { File.Delete(errfile); } catch (Exception) { }
IScriptHotfixCheck oldDllfileCheck = ScriptLoader.LoadServerLogicDllHotfixCheck(m_appParam.ServerConfig.scriptfile);
IScriptHotfixCheck newDllfileCheck = ScriptLoader.LoadServerLogicDllHotfixCheck(m_appParam.hotfixcheckDllFileFullName);
string oldCheckString = oldDllfileCheck.GetCheckString();
string newCheckString = newDllfileCheck.GetCheckString();
TraceLog.Trace("old file {0} check = {1}", m_appParam.ServerConfig.scriptfile, oldCheckString);
TraceLog.Trace("new file {0} check = {1}", m_appParam.hotfixcheckDllFileFullName, newCheckString);
if (oldCheckString != newCheckString)
{
string checkError = string.Format("ServerEntry.InitShmCommand hotfixCheck failed! oldCheckString {0} != newCheckString {1}", oldCheckString, newCheckString);
TraceLog.Error(checkError);
//写错误文件
try { File.WriteAllText(errfile, checkError); } catch (Exception) { }
m_app.Alerter.AlertMsg(checkError);
return -1;
}
else
{
TraceLog.Trace("new file {0} is same to old fil {1}", m_appParam.hotfixcheckDllFileFullName, m_appParam.ServerConfig.scriptfile);
}
return 9997;
}
//其他代理模式就是各种命令模式,写入命令后退出
else
{
m_shareCommand = new ShareMemoryCommand(m_appParam.ServerID);
bool attachSuccess = m_shareCommand.Attach();
if (attachSuccess == false)
{
string strError = string.Format("server id {0} name {1} not running,please start server use start param first!"
, ServerIDUtils.IDToString(m_appParam.ServerID)
, m_appParam.ServerConfig.scriptTypeName);
Console.WriteLine(strError);
return 1;
}
if (m_appParam.RunMode == AppRunMode.Stop)
{
m_shareCommand.WriteCommand("stop");
return 2;
}
if(m_appParam.RunMode == AppRunMode.Hotfix)
{
if (m_appParam.forceHotfix)
{
m_shareCommand.WriteCommand("hotfixforce");
}
else
{
m_shareCommand.WriteCommand("hotfix");
}
return 3;
}
//ReloadConfig可以带参数
if (m_appParam.RunMode == AppRunMode.ReloadConfig)
{
m_shareCommand.WriteCommand(m_appParam.CommandAllText);
return 4;
}
//GmCommand可以带参数
if (m_appParam.RunMode == AppRunMode.GmCommand)
{
m_shareCommand.WriteCommand(m_appParam.CommandAllText);
return 5;
}
if (m_appParam.RunMode == AppRunMode.ReloadCluster)
{
m_shareCommand.WriteCommand("reloadcluster");
return 7;
}
return 9999;
}
//return 0;
}
static void AttachToProcess(int pid)
{
try
{
Type dteType = Type.GetTypeFromProgID("VisualStudio.DTE.17.0"); // 适用于 VS 2022
if (dteType == null)
{
Console.WriteLine("未找到 Visual Studio");
return;
}
dynamic dte = Activator.CreateInstance(dteType, true);
dte.MainWindow.Activate();
dte.Debugger.AttachProcesses(dte.Debugger.LocalProcesses.Item(pid));
Console.WriteLine("成功附加到进程:" + pid);
}
catch (Exception ex)
{
Console.WriteLine("附加失败:" + ex.Message);
}
}
private void TryReadShmCommand(long tMs)
{
if(m_lastReadShmCommandTime == 0)
{
m_lastReadShmCommandTime = tMs;
return;
}
//xx毫秒读一次,不存在性能问题
if(tMs - m_lastReadShmCommandTime >= 200)
{
m_lastReadShmCommandTime = tMs;
string strCommand = m_shareCommand.ReadCommand();
if (strCommand == null)
{
return;
}
TraceLog.Debug("server read command from shm, command: {0}", strCommand);
//设置这个供工作线程读取
m_serverCommand = strCommand;
}
}
//挂载事件,OnTick,OnMessage...
private void AttachScriptEvent()
{
m_app.OnClusterMessage += m_scriptObj.OnMessage;
}
//卸载事件,OnTick,OnMessage...
private void DetachScriptEvent()
{
m_app.OnClusterMessage -= m_scriptObj.OnMessage;
}
private void OnTick(long tMs)
{
//优先执行命令
if(string.IsNullOrEmpty(m_serverCommand) == false)
{
string strcommand = m_serverCommand;
m_serverCommand = null;
ProcessShmCommand(strcommand);
}
if (m_scriptObj != null)
{
m_scriptObj.OnTick(tMs);
}
//临时测试reload
TestReload(tMs);
}
private bool NeedTestReload()
{
return m_reloadedCount < m_testReloadCount;
}
private void TestReload(long tMs)
{
if (NeedTestReload() == false)
{
return;
}
if (m_needReloadTime == 0)
{
m_needReloadTime = tMs + 600000;
return;
}
//每10分钟reload一次
if (tMs > m_needReloadTime)
{
TraceLog.Debug("ServerEntry.TestReload reload now...");
m_reloadedCount++;
ReloadScriptDll(true);
m_needReloadTime = tMs + 600000;
}
}
//从磁盘加载dll文件
private void LoadScriptObj()
{
m_scriptObj = ScriptLoader.LoadServerLogicDll(m_appParam.ServerConfig.scriptfile, m_appParam.ServerConfig.scriptTypeName);
}
private bool ReloadScriptObj(bool force)
{
m_scriptObjReloaded = ScriptLoader.LoadServerLogicDll(m_appParam.ServerConfig.scriptfile, m_appParam.ServerConfig.scriptTypeName);
if (m_scriptObjReloaded == null)
{
return false;
}
string oldCheckString = m_scriptObj.GetScriptHotfixCheck().GetCheckString();
string newCheckString = m_scriptObjReloaded.GetScriptHotfixCheck().GetCheckString();
if (oldCheckString != newCheckString)
{
string checkError = string.Format("ServerEntry.ReloadScriptObj hotfixCheck failed! oldCheckString {0} != newCheckString {1}", oldCheckString, newCheckString);
TraceLog.Error(checkError);
//如果非强制hotfix,check失败直接返回,不加继续加载逻辑了
if (force == false)
{
m_app.Alerter.AlertMsg(checkError);
return false;
}
}
TraceLog.Debug("ServerEntry.ReloadScriptObj hotfixCheck success CheckString {0} bforce {1}", oldCheckString, force);
return m_scriptObjReloaded != null;
}
private void InitScript()
{
if(m_scriptObj == null)
{
Console.WriteLine("InitScript no script obj loaded {0}", m_appParam.ServerConfig.scriptTypeName);
return;
}
m_scriptObj.OnCreate(m_app);
AttachScriptEvent();
}
private void ReloadScriptDll(bool force)
{
if(ReloadScriptObj(force) == false)
{
Console.WriteLine("ReloadScriptDll no script obj loaded");
return;
}
DetachScriptEvent();
ServiceMgr.Instance.DisposeAllService();
//删除老的script对象
m_scriptObj.Dispose();
m_scriptObj = null;
//新的script对象
m_scriptObj = m_scriptObjReloaded;
m_scriptObjReloaded = null;
m_scriptObj.OnHotfix(m_app);
AttachScriptEvent();
TraceLog.Debug("ServerEntry.ReloadScriptDll file {0} class {1} success."
, m_appParam.ServerConfig.scriptfile, m_appParam.ServerConfig.scriptTypeName);
}
public void WorkThreadRunFun()
{
m_app.Run();
//正常进程退出sleep一下,确保log写完
Thread.Sleep(100);
//正常服务器stop,设置m_serverCanExit
m_serverCanExit = true;
}
private void MainThreadMonitorLoop()
{
long lastServerTickCount = 0;
long workThreadNoRunStartTime = 0;
long workThreadNoRunTickCount = 0;
while (m_serverCanExit == false)
{
lastServerTickCount = m_app.TotalTickCount;
long tLastMs = AppTime.GetNowSysMs();
//读取命令
TryReadShmCommand(tLastMs);
Thread.Sleep(100);
//启动前30秒由于需要编译,不判断
if (m_serverStartTime != 0
&& tLastMs - m_serverStartTime > 30000)
{
long newServerTickCount = m_app.TotalTickCount;
bool workThreadNoTick = false;
//都sleep 100 了,发现工作线程一次tick都没结束,不应该啊
if (newServerTickCount == lastServerTickCount)
{
if (workThreadNoRunStartTime == 0)
{
workThreadNoRunStartTime = tLastMs;
workThreadNoRunTickCount = newServerTickCount;
}
//工作线程没有响应,告警
if (workThreadNoRunTickCount == newServerTickCount)
{
workThreadNoTick = true;
if (AppTime.GetNowSysMs() - workThreadNoRunStartTime >= 5000)
{
TraceLog.Error("server id {0} dead at {1}", ServerIDUtils.IDToString(m_appParam.ServerID), DateTime.Now);
workThreadNoRunStartTime = 0;
m_app.Alerter.AlertMsg("dead");
}
}
}
if (! workThreadNoTick)
{
if (workThreadNoRunStartTime != 0)
{
workThreadNoRunStartTime = 0;
workThreadNoRunTickCount = 0;
}
}
}
}
}
//启动服务器
public void Start()
{
int bRet = Init();
if(bRet == 0)
{
m_workThread = new Thread(WorkThreadRunFun, 1024 * 1024 * 10);
//设置成后台线程,如果不是后台线程,线程不是主动关闭,则会一直运行,那怕进程主线程退出也没用
m_workThread.IsBackground = true;
m_workThread.Start();
//主线程循环,读取服务器命令输入,并监控逻辑线程
MainThreadMonitorLoop();
//到这里说明逻辑线程结束了,服务器需要退出
//游戏服务器退出的时候需要调用,资源释放使用
//这个需要在主线程运行,如果在游戏逻辑线程调用,有可能主线程正在readcommand,会抛出异常
if (m_shareCommand != null)
{
m_shareCommand.DeleteFile();
m_shareCommand.Close();
m_shareCommand = null;
}
if (m_procMutex != null)
{
m_procMutex.Dispose();
m_procMutex = null;
}
TraceLog.Debug("server id {0} stoped at {1}", ServerIDUtils.IDToString(m_appParam.ServerID), DateTime.Now);
}
else
{
//代理服务器退出的时候需要调用,资源释放使用
if (m_shareCommand != null)
{
m_shareCommand.Close();
m_shareCommand = null;
}
}
if (bRet != 0)
{
//> 0 算是正常执行完了程序,比如reload等
if(bRet > 0)
{
return;
}
string strError = string.Format("call Init failed,server end");
try
{
TraceLog.Error(strError);
}
catch(Exception ex)
{
ex.Source = null;
}
Console.WriteLine(strError);
return;
}
}
//读取指令
private void ProcessShmCommand(string strCommand)
{
TraceLog.Debug("ProcessShmCommand cmd:{0}", strCommand);
if (strCommand == "stop")
{
ProcessShmCommand_Stop();
}
else if(strCommand == "hotfix")
{
ProcessShmCommand_Hotfix(false);
}
else if (strCommand == "hotfixforce")
{
ProcessShmCommand_Hotfix(true);
}
else if(strCommand == "reloadconfig")
{
ProcessShmCommand_ReloadConfig(null);
}
else if (strCommand == "reloadcluster")
{
ProcessShmCommand_ReloadCluster();
}
else
{
if(strCommand.Contains(' '))
{
string[] splitStr = strCommand.Split(' ');
if(splitStr.Length >= 2 && splitStr[0] == "gmcmd")
{
ProcessShmCommand_GmCommand(splitStr);
}
if (splitStr.Length == 2 && splitStr[0] == "reloadconfig")
{
ProcessShmCommand_ReloadConfig(splitStr[1]);
}
}
}
}
private void ProcessShmCommand_Stop()
{
TraceLog.Debug("ProcessShmCommand_Stop");
m_app.StopServer();
m_scriptObj.OnStop();
return;
}
private void ProcessShmCommand_Hotfix(bool force)
{
TraceLog.Debug("ProcessShmCommand_Hotfix reload script dll bforce {0}", force);
ReloadScriptDll(force);
}
private void ProcessShmCommand_ReloadConfig(string excelConfigFile)
{
TraceLog.Debug("ProcessShmCommand_ReloadConfig {0}", excelConfigFile);
ReloadConfig(excelConfigFile);
TraceLog.Debug("ProcessShmCommand reloadConfig success param {0}", excelConfigFile);
}
//重新加载管道配置文件
private void ProcessShmCommand_ReloadCluster()
{
TraceLog.Debug("ProcessShmCommand_ReloadCluster {0}",m_app.AppParam.ClusterFileName);
m_app.GetCluster().ReloadClusterAppByConfig(m_app.AppParam.ClusterFileName);
}
private void ReloadConfig(string excelConfigFile)
{
//重新加载配置文件
m_appParam.Reload();
int iLogLevel = LogLevel.ParseFromString(m_appParam.ServerConfig.loglevel);
TraceLog.Debug("ProcessShmCommand_ReloadConfig set loglevel to {0}:{1}", m_appParam.ServerConfig.loglevel, iLogLevel);
//更新日志等级
SetLogReloadableParam();
//设置configFilePath
if(m_appParam.ServerConfig.configDataPath != null && m_appParam.ServerConfig.configDataPath != "")
{
GameConfigMgr.Instance.SetFilePath(m_appParam.ServerConfig.configDataPath);
}
else
{
GameConfigMgr.Instance.SetFilePath("../cfg/data/");
}
//可以传入nodesc表示不加载desc配置(策划配置表)
if(excelConfigFile != "nodesc")
{
//重新加载游戏策划excel配置文件
if (string.IsNullOrEmpty(excelConfigFile))
{
TraceLog.Debug("ProcessShmCommand_ReloadConfig GameConfigMgr ReadAllConfig ");
GameConfigMgr.Instance.ReadAllConfig();
}
else
{
TraceLog.Debug("ProcessShmCommand_ReloadConfig GameConfigMgr ReadOneConfig {0} ", excelConfigFile);
GameConfigMgr.Instance.ReadOneConfig(excelConfigFile);
}
}
m_scriptObj.OnReloadConfig(excelConfigFile);
}
private void ProcessShmCommand_GmCommand(string[] splitStr)
{
//TraceLog.Debug("ProcessShmCommand_GmCommand");
GmCommandMgr.Instance.HandlerGmCommandBySplit(splitStr);
}
}
}