监听端口守护进程
场景:公司写的程序老是崩掉,各种改版无果,遂写一个守护进程来监听该程序,如果崩掉,就自动重启。
1. 添加进程管理类 ProcessHelper
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Guard { public class ProcessHelper { /// <summary> /// 查看进程是否存在 /// </summary> /// <param name="name">进程名称</param> /// <returns></returns> public static bool Exist(string name) { return Process.GetProcessesByName(name).Length > 0; } /// <summary> /// 开启进程 /// </summary> /// <param name="path">进程所在物理路径</param> /// <returns></returns> public static void Start(string path) { Process m_Process = new Process(); m_Process.StartInfo.FileName = path; //开启新的窗体 m_Process.StartInfo.UseShellExecute = true; m_Process.Start(); } /// <summary> /// 关闭进程 /// </summary> /// <param name="name">进程名称</param> /// <returns></returns> public static void Stop(string name) { Process proc = Process.GetProcessesByName(name).FirstOrDefault(); if (proc != null) proc.Kill(); } } }
2. 添加配置文件 App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!--守护间隔时间--> <add key="second" value="10"/> <!--守护程序端口--> <add key="port" value="104"/> <!--守护程序路径--> <add key="path" value="D:\项目文件\某某系统\XXXXX\bin\Debug\XXXXXXXXXX.exe"/> </appSettings> </configuration>
3. 在类型库里面添加引用 System.configuration,方便获取配置文件自定义的值
4. 实现代码
using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Text; using System.Threading; namespace Guard { class Program { static void Main(string[] args) { try { int second = Convert.ToInt32(ConfigurationManager.AppSettings["second"]); int port = Convert.ToInt32(ConfigurationManager.AppSettings["port"]); string path = ConfigurationManager.AppSettings["path"]; while (true) { Console.WriteLine("Port: " + port + " status: " + (PortInUse(port) ? "存在" : "不存在")); if (!PortInUse(port)) //如果监听的端口不存在,就启动对应的程序 { WriteLog("启动端口" + port + "的程序"); ProcessHelper.Start(path); } Thread.Sleep(second * 1000); } } catch (Exception ex) { WriteLog("发生错误:" + ex.Message); } } /// <summary> /// 日志记录 /// </summary> /// <param name="errMsg"></param> private static void WriteLog(string errMsg) { string sFilePath = AppDomain.CurrentDomain.BaseDirectory + "\\Log"; string sFileName = DateTime.Now.ToString("yyyy-MM-dd") + ".log"; sFileName = sFilePath + "\\" + sFileName; Directory.CreateDirectory(sFilePath); FileStream fs; StreamWriter sw; if (File.Exists(sFileName)) fs = new FileStream(sFileName, FileMode.Append, FileAccess.Write); else fs = new FileStream(sFileName, FileMode.Create, FileAccess.Write); sw = new StreamWriter(fs); sw.WriteLine("时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); sw.WriteLine("信息:" + errMsg); sw.WriteLine(""); sw.Close(); fs.Close(); } /// <summary> /// 判断端口是否占用 /// </summary> /// <param name="port"></param> /// <returns></returns> public static bool PortInUse(int port) { bool inUse = false; IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners(); foreach (IPEndPoint endPoint in ipEndPoints) { if (endPoint.Port == port) { inUse = true; break; } } return inUse; } } }
其他:程序崩掉,但没有自动关闭控制台的窗体,便不能通过端口监听到是否崩掉,遂修改系统注册表:
运行注册表编辑器,依次定位到 HKEY_CURRENT_USER\Software\Microsoft\Windows\WindowsError Reporting,
在右侧窗口中找到并双击打开DontshowUI,然后在弹出的窗口中将默认值“0”修改为“1”。 那么,当程序崩溃时,就不会再出现”xx程序已停止工作”的提示框,崩溃程序进程会自动退出。 这种修改系统注册表的方法是最方便和直接的,但会对所有程序生效。
如图:
扩展:如果用配置文件保存信息,按规范只能存一条,如果我们要守护多个程序,就需要保存多条信息,于是将信息保存到xml文件中来处理。
1. 添加 DataSet.xml 文件
<?xml version="1.0" encoding="utf-8" ?> <Monitor> <Second>10</Second> <Programs> <Program> <Port>104</Port> <Path>D:\项目文件\某某系统\XXXXX\bin\Debug\XXXXXXX.exe</Path> </Program> <Program> <Port>105</Port> <Path>D:\项目文件\某某系统\XXXXX\bin\Debug - 副本\XXXXXXX.exe</Path> </Program> </Programs> </Monitor>
2. 代码迭代
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Xml; namespace Guard { class Program { static void Main(string[] args) { try { int second = 120; //守护间隔时间 DataTable dt = Select("DataSet.xml", out second); while (true) { foreach (DataRow row in dt.Rows) { if (!PortInUse(Convert.ToInt32(row["port"]))) //如果监听的端口不存在,就启动对应的程序 { Console.WriteLine("启动端口 " + row["port"] + " 的程序"); WriteLog("启动端口 " + row["port"] + " 的程序"); ProcessHelper.Start(row["path"].ToString()); } } Thread.Sleep(second * 1000); } } catch (Exception ex) { WriteLog("发生错误:" + ex); Console.WriteLine(ex); } } /// <summary> /// 查询xml值 /// </summary> /// <param name="xmlPath"></param> /// <param name="second"></param> /// <returns></returns> public static DataTable Select(string xmlPath, out int second) { XmlDocument xmlDoc = new XmlDocument(); try { xmlDoc.Load(xmlPath); //var root = xmlDoc.DocumentElement;//取到根结点 XmlNode secondChild = xmlDoc.SelectSingleNode("Monitor/Second"); //取指定的单个结点 second = Convert.ToInt32(secondChild.InnerText); XmlNode xmldt = xmlDoc.SelectSingleNode("Monitor/Programs"); DataSet ds = GetDataSet(xmldt.OuterXml); return ds.Tables["Program"]; } catch (Exception ex) { WriteLog("发生错误1:" + ex); Console.WriteLine(ex); second = 120; return null; } } /// <summary> /// 读取Xml字符串返回DataSet /// </summary> /// <param name="StringXml">字符串</param> /// <returns>DataSet</returns> public static DataSet GetDataSet(string StringXml) { StringReader stringReader = null; XmlTextReader xmlTextReader = null; DataSet rDataSet = new DataSet(); try { stringReader = new StringReader(StringXml); xmlTextReader = new XmlTextReader(stringReader); rDataSet.ReadXml(xmlTextReader); } catch (Exception ex) { WriteLog("发生错误2:" + ex); Console.WriteLine(ex); } finally { if (xmlTextReader != null) { xmlTextReader.Close(); stringReader.Close(); stringReader.Dispose(); } } return rDataSet; } /// <summary> /// 日志记录 /// </summary> /// <param name="errMsg"></param> private static void WriteLog(string errMsg) { string sFilePath = AppDomain.CurrentDomain.BaseDirectory + "\\Log"; string sFileName = DateTime.Now.ToString("yyyy-MM-dd") + ".log"; sFileName = sFilePath + "\\" + sFileName; Directory.CreateDirectory(sFilePath); FileStream fs; StreamWriter sw; if (File.Exists(sFileName)) fs = new FileStream(sFileName, FileMode.Append, FileAccess.Write); else fs = new FileStream(sFileName, FileMode.Create, FileAccess.Write); sw = new StreamWriter(fs); sw.WriteLine("时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); sw.WriteLine("信息:" + errMsg); sw.WriteLine(""); sw.Close(); fs.Close(); } /// <summary> /// 判断端口是否占用 /// </summary> /// <param name="port"></param> /// <returns></returns> public static bool PortInUse(int port) { bool inUse = false; IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners(); foreach (IPEndPoint endPoint in ipEndPoints) { if (endPoint.Port == port) { inUse = true; break; } } return inUse; } } }