背景
玩过DNF和梦幻的人都知道游戏可以双开,最近在想如何实现防止应用重复启动。即启动时,若发现该应用正在运行,则退出并通知已打开的应用显示在最前端。主要实现以下两部分:
- 判断应用程序是否已经启动;
- 与已存在的应用进行通信,通知其显示在最前端。
防止多重启动实现方法
QLockFile
QLockFile指定一个临时的"锁文件"帮助进程判断是否存在多个。刚启动应用时,使用QLockFile创建"锁文件"并进行加锁,当第二个应用启动时尝试获得该锁,若获得失败则认为该应用已经启动。若QLockFile已获得锁,则其在销毁时会删除锁文件。假若程序崩溃,锁文件会残留且会被认为失效,新程序在尝试上锁时会发现其已经失效,会使用staleLockTime进行检测,若尝试上锁时距离文件最后一次修改在staleLockTime时间内,则会上锁失败。若希望程序崩溃后可以立即重启,可将检测时间设置较小值。(QLockFile具体用法参见Qt帮助文档)
QLocalServer和QLocalSocket
使用QLocalFile获得应用运行的情况后,我们需要进行进程间通信(IPC),新启动的实例通知已存在的实例显示在最前端,然后新实例退出。
QLocalServer指定一个本地套接字的服务器,实现本地套接字的连接功能。
listen:调用此函数开始监听指定key值,当本地连接发生时会发送 newConnection信号,连接此信号可以对连接进行处理。
nextPendingConnection:调用此函数获得一个新的本地套接字,该套接字已经成功连接,可以与客户进行通信。(在server销毁时,该套接字也会被销毁)
QLocalSocket指定一个本地套接字,可以看成与本地服务器连接的客户。
connectToServer:指定server监听的key值,并与其尝试连接。
waitForConnected:当连接成功返回true或超时返回false,否则一直等待。
disconnectFromServer:尝试关闭套接字,如果有数据正在写入,则写入完成后关闭断开链接呢。
代码实现
初始化函数,根据初始化的返回值来判断程序是否退出。
int Widget::init()
{
m_LockFile = new QLockFile(QApplication::applicationDirPath() + "/" + "lockfile.lock");
//设置失效锁文件的检测时间,设置0时貌似有问题
m_LockFile->setStaleLockTime(1);
//尝试获得锁
if (m_LockFile->tryLock(0)) {
//获得锁成功,创建服务器进行监听
m_LocalSever = new QLocalServer(this);
connect(m_LocalSever, &QLocalServer::newConnection,
this, &Widget::onConnection);
m_LocalSever->listen(QApplication::applicationName());
} else {
//获得锁失败,创建本地套接字(客户),请求连接并发送消息
m_LocalSocket = new QLocalSocket(this);
m_LocalSocket->connectToServer(QApplication::applicationName());
if (m_LocalSocket->waitForConnected(0)) {
//连接成功发送消息,通知已启动应用显示在最前端,返回-1退出
m_LocalSocket->write("showTop");
return -1;
}
}
return 0;
}
服务端接收到连接请求,进行处理
void Widget::onConnection()
{
//获得与客户通信的本地套接字
QLocalSocket *client = m_LocalSever->nextPendingConnection();
connect(client, &QLocalSocket::readyRead, this, [this, client](){
//读出客户发来消息
QByteArray arr = client->readLine();
if (arr == "showTop") {
if (isMinimized()) {
showNormal();
return;
}
SetWindowPos((HWND)winId(), HWND_TOPMOST, 0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
SetWindowPos((HWND)winId(), HWND_NOTOPMOST, 0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
}
});
//断开连接删除链接套接字
connect(client, &QLocalSocket::disconnected, this, [client](){
client->deleteLater();
});
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
if (w.init() != 0) {
return 0;
}
w.show();
return a.exec();
}
应用的重启
void Widget::on_pushButton_clicked()
{
QString program = QApplication::applicationFilePath();
QStringList arguments = QApplication::arguments();
QProcess::startDetached(program, arguments);
QApplication::quit();
}