本案例對應的源代碼目錄:src/chapter04/ks04_04。
在開發C/S(Client/Server,客戶端/服務端)模式的軟件時,服務端程序(有時也稱作服務)經常運行在兩種模式下。
(1)終端模式。
終端模式,也可稱作命令行模式。在這種模式下,服務端程序佔用終端(命令行)運行,用戶既可以看到服務端程序向終端輸出的信息,也可以在終端輸入命令以調整程序的行爲。
(2)後臺模式。
後臺模式就是Windows的服務模式(在Linux、Unix下也有服務模式)。在這種模式下,服務端程序以後臺服務方式運行,而且沒有任何界面。用戶無法通過終端查看模塊狀態或者輸入命令,因爲根本就沒有終端。當軟件運行在這種模式的時候,維護人員給服務器加電後就可以不管了,服務器加電啓動後進入操作系統並且自動啓動預先配置好的服務。因爲這種模式幾乎無須人員干預,所以對用戶來說非常方便。
通常可以在軟件中通過命令行參數的方式區分這兩種模式。如果軟件運行在終端模式,可以將輸出信息發送到標準輸出(也就是命令行);如果軟件運行在後臺模式,可以將輸出信息保存到文件。那麼該怎麼實現這樣的信息輸出功能呢?
Qt提供了qDebug()來實現輸出功能。下面分4種場景介紹qDebug()相關的功能。
(1)用qDebug()<<方式輸出信息。
(2)使用qDebug(“%”)格式化輸出信息。
(3)將自定義類輸出到qDebug()。
(4)將標準輸出重定向到文件。
下面進行詳細介紹。
1.用qDebug()<< 方式輸出信息
最簡單的方法是直接向終端輸出信息,方法是使用<<操作符實現信息輸出,見代碼清單4-22。
#include <QDebug> void example01() { int iVal = 334; QString str = "I live in China"; qDebug() << "My Value is " << iVal << ". " << str; qWarning() << "My Value is " << iVal << ". " << str; qCritical() << "My Value is " << iVal << ". " << str; } |
從代碼清單4-22可以看出,使用<<操作符將變量輸出到qDebug()的方法跟STL的cout類似,即把變量左移到qDebug()即可。Qt的常用類都可以輸出到qDebug(),原生數據類型也是。qWarning()、qCritical()的用法與qDebug()相同,只是嚴重等級不同。使用時需要包含<QDebug>文件。
2.使用qDebug(“%”)格式化輸出信息
爲了便於信息的閱讀,實際工作中運行的軟件一般都採用格式化的方式輸出信息,見代碼清單4-23。
void example02(){ QString str = "China"; QDateTime dt = QDateTime::fromTime_t(time(NULL)); qDebug("I live in %s. Today is %04d-%02d-%02d", str.toLocal8Bit().data(), dt.date().year(), dt.date().month(), dt.date().day()); qWarning("I live in %s. Today is %04d-%02d-%02d", str.toLocal8Bit().data(), dt.date().year(), dt.date().month(), dt.date().day()); qCritical("I live in %s. Today is %04d-%02d-%02d", str.toLocal8Bit().data(), dt.date().year(), dt.date().month(), dt.date().day()); // 下面幾行代碼如果解封,其功能是彈出異常界面,並顯示給出的異常信息。 // qFatal("I live in %s. Today is %04d-%02d-%02d", ① // str.toLocal8Bit().data(), // dt.date().year(), dt.date().month(), dt.date().day()); } |
在代碼清單4-23中,使用類似sprintf()的方式實現信息的格式化輸出。代碼中用%語法將信息格式化。qWarning()、qCritical()、qFatal()的用法與之相同。標號①處封掉的代碼中,qFatal()正常運行的效果是彈出異常界面。
3.將自定義類輸出到qDebug()
除了Qt自帶的類之外,還可以將項目中的自定義類輸出到qDebug(),如代碼清單4-24所示。
// myclass.h #pragma once #include <QDebug> #include <QString> class CMyClass { ... }; QDebug operator<<(QDebug debug, const CMyClass &mc); ① |
代碼清單4-24提供了自定義類CMyClass的頭文件。爲了將自定義類輸出到qDebug,在標號①處爲CMyClass編寫左移操作符的重載接口。該接口的實現見代碼清單4-25。在代碼清單4-25中的重載接口內部,根據實際需要將CMyClass類對象mc的數據輸出到debug對象。
// myclass.cpp #include "myclass.h" QDebug operator<<(QDebug debug, const CMyClass &mc) { debug << "My id is " << mc.getId() << ", My Name is " << mc.getName() << ""; return debug; } |
完成CMyClass類向qDebug()的左移操作符的重載操作後,就可以在代碼中使用它了,見代碼清單4-26中標號①處。
代碼清單4-26
void example03(){ CMyClass mc; mc.setId(10000); mc.setName(QString::fromLocal8Bit("秦始皇")); qDebug() << mc; ① } |
4.將標準輸出重定向到文件
除了將信息輸出到終端,還可以通過重定向的方式將信息輸出到文件。當軟件模塊以服務模式運行在後臺時,如果能把調試信息輸出到文件中,就可以方便地監視軟件運行狀態。這將用到Qt的重定向輸出接口的註冊函數qInstallMessageHandler()。該函數的原型爲:
Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler); |
從qInstallMessageHandler()的定義可以看出,需要給它傳入一個QtMessageHandler類型的新的重定向輸出函數地址,然後它返回前一個(舊的)QtMessageHandler函數地址。QtMessageHandler定義如下。
typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &); |
爲了使用qInstallMessageHandler(),開發者需要定義自己的重定向接口customMessageHandler(),如代碼清單4-27所示。
QMutex g_mutex; // 爲了支持多線程功能,需要使用鎖來保護對日誌文件的操作。 ① QtMessageHandler g_systemDefaultMessageHandler = NULL; // 用來保存系統默認的輸出接口 ② void customMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& info) { // 把信息格式化 QString log = QString::fromLocal8Bit("msg-[%1], file-[%2], func-[%3], cate-[%4]\r\n").arg(info).arg(context.file).arg(context.function).arg(context.category); bool bok = true; switch (type) { case QtDebugMsg: log.prepend("Qt dbg:"); break; case QtWarningMsg: log.prepend("Qt warn:"); break; case QtCriticalMsg: log.prepend("Qt critical:"); break; case QtFatalMsg: log.prepend("Qt fatal:"); break; case QtInfoMsg: log.prepend("Qt info:"); break; default: bok = false; break; } if (bok) { // 加鎖 QMutexLocker locker(&g_mutex); ③ QString strFileName = getPath("$TRAINDEVHOME/bin/log04_04.inf"); QFile file(strFileName); if (!file.open(QFile::ReadWrite | QFile::Append)) { return; } file.write(log.toLocal8Bit().data()); file.close(); } if (bok) { // 調用系統原來的函數完成信息輸出,比如輸出到調試窗口 if(NULL != g_systemDefaultMessageHandler) { ④ g_systemDefaultMessageHandler(type, context, log); } } } // main.cpp int main(int argc, char * argv[]) { QApplication app(argc, argv); // 輸出重定向 g_systemDefaultMessageHandler = qInstallMessageHandler(customMessageHandler); ⑤ ... } |
代碼清單4-27中定義的customMessageHandler()接口提供3個參數。參數type用來區分報警等級,其取值見表4-2。參數context用來指示上下文,比如輸出信息時所在文件、行號、所在函數等。參數info用來描述需要輸出的信息內容。在代碼清單4-27中的customMessageHandler()接口中,根據type的不同,對info進行了重新組織並將格式化後的信息存放到log中,最後將log寫入日誌文件。爲了防止多線程對同一個日誌文件的操作,在標號①處定義一個互斥對象g_mutex,並在標號③處通過QMutexLocker自動鎖來操作g_mutex,以便對日誌文件的操作進行互斥。QMutexLocker實現的功能是在構造QMutexLocker對象時可以對傳入的g_mutex進行加鎖處理,並在析構時對g_mutex進行解鎖處理,這樣就實現了加鎖解鎖的自動化操作,開發者無須關注加鎖、解鎖操作。爲了調用系統原來的信息輸出功能(比如將信息輸出到調試窗口),可以先定義變量用來保存舊的信息輸出接口,見標號②處、標號⑤處代碼,然後在標號④處調用舊的信息輸出接口將信息輸出到調試窗口。在標號⑤處,main()函數中調用qInstallMessageHandler()來註冊自定義的重定向輸出接口customMessageHandler,這樣當後續代碼中調用qDebug()、qWarning()、qCritical()、qFatal()時程序就會自動調用自定義的customMessageHandler()接口來輸出信息。請注意,在Release版本中有可能出現參數context對象中的文件信息和行數爲空,原因是Qt在Release版本默認丟棄了文件信息、行數等信息。解決方案是在項目的pro文件中定義一個宏:
// ks04_04.pro DEFINES += QT_MESSAGELOGCONTEXT |
表4-2 QtMsgType取值
取值 |
說明 |
取值 |
說明 |
QtDebugMsg |
調試類信息 |
QtFatalMsg |
致命錯誤信息 |
QtWarningMsg |
一般的警告信息 |
QtInfoMsg |
一般的信息提示 |
QtCriticalMsg |
嚴重錯誤信息 |
QtSystemMsg = QtCriticalMsg |
系統信息 |
----------------------------------------------------------------------------------------------------------------------------------------------