Qt 使用 QNetworkAccessManager 访问网络,这里对其进行了简单的封装,访问网络的代码可以简化为:

1
2
3
HttpClient("http://localhost:8080/device").get([](const QString &response) {
qDebug() << response;
});

更多的使用方法请参考 main() 里的例子。HttpClient 的实现为 HttpClient.h 和 HttpClient.cpp 部分。

main.cpp

main() 函数里展示了 HttpClient 的使用示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "HttpClient.h"
 
#include <QDebug>
#include <QFile>
#include <QApplication>
#include <QNetworkAccessManager>
 
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
 
{
// 在代码块里执行网络访问,是为了测试 HttpClient 对象在被析构后,网络访问的回调函数仍然能正常执行
// [[1]] GET 请求无参数
HttpClient("http://localhost:8080/device").get([](const QString &response) {
qDebug() << response;
});
 
// [[2]] GET 请求有参数,有自定义 header
HttpClient("http://localhost:8080/signIn")
.addParam("id", "1")
.addParam("name", "诸葛亮")
.addHeader("token", "123AS#D")
.get([](const QString &response) {
qDebug() << response;
});
 
// [[3]] POST 请求有参数,有自定义 header
HttpClient("http://localhost:8080/signIn")
.addParam("id", "2")
.addParam("name", "卧龙")
.addHeader("token", "DER#2J7")
.addHeader("content-type", "application/x-www-form-urlencoded")
.post([](const QString &response) {
qDebug() << response;
});
 
// [[4]] 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,调用 useManager()
// 使用共享的 QNetworkAccessManager,它不会被 HttpClient 删除。
// 如果下面的代码不传入 QNetworkAccessManager,从任务管理器里可以看到创建了几千个线程。
QNetworkAccessManager *manager = new QNetworkAccessManager();
for (int i = 0; i < 5000; ++i) {
HttpClient("http://localhost:8080/device").useManager(manager).get([=](const QString &response) {
qDebug() << response << ", " << i;
});
}
 
// [[5]] 下载
QFile *file = new QFile("dog.png");
if (file->open(QIODevice::WriteOnly)) {
HttpClient("http://xtuer.github.io/img/dog.png").setDebug(true).download([=](const QByteArray &data) {
file->write(data);
}, [=] {
file->flush();
file->close();
file->deleteLater();
 
qDebug() << "Download file finished";
});
}
 
// [[6]] 上传
HttpClient("http://localhost:8080/upload").upload("/Users/Biao/Pictures/ade.jpg");
}
 
return a.exec();
}

HttpClient.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H
 
#include <functional>
 
class QString;
class QByteArray;
struct HttpClientPrivate;
class QNetworkReply;
class QNetworkAccessManager;
 
