Qt之高级网络操作(HTTP/FTP快速上手)

简述

Qt Network 模块中提供了一些高级别的类,例如:QNetworkRequest、QNetworkReply 和 QNetworkAccessManager,使用常见的协议执行网络操作。

在分享的过程中,顺便介绍下 Fiddler,便于我们调试。Fiddler 是一个 HTTP 协议调试代理工具。当然,也可以使用其它 Web/HTTP 调试工具。

  • 简述
  • HTTP 消息结构
  • 支持的协议
  • 请求
  • 传递 URL 参数
  • 代理
  • 更加复杂的 POST 请求
  • 定制请求头
  • 响应内容
  • 响应状态码
  • 响应头
  • 错误

HTTP 消息结构

先来看一下 HTTP 的消息结构。

  • Request

    请求行:Request 消息中的第一行,由请求方式、请求URL、HTTP协议及版本三部分组成。

    请求头:其中 Content-Type 指定了客户端发送的内容格式。例如:Content-Type: application/json,指客户端发送的内容格式为 Json。

    请求体:要发送的表单数据。

  • Response

    状态行:Response 消息中的第一行,由 HTTP 协议版本号、状态码、状态消息三部分组成。状态码用来告诉 HTTP 客户端,HTTP 服务器是否产生了预期的 Response。HTTP/1.1 中定义了 5 类状态码, 状态码由三位数字组成,第一个数字定义了响应的类别:

    • 1XX:提示信息 - 表示请求已被成功接收,继续处理。
    • 2XX:成功 - 表示请求已被成功接收、理解、接受。
    • 3XX:重定向 - 要完成请求必须进行更进一步的处理。
    • 4XX:客户端错误 - 请求有语法错误或请求无法实现。
    • 5XX:服务器端错误 - 服务器未能实现合法的请求。

    响应头:其中 Content-Type 指定了服务器返回的内容格式。例如:Content-Type: application/json,指服务器返回的内容格式为 Json。

    响应体:服务器返回的内容。

注意:这里,针对 HTTP 协议只做了一些简单的介绍,便于下面讲解,关于协议中的其他部分,有兴趣的童鞋可自行研究。

支持的协议

在进行网络请求之前,首先,要查看 QNetworkAccessManager 支持的协议:

QNetworkAccessManager *manager = new QNetworkAccessManager(this);
qDebug() << manager->supportedSchemes();

通过调用 supportedSchemes(),列出了支持的所有 URL schemes:

(“ftp”, “file”, “qrc”, “http”, “https”, “data”)

下面,我们主要以 HTTP 为例。

请求

构建一个请求非常简单,本例中,我们尝试获取某个网页,以 CSDN 为例:

QString baseUrl = "http://www.csdn.net/";

QNetworkRequest request;
request.setUrl(QUrl(baseUrl));

现在,名为 request 的 QNetworkRequest 请求对象就构建成功了,为了获取所有想要的信息,我们需要将该请求发送出去。

QNetworkReply *pReplay = manager->get(request);

这时,会获取一个名为 pReplay 的 QNetworkReply 响应对象。等待响应完成,就可以从这个对象中获取所有想要的信息。

QByteArray bytes = pReplay->readAll();
qDebug() << bytes;

完整的源码:

// URL
QString baseUrl = "http://www.csdn.net/";

// 构造请求
QNetworkRequest request;
request.setUrl(QUrl(baseUrl));

QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// 发送请求
QNetworkReply *pReplay = manager->get(request);

// 开启一个局部的事件循环,等待响应结束,退出
QEventLoop eventLoop;
QObject::connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();

// 获取响应信息
QByteArray bytes = pReplay->readAll();
qDebug() << bytes;

因为请求的过程是异步的,所以此时使用 QEventLoop 开启一个局部的事件循环,等待响应结束,事件循环退出。

注意:开启事件循环的同时,程序界面将不会响应用户操作(界面被阻塞)。

简便的 API 意味着所有 HTTP 请求类型都是显而易见的。那么其他 HTTP 请求类型:POST,PUT 又是如何的呢?都是一样的简单:

