目錄
摘要
一、進程間保活的基本原理
?二、具體步驟及代碼示例
三、常見問題與優化
四、總體方案
摘要
????????在一些需要長時間運行的應用程序中,確保進程在意外退出時能夠自動重啟是一項非常重要的任務。尤其是在嵌入式開發、后臺服務以及需要高可用性的場景下,進程保活機制至關重要。在Qt中,我們可以使用一些技巧來實現進程間通信(IPC)和進程自動重啟的功能,從而保證應用的穩定性和可靠性。本文將介紹一種在Qt中實現進程間保活的工具和方法,并提供具體的代碼實例。
一、進程間保活的基本原理
????????進程間保活的基本思路是通過進程間通信(IPC)或者定時器等方式監控目標進程的狀態。一旦發現進程崩潰或異常退出,就會啟動一個新的進程來替代原有的進程。這樣,確保了應用的持續運行。
Qt中可使用以下方法來實現進程間保活:
1. QProcess:用于啟動和監控外部進程。
2. 定時器:定期檢查目標進程是否正常運行。
3. QTimer:用于設置定時檢查機制。
?二、具體步驟及代碼示例
????????我們將通過一個簡單的實例來演示如何實現進程保活。假設我們有一個主進程,負責啟動一個子進程。如果子進程崩潰或退出,主進程將自動重啟該子進程。
class ProcessMonitor : public QObject
{Q_OBJECTpublic:ProcessMonitor(const QString &processPath, QObject *parent = nullptr): QObject(parent), m_processPath(processPath){// 定時器每隔5秒檢查一次子進程m_timer = new QTimer(this);connect(m_timer, &QTimer::timeout, this, &ProcessMonitor::checkProcess);m_timer->start(5000); // 每5秒檢查一次// 啟動子進程startProcess();}private slots:void checkProcess(){// 如果子進程已經退出,重新啟動它if (m_process.state() == QProcess::NotRunning) {qDebug() << "子進程已經退出,重啟中...";startProcess();} else {qDebug() << "子進程正在運行...";}}void startProcess(){m_process.start(m_processPath);if (m_process.waitForStarted()) {qDebug() << "子進程啟動成功!";} else {qDebug() << "子進程啟動失敗!";}}private:QProcess m_process;QTimer *m_timer;QString m_processPath;
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 設定子進程路徑QString processPath = "/path/to/your/child_process"; ?// 替換為子進程的實際路徑// 創建進程監控器ProcessMonitor monitor(processPath);return a.exec();
}
在主進程中,我們將使用QProcess來啟動子進程,并通過定時器定期檢查子進程是否仍在運行。
說明:
QProcess:用于啟動子進程`m_process.start(m_processPath)啟動指定路徑的程序。
QTimer:定時器每隔5秒檢查一次子進程是否還在運行。如果子進程已經退出,則會調用startProcess()重新啟動子進程。
checkProcess():檢查子進程的狀態,如果發現子進程已經停止運行,則會重新啟動它。
三、常見問題與優化
1. 如何判斷子進程是否異常退出?
? ? ? ?可以通過QProcess::errorOccurred()和QProcess::exitCode()來進一步判斷子進程是否是正常退出,還是由于錯誤退出。如果是異常退出,可以通過記錄錯誤日志來提高系統的可靠性。
2. 如何控制重啟次數?
? ?可以在checkProcess()方法中加入計數器,限制子進程重啟的次數,防止子進程頻繁崩潰后導致死循環。
3. 如何處理進程間的通信?
? ?如果需要進程間的通信,可以通過QProcess::setProcessChannelMode()方法指定通信模式,使用QProcess::write()和QProcess::read()進行數據交換。
四、總體方案
1.使用任務列表監控并重啟一個程序
#include <QCoreApplication>
#include <QProcess>
#include <QDir>
#include <QDebug>
#include <QList>
#include <QProcess>
#include <QRegExp>bool isProcessRunning(const QString& processName)
{// 使用 tasklist 命令獲取當前正在運行的進程QProcess process;process.start("tasklist");process.waitForFinished();// 獲取進程列表輸出QString output = process.readAllStandardOutput();// 使用正則表達式檢查進程是否在列表中QRegExp regex(processName);return regex.indexIn(output) != -1;
}void startProcess(const QString& processPath)
{QProcess::startDetached(processPath);
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);QString processName = "Lourker.exe"; // 要檢查的進程名QString processPath = "C:\\path\\to\\Lourker.exe"; // 進程的路徑// 檢查進程是否已經在運行if (!isProcessRunning(processName)) {qDebug() << processName << "is not running, starting it now...";startProcess(processPath);} else {qDebug() << processName << "is already running.";}return a.exec();
}
2.使用進程間通信重啟一個程序
// 進程間通信m_timerKeepLive = new QTimer(this);connect(m_timerKeepLive, SIGNAL(timeout()), this, SLOT(slotSendKeepLiveData()));if (NULL == m_server){m_server = new QLocalServer(this);}for (int i = 0; i < 100; i++){QString strServerName = QString("myserver_%1").arg(i);if( m_server->listen(strServerName) ) //監聽{connect(m_server, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));m_formDebug->slotShowAppend(SHOW_RECV, QString("進程間通信 %1 監聽成功").arg(strServerName));m_timerKeepLive->start(10*1000); //每10s發送1次LockerUI的保活心跳m_unLossConnect = QDateTime::currentDateTime().toTime_t();ok = false;break;}else{m_formDebug->slotShowAppend(SHOW_ERR, QString("進程間通信 %1 監聽失敗").arg(strServerName));}}
// 進程間通信新連接
void Widget::slotNewConnection()
{m_formDebug->slotShowAppend(SHOW_RECV, "進程間通信 發現新連接!!");QLocalSocket *newsocket = m_server->nextPendingConnection(); //獲取連接上的客戶端句柄m_client = newsocket;connect(newsocket, SIGNAL(readyRead()), this, SLOT(slotReadData())); //關聯數據接收槽函數}// 進程間通信接收數據
void Widget::slotReadData()
{// 取得是哪個 localsocket 可以讀數據了QLocalSocket *local = static_cast<QLocalSocket *>(sender());if (!local)return;QByteArray rcv_data = local->readAll();//qDebug() << "77 rcv_data:" << rcv_data;//m_formDebug->slotShowAppend(SHOW_RECV, QString("進程間通信 接收數據:%1").arg(QString(rcv_data)));// 檢查收到的數據是否以 "hello" 結尾if (rcv_data.endsWith("hello")){ok = true;m_unLossConnect = QDateTime::currentDateTime().toTime_t();// 提取路徑信息,假設 hello 前面是路徑QString pathData = QString(rcv_data).chopped(5); // 去掉 "hello"if(pathData != App::targetKeepLiveAppPath){App::targetKeepLiveAppPath = pathData; // 存儲路徑信息App::writeConfig(); // 寫入配置文件}// 回復心跳QString exePath = qApp->applicationFilePath();QByteArray replyData = QString("%1 OK").arg(exePath).toLatin1();local->write(replyData);//m_formDebug->slotShowAppend(SHOW_SEND, QString("進程間通信 發送數據:%1").arg(QString(replyData)));//qDebug() << "已更新 App::TargetAppPath:" << App::targetKeepLiveAppPath; // 打印更新后的路徑}
}
// 發送保活信息
void Widget::slotSendKeepLiveData()
{QString exePath = qApp->applicationFilePath();// 檢查 m_client 是否有效if (NULL != m_client){QString message = QString("%1 OK").arg(exePath); // 當前exe路徑m_client->write(message.toLatin1());//m_formDebug->slotShowAppend(SHOW_SEND, QString("進程間通信 主動發送數據:%1").arg(message));//qDebug()<< "7777" << message;}if(ok){//記錄當前回復時間m_unLossConnect = QDateTime::currentDateTime().toTime_t();ok=false;}int nLossInt = QDateTime::currentDateTime().toTime_t() - m_unLossConnect;//超時幾秒qDebug()<< "66666"<< nLossInt;if (nLossInt >= 2 * 10) //表示超時的總秒數20s未回復{m_timerKeepLive->stop();m_unLossConnect = QDateTime::currentDateTime().toTime_t();// 檢查目標應用程序是否在運行if (isAppRunning(App::targetKeepLiveAppPath)){killApp(App::targetKeepLiveAppPath); // 強制關閉應用程序qDebug() << "成功退出應用程序:" << App::targetKeepLiveAppPath;}else{qDebug() << "應用程序未運行,準備重啟:" << App::targetKeepLiveAppPath;}// 延遲啟動應用程序QTimer::singleShot(1000, this, [this] { startApp(App::targetKeepLiveAppPath); }); // 延遲啟動qDebug() << "計劃在1秒后重啟應用程序:" << App::targetKeepLiveAppPath;//QTimer::singleShot(1000, this, SLOT(startApp(App::targetKeepLiveAppPath))); // 延遲啟動}
}
void Widget::killApp(const QString &appPath)
{QString appName = QFileInfo(appPath).fileName(); // 獲取應用程序名稱QProcess::execute(QString("taskkill /F /IM %1").arg(appName)); // 強制關閉進程qDebug() << "已強制關閉應用程序:" << appName;
}bool Widget::isAppRunning(const QString &appPath)
{QProcess process;process.start(QString("tasklist /FI \"imagename eq %1\"").arg(QFileInfo(appPath).fileName())); // 獲取進程名稱process.waitForFinished(5000); // 阻塞 5 秒等待 tasklist 執行完成QString outputStr = QString::fromLocal8Bit(process.readAllStandardOutput()); // 獲取進程信息return outputStr.contains(QFileInfo(appPath).fileName()); // 返回是否找到進程
}
void Widget::startApp(const QString &appPath)
{// 啟動應用程序并檢查返回值if (QProcess::startDetached(appPath)){qDebug() << "應用程序啟動成功:" << appPath;}else{qDebug() << "啟動應用程序失敗:" << appPath;}writeLog(QString("已重啟保活軟件1次,重啟時間: %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")),false);m_unLossConnect = QDateTime::currentDateTime().toTime_t();m_timerKeepLive->start(10*1000);}