在arm上做了Qt的應用程式,為了在區域網實現web頁的存取方式來設定arm上Qt的程式,區域網輕量級http伺服器是很好的實現方式之一,有機會做國產麒麟上Qt的http伺服器,正好接觸到了QtWebApp可以實現。
本篇實戰解說QtWebApp的輕量級Demo。
本篇篇幅較長,為了保持基礎的完整性將必要的東西都放在本篇。
QtWepApp是一個C++中的HTTP伺服器庫,其靈感來自Java Servlet。適用於Linux、Windows、Mac OS和Qt Framework支援的許多其他作業系統。
QtWebApp包含以下元件:
// The main program starts the HTTP server
int main(int argc, char *argv[])
{
QCoreApplication app(argc,argv);
new HttpListener(
new QSettings("configfile.ini", QSettings::IniFormat, &app),
new MyRequestHandler(&app),
&app);
return app.exec();
}
// The request handler receives and responds HTTP requests
void MyRequestHandler::service(HttpRequest& request, HttpResponse& response)
{
// Get a request parameters
QByteArray username=request.getParameter("username");
// Set a response header
response.setHeader("Content-Type", "text/html; charset=UTF-8");
// Generate the HTML document
response.write("<html><body>");
response.write("Hello ");
response.write(username);
response.write("</body></html>");
}
大約2MB的小記憶體需求使web伺服器有資格用於嵌入式系統(PS:非常符合後續arm產品定位)。對於更大的網路服務來說,它也足夠強大。
記錄器通過將偵錯訊息保留在記憶體中直到出現錯誤來提高磁碟空間和效能。只要一切正常,就不會編寫偵錯訊息。
對記錄器設定的更改將自動變為活動狀態,而無需重新啟動程式。
該庫使用Qt 4.7至6.x版本執行。如果是Qt 6,則需要安裝Qt5Compat庫。它包含對許多8位元字元編碼的支援,Qt6預設情況下不再支援這些編碼。可以在LGPL許可證的條件下使用該軟體。
作者專案的起源
多年前,一位經驗豐富的Java開發人員堅持認為Java是網際網路語言,因為在其他程式語言中進行網際網路通訊要複雜得多。這不正確,但拒絕相信。所以開始挑戰。
任務:與Qt4相比,僅使用Java 6執行時庫對具有一些基本功能的HTTP伺服器進行程式設計。
兩個專案都很相似,實現了任務。同時,Qt/C++程式比Java版本小得多,也快得多。
幾年後,用這個原型製作了一個庫,並將其用於一些私人專案。大學鼓勵釋出程式碼。從那以後,再也不用這個專案了,但還是進行了一些改進,讓人們感到高興。
有趣的是,Qt製造商多年來一直在開發標準HTTP伺服器,但到2022年,它仍然不包括在Qt庫中。這也許可以解釋為什麼很多人使用的庫。
官方:http://www.stefanfrings.de/qtwebapp/QtWebApp.zip
連結:https://pan.baidu.com/s/1v9DTrajX8Mv-xnhScDhN8g?pwd=1234
使用Qt和QtWebApp在C++中開發HTTP Web伺服器應用程式。必須已經瞭解C++和HTML的基本知識。
安裝好Qt,下載QtWebApp原始碼;
安裝好Qt,下載QtWebApp原始碼,然後對應不同linux安裝一些軟體如下:
sudo apt install build-essential gdb libgl1-mesa-dev
yum groupDebian, Ubuntunstall "C Development Tools and Libraries"
sudo yum install mesa-libGL-devel
sudo zypper install -t pattern devel_basis
為的程式設計專案建立一個目錄,然後在那裡下載並提取QtWebApp ZIP檔案。啟動QT Creator IDE並開啟專案檔案Demo1/Demo1.pro。這將開啟「設定專案」對話方塊:
只需點選突出顯示的「設定專案」按鈕即可。然後點選左邊框中的綠色「Run」按鈕,構建並執行演示程式。當的計算機正忙時,可以通過單擊底部邊框中的相關按鈕來觀看「編譯輸出」窗格。如果一切正常,將開啟一個黑色控制檯視窗,告訴演示應用程式正在使用哪個組態檔:
開啟URL http://localhost:8080在web瀏覽器中檢查演示web伺服器是否正常工作:
如果在螢幕上看到那個網站,所有需要的軟體都能正常工作。現在可以關閉演示應用程式。
如果曾經使用Java Servlet API開發過web伺服器應用程式,會感覺像在家一樣。的庫提供了幾乎相同的功能。將向展示如何使用QtWebApp編寫一個最小的web伺服器應用程式。
提取程式設計資料夾中的QtWebApp.zip檔案,並建立一個名為「MyFirstWebApp」的新Qt控制檯專案(如果尚未完成)。然後,應該擁有與相同的資料夾結構:
將以下行新增到MyFirstWebApp專案的專案檔案中:
QT += network
include(../QtWebApp/QtWebApp/httpserver/httpserver.pri)
第一行啟用Qt的網路模組,第二行包括QtWebApp的HTTP伺服器模組的原始碼。因此,當編譯程式時,HTTP伺服器將成為可執行檔案的一部分。
作為替代方案,可以使用共用庫。要生成它,請開啟專案QtWebApp/QtWebApp-QtWebApp.pro並構建它。然後檢視QtWebApp/Demo2/Demo2.pro,瞭解如何連結到共用庫。然而,建議包含如上所示的原始碼,因為這樣不太容易出錯。
下一步是建立組態檔MyFirstWebApp/etc/webapp1.ini。需要使用作業系統的檔案管理器來執行此操作,因為Qt Creator無法建立新資料夾。檔案內容為:
[listener]
;host=192.168.0.100
port=8080
minThreads=4
maxThreads=100
cleanupInterval=60000
readTimeout=60000
maxRequestSize=16000
maxMultiPartSize=10000000
主機和埠引數指定web伺服器偵聽的IP地址和埠。如果註釋掉主機(如上所述),則伺服器將偵聽所有網路介面。公共web伺服器使用埠80,而內部web伺服器通常在埠8080上偵聽。可以使用任何喜歡的自由埠。
Unix使用者應該記住,1024以下的埠號是為「root」使用者保留的。Windows使用者可能需要設定Windows防火牆以允許從其他計算機進行存取。
QtWebApp可以同時處理多個HTTP請求,因此它是多執行緒的。由於啟動一個新執行緒需要花費大量時間,QtWebApp會將執行緒重新用於後續的HTTP請求。
maxThreads值指定並行工作執行緒的最大數量。在進入生產環境之前,應該使用負載生成器工具來了解伺服器在不耗盡記憶體或變得遲緩的情況下可以處理多少負載。
minThreads空閒時並行工作執行緒的最小數量。web伺服器總是以一個空執行緒池開始。執行緒是在HTTP請求傳入時按需建立的。空閒執行緒由計時器緩慢關閉。每個cleanupInterval(以毫秒為單位),伺服器都會關閉一個空閒執行緒,但是minThreads的數量總是保持執行。使用給定的值,的伺服器最多可以處理100個並行HTTP連線。它保持4個空閒的工作執行緒執行,以確保良好的響應時間。
readTimeout設定通過開啟大量連線而不使用這些連線來保護伺服器免受簡單的拒絕服務攻擊的超時時間。空閒連線在該毫秒數之後關閉。在正常情況下,網路瀏覽器負責關閉連線。
maxRequestSize保護伺服器不受非常大的HTTP請求造成的記憶體過載的影響。此值適用於常規請求。
maxMultiPartSize值適用於web瀏覽器將檔案上載到伺服器時發生的多部件請求。如果想接收10兆位元組的檔案,由於HTTP協定開銷,必須將此值設定得稍大一些。
上傳的檔案儲存在臨時檔案中。臨時資料夾的位置由作業系統定義。
繼續建立的第一個web應用程式。要使此組態檔在Qt Creator中可見,請在專案檔案中新增一行:
OTHER_FILES += etc/webapp1.ini
現在新增一些程式碼來載入該檔案:
#include <QCoreApplication>
#include <QSettings>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QSettings* listenerSettings=
new QSettings("/home/sfrings/programming/MyFirstWebApp/etc/webapp1.ini",QSettings::IniFormat,&app);
qDebug("config file loaded");
return app.exec();
}
但更喜歡在幾個資料夾中自動搜尋組態檔,這樣就可以在IDE內外執行應用程式,而無需更改路徑:
#include <QCoreApplication>
#include <QSettings>
#include <QFile>
#include <QDir>
#include <QString>
/**
* Search the configuration file.
* Aborts the application if not found.
* @return The valid filename
*/
QString searchConfigFile() {
QString binDir=QCoreApplication::applicationDirPath();
QString appName=QCoreApplication::applicationName();
QString fileName("Demo1.ini");
QStringList searchList;
searchList.append(binDir);
searchList.append(binDir+"/etc");
searchList.append(binDir+"/../etc");
searchList.append(binDir+"/../"+appName+"/etc"); // for development with shadow build (Linux)
searchList.append(binDir+"/../../"+appName+"/etc"); // for development with shadow build (Windows)
searchList.append(QDir::rootPath()+"etc/opt");
searchList.append(QDir::rootPath()+"etc");
foreach (QString dir, searchList)
{
QFile file(dir+"/"+fileName);
if (file.exists())
{
fileName=QDir(file.fileName()).canonicalPath();
qDebug("Using config file %s",qPrintable(fileName));
return fileName;
}
}
// not found
foreach (QString dir, searchList)
{
qWarning("%s/%s not found",qPrintable(dir),qPrintable(fileName));
}
qFatal("Cannot find config file %s",qPrintable(fileName));
return nullptr;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Load the configuration file
QString configFileName=searchConfigFile();
QSettings* listenerSettings=new QSettings(configFileName, QSettings::IniFormat, &app);
qDebug("config file loaded");
return app.exec();
}
過程searchConfigFile()在多個資料夾中搜尋檔案。
方法**QDir::canonicalPath()將相對路徑名轉換為絕對形式,這在下面的偵錯訊息中看起來更好。
如果找不到該檔案,則應用程式會輸出一條帶有qFatal()**的錯誤訊息,這也會中止程式。
一旦載入了組態檔,就可以建立一個HTTP偵聽器物件,它是web伺服器的核心:
#include <QCoreApplication>
#include <QSettings>
#include <QFile>
#include <QDir>
#include <QString>
#include "httplistener.h"
#include "httprequesthandler.h"
using namespace stefanfrings;
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Load the configuration file
QString configFileName=searchConfigFile();
QSettings* listenerSettings=new QSettings(configFileName, QSettings::IniFormat, &app);
listenerSettings->beginGroup("listener");
// Start the HTTP server
new HttpListener(listenerSettings, new HttpRequestHandler(&app), &app);
return app.exec();
}
方法**QSettings::beginGroup()**從組態檔中選擇組「[listener]」。稍後將新增更多組。
HttpRequestHandler接收所有傳入的HTTP請求,並生成響應。預設情況下,請求處理程式只返回一個錯誤頁面。
在堆上用「new」建立HttpListener是很重要的,否則它將在程式啟動後立即終止。
執行程式並開啟URLhttp://localhost:8080在web瀏覽器中。將在控制檯視窗中收到錯誤頁面「501未實現」和偵錯訊息。
這是很多會減慢程式速度的訊息,但它們對偵錯很有幫助。在Qt Creator的左邊框中,可以通過單擊紫色按鈕將構建模式從「偵錯」更改為「釋出」。釋出版本不那麼冗長:
因此,對於生產,應該更喜歡釋出版本。
為了輸出「Hello World」訊息,必須編寫自己的請求處理程式。用滑鼠右鍵單擊src資料夾,選擇「新增新…」,然後選擇「C++類別」。
#ifndef HELLOWORLDCONTROLLER_H
#define HELLOWORLDCONTROLLER_H
#include "httprequesthandler.h"
using namespace stefanfrings;
class HelloWorldController : public HttpRequestHandler {
Q_OBJECT
public:
HelloWorldController(QObject* parent=0);
void service(HttpRequest& request, HttpResponse& response);
};
#endif // HELLOWORLDCONTROLLER_H
#include "helloworldcontroller.h"
HelloWorldController::HelloWorldController(QObject* parent)
: HttpRequestHandler(parent) {
// empty
}
void HelloWorldController::service(HttpRequest &request, HttpResponse &response) {
response.write("Hello World",true);
}
可選引數「true」表示這是當前HTTP請求的最後一次write()呼叫。
main.cpp中的兩個更改:
#include "helloworldcontroller.h"
new HttpListener(listenerSettings,new HelloWorldController(&app),&app);
執行程式並開啟URLhttp://localhost:8080在網路瀏覽器中。
因為http很多時候是放在一個Qt介面裡面,所以搭建的是QWidget工程模板,非控制檯,有需要自行切換下。
略;
將QtWebApp中的httpserver,符合模組化設計準則,如下圖:
新增模組進入工程:
httpserver模組,QtWebApp自帶的三方模組
# httpserver模組,QtWebApp自帶的三方模組
include ($$PWD/modules/httpserver/httpserver.pri)
第三方的模組。
再建立一個http管理類來處理,如下:
再建立基本設定:
至此,基礎模組搭建好,下面需要開始寫http的訊息處理過程。
繼承HttpRequestHandler訊息處理類,開始新建一個類:
引入標頭檔案,名稱空間,做一些基礎處理:
然後要實現service服務介面:
如下圖:
這裡已經將http的輕量級伺服器已經子執行緒模組化融入帶介面的qt應用中(帶不帶介面融入過程都一樣,只是QApplication和QCoreApplication以及在哪初始化的問題了)
測試127.0.0.1移植連線補上,檢視 「入坑二」。
然後測試開啟成功:
這裡有個字元編碼的問題也要同時解決一下,一般來說都是utf-8,所以要字元編碼修改一下。
我們忽略系統編碼,統一進行utf-8進行轉換,避免因為系統問題而去單獨處理這個問題:
然後測試網頁:
除了紀錄檔,沒發現三方模組中有是否監聽成功的反饋,所以紀錄檔就顯得很重要,紀錄檔在下一篇再融於進來
至此,一個基礎子執行緒模組化的http服務的qt介面應用Demo就完成了。
#ifndef HTTPSERVERMANAGER_H
#define HTTPSERVERMANAGER_H
#include <QObject>
#include <QMutex>
#include "httplistener.h"
#include "HelloWorldRequestHandler.h"
class HttpServerManager : public QObject
{
Q_OBJECT
private:
explicit HttpServerManager(QObject *parent = 0);
public:
static HttpServerManager *getInstance();
public:
QString getIp() const; // 伺服器監聽ip(若為空,則表示監聽所有ip
quint16 getPort() const; // 伺服器監聽埠
int getMinThreads() const; // 空閒最小執行緒數
int getMaxThreads() const; // 負載最大執行緒數
int getCleanupInterval() const; // 空執行緒清空間隔(單位:毫秒)
int getReadTimeout() const; // 保持連線空載超時時間(單位:毫秒)
int getMaxRequestSize() const; // 最大請求數
int getMaxMultiPartSize() const; // 上載檔案最大數(單位:位元組)
public:
void setIp(const QString &ip); // 伺服器監聽ip(若為空,則表示監聽所有ip
void setPort(const quint16 &port); // 伺服器監聽埠
void setMinThreads(int minThreads); // 空閒最小執行緒數
void setMaxThreads(int maxThreads); // 負載最大執行緒數
void setCleanupInterval(int cleanupInterval); // 空執行緒清空間隔(單位:毫秒)
void setReadTimeout(int readTimeout); // 保持連線空載超時時間(單位:毫秒)
void setMaxRequestSize(int value); // 最大請求數
void setMaxMultiPartSize(int value); // 上載檔案最大數(單位:位元組)
public slots:
void slot_start();
void slot_stop();
private:
static HttpServerManager *_pInstance;
static QMutex _mutex;
private:
bool _running;
private:
HttpListener *_pHttpListener; // http服務監聽器
QSettings *_pSettings; // 組態檔
private:
QString _ip; // 伺服器監聽ip(若為空,則表示監聽所有ip)
quint16 _port; // 伺服器監聽埠
int _minThreads; // 空閒最小執行緒數
int _maxThreads; // 負載最大執行緒數
int _cleanupInterval; // 空執行緒清空間隔(單位:毫秒)
int _readTimeout; // 保持連線空載超時時間(單位:毫秒)
int _maxRequestSize; // 最大請求數
int _maxMultiPartSize; // 上載檔案最大數(單位:位元組)
};
#endif // HTTPSERVERMANAGER_H
#include "HttpServerManager.h"
#include <QApplication>
#include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")
HttpServerManager *HttpServerManager::_pInstance = 0;
QMutex HttpServerManager::_mutex;
HttpServerManager::HttpServerManager(QObject *parent)
: QObject(parent),
_pHttpListener(0),
_pSettings(0),
_running(false),
_port(8088),
_minThreads(2),
_maxThreads(10),
_cleanupInterval(60000),
_readTimeout(60000),
_maxRequestSize(100),
_maxMultiPartSize(1024*1024*1024)
{
}
HttpServerManager *HttpServerManager::getInstance()
{
if(!_pInstance)
{
QMutexLocker lock(&_mutex);
if(!_pInstance)
{
_pInstance = new HttpServerManager();
}
}
return _pInstance;
}
void HttpServerManager::slot_start()
{
if(_running)
{
LOG << "It's running!!!";
return;
}
_running = true;
LOG << "Succeed to run";
// 啟動http的監聽
{
QString httpServerPath = QString("%1/etc/httpServer.ini").arg(qApp->applicationDirPath());
if(!_pSettings)
{
LOG << httpServerPath << "exit:" << QFile::exists(httpServerPath);
_pSettings = new QSettings(httpServerPath, QSettings::IniFormat);
}
#if 0
if(!_ip.isEmpty())
{
_pSettings->setValue("host" , _ip); // ;在ini裡面是註釋了
}
_pSettings->setValue("port" , _port);
_pSettings->setValue("minThreads" , _minThreads