最近在做一個(gè)PC端的數(shù)據(jù)采集并帶有一定互動(dòng)功能的小程序唱凯,需要與后臺(tái)進(jìn)行通信,從后臺(tái)下載資源(視頻谎痢、圖片等)磕昼。在使用Qt的網(wǎng)絡(luò)庫(kù)實(shí)現(xiàn)http網(wǎng)絡(luò)通信的時(shí)候出現(xiàn)了一點(diǎn)問(wèn)題(問(wèn)題描述:使用QNetworkAccessManager的 finished(QNetworkReply *reply)信號(hào)來(lái)處理已經(jīng)發(fā)送的請(qǐng)求的結(jié)果時(shí),如果多個(gè)請(qǐng)求同時(shí)發(fā)送节猿,使用這種方式僅能收到其中一個(gè)請(qǐng)求的reply)票从,因此去查閱了Qt有關(guān)網(wǎng)絡(luò)部分的源碼,問(wèn)題的解決得益于分析源碼的過(guò)程滨嘱。
本次分析的是qtbase-opensource-src-5.8.0版本中網(wǎng)絡(luò)部分的源碼峰鄙,對(duì)比之前Qt4版本的源碼發(fā)生了比較大的改變,Qt5中http/https與其他的協(xié)議(ftp太雨、spdy等)的實(shí)現(xiàn)分離吟榴,http2.0也是單獨(dú)實(shí)現(xiàn),http完全從backend的抽象工廠模式中剝離出來(lái)囊扳,新的http采用外觀模式將http部分的實(shí)現(xiàn)隱藏吩翻,在源碼內(nèi)部?jī)H暴露出QHttpThreadDelegate,通過(guò)QHttpThreadDelegate與QNetworkReplyHttpImpl之間跨線程的信號(hào)槽來(lái)實(shí)現(xiàn)通信以及數(shù)據(jù)交互锥咸。
從Qt的幫助文檔中可以了解到http部分對(duì)用戶(hù)開(kāi)放的接口情況狭瞎,QNetworkAccessManager、QNetworkRequest搏予、QNetworkReply三個(gè)類(lèi)包裝在Qt上完成http通信所需要使用的接口熊锭。
QNetworkAccessManager -- 負(fù)責(zé)請(qǐng)求的創(chuàng)建以及狀態(tài)管理
QNetworkRequest -- 負(fù)責(zé)包裝http請(qǐng)求,包括設(shè)置協(xié)議頭雪侥、解析協(xié)議頭
QNetworkReply -- 負(fù)責(zé)接收對(duì)應(yīng)請(qǐng)求的返回信息
關(guān)于API的使用就不再贅述碗殷,Qt的文檔中有非常詳細(xì)的說(shuō)明與例子可以參考。下面對(duì)Qt內(nèi)部的實(shí)現(xiàn)流程進(jìn)行分析速缨。
1.QNetworkAccessManager --
createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
返回一個(gè)QNetworkReply 對(duì)象來(lái)處理網(wǎng)絡(luò)請(qǐng)求锌妻,參數(shù)outgoingData是put/post需要上傳的數(shù)據(jù),傳入QJson類(lèi)型需要轉(zhuǎn)為QByteArray鸟廓,在put/post內(nèi)部轉(zhuǎn)為QBuffer(輸出流對(duì)象)再傳給createRequest从祝,默認(rèn)情況下會(huì)調(diào)用QNetworkCookieJar::cookiesForUrl()來(lái)保存請(qǐng)求的信息。
1)http/https 的創(chuàng)建方式
// Since Qt 5 we use the new QNetworkReplyHttpImpl
if (scheme == QLatin1String("http") || scheme == QLatin1String("preconnect-http")
|| scheme == QLatin1String("https") || scheme == QLatin1String("preconnect-https")) {
QNetworkReplyHttpImpl *reply = new QNetworkReplyHttpImpl(this, request, op, outgoingData);
connect(this, SIGNAL(networkSessionConnected()),
reply, SLOT(_q_networkSessionConnected()));
return reply;
}
2)其他協(xié)議的創(chuàng)建方式
// first step: create the reply
QNetworkReplyImpl *reply = new QNetworkReplyImpl(this);
if (!isLocalFile) {
connect(this, SIGNAL(networkSessionConnected()),
reply, SLOT(_q_networkSessionConnected()));
}
QNetworkReplyImplPrivate *priv = reply->d_func();
priv->manager = this;
// second step: fetch cached credentials
// This is not done for the time being, we should use signal emissions to request
// the credentials from cache.
// third step: find a backend
priv->backend = d->findBackend(op, request);
if (priv->backend) {
priv->backend->setParent(reply);
priv->backend->reply = priv;
}
reply->setSslConfiguration(request.sslConfiguration());
// fourth step: setup the reply
priv->setup(op, request, outgoingData);
i)在當(dāng)前版本的源碼中已經(jīng)找不到QNetworkAccessHttpBackend的工廠類(lèi)實(shí)現(xiàn)了引谜,在同步的http請(qǐng)求中依然能夠看到相關(guān)的代碼牍陌,關(guān)于Backend工廠的創(chuàng)建有一段比較有意思的包含了原子操作的代碼(個(gè)人猜測(cè)用于實(shí)現(xiàn)享元模式,還有待仔細(xì)分析)员咽。我們使用的http請(qǐng)求默認(rèn)為異步請(qǐng)求毒涧,而且Qt沒(méi)有開(kāi)放synchronous的設(shè)置,即我們提交的所有http/https請(qǐng)求都是異步處理的(源碼中關(guān)于同步請(qǐng)求部分的處理可以暫時(shí)忽略)贝室。
ii)可以看出QNetworkReplyHttpImpl 是作為通信中轉(zhuǎn)以及數(shù)據(jù)交互非常重要的類(lèi)契讲。
2.QNetworkReplyHttpImpl --
postRequest(const QNetworkRequest &newHttpRequest)
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
// deleteLater 可以安全的刪除delegate
QObject::connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater()));
//通信狀態(tài)以及數(shù)據(jù)交互
...
// 用于開(kāi)始請(qǐng)求.
QObject::connect(q, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
QObject::connect(q, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest()));
// 控制連接的緩沖區(qū)
QObject::connect(q, SIGNAL(readBufferSizeChanged(qint64)), delegate, SLOT(readBufferSizeChanged(qint64)));
QObject::connect(q, SIGNAL(readBufferFreed(qint64)), delegate, SLOT(readBufferFreed(qint64)));
// 移動(dòng) delegate 到 http thread
delegate->moveToThread(thread);
//轉(zhuǎn)入子線程處理請(qǐng)求
emit q->startHttpRequest();
3.QHttpThreadDelegate --
startRequest()
//檢查是否有本地?cái)?shù)據(jù)
if (!connections.hasLocalData()) {
connections.setLocalData(new QNetworkAccessCache());
}
QUrl urlCopy = httpRequest.url();
urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
// 一個(gè) http 對(duì)象實(shí)質(zhì)上就是 QHttpNetworkConnection
httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
if (httpConnection == 0) {
// no entry in cache; create an object
#ifdef QT_NO_BEARERMANAGEMENT
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
connectionType);
#else
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
connectionType,
networkSession);
//向httpConnection 傳遞請(qǐng)求
httpReply = httpConnection->sendRequest(httpRequest);
httpReply->setParent(this);
//關(guān)聯(lián)reply中我們需要處理的信號(hào)并跳轉(zhuǎn)
...
QHttpThreadDelegate 中聚合了 QHttpNetworkRequest仿吞、QHttpNetworkReply、QNetworkAccessCachedHttpConnection捡偏、QNetworkSession唤冈、QNetworkAccessAuthenticationManager等。