Qt 客户端应用程序多开要注意的问题

很多客户端程序同一台电脑只允许开启一个进程,这个是很有必要的。那将一个原本单开的程序改为允许多开要注意些什么呢?

本地配置

如果多个进程读写同一个配置会造成混乱(只读的配置除外),所以要控制同一个目录不允许开两个。

进程名

如果两个进程的进程名相同,当通过进程名做某件事的时候会出问题(通过进程名关闭,显示程序),所以要保证进程名不一样。

当我们把某些信息存储在系统的一些标准目录下时(如AppData低权限读写目录)如果进程名相同,那么两个进程会读写同一个文件也会造成问题。

全局信息

比如客户端连接的是qpid broker,当与服务端通信的时候需要指定消息队列,如果两个进程用的都是同一个消息队列,很明显会造成其中一个客户端收不到消息,此时可以在消息队列名后面加一个当前进程ID来解决这个问题

下面这段代码可以用来保证同一个程序只有运行一个进程:
RunGuard.h

#pragma once

#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>

class RunGuard
{

public:
    RunGuard(const QString& key);
    ~RunGuard();

    bool isAnotherRunning();
    bool tryToRun();
    void release();

private:
    const QString key;
    const QString memLockKey;
    const QString sharedmemKey;

    QSharedMemory sharedMem;
    QSystemSemaphore memLock;

    Q_DISABLE_COPY(RunGuard)
};

RunGuard.cpp

#include "RunGuard.h"

#include <QCryptographicHash>
#include <QDebug>

namespace
{
    QString generateKeyHash(const QString& key, const QString& salt)
    {
        QByteArray data;

        data.append(key.toUtf8());
        data.append(salt.toUtf8());
        data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();

        return data;
    }

    class MemLockGuard {
    public:
        MemLockGuard(QSystemSemaphore &lock) : lock_(lock) { lock_.acquire(); }
        ~MemLockGuard() { lock_.release(); }

    private:
        QSystemSemaphore &lock_;
    };
}

RunGuard::RunGuard(const QString& key)
    : key(key)
    , memLockKey(generateKeyHash(key, "_memLockKey_"))
    , sharedmemKey(generateKeyHash(key, "_sharedmemKey_"))
    , sharedMem(sharedmemKey)
    , memLock(memLockKey, 1)
{
    MemLockGuard lock(memLock);
    {
        QSharedMemory fix(sharedmemKey);
        fix.attach();
    }
}

RunGuard::~RunGuard()
{
    release();
}

bool RunGuard::isAnotherRunning()
{
    if (sharedMem.isAttached()) {
        return false;
    }

    MemLockGuard lock(memLock);
    const bool isRunning = sharedMem.attach();
    if (isRunning) {
        sharedMem.detach();
    }

    return isRunning;
}

bool RunGuard::tryToRun()
{
    qDebug() << "try run key:" << key;
    // Extra check
    if (isAnotherRunning()) {
        qDebug() << "try run failed, key:" << key;
        return false;
    }

    bool result = false;
    {
        MemLockGuard lock(memLock);
        result = sharedMem.create(sizeof(quint64));
    }

    if (!result) {
        qDebug() << "try run failed, create error:" << sharedMem.errorString();
        release();
    }
    return result;
}

void RunGuard::release()
{
    MemLockGuard lock(memLock);
    if (sharedMem.isAttached()) {
        sharedMem.detach();
    }
}

多开保证目录和进程名不同:

    QString programPath = QString::fromStdWString(AnsiToUnicode(argv[0]));
    int lastSlash = programPath.replace('/', '\\').lastIndexOf('\\');
    RunGuard dirGuard(programPath.mid(0, lastSlash).toLower());    
    RunGuard nameGuard(programPath.mid(lastSlash + 1).toLower());
    if (!dirGuard.tryToRun() || !nameGuard.tryToRun()) {
        // 通过进程名打开先前进程的主窗口
        ShowMainWindow(programPath.mid(lastSlash + 1).toStdWString().c_str());
        return 0;
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注