class HttpClient {
public:
HttpClient(const QString &url);
~HttpClient();
 
/**
* @brief 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,
* 可以使用传人的 QNetworkAccessManager,它不会被 HttpClient 删除。
* 如果没有使用 useManager() 传入一个 QNetworkAccessManager,则 HttpClient 会自动的创建一个,并且在网络访问完成后删除它。
* @param manager QNetworkAccessManager 对象
* @return 返回 HttpClient 的引用,可以用于链式调用
*/
HttpClient& useManager(QNetworkAccessManager *manager);
 
/**
* @brief 参数 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
* @param debug 是否启用调试模式
* @return 返回 HttpClient 的引用,可以用于链式调用
*/
HttpClient& debug(bool debug);
 
/**
* @brief 增加参数
* @param name 参数的名字
* @param value 参数的值
* @return 返回 HttpClient 的引用,可以用于链式调用
*/
HttpClient& addParam(const QString &name, const QString &value);
 
/**
* @brief 增加访问头
* @param header 访问头的名字
* @param value 访问头的值
* @return 返回 HttpClient 的引用,可以用于链式调用
*/
HttpClient& addHeader(const QString &header, const QString &value);
 
/**
* @brief 添加 POST 表单使用的头信息,等价于 addHeader("content-type", "application/x-www-form-urlencoded")
* @return 返回 HttpClient 的引用,可以用于链式调用
*/
HttpClient& addFormHeader();
 
/**
* @brief 执行 GET 请求
* @param successHandler 请求成功的回调 lambda 函数
* @param errorHandler 请求失败的回调 lambda 函数
* @param encoding 请求响应的编码
*/
void get(std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler = NULL,
const char *encoding = "UTF-8");
 
/**
* @brief 执行 POST 请求
* @param successHandler 请求成功的回调 lambda 函数
* @param errorHandler 请求失败的回调 lambda 函数
* @param encoding 请求响应的编码
*/
void post(std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler = NULL,
const char *encoding = "UTF-8");
 
/**
* @brief 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
* @param readyRead 有数据可读取时的回调 lambda 函数
* @param finishHandler 请求处理完成后的回调 lambda 函数
* @param errorHandler 请求失败的回调 lambda 函数
*/
void download(std::function<void (const QByteArray &)> readyRead,
std::function<void ()> finishHandler = NULL,
std::function<void (const QString &)> errorHandler = NULL);
 
/**
* @brief 上传文件
* @param path 要上传的文件的路径
* @param successHandler 请求成功的回调 lambda 函数
* @param errorHandler 请求失败的回调 lambda 函数
* @param encoding 请求响应的编码
*/
void upload(const QString &path, std::function<void (const QString &)> successHandler = NULL,
std::function<void (const QString &)> errorHandler = NULL,
const char *encoding = "UTF-8");
 
private:
/**
* @brief 执行请求的辅助函数
* @param posted 为 true 表示 POST 请求,为 false 表示 GET 请求
* @param successHandler 请求成功的回调 lambda 函数
* @param errorHandler 请求失败的回调 lambda 函数
* @param encoding 请求响应的编码
*/
void execute(bool posted,
std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler,
const char *encoding);
 
/**
* @brief 读取服务器响应的数据
* @param reply 请求的 QNetworkReply 对象
* @param encoding 请求响应的编码,默认使用 UTF-8
* @return 服务器端响应的字符串
*/
QString readResponse(QNetworkReply *reply, const char *encoding = "UTF-8");
 
HttpClientPrivate *d;
};
 
#endif // HTTPCLIENT_H

HttpClient.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#include "HttpClient.h"
 
#include <QDebug>
#include <QFile>
#include <QHash>
#include <QUrlQuery>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QHttpPart>
#include <QHttpMultiPart>
 
struct HttpClientPrivate {
HttpClientPrivate(const QString &url) : url(url), networkAccessManager(NULL), useInternalNetworkAccessManager(true), debug(false) {}
 
QString url; // 请求的 URL
QUrlQuery params; // 请求的参数
QHash<QString, QString> headers; // 请求的头
QNetworkAccessManager *networkAccessManager;
bool useInternalNetworkAccessManager; // 是否使用内部的 QNetworkAccessManager
bool debug;
};
 
HttpClient::HttpClient(const QString &url) : d(new HttpClientPrivate(url)) {
// qDebug() << "HttpClient";
}
 
HttpClient::~HttpClient() {
// qDebug() << "~HttpClient";
delete d;
}
 
HttpClient &HttpClient::useManager(QNetworkAccessManager *manager) {
d->networkAccessManager = manager;
d->useInternalNetworkAccessManager = false;
return *this;
}
 
// 传入 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
HttpClient &HttpClient::debug(bool debug) {
d->debug = debug;
return *this;
}
 
// 增加参数
HttpClient &HttpClient::addParam(const QString &name, const QString &value) {
d->params.addQueryItem(name, value);
return *this;
}
 
// 增加访问头
HttpClient &HttpClient::addHeader(const QString &header, const QString &value) {
d->headers[header] = value;
return *this;
}
 