QNetworkReply *pPostReplay = manager->post(request, QByteArray());
QNetworkReply *pPutReplay = manager->put(request, QByteArray());

漂亮,对吧?但这也仅是冰山一角。

传递 URL 参数

你也许经常想为 URL 的查询字符串(query string)传递某种数据。如果你是手工构建 URL,那么数据会以键/值对的形式置于 URL 中,跟在一个问号的后面。例如:http://www.zhihu.com/search?type=content&q=Qt。Qt 提供了 QUrlQuery 类,可以很便利地提供这些参数。

举例来说,如果你想传递 type=contentq=Qthttp://www.zhihu.com/search,可以使用如下代码:

// URL
QString baseUrl = "http://www.zhihu.com/search";
QUrl url(baseUrl);

// key-value 对
QUrlQuery query;
query.addQueryItem("type", "content");
query.addQueryItem("q", "Qt");

url.setQuery(query);

通过打印输出该 URL,你能看到 URL 已被正确编码:

qDebug() << url.url();
// "http://www.zhihu.com/search?type=content&q=Qt"

更多参考:Qt之QUrlQuery

代理

在发送请求的时候,如果要通过 Fiddler 分析,就必须设置代理,主要使用 QNetworkProxy :

QNetworkProxy proxy;
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName("127.0.0.1");
proxy.setPort(8888);
manager->setProxy(proxy);

更多参考:Qt之QNetworkProxy(网络代理)

更加复杂的 POST 请求

httpbin 是一个使用 Python + Flask 编写的 HTTP 请求和响应服务,主要用于测试 HTTP 库。

通常,你想要发送一些编码为表单形式的数据 - 非常像一个 HTML 表单。要实现这个,只需简单地传递一个QByteArray 给 data 参数。你的数据在发出请求时会自动编码为表单形式:

// URL
QString baseUrl = "http://httpbin.org/post";
QUrl url(baseUrl);

// 表单数据
QByteArray dataArray;
dataArray.append("key1=value1&");
dataArray.append("key2=value2");

// 构造请求
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setUrl(url);

QNetworkAccessManager *manager = new QNetworkAccessManager(this);

// 发送请求
manager->post(request, dataArray);

还可以使用 json 参数直接传递,然后它就会被自动编码:

// URL
QString baseUrl = "http://httpbin.org/post";
QUrl url(baseUrl);

// Json数据
QJsonObject json;
json.insert("User", "Qter");
json.insert("Password", "123456");

QJsonDocument document;
document.setObject(json);
QByteArray dataArray = document.toJson(QJsonDocument::Compact);

// 构造请求
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(url);

QNetworkAccessManager *manager = new QNetworkAccessManager(this);

// 连接信号槽
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *)));

// 发送请求
manager->post(request, dataArray);

为了不阻塞界面,我们不再使用 QEventLoop,而用 QNetworkAccessManager 对应的信号,当响应结束就会发射 finished() 信号,将其链接到对应的槽函数上即可。

定制请求头

如果你想为请求添加 HTTP 头部,只要简单地调用 setHeader() 就可以了。

例如,发送的请求时,使用的 User-Agent 是 Mozilla/5.0 , 为了方便以后追踪版本信息,可以将软件的版本信息写入到 User-Agent 中。

QNetworkRequest request;
request.setHeader(QNetworkRequest::UserAgentHeader, "my-app/0.0.1");

User-Agent:包含发出请求的用户信息。

当然,除了 User-Agent 之外,QNetworkRequest::KnownHeaders 还包含其他请求头,它就是为 HTTP 头部而生的。根据 RFC 2616, HTTP 头部是大小写不敏感。

如果 QNetworkRequest::KnownHeaders 不满足需要,使用 setRawHeader()。

响应内容

要获取响应的内容,可以调用 readAll(),由于上述的 POST 请求返回的数据为 Json 格式,将响应结果先转化为 Json,然后再对其解析:

