using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Threading.Tasks; using Sog; using Google.Protobuf.WellKnownTypes; namespace SMCenter { // 通用命令处理 public class CommCmdProc : BaseCmdProc { private long LastCheckTimeoutSecond; public override void ClearData() { m_nowCmd = null; LastCheckTimeoutSecond = 0; } // cmdParams[0] is serverId public override int DoCmd(out string msg) { string lowerCMD = m_nowCmd.CMD; if (m_nowCmd.CmdParams.Count < 1 || string.IsNullOrEmpty(m_nowCmd.CmdParams[0])) { msg = "invalid cmd param"; return -1; } if(lowerCMD == "list") { SMProcAppMgr.Instance.GetAllMatchingProc("*.*.*", m_nowCmd.m_procs); if(m_nowCmd.CmdParams[0] != "*") { m_nowCmd.m_procs = m_nowCmd.m_procs.Where(p => p.Value.SMApp.HostName == m_nowCmd.CmdParams[0]).ToDictionary(p => p.Key, p => p.Value); m_nowCmd.m_procs = m_nowCmd.m_procs.OrderBy(o => o.Value.SMApp.HostName).ToDictionary(p => p.Key, o => o.Value); } if(m_nowCmd.m_procs.Count == 0) { msg = "invalid cmd param or HostName is not exist"; return -1; } } else { if (CmdUtils.CheckValidServerId(m_nowCmd.CmdParams[0], out string fixSvrId) == false) { msg = "invalid server id"; return -1; } if (fixSvrId != null) { m_nowCmd.CmdParams[0] = fixSvrId; } SMProcAppMgr.Instance.GetAllMatchingProc(m_nowCmd.CmdParams[0], m_nowCmd.m_procs); } TraceLog.Debug("CommCmdProc.DoCmd {0} match proc num {1}", m_nowCmd.CMD, m_nowCmd.m_procs.Count); //需要排序的命令 if (lowerCMD == "start") { //根据sequence排序 m_nowCmd.m_procs = m_nowCmd.m_procs.OrderBy(p => p.Value.SMApp.HostName).ThenBy(o => o.Value.SMApp.Sequence).ToDictionary(p => p.Key, o => o.Value); } else if (lowerCMD == "stop") { //根据sequence排序 m_nowCmd.m_procs = m_nowCmd.m_procs.OrderBy(p => p.Value.SMApp.HostName).ThenByDescending(o => o.Value.SMApp.Sequence).ToDictionary(p => p.Key, o => o.Value); } if(m_nowCmd.m_procs.Count == 0) { msg ="not exist ServerID: " + m_nowCmd.CmdParams[0]; return -1; } var regHost = SMCenterNet.Instance.GetAllAgent(); foreach (var procInfo in m_nowCmd.m_procs) { if(regHost.Find( p => p.HostName == procInfo.Value.SMApp.HostName) == null) { procInfo.Value.SendCmdToHostTime = 1; procInfo.Value.AckResultCode = ResResultCode.Fail; procInfo.Value.AckMessage = procInfo.Value.SMApp.HostName + " Agent Not Running"; procInfo.Value.AckAddInfo = "0 KB"; } } if (IsAllProcCmdAcked()) { AckConsoleCmdResult(); } msg = m_nowCmd.ConsoleInput + " begin ..."; TraceLog.Trace("CommCmdProc.DoCmd {0}", msg); return 0; } public override int UpdateCmd(long tMs) { if (m_nowCmd.m_procs.Count == 0) { return 0; } if (IsAllProcCmdAcked()) { return 0; } CheckProcTimeout(tMs); if (m_nowCmd.CMD =="start" || m_nowCmd.CMD == "stop") { UpdateSequenceCmd(tMs); } else { //send one cmd per tick foreach (var cmdInfo in m_nowCmd.m_procs.Values) { if (cmdInfo.SendCmdToHostTime == 0) { SendCmdToHost(cmdInfo); cmdInfo.SendCmdToHostTime = tMs / 1000; break; } } } return 1; } private void CheckProcTimeout(long tMs) { long nowSecond = tMs / 1000; if(nowSecond - LastCheckTimeoutSecond < 2) { return; } LastCheckTimeoutSecond = nowSecond; foreach (var cmdInfo in m_nowCmd.m_procs.Values) { if (cmdInfo.SendCmdToHostTime > 0 && cmdInfo.AckResultCode == ResResultCode.NoRes) { int timeoutSecond = 15; if(m_nowCmd.CMD == "stop") { timeoutSecond = 60; } if (nowSecond - cmdInfo.SendCmdToHostTime > timeoutSecond) { TraceLog.Error("CommCmdProc.CheckProcTimeout cmd {0} serverid {1} timeout" , m_nowCmd.CMD, cmdInfo.SMApp.ServerIDStr); cmdInfo.AckResultCode = ResResultCode.TimeOut; if (IsAllProcCmdAcked()) { AckConsoleCmdResult(); } } } } } private int UpdateSequenceCmd(long tMs) { //根据Sequence一次发送一批命令 int thisSendSequence = -1; bool needExit = false; CmdProcInfo errorCmdProc = null; foreach (CmdProcInfo cmdProc in m_nowCmd.m_procs.Values) { // 停服时, 如果是*.*.*这种形式的输入, 会跳过某些appType, 比如Version和GateVersion if (m_nowCmd.CMD == "stop") { string appType = m_nowCmd.CmdParams[0].Split('.')[1]; if (appType == "*" && SMCenterUtils.Config.excludeAppType.Contains(cmdProc.SMApp.AppType)) { cmdProc.SendCmdToHostTime = tMs / 1000; cmdProc.AckResultCode = ResResultCode.Success; cmdProc.AckMessage = " Cannot stop *.*.*, Run [stop *.app.*] or Run [list *] check server state"; continue; } } //前面优先级的进程命令未成功完成,直接退出循环,超时的情况也不能继续往下走 if (cmdProc.SendCmdToHostTime > 0 && cmdProc.AckResultCode != ResResultCode.Success) { if (cmdProc.AckResultCode == ResResultCode.Exception || cmdProc.AckResultCode == ResResultCode.Fail || cmdProc.AckResultCode == ResResultCode.TimeOut) { errorCmdProc = cmdProc; needExit = true; } break; } //发现没有发送CMD的进程,取出Sequence记录,相同Sequence的进程可以一起发送命令 if (cmdProc.SendCmdToHostTime == 0) { if(thisSendSequence == -1) { thisSendSequence = cmdProc.SMApp.Sequence; } else if (thisSendSequence != cmdProc.SMApp.Sequence) { break; } SendCmdToHost(cmdProc); cmdProc.SendCmdToHostTime = tMs / 1000; } } if(needExit && errorCmdProc != null) { // 退出时把本次执行cmd的所有proc.result都设置成出错的proc的错误码 foreach (CmdProcInfo cmdProc in m_nowCmd.m_procs.Values) { cmdProc.AckResultCode = errorCmdProc.AckResultCode; } AckConsoleCmdResult(); return -1; } return 1; } public override void OnAgentDoCommandRes(ClientInfo client, SMAgentDoCommandRes res) { if (m_nowCmd.SeqNum != res.SeqNum) { TraceLog.Error("CommCmdProc.OnAgentDoCommandRes CMD {0} serverId {1}, seqNum not same, skip, m_nowCmd.SeqNum {2}" , res.Command, res.ServerId, m_nowCmd.SeqNum); return; } if(m_nowCmd.m_procs.ContainsKey(res.ServerId) == false) { TraceLog.Error("CommCmdProc.OnAgentDoCommandRes CMD {0} serverId {1} not in m_procs, skip" , res.Command, res.ServerId); return; } CmdProcInfo proc = m_nowCmd.m_procs[res.ServerId]; proc.AckResultCode = res.ResultCode; proc.AckMessage = res.Result; proc.AckAddInfo = res.AddInfo; TraceLog.Trace("+++proc+++ " + "\t\t\t" + proc.SMApp.Name+ "\t\t\t\t" + proc.SMApp.ServerIDStr + "\t\t" + res.Command + "\t\t" + res.Result + "\t\t" + res.AddInfo); if(IsAllProcCmdAcked()) { AckConsoleCmdResult(); } TraceLog.Trace("CommCmdProc.OnAgentDoCommandRes CMD {0} serverId {1}, result {2}" , res.Command, res.ServerId, res.Result); } private bool IsAllProcCmdAcked() { foreach(var proc in m_nowCmd.m_procs) { if(proc.Value.AckResultCode == ResResultCode.NoRes) { return false; } } return true; } private void AckConsoleCmdResult() { SMConsoleCommandRes res = new SMConsoleCommandRes(); res.Command = m_nowCmd.ConsoleInput; res.Message = ""; List hostName = new List(); foreach (var procInfo in m_nowCmd.m_procs) { string procResult = ""; if (! hostName.Contains(procInfo.Value.SMApp.HostName)) { procResult += string.Format("\n--------------- HostName:{0} ---------------", procInfo.Value.SMApp.HostName); hostName.Add(procInfo.Value.SMApp.HostName); } procResult += "\n " + procInfo.Value.SMApp.ServerIDStr + "\t"; switch(procInfo.Value.AckResultCode) { case ResResultCode.NoRes: case ResResultCode.TimeOut: procResult += "Timeout"; break; case ResResultCode.Success: procResult += "Success"; break; case ResResultCode.Fail: procResult += "Fail"; break; case ResResultCode.Exception: procResult += "Exception"; break; case ResResultCode.Running: procResult += "Running"; break; case ResResultCode.NotRunning: procResult += "Not Running"; break; default: break; } //不会空的情况下定义为错误信息 if(string.IsNullOrEmpty(procInfo.Value.AckMessage) == false) { procResult += " (INFO:" + procInfo.Value.AckMessage + ")"; } //这个携带一些其他数据信息(目前只有内存消息) if (string.IsNullOrEmpty(procInfo.Value.AckAddInfo)) { procInfo.Value.AckAddInfo = "0 KB"; } procResult += string.Format(" \tMemoryUsage:{0,-30}", procInfo.Value.AckAddInfo); procResult += string.Format("\t\"{0}@{1}\"",procInfo.Value.SMApp.Name, procInfo.Value.SMApp.HostName); procResult += string.Format("\t{0}", procInfo.Value.SMApp.CfgPath); procResult += string.Format("\t{0}", procInfo.Value.SMApp.WorkPath); res.Message += procResult; } SMCenterNet.Instance.SendMsg(m_nowCmd.SessionId, SMMsgID.ConsoleCommandRes, res, m_nowCmd.HttpId); } private void SendCmdToHost(CmdProcInfo cmdInfo) { string hostname = cmdInfo.SMApp.HostName; ClientInfo client = SMCenterNet.Instance.GetClientInfoByName(hostname); if(client == null) { TraceLog.Error("CommCmdProc.SendCmdToHost host {0} agent not register!", hostname); return; } SMAgentDoCommandReq req = new SMAgentDoCommandReq(); req.Command = m_nowCmd.CMD; req.SeqNum = m_nowCmd.SeqNum; req.ServerId = cmdInfo.SMApp.AppId; req.WorkPath = cmdInfo.SMApp.WorkPath; req.StopTimeout = cmdInfo.SMApp.StopTimeout; req.ExeFileName = cmdInfo.SMApp.ExeFileName; //构造命令行参数 req.CmdArgs = GetCmdArgs(cmdInfo, m_nowCmd); client.SendMsg(req, SMMsgID.AgentDoCommandReq); } public string GetExeFileName() { string exename = "SogLoader"; if (OSUtils.IsWindows()) { exename += ".exe"; } return exename; } private string GetCmdArgs(CmdProcInfo cmdProcInfo, CmdInfo cmdInfo) { //统一是这个格式,有些命令agent会特殊处理,比如check //--name=Game这个参数纯粹是为了肉眼好看,只是起到注释作用,ps看进程的时候进程太多,只看id眼花 string args = string.Format("--id={0} --cluster={1}/cluster.json --name={2} {3}" , cmdProcInfo.SMApp.ServerIDStr,cmdProcInfo.SMApp.CfgPath, cmdProcInfo.SMApp.Name, cmdInfo.CMD); //gm指令的话需要带上参数 //这里的gm指令只能输入一些简单字符,比如中文没法输入,代码不好写,算了,复杂的gm指令通过经营分析系统去做,SMS就不管了 //gmcmd 1.200.1 AddChip 50000 -t10001 //类似这种简单的指令没有问题 //包含特殊字符,或者中文,其他非字符的gm指令非得用SMS输入也不是不可以 //gm指令本身支持base64转码 //gmcmd 1.200.1 AddChip 500000000 -t 1000001 //用base64编码替换AddChip后面的参数 //gmcmd 1.200.1 AddChip -bNTAwMDAwMDAwIC10IDEwMDAwMDE= //gmcmd 1.200.1 AddChip -b NTAwMDAwMDAwIC10IDEwMDAwMDE= //以上两种都可以 if (cmdInfo.CMD == "gmcmd" && cmdInfo.CmdParams.Count > 1) { //第0个是服务器id for(int i= 1; i < cmdInfo.CmdParams.Count; i++) { args += cmdInfo.CmdParams[i]; if(i != cmdInfo.CmdParams.Count -1) { args += " "; } } } return args; } } }