HttpClient &HttpClient::addFormHeader() {
return addHeader("content-type", "application/x-www-form-urlencoded");
}
 
// 执行 GET 请求
void HttpClient::get(std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler,
const char *encoding) {
execute(false, successHandler, errorHandler, encoding);
}
 
// 执行 POST 请求
void HttpClient::post(std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler,
const char *encoding) {
execute(true, successHandler, errorHandler, encoding);
}
 
// 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
void HttpClient::download(std::function<void (const QByteArray &)> readyRead,
std::function<void ()> finishHandler,
std::function<void (const QString &)> errorHandler) {
// 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
if (!d->params.isEmpty()) {
d->url += "?" + d->params.toString(QUrl::FullyEncoded);
}
 
if (d->debug) {
qDebug() << QString("URL: %1?%2").arg(d->url).arg(d->params.toString());
}
 
QUrl urlx(d->url);
QNetworkRequest request(urlx);
bool internal = d->useInternalNetworkAccessManager;
QNetworkAccessManager *manager = internal ? new QNetworkAccessManager() : d->networkAccessManager;
QNetworkReply *reply = manager->get(request);
 
// 有数据可读取时回调 readyRead()
QObject::connect(reply, &QNetworkReply::readyRead, [=] {
readyRead(reply->readAll());
});
 
// 请求结束
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NoError && NULL != finishHandler) {
finishHandler();
}
 
// 释放资源
reply->deleteLater();
if (internal) {
manager->deleteLater();
}
});
 
// 请求错误处理
QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
if (NULL != errorHandler) {
errorHandler(reply->errorString());
}
});
}
 
void HttpClient::upload(const QString &path,
std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler,
const char *encoding) {
if (d->debug) {
qDebug() << QString("URL: %1").arg(d->url);
}
 
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
 
QFile *file = new QFile(path);
file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
 
// 如果文件打开失败,则释放资源返回
if(!file->open(QIODevice::ReadOnly)) {
if (NULL != errorHandler) {
errorHandler(QString("文件打开失败: %1").arg(file->errorString()));
multiPart->deleteLater();
return;
}
}
 
// 表明是文件上传
QString disposition = QString("form-data; name=\"file\"; filename=\"%1\"").arg(file->fileName());
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
imagePart.setBodyDevice(file);
multiPart->append(imagePart);
 
bool internal = d->useInternalNetworkAccessManager;
QNetworkRequest request(QUrl(d->url));
QNetworkAccessManager *manager = internal ? new QNetworkAccessManager() : d->networkAccessManager;
QNetworkReply *reply = manager->post(request, multiPart);
multiPart->setParent(reply);
 
// 请求结束时一次性读取所有响应数据
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NoError && NULL != successHandler) {
successHandler(readResponse(reply, encoding)); // 成功执行
}
 
// 释放资源
reply->deleteLater();
if (internal) {
manager->deleteLater();
}
});
 
// 请求错误处理
QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
if (NULL != errorHandler) {
errorHandler(reply->errorString());
}
});
}
 
// 执行请求的辅助函数
void HttpClient::execute(bool posted,
std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler,
const char *encoding) {
// 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
if (!posted && !d->params.isEmpty()) {
d->url += "?" + d->params.toString(QUrl::FullyEncoded);
}
 
if (d->debug) {
qDebug() << QString("URL: %1?%2").arg(d->url).arg(d->params.toString());
}
 
QUrl urlx(d->url);
QNetworkRequest request(urlx);
 
// 把请求的头添加到 request 中
QHashIterator<QString, QString> iter(d->headers);
while (iter.hasNext()) {
iter.next();
request.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8());
}
 
// 注意: 不能在 Lambda 表达式里使用 HttpClient 对象的成员数据,因其可能在网络访问未结束时就已经被析构掉了,
// 所以如果要使用它的相关数据,定义一个局部变量来保存其数据,然后在 Lambda 表达式里访问这个局部变量
 
// 如果不使用外部的 manager 则创建一个新的,在访问完成后会自动删除掉
bool internal = d->useInternalNetworkAccessManager;
QNetworkAccessManager *manager = internal ? new QNetworkAccessManager() : d->networkAccessManager;
QNetworkReply *reply = posted ? manager->post(request, d->params.toString(QUrl::FullyEncoded).toUtf8()) : manager->get(request);
 
// 请求结束时一次性读取所有响应数据
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NoError && NULL != successHandler) {
successHandler(readResponse(reply, encoding)); // 成功执行
}
 
// 释放资源
reply->deleteLater();
if (internal) {
manager->deleteLater();
}
});
 
// 请求错误处理
QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
if (NULL != errorHandler) {
errorHandler(reply->errorString());
}
});
}
 
QString HttpClient::readResponse(QNetworkReply *reply, const char *encoding) {
QTextStream in(reply);
QString result;
in.setCodec(encoding);
 
while (!in.atEnd()) {
result += in.readLine();
}
 
return result;
}

服务器端处理请求的代码

这里的服务器端处理请求的代码使用了 SpringMVC 实现,作为参考,可以使用其他语言实现,例如 PHP,C#。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@GetMapping("/device")
@ResponseBody
public String detectDevice(Device device) {
if (device.isMobile()) {
return "Mobile";
} else if (device.isTablet()) {
return "Tablet";
} else {
return "Desktop";
}
}
 
// URL: /signIn?id=1&name=xxx
@GetMapping("/signIn")
@ResponseBody
public String singInGet(@RequestParam String id,
@RequestParam String name,
@RequestHeader(value="token", required=false) String token) throws Exception {
name = new String(name.getBytes("iso8859-1"), "UTF-8");
return String.format("GET: id: %s, name: %s, token: %s", id, name, token);
}
 
// URL: /signIn
@PostMapping("/signIn")
@ResponseBody
public String singInPost(@RequestParam String id,
@RequestParam String name,
@RequestHeader(value="token", required=false) String token) throws Exception {
return String.format("POST: id: %s, name: %s, token: %s", id, name, token);
}
 
// 上传
@PostMapping("/upload")
@ResponseBody
public Result uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
file.transferTo(new File("/Users/Biao/Desktop/" + file.getOriginalFilename()));
 
return Result.ok("OK", file.getOriginalFilename());
}
 
 
http://www.qtdebug.com/qt-httpclient/

Qt 访问网络的 HttpClient(封装QNetworkAccessManager,且有服务端)的更多相关文章

  1. Qt编写网络调试助手(TCP客户端+TCP服务端+UDP服务端)终极版开源

    时隔半年,对网络调试助手工具进行所有代码重写,这次目录结果整齐的一逼,代码整齐的一逼,非常完善了,打死也不再改版了.这次真的打死也不再改版了.旧版本1:http://www.qtcn.org/bbs/ ...

  2. iOS:根据日志去定位网络请求发生的错误是由于服务端造成的,还是客户端造成的?

    一.介绍 在项目开发中,服务端和客户端的协作尤为重要,而连接它们的最重要的环节之一就是网络请求,对于服务端而言,如果这个环节出现了错误,那么安全性就无从谈起,同时对于客户端而言,如果这个模块出现了错误 ...

  3. java 网络编程基础 TCP/IP协议:服务端ServerSocket;客户端Socket; 采用多线程方式处理网络请求

    1.Java中客户端和服务器端通信的简单实例 Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一 ...

  4. java httpclient发送json 请求 ,go服务端接收

    /***java客户端发送http请求*/package com.xx.httptest; /** * Created by yq on 16/6/27. */ import java.io.IOEx ...

  5. 《UNIX网络编程》之多客户连接服务端,可重用套接字对

    该网络编程之客户端与服务端程序模板支持: 1. 多客户端同时连接服务端,即服务程序可以同时为多个客户端服务: 2. 服务端支持套接字对重用,即即使处于TIME_WAIT状态,仍可支持服务端重启: 3. ...

  6. [Swift通天遁地]四、网络和线程-(14)创建一个Socket服务端

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  7. linux网络编程学习笔记之三 -----多进程并发服务端

    首先是fork()函数.移步APUE 8.3.  比較清晰的解释能够參考http://blog.csdn.net/lingdxuyan/article/details/4993883和http://w ...

  8. 网络编程_TCP协议_客户端与服务端

    客户端发数据到服务端 Tcp传输,客户端建立的过程. 1,创建tcp客户端socket服务.使用的是Socket对象.建议该对象一创建就明确目的地.要连接的主机. 2,如果连接建立成功,说明数据传输通 ...

  9. android post 方式 访问网络 实例

    android post 方式 访问网络 实例 因为Android4.0之后对使用网络有特殊要求,已经无法再在主线程中访问网络了,必须使用多线程访问的模式 该实例需要在android配置文件中添加 网 ...

