使Qt應用程序能夠單實例運行的典型實現方法是使用共享內存實現。該方法實現簡單,代碼簡潔。
但有一個致命缺陷:共享內存(QSharedMemory)實現的單程序運行,當運行環境是UNIX時,並且程序不幸崩潰,會導致共享內存無法釋放,從而無法重新運行程序!
所以應該尋找其他的使Qt應用程序能夠單實例運行的方案。於是找到LocalSocket和LocalServer通訊方案(據說Qt官方商業版的QSingleApplication的原理好像跟這個差不多)。
“要用到Qt的QLocalSocket,QLocalServer類,這兩個類從接口上看和網絡通信socket沒有區別,但是它並不是真正的網絡API,只是模仿了而已。這兩個類在Unix/Linux系統上采用Unix域socket實現,而在Windows上則采用有名管道(named pipe)來實現。”
參見:
http://www.oschina.net/code/snippet_54100_629
http://blog.csdn.net/qq19831030qq/article/details/6199896
上面的方案實際操作過程中出現很多問題:
QString serverName = QCoreApplication::applicationName(); //獲取到的serverName為空
為了解決上面的問題,最直接的測試方法是先手動指定一個serverName,
然后將QFile::remove(m_localServer->serverName());改成QFile::remove(serverName);
當手動指定serverName時,習慣上將serverName設為當前的應用程序名,在linux下這又導致下面的問題
QFile::remove(m_localServer->serverName()); //執行刪除失敗(實際測試發現失敗原因是:嘗試刪除應用程序文件自身!)
上面的兩個問題導致Qt應用程序無法單實例運行。
后來終於在StackOverflow上面看到解決方案:使用QLocalServer::removeServer()刪除LocalServer名
下面是實現代碼:
頭文件:
1 #ifndef SINGLEAPPLICATION_H 2 #define SINGLEAPPLICATION_H 3 4 #include <QObject> 5 #include <QApplication> 6 #include <QtNetwork/QLocalServer> 7 #include <QWidget> 8 9 class SingleApplication : public QApplication { 10 Q_OBJECT 11 public: 12 SingleApplication(int &argc, char **argv); 13 14 bool isRunning(); // 是否已經有實例在運行 15 QWidget *w; // MainWindow指針 16 17 private slots: 18 // 有新連接時觸發 19 void _newLocalConnection(); 20 21 private: 22 // 初始化本地連接 23 void _initLocalConnection(); 24 // 創建服務端 25 void _newLocalServer(); 26 // 激活窗口 27 void _activateWindow(); 28 29 bool _isRunning; // 是否已經有實例在運行 30 QLocalServer *_localServer; // 本地socket Server 31 QString _serverName; // 服務名稱 32 }; 33 34 #endif // SINGLEAPPLICATION_H
CPP文件:
1 #include "SingleApplication.h" 2 #include <QtNetwork/QLocalSocket> 3 #include <QFileInfo> 4 5 #define TIME_OUT (500) // 500ms 6 7 SingleApplication::SingleApplication(int &argc, char **argv) 8 : QApplication(argc, argv) 9 , w(NULL) 10 , _isRunning(false) 11 , _localServer(NULL) { 12 13 // 取應用程序名作為LocalServer的名字 14 _serverName = QFileInfo(QCoreApplication::applicationFilePath()).fileName(); 15 16 _initLocalConnection(); 17 } 18 19 20 //////////////////////////////////////////////////////////////////////////////// 21 // 說明: 22 // 檢查是否已經有一個實例在運行, true - 有實例運行, false - 沒有實例運行 23 //////////////////////////////////////////////////////////////////////////////// 24 bool SingleApplication::isRunning() { 25 return _isRunning; 26 } 27 28 //////////////////////////////////////////////////////////////////////////////// 29 // 說明: 30 // 通過socket通訊實現程序單實例運行,監聽到新的連接時觸發該函數 31 //////////////////////////////////////////////////////////////////////////////// 32 void SingleApplication::_newLocalConnection() { 33 QLocalSocket *socket = _localServer->nextPendingConnection(); 34 if(socket) { 35 socket->waitForReadyRead(2*TIME_OUT); 36 delete socket; 37 38 // 其他處理,如:讀取啟動參數 39 40 _activateWindow(); 41 } 42 } 43 44 //////////////////////////////////////////////////////////////////////////////// 45 // 說明: 46 // 通過socket通訊實現程序單實例運行, 47 // 初始化本地連接,如果連接不上server,則創建,否則退出 48 //////////////////////////////////////////////////////////////////////////////// 49 void SingleApplication::_initLocalConnection() { 50 _isRunning = false; 51 52 QLocalSocket socket; 53 socket.connectToServer(_serverName); 54 if(socket.waitForConnected(TIME_OUT)) { 55 fprintf(stderr, "%s already running.\n", 56 _serverName.toLocal8Bit().constData()); 57 _isRunning = true; 58 // 其他處理,如:將啟動參數發送到服務端 59 return; 60 } 61 62 //連接不上服務器,就創建一個 63 _newLocalServer(); 64 } 65 66 //////////////////////////////////////////////////////////////////////////////// 67 // 說明: 68 // 創建LocalServer 69 //////////////////////////////////////////////////////////////////////////////// 70 void SingleApplication::_newLocalServer() { 71 _localServer = new QLocalServer(this); 72 connect(_localServer, SIGNAL(newConnection()), this, SLOT(_newLocalConnection())); 73 if(!_localServer->listen(_serverName)) { 74 // 此時監聽失敗,可能是程序崩潰時,殘留進程服務導致的,移除之 75 if(_localServer->serverError() == QAbstractSocket::AddressInUseError) { 76 QLocalServer::removeServer(_serverName); // <-- 重點 77 _localServer->listen(_serverName); // 再次監聽 78 } 79 } 80 } 81 82 //////////////////////////////////////////////////////////////////////////////// 83 // 說明: 84 // 激活主窗口 85 //////////////////////////////////////////////////////////////////////////////// 86 void SingleApplication::_activateWindow() { 87 if(w) { 88 w->show(); 89 w->raise(); 90 w->activateWindow(); // 激活窗口 91 } 92 }
調用示例:
1 #include "MainWindow.h" 2 #include "SingleApplication.h" 3 4 int main(int argc, char *argv[]) { 5 SingleApplication a(argc, argv); 6 if(!a.isRunning()) { 7 MainWindow w; 8 a.w = &w; 9 10 w.show(); 11 12 return a.exec(); 13 } 14 return 0; 15 }