参考引用
- QT开发专题-天气预报
1. JSON 数据格式
1.1 什么是 JSON
- JSON (JavaScript Object Notation),中文名 JS 对象表示法,因为它和 JS 中对象的写法很类似
- 通常说的 JSON,其实就是 JSON 字符串,本质上是一种特殊格式的字符串
- JSON 是一种轻量级的数据交换格式,客户端和服务端数据交互,基本都是 JSON 格式的
- JSON 有以下特点
- 便于阅读和书写
- 除了 JSON 格式,还有一种数据传输格式 XML,相对于 XML,JSON 更加便于阅读和书写独立于编程语言
- 网络传输的标准数据格式
- 完全独立于编程语言
- 几乎在所有的编程语言和开发环境中,都有解析和生成 JSON 字符串的库
// C Jansson cJSON// C++ jsonCpp、JSON for Modern C++// Java json-lib、org-json// Qt QJSONxxx
- 便于阅读和书写
1.2 JSON 的两种数据格式
JSON 有两种数据格式
- JSON 对象(被大括号包裹)
- JSON 数组(被中括号包裹)
1.2.1 JSON 数组
- JSON 数组格式
[元素1, 元素2, 元素3, ... 元素n]
- 类似于 c/C++ 中的数组,元素之间以逗号分隔。不同的是,JSON 数组中的元素可以是不同的数据类型
- 包括:整型、浮点、字符串、布尔类型、JSON 数组、JSON 对象、空值
// JSON 数组中的元素是同一类型 [1, 2, 3, 4] ["Spring", "Summer", "Autumn", "Winter"]// JSON 数组中的元素是不同类型 [1, 2.5, "hello", true, false, null]// JSON 数组的嵌套 [[1, 2, 3, 4],["Spring", "Summer", "Autumn", "Winter"],[1, 2.5, "hello", true, false, null] ]// JSON 数组嵌套 JSON 对象 [{"name": "Tom","age": 18,"gender": "male"}{"name": "Lucy","age": 20,"gender": "female"} ]
1.2.2 JSON 对象
- JSON 对象格式
{"key1": value1,"key2": value2,"key3": value3 }
- JSON 对象内部使用键值对的方式来组织
- 键和值之间使用冒号分隔,多个键值之间使用逗号分隔
- 键是字符串类型,值的类型可以是:整型、浮点、字符串、布尔类型、JSON 数组、JSON 对象、空值
{"name": "Tom","age": 18,"gender": "male" }
- JSON 对象中,还可以嵌套 JSON 对象和 JSON 数组
{"name": "China","info": {"capital": "beijing","asian": true,"founded": 1949},"provinces": [{"name": "hunan","capital": "changsha"}, {"name": "hubei","capital": "wuhan"}] }
1.3 JSON 在线解析
- JSON 本质就是一种特殊格式的字符串
- 实际工作中,这个 JSON 字符串可能是自己手写的,也可能是来自网络接收的数据
- 下面的一段 JSON 字符串,它可能是自己写的,也可能是服务端返回的
- 它是压缩的格式,也就是没有换行和缩进,不方便判断格式是否正确
- 可以通过 JSON 在线解析工具来校验这个 JSON 的格式是否正确
{"name":"China","info":{"capital":"beijing","asian":true,"founded":1949},"provinces": [{"name":"hunan","capital":"changsha"},{"name":"hubei","capital": "huhan"}]}
1.4 Qt 中使用 JSON
1.4.1 JSON 相关的类
(1)QJsonObject
- QJsonObject 封装了 JSON 中的对象,可以存储多个键值对
- 其中,键为字符串类型,值为 QJsonValue 类型
- 创建一个 QJsonObject 对象
QJsonObject::QJsonObject();
- 将键值对添加到 QJsonObject 对象中
QJsonObject::iterator insert(const QString &key, const QJsonValue &value);
- 获取 QJsonObject 对象中键值对的个数
int QJsonObject::count() const; int QJsonObject::size() const; int QJsonObject::length() const;
- 通过 key 得到 value
QJsonValue QJsonObject::value(const QString &key) const; QJsonValue QJsonObject::operator[](const QString &key) const;
- 检查 key 是否存在
iterator QJsonObject::find(const QString &key); bool QJsonObject::contains(const QString &key) const;
- 遍历 key
QStringList QJsonObject::keys() const;
(2)QJsonArray
- QJsonArray 封装了 Json 中的数组,数组中元素类型统一为 QJsonValue 类型
- 创建一个 QJsonArray
QJsonArray::QJsonArray();
- 添加数组元素
// 添加到头部和尾部 void QJsonArray::append(const QJsonValue &value); void QJsonArray::prepend(const QJsonValue &value);// 插入到 i 的位置之前 void QJsonArray::insert(int i, const QJsonValue &value);// 添加到头部和尾部 void QJsonArray::push_back(const QJsonValue &value); void QJsonArray::push_front(const QJsonValue &value);
- 获取 QJsonArray 中元素个数
int QJsonArray::count() const; int QJsonArray::size() const;
- 获取元素的值
// 获取头部和尾部 QJsonValue QJsonArray::first() const; QJsonValue QJsonArray::last() const;// 获取指定位置 QJsonValue QJsonArray::at(int i) const; QJsonValueRef QJsonArray::operator[](int i);
- 删除元素
// 删除头部和尾部 void QJsonArray::pop_back(); void QJsonArray::pop_front();void QJsonArray::removeFirst(); void QJsonArray::removeLast();// 删除指定位置 void QJsonArray::removeAt(int i); QJsonValue QJsonArray::takeAt(int i);
(3)QJsonValue
-
封装了 JSON 支持的六种数据类型
QJsonValue::Bool // 布尔类型 QJsonValue::Double // 浮点(含整型)类型 QJsonValue::String // 字符串类型 QJsonValue::Array // Json 数组类型 QJsonValue::Object // Json 对象类型 QJsonValue::Null // 空值类型
-
可以通过以下方式构造 QJsonValue 对象
// 字符串 QJsonValue(const char *s); QJsonValue(QLatin1String s); QJsonValue(const QString &s);// 整型 and 浮点型 QJsonValue(qint64 v); QJsonValue(int v); QJsonValue(double v);// 布尔类型 QJsonValue(bool b);// Json 对象 QJsonValue(const QJsonObject &o);// Json 数组 QJsonValue(const QJsonArray &a);// 空值类型 QJsonValue(QJsonValue::Type type = Null);
-
判断一个 QJsonValue 对象内部封装数据类型
// 是否是字符串类型 bool isString() const;// 是否是浮点类型(整形也是通过该函数判断) bool isDouble const;// 是否是布尔类型 bool isBool() const;// 是否是Json对象 bool isObject() const;// 是否是 Json 数组 bool isArray() const;// 是否是未定义类型(无法识别的类型) bool isUndefined() const;// 是否是空值类型 bool isNull() const;
-
数据类型之间的转换 API 函数
// 转换为字符串类型 QString toString() const; QString toString(const QString &defaultValue) const;// 转换为浮点类型 double toDouble(double defaultValue = 0) const;// 转换为整形 int toInt(int defaultValue = 0) const;// 转换为布尔类型 bool toBool(bool defaultValue = false) const;// 转换为 Json 对象 QJsonObject toObject() const; QJsonObject toObject(const QJsonObject &defaultValue) const;// 转换为 Json 数组 QJsonArray toArray() const; QJsonArray toArray(const QJsonArray &defaultValue) const;
(3)QJsonDocument
-
它封装了一个完整的 JSON 文档
- 它可以从 UTF-8 编码的基于文本的表示,以及 Qt 本身的二进制格式读取和写入该文档
- QJsonObject 和 QJsonArray 不能直接转换为字符类型,需通过 QJsonDocument 来完成二者的转换
-
QJsonObject/QJsonArray ==> 字符串
// 1. 创建 QJsonDocument 对象 // 以 QJsonObject 或 QJsonArray 为参数来创建 QJsonDocument 对象 QJsonDocument::QJsonDocument(const QJsonObject &object); QJsonDocument::QJsonDocument(const QJsonArray &array);// 2. 将 QJsonDocument 对象中的数据进行序列化 // 通过调用 toXXX() 方法就可得到文本格式或者二进制格式的 Json 字符串 QByteArray QJsonDocument::toBinaryData() const; // 二进制格式的 json 字符串 QByteArray QJsonDocument::toJson(JsonFormat format = Indented) const; // 文本格式// 3. 使用得到的字符串进行数据传输,或者保存到文件
-
字符串 ==> QJsonObject/QJsonArray
- 通常,通过网络接收或者读取磁盘文件,会得到一个 JSON 格式的字符审,之后可以按照如下步骤,解析出 JSON 字符串中的一个个字段
// 1. 将 JSON 字符串转换为 QJsonDocument 对象 [static] QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data,DataValidation validation = Validate); [static] QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error = Q_NULLPTR);// 2. 将文档对象转换为 json 数组 / 对象 // 2.1 判断文档对象中存储的数据是 JSON 数组还是 JSON 对象 bool QJsonDocument::isArray() const; bool QJsonDocument::isObject() const;// 2.2 转换为 JSON 数组或 JSON 对象 QJsonObject QJsonDocument::object() const; QJsonArray QJsonDocument::array() const;// 3. 调用 QJsonArray / QJsonObject 类提供的 API 获取存储在其中的数据
1.4.2 构建 JSON 字符串
- 在网络传输时,通常是传输的 JSON 字符串,传输之前,首先要生成 JSON 字符串
- 接下来使用 Qt 提供的工具类,来生成如下格式的 JSON 字符串
{"name": "China","info": {"capital": "beijing","asian": true,"founded": 1949},"provinces": [{"name": "hunan","capital": "changsha"}, {"name": "hubei","capital": "wuhan"}] }
- 代码实现
#include <QCoreApplication>#include <QJsonObject> #include <QJsonArray> #include <QJsonDocument>#include <QFile> #include <QByteArray> #include <QDebug> #include <QString>void writeJson() {QJsonObject rootObj;// 1. 插入 name 字段rootObj.insert("name", "China");// 2. 插入 info 字段QJsonObject infoObj;infoObj.insert("capital", "beijing");infoObj.insert("asian", true);infoObj.insert("founded", 1949);rootObj.insert("info", infoObj);// 3. 插入 provinces 字段QJsonArray provinceArray;QJsonObject hunanObj;hunanObj.insert("name", "hunan");hunanObj.insert("capital", "changsha");QJsonObject hubeiObj;hubeiObj.insert("name", "hubei");hubeiObj.insert("capital", "wuhan");provinceArray.append(hunanObj);provinceArray.append(hubeiObj);rootObj.insert("provinces", provinceArray);// 4. 将 QJsonObject 对象 rootObj 转换为 Json 字符串QJsonDocument doc(rootObj);QByteArray json = doc.toJson();// 5.1 打印输出qDebug() << QString(json).toUtf8().data();// 5.2 将 json 字符串写入文件QFile file("d:\\china.json");file.open(QFile::WriteOnly);file.write(json);file.close(); }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);writeJson();return a.exec(); }
1.4.3 解析 JSON 字符串
- 通常接收网络数据的格式是JSON 格式,在接收完毕之后,需要解析出其中的每一个字段,根据各个字段的值做相应的显示或者其他处理
#include <QCoreApplication>#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonValue>#include <QFile>
#include <QByteArray>
#include <QDebug>
#include <QString>
#include <QStringList>void fromJson() {// 1. 读取文件QFile file("d:\\china.json");file.open(QFile::ReadOnly);QByteArray json = file.readAll();file.close();QJsonDocument doc = QJsonDocument::fromJson(json);if (!doc.isObject()) {qDebug() << "Not an object!";return;}// 2. 开始解析QJsonObject obj = doc.object();QStringList keys = obj.keys();for (int i = 0; i < keys.size(); i++) {// 获取 key-value 对QString key = keys[i];QJsonValue value = obj.value(key);if (value.isBool()) {qDebug() << key << ":" << value.toBool();} else if (value.isString()) {qDebug() << key << ":" << value.toString();} else if (value.isDouble()) {qDebug() << key << ":" << value.toInt();} else if (value.isObject()) {qDebug() << key << ":";QJsonObject infoObj = value.toObject();QString capital = infoObj["capital"].toString();bool asian = infoObj["asian"].toBool();int founded = infoObj["founded"].toInt();qDebug() << " " << "capital" << capital;qDebug() << " " << "asian" << asian;qDebug() << " " << "founded" << founded;} else if (value.isArray()) {qDebug() << key;QJsonArray provinceArray = value.toArray();for (int i = 0; i < provinceArray.size(); i++) {QJsonObject provinceObj = provinceArray[i].toObject();QString name = provinceObj["name"].toString();QString capital = provinceObj["capital"].toString();qDebug() << " " << "name" << ":" << name << ", capital" << ":" << capital;}}}
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);fromJson();return a.exec();
}
- 控制台输出
"info" :capital "beijing"asian truefounded 1949 "name" : "China" "provinces"name : "hunan" , capital : "changsha"name : "hubei" , capital : "wuhan"
2. HTTP 通信
2.1 HTTP 概述
HTTP:超文本传输协议 (HyperText Transfer Protocol),HTTP 是浏览器端 web 通信的基础
2.1.1 两种架构
- B/S 架构:Browser/server,浏览器/服务器架构
- B:浏览器,比如 Firefox、Internet Explorer、Google Chrome、Safari、Opera 等
- S:服务器,比如 Apache、nginx 等
- C/S 架构:client/server,客户端/服务器架构
- B/S 架构相对于 C/S 架构,客户机上无需安装任何软件,使用浏览器即可访问服务器,因此,越来越多的 C/S 架构正被 B/S 架构所替代
2.1.2 基于请求响应的模式
- HTTP 协议永远都是客户端发起请求,服务器做出响应
- 也就是说,请求必定是先从客户端发起的,服务器端在没有接收到请求之前不会发送任何响应
- 这就无法实现这样一种场景:服务端主动推送消息给客户端
2.1.3 无状态
-
当浏览器第一次发送请求给服务器时,服务器做出了响应
-
当浏览器第二次发送请求给服务器时,服务器同样可以做出响应,但服务器并不知道第二次的请求和第一次来自同一个浏览器
- 也就是说,服务器不会记住你是谁,所以是无状态的
-
而如果要使 HTTP 协议有状态,就可以使浏览器访问服务器时,加入 cookie,这样,只要你在请求时有了这个 cookie,服务器就能够通过 cookie 知道,你就是之前那个浏览器,这样,HTTP 协议就有状态了
2.1.4 请求报文
请求报文由四部分组成
- 请求行 + 请求头(请求首部字段) + 空行 + 实体
-
请求行
- 请求方法:比如 GET、POST
- 资源对象 (URL)
- 协议名称和版本号 (HTTP/1.1)
-
请求头(请求首部字段)
- 请求头用于告诉服务器该请求的一些信息,起到传递额外信息的目的
- 请求头用于告诉服务器该请求的一些信息,起到传递额外信息的目的
-
空行
- 空行是为了区分请求头和请求实体
-
请求实体
- 请求实体即真正所需要传输的数据
2.1.5 响应保文
响应报文同样是由四部分组成
- 状态行 + 响应头(响应报文首部) + 空行 + 消息体
-
状态行
- HTTP 版本
- 状态码 (表示相应的结果)
- 原因短语 (解释)
-
响应头(响应报文首部)
- 和请求报文首部一样,响应报文首部同样是为了传递额外信息
- 和请求报文首部一样,响应报文首部同样是为了传递额外信息
-
空行
- 同样是为了区别响应实体和响应首部
-
响应实体
- 真正存储响应信息的部分
2.1.6 请求方式
- HTTP 常用的请求方式有很多中,最常用的是 GET 和 POST
- 二者最主要的区别就是
- GET 请求的参数位于 URL 中,会显示在地址栏上
- POST 请求的参数位于 request body 请求体中
因此,GET 请求的安全性不如 POST 请求,并且 GET 请求的参数有长度限制,而 POST 没有
2.2 调试利器 Postman
- HTTP 包含客户端和服务端,试想下面的两种情况(Postman 使用场景)
- 服务端开发完毕,而客户端还未完成开发,此时服务端开发人员能否对自己写的服务端程序进行测试呢?
- 客户端开发人员访问服务端出错,比如无法访问服务端,有没有第三方的测试工具做进一步验证呢?
- Postman 是一个接口测试工具,主要是用来模拟各种 HTTP 请求 (比如 GET 请求、POST 请求等),在做接口测试的时候,Postman 相当于客户端,它可模拟用户发起的各类 HTTP 请求,将请求数据发送至服务端,并获取对应的响应结果
2.2.1 安装
- Postman 下载
2.2.2 发送请求
- 这里以获取北京的天气为例
- 获取北京天气的 URL 为:http://t.weather.itboy.net/api/weather/city/101010100
- 其中,101010100 是北京的城市编码,是 9 位的
2.3 Qt 实现 HTTP 请求
2.3.1 创建 “网络访问管理” 对象
- 首先需要创建一个 QNetworkAccessManager 对象,这是 Qt 中进行 HTTP 请求的开端
mNetAccessManager = new QNetworkAccessManager(this);
2.3.2 关联信号槽
- 在发送 HTTP 请求之前,先关联信号槽
// 获取到数据之后 connect(mNetAccessManager, &QNetworkAccessManager::finished, this, &MainWindow::onReplied);
2.3.3 发送请求
- 根据请求的地址构建出一个 Qurl 对象,然后直接调用 QNetworkAccessManager 的 get 方法,即可发送一个 GET 请求
Qurl ur1("http://t.weather.itboy.net/api/weather/city/101010100"); mNetAccessManager->get(QNetworkRequest(url));
2.3.4 接收数据
- 由于上面绑定了信号槽,服务器返回数据后,自动调用自定义的槽函数 onReplied
- 如下是 onReplied 函数的标准写法,QNetworkReply 中封装了服务器返回的所有数据,包括响应头、响应的状态码、响应体等
void MainWindow::onReplied(QNetworkReply *reply) {// 响应的状态码为200,表示请求成功int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();qDebug() << "operation:" << reply->operation(); // 请求方式qDebug() << "status code:" << status_code; // 状态码qDebug() << "url:" << reply->url(); // urlqDebug() << "raw header:" << reply->rawHeaderList(); // headerif (reply->error() != QNetworkReply::NoError || status_code != 200) {QMessageBox::warning(this, "提示", "请求数据失败!", QMessageBox::OK);} else {// 获取响应信息QByteArray reply_data = reply->readAll();QByteArray byteArray = QString(reply_data).toUtf8();qDebug() << "read all:" << byteArray.data();// parseJson()}reply->deleteLater(); }
3. 详细代码实现
- WeatherForecast