Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Qt之高級網絡操作(HTTP/FTP快速上手)

Qt之高級網絡操作(HTTP/FTP快速上手)

編輯:關於Android編程

簡述

Qt Network 模塊中提供了一些高級別的類,例如:QNetworkRequest、QNetworkReply 和 QNetworkAccessManager,使用常見的協議執行網絡操作。

在分享的過程中,順便介紹下 Fiddler,便於我們調試。Fiddler 是一個 HTTP 協議調試代理工具。當然,也可以使用其它 Web/HTTP 調試工具。

HTTP 消息結構

先來看一下 HTTP 的消息結構。

這裡寫圖片描述vcq9oaLH68fzVVJMoaJIVFRQ0K3S6bywsOaxvsj9sr+31tfps8mhozwvcD4NCjxwPsfrx/PNt6O6xuTW0CBDb250ZW50LVR5cGUg1ri2qMHLv827p7bLt6LLzbXExNrI3bjxyr2ho8D9yOejukNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbqOs1ri/zbuntsu3osvNtcTE2sjduPHKvc6qIEpzb26hozwvY29kZT48L3A+DQo8cD7H68fzzOWjutKqt6LLzbXEse21pcr9vt2hozwvY29kZT48L3A+DQo8cD5SZXNwb25zZTwvY29kZT48L3A+DQo8cD7XtMys0NCjulJlc3BvbnNlIM/7z6LW0LXEtdrSu9DQo6zTySBIVFRQINCt0umw5rG+usWhote0zKzC66Gi17TMrM/7z6LI/bK/t9bX6bPJoaPXtMyswuvTw8C0uObL3yBIVFRQIL/Nu6e2y6OsSFRUUCC3/s7xxvfKx7fxsvrJ+sHL1KTG2rXEIFJlc3BvbnNloaNIVFRQLzEuMSDW0Lao0uXByyA1IMDg17TMrMLro6wg17TMrMLr08nI/c67yv3X1tfps8mjrLXa0ru49sr919a2qNLlwcvP7NOmtcTA4LHwo7o8L2NvZGU+PC9wPg0KMVhYo7rM4cq+0MXPoiAtILHtyr7H68fz0tGxu7PJuaa908rVo6y8zND4tKbA7aGjIDJYWKO6s8m5piAtILHtyr7H68fz0tGxu7PJuaa908rVoaLA7b3ioaK908rcoaMgM1hYo7rW2Laoz/IgLSDSqs3qs8nH68fzsdjQ67340NC4/L340ruyvbXEtKbA7aGjIDRYWKO6v827p7bLtO3O8yAtIMfrx/PT0NPvt6i07c7zu/LH68fzzt63qMq1z9ahoyA1WFijurf+zvHG97bLtO3O8yAtILf+zvHG9860xNzKtc/Wus+3qLXEx+vH86GjIDwvY29kZT4NCjxwPs/s06bNt6O6xuTW0CBDb250ZW50LVR5cGUg1ri2qMHLt/7O8cb3t7W72LXExNrI3bjxyr2ho8D9yOejukNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbqOs1ri3/s7xxve3tbvYtcTE2sjduPHKvc6qIEpzb26hozwvY29kZT48L2NvZGU+PC9wPg0KPHA+z+zTpszlo7q3/s7xxve3tbvYtcTE2sjdoaM8L2NvZGU+PC9jb2RlPjwvcD4NCjxwPjxzdHJvbmc+16LS4qO6PC9zdHJvbmc+1eLA76Os1eu21CBIVFRQINCt0unWu9f2wcvSu9CpvPK1pbXEvenJ3KOssePT2s/Cw+a9sr3io6y52NPa0K3S6dbQtcTG5Mv7sr+31qOs09DQy8iktcTNr9Csv8nX1NDQ0dC+v6GjPC9jb2RlPjwvY29kZT48L3A+DQo8aDEgaWQ9"支持的協議">支持的協議

在進行網絡請求之前,首先,要查看 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=content 和 q=Qt 到 http://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 狀態碼請參考:

Status codes List of HTTP status codes

最常見的是 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"

准備好了嗎?趕快去試試吧!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved