参考引用
- 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 true founded 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(); // url qDebug() << "raw header:" << reply->rawHeaderList(); // header if (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