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); } } }