行业知识
全志H3 - Qt&QtWebApp搭建Http Server(无X11系统)
目前我手里正好有一块NanoPi M1加法板,NanoPi M1(以下简称M1)是友善之臂团队面向创客、嵌入式爱好者,电子艺术家、发烧友等群体推出的又一款完全开源的掌上创客神器,它的大小只有树莓派的大约2/3,可运行Debian、Ubuntu-MATE、Ubuntu-Core、Android等操作系统。
NanoPi M1采用了全志高性能处理器Allwinner H3,集成以太网、红外接收、视频/音频输出等接口,支持HDMI、AVOUT视频输出等功能。
尽管体积很小,设计却紧凑美观。NanoPi M1引出了相当丰富的接口,包括HDMI、以太网、USB-Host、USB-OTG、DVP camera和AVOUT(音频+视频)等。而且集成了板载麦克风,红外接收器,并且兼容树莓派GPIO口,并且拥有独立的调试串口等。
本机的目的主要是在当前系统上编写Qt应用程序,因此不会过多的深入了解有关系统底层的知识,更多的内容可以参考《 NanoPi M1/zh》。
一、目的
本次实现使用的这款开发板已经烧录了:FriendlyCore系统固件,基于Ubuntu core 22.04构建,内置Qt4.8,但是该系统并没有X11桌面环境。
这里我们需要在该系统上开发一款Qt应用程序,通过LCD显示屏显示车牌识别的结果。车牌识别的结果可以从车牌识别系统获取到,而我们要做的仅仅是在显示屏上显示识别到的车牌信息。
1.1 车牌识别系统
车牌识别系统识别到的信息包含以下几种:
| 1. 入口静止 | 2. 出口静止 | 3. 入口忙碌 | 4. 出口忙碌,不收费 | 5. 出口忙收费 |
|---|---|---|---|---|
| 欢迎光临 | 一路平安 | 粤B12345 | 粤B12345 | 粤B12345 |
| logo | logo | 临时车/欢迎光临,剩余8/天/欢迎光临 | 临时车 | 停车:1小时3分钟 |
| 余位显示 | 余位 | 停车3小时5分钟 | 二维码 | |
| 一路平安 | ¥100元 | |||
| 语音 | 语音 | 语音 |
车牌识别系统支持的通信方式有多种:
- HTTP接口: http://IP:8080/API ,数据格式JSON;
- UDP端口6666,协议格式JSON;
- MQTT通讯(互联网,云平台对接);
- TCP客户端模式,服务端模式均支持;
- 485通讯;
1.2 协议内容
无论采用哪种通信方式,车牌识别系统发送的协议内容是固定的,具体如下:
字段 说明 类型 类型 requestid 消息 ID,会返回响应 String 是 servicename 业务名称 String 是 data 具体的业务 json json 是 sign MD5 签名 是 业务servicename种类有多种:
- welcome:欢迎光临;
- payinfo:缴费二维码;
- parkNuminfo:余位显示;
- byebye:一路平安;
- noPass:禁止通行;
- waitInfo:人工确认;
- volume:音量;
- DisplayVoice: 简化指令;
- advLoadImage:广告下载,文件方式(速度较慢),URL 方式(简单易用);
- cmd485:485 数据包;
- UpdateTime:更新时间;
这里我们要做的就是在显示屏上显示servicename为welcome和advLoadImage的内容。
1.2.1 welcome
字段 说明 类型 必须 carmark 车牌 String 是 line1 欢迎回家 string line2 临时车 int line3 请入场停车 voice 粤 B12345,临时车,欢迎光临 String companyName 深圳 XX 科技有限公司 pictureUsed 0~4,对应servicename为advLoadImage业务发送的4张背景图 int 如果显示屏字体,颜色大小不合适,可以增加以下字段调整:
字段 说明 类型 必须 line1Size 字体大小 int 否,0 为默认值,其是按大小的 line2Size 字体大小 int 否,0 为默认值,其是按大小的 line3Size 字体大小 int 否,0 为默认值,其是按大小的 carmarkSize 字体大小 int 否,0 为默认值,其是按大小的 companyNameSize 字体大小 int 否,0 为默认值,其是按大小的 carmarkColor 颜色 String #FFFFFF line1Color 颜色 String #FFFFFF line2Color 颜色 String #FFFFFF line3Color 颜色 String #FFFFFF companyNameColor 颜色 String #FFFFFF 示例1:
{"requestid": "bff09428-6200-42e0-b7b5-560c5a8f4fdd","servicename": "welcome","data": {"carmark": "粤 B12345","line1": "欢迎光临","line2": "临时车","line3": "请入场停车","voice": "粤 B12345,临时车,欢迎光临","companyName": "深圳市 XX 科技有限公司", "pictureUsed": "0" }}示例2:
{ "requestid": "65dc4092d6c5f", "servicename": "welcome", "data": { "carmark": "Welcome", "line1": "", "line2": "LCD002", "line3": "", "voice": "", "companyName": "JH-LOCAL-PARK", "line1Size": "118", "line2Size": "88", "line3Size": "88", "carmarkSize": "88", "companyNameSize": 118, "carmarkColor": "#FFFFFF", "line1Color": "#FFFFFF", "line2Color": "#FFFFFF", "line3Color": "#FFFFFF", "companyNameColor": "#FFFFFF", "pictureUsed": "0" }}1.2.2 advLoadImage
字段 说明 类型 必须 ImageName 广告图片名 String 文件下载方式必填 ImageUrl URL string 网络连接方式下载必填 Data Base64 数据 string 文件下载方式必填 AdvName 广告 string bg0,bg1,bg2,bg3,bg4 logo 必填
只有5张广告图片,宽高:9:16,推荐 1920*1080文件下载方式:通常适用于没有外网的方式,就是操作复杂点,数据包比较大。
网络连接方式方式,只需要提供URL,APP自动下载广告。
示例1:
{ "requestid": "fcfc7fd0-c4ee-48c7-9e5a-3b368e136907", "servicename": "advLoadImage", "data": { "ImageName": "764f23a1b5022f349541ba509e92b92.jpg", "ImageUrl": null, "AdvName": "bg0", "Data": "/9j/4AAQSkZJRgABAQEAAAAAAAD/7gAOQWRvYmUAZAAAAAAB/+EAQEV4aWYAAE1N ACoAAAAIAA....." }}示例2:
{ "requestid": "65dc4092d6c5f", "servicename": "BackgroundImage", "data": { "ImageName": "", "AdvName": "bg0", "ImageUrl": "http://local.whizz-park.com:10180/storage/lcd/bg_blue_20231226103610.jpeg", "Data": "" }}1.2.3 报文响应
当我们的Qt应用程序接收到车牌识别系统发送过来的请求,我们需要对此作出响应,响应格式大致如下:
{"msg": "操作成功","requestId": "65dc4092d6c5f","status": true,"version": "231102"}1.2.4 注意
车牌识别系统首先会通过advLoadImage服务将5张背景图片发送到我们Qt应用程序,我们需要将这些背景图片保存下来,比如以下背景图片:
在后续接收到welcome服务的时候根据pictureUsed选择某张图片作为背景图片,并将服务中带有的信息显示出来,显示效果如下:
上面的内容和请求字段对应关系如下:
- WWW 2388:对应carmark字段;
- SORRY:对应line1字段;
- No entry record found.:对应line2字段;
- Please press intercom:对应line3字段;
二、 QtWebApp介绍
由于我们要实现在局域网车牌识别系统能够通过Http协议访问我们的应用程序,因此需要使用Qt搭建Http服务器,这里我们使用QtWebApp来实现。
QtWepApp是一个C++中的Http服务器库,其灵感来自Java Servlet。适用于Linux、Windows、Mac OS和Qt Framework支持的许多其他操作系统。QtWebApp包含以下组件:
- HTTP(S)1.0和1.1服务器;
- 模板引擎;
- 缓冲记录器。
2.1 源码下载
QtWebApp下载地址:http://www.stefanfrings.de/qtwebapp/QtWebApp.zip;
我们需要下载QtWebApp源码,这里我下载到/opt/qt-project目录;
root@zhengyang:/opt/qt-project# wget http://www.stefanfrings.de/qtwebapp/QtWebApp.zip
2.2 测试
解压QtWebApp.zip:
root@zhengyang:/opt/qt-project# unzip QtWebApp.ziproot@zhengyang:/opt/qt-project# apt install treeroot@zhengyang:/opt/qt-project# tree -L 2 QtWebAppQtWebApp├── Demo1│ ├── build│ ├── Demo1│ ├── Demo1.pro│ ├── Demo1.pro.user│ ├── etc│ ├── logs│ ├── Makefile│ └── src├── Demo2│ ├── Demo2.pro│ ├── Demo2.pro.user│ └── src└── QtWebApp ├── doc ├── Doxyfile ├── httpserver ├── logging ├── QtWebApp.pro ├── QtWebApp.pro.user └── templateengine
2.2.1 测试源码
查看测试代码:
root@zhengyang:/opt/qt-project/QtWebApp# cd Demo1/root@zhengyang:/opt/qt-project/QtWebApp/Demo1#root@zhengyang:/opt/qt-project/QtWebApp/Demo1# ls -l总用量 36-rwxrwxrwx 1 root root 1450 3月 19 2022 Demo1.pro-rw-r--r-- 1 root root 19374 9月 8 00:23 Demo1.pro.userdrwxr-xr-x 5 root root 4096 1月 17 2023 etcdrwxr-xr-x 2 root root 4096 3月 19 2022 logsdrwxr-xr-x 3 root root 4096 10月 1 2022 src
2.2.2 编译运行
编译:
root@zhengyang:/opt/qt-project/QtWebApp/Demo1# mkdir buildroot@zhengyang:/opt/qt-project/QtWebApp/Demo1# cd build root@zhengyang:/opt/qt-project/QtWebApp/Demo1/build# qmake ..root@zhengyang:/opt/qt-project/QtWebApp/Demo1/build# make
运行程序:
root@zhengyang:/opt/qt-project/QtWebApp/Demo1/build# ./Demo1Using config file /opt/qt-project/QtWebApp/Demo1/etc/Demo1.iniLogging to /dev/stdoutQFile::at: Cannot set file position 005.03.2024 23:08:11.790 0 DEBUG 0x7f17992ddbc0 TemplateLoader: path=/opt/qt-project/QtWebApp/Demo1/etc/templates, codec=UTF-805.03.2024 23:08:11.790 0 DEBUG 0x7f17992ddbc0 TemplateCache: timeout=60000, size=100000005.03.2024 23:08:11.790 0 DEBUG 0x7f17992ddbc0 HttpSessionStore: Sessions expire after 3600000 milliseconds05.03.2024 23:08:11.790 0 DEBUG 0x7f17992ddbc0 StaticFileController: docroot=/opt/qt-project/QtWebApp/Demo1/etc/docroot, encoding=UTF-8, maxAge=6000005.03.2024 23:08:11.790 0 DEBUG 0x7f17992ddbc0 StaticFileController: cache timeout=60000, size=100000005.03.2024 23:08:11.790 0 DEBUG 0x7f17992ddbc0 RequestMapper: created05.03.2024 23:08:11.809 0 DEBUG 0x7f17992ddbc0 HttpListener: Listening on port 808005.03.2024 23:08:11.809 1 WARNING 0x7f17992ddbc0 Application has started
打开PC Web浏览器,在浏览器输入http://192.168.0.200:8080/:
其中192.168.0.200为程序运行所在宿主机的ip地址。
三、QtWebApp使用
由于我们的开发板已经内置了Qt4.8,因此我们最好在宿主机上搭建Qt4.8的环境,这样方便将代码直接拷贝到开发板编译运行。
但是这里我们并不打算这么搞了,因此我的ubuntu宿主机安装的是Qt6.5.0,具体可以参考《Rockchip RK3588 - linux下Qt和opencv交叉编译环境搭建》。
这里我直接在宿主机上使用Qt6.5.0进行开发,最后再移植到开发板上编译调整。
3.1 新建项目
在宿主机打开Qt Creator:
zhengyang@zhengyang:/$ sudo /opt/qtcreator-11.0.0/bin/qtcreator
在Qt Creator首页点击【创建项目】 ,选中【Application(Qt)】- 【Qt Widgets Application】;接着进行如下设置:
- Location:名称PlateLCDDisplay,创建路径/opt/qt-project;
- 构建系统:选择qmake;
- Details:跳过;
- Tranlation:跳过;
- 构建套件:选择桌面;
- 汇总:跳过。
新建的项目目录结构如下:
root@zhengyang:/opt/qt-project# tree -L 2 PlateLCDDisplay/PlateLCDDisplay/├── main.cpp├── mainwindow.cpp├── mainwindow.h├── mainwindow.ui├── PlateLCDDisplay.pro└── PlateLCDDisplay.pro.user
选择【工具】- 【外部】-【配置】-【文本编辑器】- 【行为】- 【文件编码】, 将文件编码设置为utf-8,UTF-8 BOM选择存在则保留,最后选择应用;
这样源码文件将均采用UTF-8编码。
3.1.1 webapp.ini配置文件
在项目目录下创建etc文件夹,在文件夹下添加webapp.ini配置文件,并通过qt designer将配置文件添加到项目中;
[listener];ip=192.168.0.110port=80minThreads=4maxThreads=100cleanupInterval=60000readTimeout=10000maxRequestSize=6000000maxMultiPartSize=10000000
其中:
- ip和port:IP和端口参数指定web服务器侦听的IP地址和端口。如果注释掉IP(如上所述),则服务器将侦听所有网络接口。公共web服务器使用端口80,而内部web服务器通常在端口8080上侦听;
- minThreads和cleanupInterval:QtWebApp可以同时处理多个Http请求,因此它是多线程的。该参数用于设置空闲时并发工作线程的最小数量;web服务器总是以一个空线程池开始,线程是在Http请求传入时按需创建的,空闲线程由计时器缓慢关闭;每个cleanupInterval(以毫秒为单位),服务器都会关闭一个空闲线程;
- maxThreads:指定并发工作线程的最大数量,在进入生产环境之前,应该使用负载生成器工具来了解服务器在不耗尽内存或变得迟缓的情况下可以处理多少负载;使用给定的值,服务器最多可以处理100个并发Http连接。它保持4个空闲的工作线程运行,以确保良好的响应时间;
- readTimeout:设置可以保护服务器免受简单的拒绝服务攻击,比如打开大量连接但不使用它们;空闲连接在指定的毫秒数后会被关闭,在正常情况下,Web浏览器负责关闭连接;
- maxRequestSize:保护服务器不受非常大的Http请求造成的内存过载的影响,此值适用于常规请求;
- maxMultiPartSize:设置允许上传的最大文件大小,以防止恶意用户上传过大的文件导致服务器负载过重或资源耗尽;
3.1.2 modules模块
在项目目录下创建modules文件夹:
root@zhengyang:/opt/qt-project# cd PlateLCDDisplayroot@zhengyang:/opt/qt-project/PlateLCDDisplay# mkdir modules
3.2 httpserver模块
将/opt/qt-project/QtWebApp/QtWebApp/httpserver拷贝到modules目录下;
root@zhengyang:/opt/qt-project/PlateLCDDisplay# cp -r /opt/qt-project/QtWebApp/QtWebApp/httpserver ./modules
将以下行添加到PlateLCDDisplay.pro;
QT +=networkinclude ($$PWD/modules/httpserver/httpserver.pri)
3.3 Http管理模块
在项目modules文件夹,创建httpServerManager文件夹,同时在httpServerManager文件夹下创建如下文件:
root@zhengyang:/opt/qt-project/PlateLCDDisplay# cd modules/root@zhengyang:/opt/qt-project/PlateLCDDisplay/modules# mkdir httpServerManagerroot@zhengyang:/opt/qt-project/PlateLCDDisplay/modules# cd httpServerManagerroot@zhengyang:/opt/qt-project/PlateLCDDisplay/modules/httpServerManager# touch HttpServerManager.hroot@zhengyang:/opt/qt-project/PlateLCDDisplay/modules/httpServerManager# touch HttpServerManager.cpproot@zhengyang:/opt/qt-project/PlateLCDDisplay/modules/httpServerManager# touch HelloworldRequestHandler.hroot@zhengyang:/opt/qt-project/PlateLCDDisplay/modules/httpServerManager# touch HelloworldRequestHandler.cpproot@zhengyang:/opt/qt-project/PlateLCDDisplay/modules/httpServerManager# touch httpServerManager.pri
其中:
- httpServerManager.pri:子项目文件;
- HttpServerManager.h和HttpServerManager.cpp:Http管理器实现,用于加载web配置,并启动Http Server监听Http请求;
- HelloworldRequestHandler.h和HelloworldRequestHandler.cpp:Http请求处理实现;
3.3.1 httpServerManager.pri
httpServerManager.pri:子模块项目文件,类似Makefile,用来链接项目文件,比如头文件、源码等;
INCLUDEPATH += $$PWDDEPENDPATH += $$PWD# Enable very detailed debug messages when compiling the debug versionCONFIG(debug, debug|release) { DEFINES += SUPERVERBOSE}HEADERS += $$PWD/HttpServerManager.h \ $$PWD/HelloworldRequestHandler.hSOURCES += $$PWD/HttpServerManager.cpp \ $$PWD/HelloworldRequestHandler.cpp同时将以下行添加到PlateLCDDisplay.pro;
include ($$PWD/modules/httpServerManager/httpServerManager.pri)
3.3.2 HttpServerManager.h
#ifndef HTTPSERVERMANAGER_H#define HTTPSERVERMANAGER_H#include #include #include "httplistener.h"#include "filelogger.h"#include "HelloworldRequestHandler.h"#include "logging.h"class HttpServerManager : public QObject{ Q_OBJECTprivate: 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 maxRequestSize); /* 设置最大请求数 */ void setMaxMultiPartSize(int maxMultiPartSize); /* 设置上传文件最大大小(单位,字节)*/ QString searchConfigFile(); /* Find the configuration file */public slots: void slot_start(); void slot_stop();private: static HttpServerManager *_pInstance; static QMutex _mutex;private: bool _running;private: HttpListener *_pHttpListener; /* http服务监听器 */ QSettings *_pHttpListenerSettings; /* http服务器配置文件 */private: QString _ip; /* 服务器监听ip,若为空,则表示监听所有ip */ quint16 _port; /* 服务器监听端口 */ int _minThreads; /* 空闲最小线程数 */ int _maxThreads; /* 负载最大线程数 */ int _cleanupInterval; /* 空线程清空间隔(单位,毫秒) */ int _readTimeout; /* 保持连接空载超时时间(单位,毫秒)*/ int _maxRequestSize; /* 最大请求数 */ int _maxMultiPartSize; /* 上传文件最大大小(单位,字节) */};#endif // HTTPSERVERMANAGER_H3.3.3 HttpServerManager.cpp
在HttpServerManager.cpp文件中主要用于从多个路径查找并加载webapp.ini配置文件,同时创建HttpListener监听Http请求。
#include "HttpServerManager.h"#include #include HttpServerManager *HttpServerManager::_pInstance = 0;QMutex HttpServerManager::_mutex;HttpServerManager::HttpServerManager(QObject *parent) : QObject(parent), _pHttpListener(0), _pHttpListenerSettings(0), _running(false), _port(8088), _minThreads(2), _maxThreads(10), _cleanupInterval(60000), _readTimeout(60000), _maxRequestSize(100), _maxMultiPartSize(1024*1024*1024){}/** * 单例模式 * * @brief HttpServerManager::getInstance * @return */HttpServerManager *HttpServerManager::getInstance(){ if(!_pInstance) { QMutexLocker lock(&_mutex); if(!_pInstance) { _pInstance = new HttpServerManager(); } } return _pInstance;}/** * 启动http的监听 * @brief HttpServerManager::slot_start */void HttpServerManager::slot_start(){ if(_running) { LOG_Debug value("maxThreads").toInt()); setCleanupInterval(_pHttpListenerSettings->value("cleanupInterval").toInt()); setReadTimeout(_pHttpListenerSettings->value("readTimeout").toInt()); setMaxRequestSize(_pHttpListenerSettings->value("maxRequestSize").toInt()); setMaxMultiPartSize(_pHttpListenerSettings->value("maxMultiPartSize").toInt()); // 打印读取的属性值 LOG_Info sin_addr)); if(ret sin_family = AF_INET; rt.rt_flags = RTF_GATEWAY; /* 设置网关,如果所设置的网关与现有网关一样,会报File exists */ ret = ioctl(fd, SIOCADDRT, &rt); if(ret
上一篇:云服务器日文怎么说?
下一篇:申请服务器怎么写?