void replyFinished(QNetworkReply *reply)
{
    // 获取响应信息
    QByteArray bytes = reply->readAll();

    QJsonParseError jsonError;
    QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
    if (jsonError.error != QJsonParseError::NoError) {
        qDebug() << QStringLiteral("解析Json失败");
        return;
    }

    // 解析Json
    if (doucment.isObject()) {
        QJsonObject obj = doucment.object();
        QJsonValue value;
        if (obj.contains("data")) {
            value = obj.take("data");
            if (value.isString()) {
                QString data = value.toString();
                qDebug() << data;
            }
        }
    }
}

响应的内容可以是 HTML 页面,也可以是字符串、Json、XML等。最上面所发送的 GET 请求 获取的就是 CSDN 的首页 HTML。

响应状态码

我们可以检测响应状态码:

QVariant variant = pReplay->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (variant.isValid())
    qDebug() << variant.toInt();
// 200    

HTTP 状态码请参考:

最常见的是 200 OK,表示请求已成功,请求所希望的响应头或数据体将随此响应返回。

响应头

进入 Response Headers:

可以看到包含很多,和上面一样,使用任意 QNetworkRequest::KnownHeaders 来访问这些响应头字段。例如,Content Type:

QVariant variant = pReplay->header(QNetworkRequest::ContentTypeHeader);
if (variant.isValid())
    qDebug() << variant.toString();
// "text/html; charset=utf-8"

如果 QNetworkRequest::KnownHeaders 不满足需要,可以使用 rawHeader()。例如,响应包含了登录后的 TOKEN,位于原始消息头中:

if (reply->hasRawHeader("TOKEN"))
    QByteArray byte = reply->rawHeader("TOKEN");

它还有一个特殊点,那就是服务器可以多次接受同一 header,每次都使用不同的值。Qt 会将它们合并,这样它们就可以用一个映射来表示出来,参见 RFC 7230

A recipient MAY combine multiple header fields with the same field name into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma.

接收者可以合并多个相同名称的 header 栏位,把它们合为一个 "field-name: field-value" 配对,将每个后续的栏位值依次追加到合并的栏位值中,用逗号隔开即可,这样做不会改变信息的语义。

错误

如果请求的处理过程中遇到错误(如:DNS 查询失败、拒绝连接等)时,则会产生一个 QNetworkReply::NetworkError。

例如,我们将 URL 改为这样:

QString baseUrl = "http://www.csdnQter.net/";

发送请求后,由于主机名无效,必然会发生错误,根据 error() 来判断:

QNetworkReply::NetworkError error = pReplay->error();
switch (error) {
case QNetworkReply::ConnectionRefusedError:
    qDebug() << QStringLiteral("远程服务器拒绝连接");
    break;
case QNetworkReply::HostNotFoundError:
    qDebug() << QStringLiteral("远程主机名未找到(无效主机名)");
    break;
case QNetworkReply::TooManyRedirectsError:
    qDebug() << QStringLiteral("请求超过了设定的最大重定向次数");
    break;
deafult:
    qDebug() << QStringLiteral("未知错误");
}
// "远程主机名未找到(无效主机名)"

这时,会产生一个 QNetworkReply::HostNotFoundError 错误。

注意: QNetworkReply::TooManyRedirectsError 是 5.6 中引进的,默认限制是 50,可以使用 QNetworkRequest::setMaxRedirectsAllowed() 来改变。

如果要获取到可读的错误信息,使用:

QString strError = pReplay->errorString();
qDebug() << strError;
// "Host www.csdnqter.net not found"

准备好了吗?赶快去试试吧!

时间: 2024-10-01 10:14:59

Qt之高级网络操作(HTTP/FTP快速上手)的相关文章

笔记本上不了网如何解决|电脑网络无法连接的快速修复方法

  笔记本上不了网如何解决|电脑网络无法连接的快速修复方法.当笔记本电脑出现无法上网的问题,除了检查是否是网线的连接问题,还需要查看电脑中的驱动以及各种设置是否出错.如果想免去以上复杂的操作步骤,那么可以参考接下来介绍的方法来快速修复电脑网络无法连接的问题. 1.打开电脑管家,点击右下角的工具箱,在里面找到电脑诊所并点击进入; 2.电脑诊所里可以轻松解决各种电脑问题故障,,找到"上网异常"分类,并点击进入; 3.里面包含各种常见的上网问题,根据自己的问题选择解决方案,比如点击"

