一介绍
通过编写一个QSingleApplication类,来实现Qt程序的单例化,原文的作者是在Windows Vista + Qt4.4 下实现的,不过应用在其他平台上是没问题的。(本文是我在http://www.qtcentre.org/wiki/index.php?title=SingleApplication上看到的)
二代码
方案一:使用Qt中的QSharedMemory,QLocalServer和QLocalSocket实现(不过需要在你的.pro里加上QT += network)
别的没翻译,就是大概说了一下,直接来代码吧:
// "single_application.h"
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H
#include <QApplication>
#include <QSharedMemory>
#include <QLocalServer>
class SingleApplication : public QApplication
{
Q_OBJECT
public:
SingleApplication(int &argc, char *argv[], const QString uniqueKey);
bool isRunning();
bool sendMessage(const QString &message);
public slots:
void receiveMessage();
signals:
void messageAvailable(QString message);
private:
bool _isRunning;
QString _uniqueKey;
QSharedMemory sharedMemory;
QLocalServer *localServer;
static const int timeout = 1000;
};
#endif // SINGLE_APPLICATION_H
// "single_application.cpp"
#include <QLocalSocket>
#include "single_application.h"
SingleApplication::SingleApplication(int &argc, char *argv[], const QString uniqueKey) : QApplication(argc, argv), _uniqueKey(uniqueKey)
{
sharedMemory.setKey(_uniqueKey);
if (sharedMemory.attach())
_isRunning = true;
else
{
_isRunning = false;
// create shared memory.
if (!sharedMemory.create(1))
{
qDebug("Unable to create single instance.");
return;
}
// create local server and listen to incomming messages from other instances.
localServer = new QLocalServer(this);
connect(localServer, SIGNAL(newConnection()), this, SLOT(receiveMessage()));
localServer->listen(_uniqueKey);
}
}
// public slots.
void SingleApplication::receiveMessage()
{
QLocalSocket *localSocket = localServer->nextPendingConnection();
if (!localSocket->waitForReadyRead(timeout))
{
qDebug(localSocket->errorString().toLatin1());
return;
}
QByteArray byteArray = localSocket->readAll();
QString message = QString::fromUtf8(byteArray.constData());
emit messageAvailable(message);
localSocket->disconnectFromServer();
}
// public functions.
bool SingleApplication::isRunning()
{
return _isRunning;
}
bool SingleApplication::sendMessage(const QString &message)
{
if (!_isRunning)
return false;
QLocalSocket localSocket(this);
localSocket.connectToServer(_uniqueKey, QIODevice::WriteOnly);
if (!localSocket.waitForConnected(timeout))
{
qDebug(localSocket.errorString().toLatin1());
return false;
}
localSocket.write(message.toUtf8());
if (!localSocket.waitForBytesWritten(timeout))
{
qDebug(localSocket.errorString().toLatin1());
return false;
}
localSocket.disconnectFromServer();
return true;
方案二:使用Qt中的QSharedMemory,和QTimert实现,别的也没翻译,还是直接来代码吧:
// "single_application.h"
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H
#include <QApplication>
#include <QSharedMemory>
class SingleApplication : public QApplication
{
Q_OBJECT
public:
SingleApplication(int &argc, char *argv[], const QString uniqueKey);
bool isRunning();
bool sendMessage(const QString &message);
public slots:
void checkForMessage();
signals:
void messageAvailable(QString message);
private:
bool _isRunning;
QSharedMemory sharedMemory;
};
#endif // SINGLE_APPLICATION_H
// "single_application.cpp"
#include <QTimer>
#include <QByteArray>
#include "single_application.h"
SingleApplication::SingleApplication(int &argc, char *argv[], const QString uniqueKey) : QApplication(argc, argv)
{
sharedMemory.setKey(uniqueKey);
if (sharedMemory.attach())
_isRunning = true;
else
{
_isRunning = false;
// attach data to shared memory.
QByteArray byteArray("0"); // default value to note that no message is available.
if (!sharedMemory.create(byteArray.size()))
{
qDebug("Unable to create single instance.");
return;
}
sharedMemory.lock();
char *to = (char*)sharedMemory.data();
const char *from = byteArray.data();
memcpy(to, from, qMin(sharedMemory.size(), byteArray.size()));
sharedMemory.unlock();
// start checking for messages of other instances.
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(checkForMessage()));
timer->start(1000);
}
}
// public slots.
void SingleApplication::checkForMessage()
{
sharedMemory.lock();
QByteArray byteArray = QByteArray((char*)sharedMemory.constData(), sharedMemory.size());
sharedMemory.unlock();
if (byteArray.left(1) == "0")
return;
byteArray.remove(0, 1);
QString message = QString::fromUtf8(byteArray.constData());
emit messageAvailable(message);
// remove message from shared memory.
byteArray = "0";
sharedMemory.lock();
char *to = (char*)sharedMemory.data();
const char *from = byteArray.data();
memcpy(to, from, qMin(sharedMemory.size(), byteArray.size()));
sharedMemory.unlock();
}
// public functions.
bool SingleApplication::isRunning()
{
return _isRunning;
}
bool SingleApplication::sendMessage(const QString &message)
{
if (!_isRunning)
return false;
QByteArray byteArray("1");
byteArray.append(message.toUtf8());
byteArray.append('/0'); // < should be as char here, not a string!
sharedMemory.lock();
char *to = (char*)sharedMemory.data();
const char *from = byteArray.data();
memcpy(to, from, qMin(sharedMemory.size(), byteArray.size()));
sharedMemory.unlock();
return true;
}
方案三:
这里仅仅提供思路:使用文件锁QLockFile和,QLocalServer来实现。和方案一类似,只是用文件锁QLockFile代替QSharedMemory来确保唯一性。
方案四:
如果不需要进程间通信,可以只使用QSharedMemory或QLockFile即可。(真正使用时,c++中建议封装到类中,在析构时确保锁释放,以便下次正常启动)。以QSharedMemory做个简单例子:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSharedMemory *shareMem = new QSharedMemory(QString("SingleInstanceIdentify"));
/* if the sharedmemory has not been created, it returns false, otherwise true.
* But if the application exit unexpectedly, the sharedmemory will not detach.
* So, we try twice.
*/
volatile short i = 2;
while (i--)
{
if (shareMem->attach(QSharedMemory::ReadOnly)) /* no need to lock, bcs it's read only */
{
shareMem->detach();
}
}
if (shareMem->create(1))
{
MainWindow w;
w.show();
a.exec();
if (shareMem->isAttached())
shareMem->detach();
delete shareMem;
}
return 0;
}
linux下以文件锁实现单例化应用的简单举例说明(真正使用时,c++中建议封装到类中,在析构时确保锁释放)
#include <string>
#include <iostream>
// linux系统
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/file.h>
/**
* @brief linux下应用单例化方法之文件锁
* @param processName 进程名,用于标记唯一
* @return -1,异常;0,已经有一个实例在运行;1,可以启动新实例
* @note
* 此处仅仅介绍文件锁的用法,真实使用时,c++中最好封装到类中,在析构函数中进行解锁,以免系统异常来不及解锁
*/
int isRunning(const std::string &processName)
{
// 某些场景不用区分用户时,可以不用下面用户信息
uid_t uid = getuid();
std::string pid_file = std::string("/run/user/") + std::to_string(uid) + "/" + processName + ".pid";
/*
int open(const char *pathname, int oflag, ... );
对于 open 函数来说,第三个参数(...)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)。
pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:
O_APPEND 每次写操作都写入文件的末尾
O_CREAT 如果指定文件不存在,则创建这个文件
O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
以下三个常量同样是选用的,它们用于同步输入输出
O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC read 等待所有写入同一区域的写操作完成后再进行
O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
open 返回的文件描述符一定是最小的未被使用的描述符。
*/
int pid_file_fd = open(pid_file.data(), O_CREAT | O_TRUNC | O_RDWR, 0666);
if (0 > pid_file_fd) {
std::cerr << "Can not open pid file: " << pid_file << std::endl;
return -1;
}
// 调用系统API:linux系统使用flock函数来获得文件锁;windows系统OpenFile打开文件时设置为独占模式OF_SHARE_DENY_WRITE|OF_SHARE_DENY_READ即可实现
/*
头文件 #include<sys/file.h>
定义函数 int flock(int fd,int operation);
函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。
参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。
返回值 返回0表示成功,若有错误则返回-1,错误代码存于errno。
flock只要在打开文件后,需要对文件读写之前flock一下就可以了,用完之后再flock一下,前面加锁(LOCK_EX),后面解锁(LOCK_UN)
注意:c++中最好封装到类中,在析构函数中进行解锁,以免系统异常来不及解锁
*/
int file_lock = flock(pid_file_fd, LOCK_EX | LOCK_NB);
if (0 > file_lock) {
std::cerr << pid_file << " is running " << std::endl;
return 0;
}
// 将进程id写入文件中
__pid_t pid = getpid();
write(pid_file_fd, std::to_string(pid).data(), pid_file.size());
// fdatasync效率更高
fsync(pid_file_fd);
return 1;
}
方案五:
使用命名的互斥量或命名的信号量、事件等来确保应用的唯一性。
互斥量Mutex 是用于同步的线程或进程的互斥体。互斥量Multex 分为两种:未命名的局部互斥体和命名的系统互斥体。前者用于线程间的互斥,后者用于系统进程间的互斥。本文讨论的进程单例的实现将使用“命名的系统互斥体”。
信号量Semaphore 是用于同步的线程或进程的信号量。信号量Semaphore 也分为两种:未命名的局部信号量和命名的系统信号量。前者用于线程间的同步,后者用于系统进程间的同步。本文的进程实例数量的控制的实现将使用“命名的系统信号量”。信号量的一个优点就是可以控制进程实例数量,不只是单例。
方案五中的这几种方法都需要系统api支撑。
linux系统:
/*
命名信号量不带内存共享,编译时要带库文件-lpthread或-lrt
int sem_wait(sem_t *sem); //P操作,若是信号量大于零则减一,否则阻塞在该函数位置等待.
int sem_post(sem_t *sem); //V操作,信号量加一
sem_t *sem_open(const char *name, int oflag);//打开信号量,flag参数与打开普通文件的标记一样
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
//创建并打开信号量,value参数指的是信号量的初始值
int sem_unlink(const char *name);//删除系统创建的信号量
int sem_close(sem_t *sem);//关闭彻底销毁信号量
*/
windows下使用命名互斥体实现应用单例举例:
/*
创建互斥体实现一个程序只允许允许一个实例运行
*/
#include<process.h>
#include<windows.h>
#include<stdio.h>
int main()
{
HANDLE hMutex = CreateMutex(NULL, FALSE, L"我是互斥体");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
printf("程序已经运行了,退出!\n");
getchar();
CloseHandle(hMutex);
return 0;
}
printf("第一次运行程序!\n");
getchar();
return 0;
}
三使用
// "main.cpp"
#include "single_application.h"
int main(int argc, char *argv[])
{
SingleApplication app(argc, argv, "some unique key string");
if (app.isRunning())
{
app.sendMessage("message from other instance.");
return 0;
}
MainWindow *mainWindow = new MainWindow();
// connect message queue to the main window.
QObject::connect(&app, SIGNAL(messageAvailable(QString)), mainWindow, SLOT(receiveMessage(QString)));
// show mainwindow.
mainWindow->show();
return app.exec();
}
参考: