Qt进程间保活方案:详解如何实现进程间通信与自动保活机制

发布于:2025-07-07 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

摘要

一、进程间保活的基本原理

 二、具体步骤及代码示例

三、常见问题与优化

四、总体方案


摘要


        在一些需要长时间运行的应用程序中,确保进程在意外退出时能够自动重启是一项非常重要的任务。尤其是在嵌入式开发、后台服务以及需要高可用性的场景下,进程保活机制至关重要。在Qt中,我们可以使用一些技巧来实现进程间通信(IPC)和进程自动重启的功能,从而保证应用的稳定性和可靠性。本文将介绍一种在Qt中实现进程间保活的工具和方法,并提供具体的代码实例。

一、进程间保活的基本原理

        进程间保活的基本思路是通过进程间通信(IPC)或者定时器等方式监控目标进程的状态。一旦发现进程崩溃或异常退出,就会启动一个新的进程来替代原有的进程。这样,确保了应用的持续运行。

Qt中可使用以下方法来实现进程间保活:
1. QProcess:用于启动和监控外部进程。
2. 定时器:定期检查目标进程是否正常运行。
3. QTimer:用于设置定时检查机制。

 二、具体步骤及代码示例

        我们将通过一个简单的实例来演示如何实现进程保活。假设我们有一个主进程,负责启动一个子进程。如果子进程崩溃或退出,主进程将自动重启该子进程。

class ProcessMonitor : public QObject
{
    Q_OBJECT

public:
    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);

}


网站公告

今日签到

点亮在社区的每一天
去签到