中国电子商务协会职业经理人高级网络营销师培训合作

经过长达3个月的商量.谈判和课程磨合,我们上海映辉网络营销研究中心的网络营销实战课程,正式和中国http://www.aliyun.com/zixun/aggregation/30480.html">电子商务协会职业经理人高级网络营销师的课程融合,上海映辉网络营销研究中心全面代理中国电子商务协会职业经理人高级网络营销师在上海的招生.考核.发证事宜. 我们和中国电子商务协会职业经理认证管理办公室,中国电子商务协会网络营销专业委员会进行多次沟通,协会认为我们中心一直站在企业网络营销应用的前沿,

在网络中部署FTP服务器的四大感悟

部署与维护FTP服务器是网络管理员的基本技能.虽然如此, 但是笔者每次为企业部署完FTP服务器应用之后,总会有所收获.因为不同的企业需求不同,他们会 提出形式各样的需求.而笔者每次解决用户的一个需求之后,总会有一种成就感.这不前不久笔者刚文成一个FTP服务器的搭建.不过这个案例有点特殊,因为其FTP服务器采用的操作系统是Linux.为此感悟就更多了.感悟一:为用户分配组FTP服务器常用来放置一些工作文件.为此网络管理员在部署FTP服务器的时候,必须要注意其权限的管理.也就是说,要做到用户只能够下

Android网络操作常用的两个类

Android SDK集成了Apache HttpClient模块.要注意的是,这里的Apache HttpClient模块是 HttpClient 4.0(org.apache.http.*),而不是常见的 Jakarta Commons HttpClient 3.x (org.apache.commons.httpclient.*). HttpClient常用 HttpGet和HttpPost这两个类,分别对应Get方式和Post方式. 无论是使用HttpGet,还是使用HttpPost,都

PHP网络操作函数汇总

  这篇文章主要介绍了PHP网络操作函数汇总,本文列举了如gethostbyaddr.gethostbyname.header_remove.http_response_code.ip2long等网络中经常用到的函数,需要的朋友可以参考下 checkdnsrr - 给指定的主机(域名)或者IP地址做DNS通信检查 closelog - 关闭系统日志链接 define_syslog_variables - 初始化所有syslog相关变量 dns_check_record - checkdnsrr别

qt连接MySQL数据库操作问题

问题描述 qt连接MySQL数据库操作问题 在qt项目中,main函数中连接了数据库,且连接成功,在其他函数中如何操作数据库,使用的是MySQL数据库,新手求大神指点! 解决方案 你可以把connection保存在一个全局变量里,别的函数操作它就可以了.http://blog.csdn.net/cgzhello1/article/details/8619276 解决方案二: 把这定义成全局的, _ConnectionPtr m_pConn; 用的时候 Execute 解决方案三: 定义全局 _C

qt 连接硬件 驱动连接操作

问题描述 qt 连接硬件 驱动连接操作 跪求 QT 驱动连接实例,例如用QT写的窗口软件点击某一按钮可以启动摄像头!新手一枚,跪求大神围观! 解决方案 http://blog.csdn.net/liang19890820/article/details/12782531http://www.oschina.net/code/snippet_124925_3789

Swift HTTP网络操作库Alamofire实现文件上传详解

六,使用Alamofire进行文件上传 1,Alamofire支持如下上传类型: File Data Stream MultipartFormData 2,使用文件流的形式上传文件 let fileURL = NSBundle.mainBundle().URLForResource("hangge", withExtension: "zip")   Alamofire.upload(.POST, "http://www.hangge.com/upload.

7、Libgdx网络操作

(官网:www.libgdx.cn) Libgdx包含了一些跨平台的网络操作类,这些类在Gdx.net中. 特性 跨平台HTTP请求 多平台TCP C/S Socket支持(可配置) TCP C/S优化 跨平台浏览器访问 说明 Net.java 跨平台网络接口.通过这里获取网络交互的对象. Socket.java是一个提供远程socket地址,连接状态的接口. SocketHints.java是一个创建TCP服务端接口.提供了标准的accept()方法获取已连接的TCP客户端. ServerSo