在有些特殊項(xiàng)目中,軟件可能是無(wú)人值守的,如果程序莫名其妙掛了或者進(jìn)程被干掉了等等,這時(shí)開(kāi)發(fā)一個(gè)看門狗程序是非常有必要的,它就像一只打不死的小強(qiáng),只要程序非正常退出,它就能立即再次將被看護(hù)的程序啟動(dòng)起來(lái)。
代碼實(shí)現(xiàn)
Tips:文末有完整源代碼,就不一步一步寫了
1、創(chuàng)建一個(gè)Dog類,主要用于間隔性掃描被看護(hù)程序是否還在運(yùn)行
開(kāi)了個(gè)定時(shí)器,每5秒去檢查1次,如果沒(méi)有找到進(jìn)程則使用Process
啟動(dòng)程序
public class Dog
{
private Timer timer = new Timer();
private string processName ;
private string filePath;//要監(jiān)控的程序的路徑
public Dog()
{
timer.Interval = 5000;
timer.Tick += timer_Tick;
}
public void Start(string filePath)
{
this.filePath = filePath;
this.processName = Path.GetFileNameWithoutExtension(filePath);
timer.Enabled = true;
}
/// <summary>
/// 定時(shí)檢測(cè)系統(tǒng)是否在運(yùn)行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timer_Tick(object sender, EventArgs e)
{
try
{
Process[] myproc = Process.GetProcessesByName(processName);
if (myproc.Length == 0)
{
Log.Info("檢測(cè)到看護(hù)程序已退出,開(kāi)始重新激活程序,程序路徑:{0}",filePath);
ProcessStartInfo info = new ProcessStartInfo
{
WorkingDirectory = Path.GetDirectoryName(filePath),
FileName = filePath,
UseShellexecute = true
};
Process.Start(info);
Log.Info("看護(hù)程序已啟動(dòng)");
}
}
catch (Exception)
{
}
}
}
2、在程序入口接收被看護(hù)程序的路徑,啟動(dòng)Dog
掃描
static class Program
{
static NotifyIcon icon = new NotifyIcon();
private static Dog dog = new Dog();
/// <summary>
/// 應(yīng)用程序的主入口點(diǎn)。
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args == null || args.Length == 0)
{
MessageBox.Show("啟動(dòng)參數(shù)異常", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string filePath = args[0];
if(!File.Exists(filePath))
{
MessageBox.Show("啟動(dòng)參數(shù)異常", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
Process current = Process.GetCurrentProcess();
Process[] processes = Process.GetProcessesByName(current.ProcessName);
//遍歷與當(dāng)前進(jìn)程名稱相同的進(jìn)程列表
foreach (Process process in processes)
{
//如果實(shí)例已經(jīng)存在則忽略當(dāng)前進(jìn)程
if (process.Id != current.Id)
{
//保證要打開(kāi)的進(jìn)程同已經(jīng)存在的進(jìn)程來(lái)自同一文件路徑
if (process.MainModule.FileName.Equals(current.MainModule.FileName))
{
//已經(jīng)存在的進(jìn)程
return;
}
else
{
process.Kill();
process.WaitForExit(3000);
}
}
}
icon.Text = "看門狗";
icon.Visible = true;
Log.Info("啟動(dòng)看門狗,看護(hù)程序:{0}",filePath);
dog.Start(filePath);
Application.Run();
}
}
3、簡(jiǎn)單實(shí)現(xiàn)個(gè)日志記錄器(使用第三方庫(kù)也行,建議看護(hù)程序最好不要有任何依賴),也可直接使用我下面這個(gè),很簡(jiǎn)單,無(wú)任何依賴
public class Log
{
//讀寫鎖,當(dāng)資源處于寫入模式時(shí),其他線程寫入需要等待本次寫入結(jié)束之后才能繼續(xù)寫入
private static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
//日志文件路徑
public static string logPath = "logs\\dog.txt";
//靜態(tài)方法todo:在處理話類型之前自動(dòng)調(diào)用,去檢查日志文件是否存在
static Log()
{
//創(chuàng)建文件夾
if (!Directory.Exists("logs"))
{
Directory.createDirectory("logs");
}
}
/// <summary>
/// 寫入日志.
/// </summary>
public static void Info(string format, params object[] args)
{
try
{
LogWriteLock.EnterWriteLock();
string msg = args.Length > 0 ? string.Format(format, args) : format;
using (FileStream stream = new FileStream(logPath, FileMode.Append))
{
StreamWriter write = new StreamWriter(stream);
string content = String.Format("{0} {1}",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),msg);
write.WriteLine(content);
//關(guān)閉并銷毀流寫入文件
write.Close();
write.Dispose();
}
}
catch (Exception e)
{
}
finally
{
LogWriteLock.ExitWriteLock();
}
}
}
至此,看護(hù)程序已經(jīng)搞定。接著在主程序(被看護(hù)程序)封裝一個(gè)啟停類
4、主程序封裝看門狗啟停類
public static class WatchDog
{
private static string processName = "WatchDog"; //看護(hù)程序進(jìn)程名(注意這里不是被看護(hù)程序名,你可以試一下?lián)Q成主程序名字會(huì)使什么效果)
private static string appPath = AppDomain.CurrentDomain.BaseDirectory; //系統(tǒng)啟動(dòng)目錄
/// <summary>
/// 啟動(dòng)看門狗
/// </summary>
public static void Start()
{
try
{
string program = string.Format("{0}{1}.exe", appPath, processName);
ProcessStartInfo info = new ProcessStartInfo
{
WorkingDirectory = appPath,
FileName = program,
createNoWindow = true,
UseShellexecute = true,
Arguments = Process.GetCurrentProcess().MainModule.FileName //被看護(hù)程序的完整路徑
};
Process.Start(info);
}
catch (Exception)
{
}
}
/// <summary>
/// 停用看門狗
/// </summary>
public static void Stop()
{
Process[] myproc = Process.GetProcessesByName(processName);
foreach (Process pro in myproc)
{
pro.Kill();
pro.WaitForExit(3000);
}
}
}
原理也很簡(jiǎn)單,其中有兩點(diǎn)需要注意:
processName
字段表示看護(hù)程序,不是被看護(hù)程序,如果寫反了,嗯...(你可以試下效果)‘
Arguments
參數(shù)是被看護(hù)程序的完整路徑,因?yàn)橐话闱闆r下,是由被看護(hù)程序啟動(dòng)看護(hù)程序,所以我們可以直接使用Process.GetCurrentProcess().MainModule.FileName
獲取到被看護(hù)程序的完整路徑
5、在主程序入口點(diǎn)啟動(dòng)看門狗
public partial class App : Application
{
[STAThread]
static void Main()
{
//程序啟動(dòng)前調(diào)用看護(hù)程序
WatchDog.Start();
Application app = new Application();
MainWindow mainWindow = new MainWindow();
app.Run(mainWindow);
}
}
Winform、普通WPF、Prism等入口點(diǎn)都不太一樣,根據(jù)項(xiàng)目實(shí)際情況靈活處理即可
最后在需要正常退出程序的地方(也就是主程序關(guān)閉按鈕或其它想要正常退出程序的地方)停止看門狗程序
源代碼
https://github.com/luchong0813/WatchDogDemo
后續(xù)
如果是別人的 建議使用nssm
把別人的程序 封裝成服務(wù)由wondows去管理,可以去看我以前發(fā)表的如何用零代碼將應(yīng)用封裝成服務(wù)-NSSM,如果是自己的 需要彈框跟用戶反饋或交互,那就說(shuō)明不是無(wú)人值守,可以不用看門狗。
在類似地鐵進(jìn)站通道、機(jī)場(chǎng)安檢通道、口岸出入境通道等等都有一個(gè)引導(dǎo)程序,這種比較依賴圖形界面的可以嘗試使用看門狗程序,看門狗程序不依賴第三方庫(kù)且代碼量較少,一般不會(huì)跑飛。