随机推荐

  1. 常规容器下SpringBootServletInitializer如何实现web.xml作用解析

    在之前的<使用jsp作为视图模板&常规部署>章节有过一个实践,需要启动类继承自SpringBootServletInitializer方可正常部署至常规tomcat下,其主要能够起 ...

  2. Android Studio官方文档: 如何在你的设备上运行你的程序

    在实体设备上运行您的应用 设置您的设备,如下所示: 使用一根 USB 电缆将您的设备连接到您的开发机器. 如果您是在 Windows 上开发,可能需要为您的设备安装相应的 USB 驱动程序.如需帮助安 ...

  3. 保护SSD,设置Chrome浏览器临时文件夹到ramdisk分区

    很多用低端/山寨SSD的朋友都用Ramdisk来保护硬盘,一般都把系统temp目录和IE浏览器临时文件夹目录设到Ramdisk分区了.      最近用谷歌的chrome浏览器,发现浏览网页时候硬盘灯 ...

  4. Myeclipse 6.5 增加对 JavaEE 6 的支持

    网上找了一会没发现什么好的方法一想干脆自己动手丰衣足食,搜索MYECLIPSE_JAVAEE_5_CONTAINER找到了 MyEclipse6.5\myeclipse\eclipse\plugins ...

  5. PAT 1011-1020 题解

    早期部分代码用 Java 实现.由于 PAT 虽然支持各种语言,但只有 C/C++标程来限定时间,许多题目用 Java 读入数据就已经超时,后来转投 C/C++.浏览全部代码:请戳 本文谨代表个人思路 ...

  6. Vue插件资料

    UI组件element ★11612 - 饿了么出品的Vue2的web UI工具套件 Vux ★7503 - 基于Vue和WeUI的组件库 iview ★5801 - 基于 Vuejs 的开源 UI ...

  7. WPF依赖属性(续)(3)依赖属性存储

    原文:WPF依赖属性(续)(3)依赖属性存储          在之前的两篇,很多朋友参与了讨论,也说明各位对WPF/SL计数的热情,对DP系统各抒已见,当然也出现了一些分歧. 以下简称DP为依赖属性 ...

  8. Java--Vector类

    Java Vector 类 Vector类实现了一个动态数组.和ArrayList和相似,但是两者是不同的: Vector是同步访问的. Vector包含了许多传统的方法,这些方法不属于集合框架. V ...

  9. HDOJ 4901 The Romantic Hero

    DP....扫两次合并 The Romantic Hero Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 ...

  10. WinEdt && LaTex(四)—— 自定义新命令(newcommand、def)

    1. 新建命令 使用如下的命令:\newcommand{name}[num]{definition}: 该命令(newcommand)有两个参数,第一个 name 是你想要建立的命令的名称,第二个def ...