QT分析之网络编程(五)
今天分析QNetworkAccessManager、QNetworkRequest和QNetworkReply组成的高级抽象API序列。在动手之前,把doc中有关QNetworkAccessManager的介绍看了一遍。其使用方法大致是:
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QNetworkRequest request;
request.setUrl(QUrl("http://www.baidu.com"));
QNetworkReply * reply = manager->get(request);
connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()));
关键是后面的三行:设定URL、发送并获取响应、读取数据。
在QT自带的例子中也有QNetworkAccessManager的应用:downloadmanager
单步跟踪就用downloadmanager这个例子。
在动手跟踪之前,总结了几个问题:
1、QNetworkAccessManager是更高级的抽象,那么怎么跟QTcpSocket/QUdpSocket联系起来的呢?
2、如果没有跟QTcpSocket联系起来,那么又是怎么跟WSA序列WinAPI联系起来的呢?
3、整个逻辑过程是怎么的呢?
4、获取的(图片或者网页)数据保存在什么地方?
5、跟HTTP或者FTP有关的Cookie、认证等怎么实现的?
6、HTTP的Session相关功能实现了吗?怎么实现的?
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QNetworkRequest request;
request.setUrl(QUrl("http://www.baidu.com"));
QNetworkReply * reply = manager->get(request);
connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()));
关键是后面的三行:设定URL、发送并获取响应、读取数据。
在QT自带的例子中也有QNetworkAccessManager的应用:downloadmanager
单步跟踪就用downloadmanager这个例子。
在动手跟踪之前,总结了几个问题:
1、QNetworkAccessManager是更高级的抽象,那么怎么跟QTcpSocket/QUdpSocket联系起来的呢?
2、如果没有跟QTcpSocket联系起来,那么又是怎么跟WSA序列WinAPI联系起来的呢?
3、整个逻辑过程是怎么的呢?
4、获取的(图片或者网页)数据保存在什么地方?
5、跟HTTP或者FTP有关的Cookie、认证等怎么实现的?
6、HTTP的Session相关功能实现了吗?怎么实现的?
QT分析之网络编程(六)
QT分析之网络编程(八)
话说昨日走到QNetworkReplyImplPrivate::_q_startOperation(),勾引出QNetworkAccessHttpBackend::open(),今日接着欣赏QT之美丽。void QNetworkAccessHttpBackend::open()
{
QUrl url = request().url();
bool encrypt = url.scheme().toLower() == QLatin1String("https");
setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt);
// set the port number in the reply if it wasn't set
url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort));
QNetworkProxy *theProxy = 0;
#ifndef QT_NO_NETWORKPROXY
QNetworkProxy transparentProxy, cacheProxy;
foreach (const QNetworkProxy &p, proxyList()) {
// use the first proxy that works
// for non-encrypted connections, any transparent or HTTP proxy
// for encrypted, on
if (!encrypt
&& (p.capabilities() & QNetworkProxy::CachingCapability)
&& (p.type() == QNetworkProxy::HttpProxy ||
p.type() == QNetworkProxy::HttpCachingProxy)) {
cacheProxy = p;
transparentProxy = QNetworkProxy::NoProxy;
theProxy = &cacheProxy;
break;
}
if (p.isTransparentProxy()) {
transparentProxy = p;
cacheProxy = QNetworkProxy::NoProxy;
theProxy = &transparentProxy;
break;
}
}
// check if at least on
if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
cacheProxy.type() == QNetworkProxy::DefaultProxy) {
// unsuitable proxies
error(QNetworkReply::ProxyNotFoundError,
tr("No suitable proxy found"));
finished();
return;
}
#endif
// check if we have an open connection to this host
cacheKey = makeCacheKey(this, theProxy);
QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getCache(this);
if ((http = static_cast<QNetworkAccessHttpBackendCache *>(cache->requestEntryNow(cacheKey))) == 0) {
// no entry in cache; create an object
http = new QNetworkAccessHttpBackendCache(url.host(), url.port(), encryp t);
#ifndef QT_NO_NETWORKPROXY
http->setTransparentProxy(transparentProxy);
http->setCacheProxy(cacheProxy);
#endif
cache->addEntry(cacheKey, http);
}
setupConnection();
postRequest();
}
在这里跟QNetworkAccessHttpBackendCache类关联起来。先在全局表中查找,如果没有找到则新创建一个QNetworkAccessHttpBackendCache对象。接着 setupConnection() 里面就是把QNetworkAccessHttpBackend的信号和QNetworkAccessHttpBackendCache的槽连接起来; postRequest() 所先看是否能在Cache中找到,没找到需要的内容则发送请求。
QNetworkAccessHttpBackendCache类有两个基类。
class QNetworkAccessHttpBackendCache: public QHttpNetworkConnection,
public QNetworkAccessCache::CacheableObject
在QHttpNetworkConnection的构造中,有些我们感兴趣的东西:
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent)
{
Q_D(QHttpNetworkConnection);
d->init();
}
继续深入看QHttpNetorkConnectionPrivate::init()
void QHttpNetworkConnectionPrivate::init()
{
for (int i = 0; i < channelCount; ++i) {
#ifndef QT_NO_OPENSSL
channels[i].socket = new QSslSocket;
#else
channels[i].socket = new QTcpSocket;
#endif
connectSignals(channels[i].socket);
}
}
初始化的时候创建了QTcpSocket对象。
回到前面,继续看postRequst又做了哪些事情呢?
void QNetworkAccessHttpBackend::postRequest()
{
bool loadedFromCache = false;
QHttpNetworkRequest httpRequest;
switch (operation()) {
case QNetworkAccessManager::GetOperation:
httpRequest.setOperation(QHttpNetworkRequest::Get);
validateCache(httpRequest, loadedFromCache);
break;
case QNetworkAccessManager::HeadOperation:
httpRequest.setOperation(QHttpNetworkRequest::Head);
validateCache(httpRequest, loadedFromCache);
break;
case QNetworkAccessManager::PostOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Post);
uploadDevice = new QNetworkAccessHttpBackendIODevice(this);
break;
case QNetworkAccessManager::PutOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Put);
uploadDevice = new QNetworkAccessHttpBackendIODevice(this);
break;
default:
break; // can't happen
}
httpRequest.setData(uploadDevice);
httpRequest.setUrl(url());
QList<QByteArray> headers = request().rawHeaderList();
foreach (const QByteArray &header, headers)
httpRequest.setHeaderField(header, request().rawHeader(header));
if (loadedFromCache) {
QNetworkAccessBackend::finished();
return; // no need to send the request! :)
}
httpReply = http->sendRequest(httpRequest);
httpReply->setParent(this);
#ifndef QT_NO_OPENSSL
if (pendingSslConfiguration)
httpReply->setSslConfiguration(*pendingSslConfiguration);
if (pendingIgnoreSslErrors)
httpReply->ignoreSslErrors();
#endif
connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
connect(httpReply, SIGNAL(finished()), SLOT(replyFinished()));
connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(httpError(QNetworkReply::NetworkError,QString)));
connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged()));
}
完了下面这些动作:
1、看Cache中是否保存有过去浏览的内容,如果有还要看是否超出生存时间(Expiration Time);
2、设定Url、Header和数据内容(需要提交的数据);
3、调用QNetworkAccessHttpBackendCache::sendRequest()发送请求内容;
4、把QHttpNetworkReply的信号与QNetworkAccessHttpBackend的槽连接起来,完成后续处理。
重点看QNetworkAccessHttpBackendCache::sendRequest()的实现,QNetworkAccessHttpBackendCache类本身没有sendRequest()成员函数,其定义在QHttpNetworkConnection::sendRequest()。
QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request)
{
Q_D(QHttpNetworkConnection);
return d->queueRequest(request);
}
只是简单的调用QHttpNetworkConnectionPrivate::queueRequest()
QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetworkRequest &request)
{
Q_Q(QHttpNetworkConnection);
// The reply component of the pair is created initially.
QHttpNetworkReply *reply = new QHttpNetworkReply(request.url());
reply->setRequest(request);
reply->d_func()->connection = q;
HttpMessagePair pair = qMakePair(request, reply);
switch (request.priority()) {
case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair);
break;
case QHttpNetworkRequest::NormalPriority:
case QHttpNetworkRequest::LowPriority:
lowPriorityQueue.prepend(pair);
break;
}
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
return reply;
}
发现QHttpNetworkConnection、QHttpNetworkRequest、QHttpNetworkReply、QHttpNetworkEngine跟之前的QNetworkConnection、QNetworkRequest、QNetworkReply很接近。
在这里整个消息处理(或者是初始化动作)完成之后,按消息序列调用QHttpNetworkConnectionPrivate::_q_startNextRequest()
其实现代码:
void QHttpNetworkConnectionPrivate::_q_startNextRequest()
{
// send the current request again
if (channels[0].resendCurrent || channels[1].resendCurrent) {
int i = channels[0].resendCurrent ? 0:1;
QAbstractSocket *socket = channels[i].socket;
channels[i].resendCurrent = false;
channels[i].state = IdleState;
if (channels[i].reply)
sendRequest(socket);
return;
}
// send the request using the idle socket
QAbstractSocket *socket = channels[0].socket;
if (isSocketBusy(socket)) {
socket = (isSocketBusy(channels[1].socket) ? 0 :channels[1].socket);
}
if (!socket) {
return; // this will be called after finishing current request.
}
unqueueRequest(socket);
}
void QHttpNetworkConnectionPrivate::unqueueRequest(QAbstractSocket *socket)
{
Q_ASSERT(socket);
int i = indexOf(socket);
if (!highPriorityQueue.isEmpty()) {
for (int j = highPriorityQueue.count() - 1; j >= 0; --j) {
HttpMessagePair &messagePair = highPriorityQueue[j];
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
if (!messagePair.second->d_func()->requestIsBuffering) {
channels[i].request = messagePair.first;
channels[i].reply = messagePair.second;
sendRequest(socket);
highPriorityQueue.removeAt(j);
return;
}
}
}
if (!lowPriorityQueue.isEmpty()) {
for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) {
HttpMessagePair &messagePair = lowPriorityQueue[j];
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
if (!messagePair.second->d_func()->requestIsBuffering) {
channels[i].request = messagePair.first;
channels[i].reply = messagePair.second;
sendRequest(socket);
lowPriorityQueue.removeAt(j);
return;
}
}
}
}
按优先级次序发送请求。prepareRequest()设定HTTP请求的Header信息;关键是sendRequest()
bool QHttpNetworkConnectionPrivate::sendRequest(QAbstractSocket *socket)
{
Q_Q(QHttpNetworkConnection);
int i = indexOf(socket);
switch (channels[i].state) {
case IdleState: { // write the header
if (!ensureConnection(socket)) {
// wait for the connection (and encryption) to be done
// sendRequest will be called again from either
// _q_connected or _q_encrypted
return false;
}
channels[i].written = 0; // excluding the header
channels[i].bytesTotal = 0;
if (channels[i].reply) {
channels[i].reply->d_func()->clear();
channels[i].reply->d_func()->connection = q;
channels[i].reply->d_func()->autoDecompress = channels[i].request.d->autoDecompress;
}
channels[i].state = WritingState;
channels[i].pendingEncrypt = false;
// if the url contains authentication parameters, use the new on
// both channels will use the new authentication parameters
if (!channels[i].request.url().userInfo().isEmpty()) {
QUrl url = channels[i].request.url();
QAuthenticator &auth = channels[i].authenticator;
if (url.userName() != auth.user()
|| (!url.password().isEmpty() && url.password() != auth.password())) {
auth.setUser(url.userName());
auth.setPassword(url.password());
copyCredentials(i, &auth, false);
}
// clear the userinfo, since we use the same request for resending
// userinfo in url can conflict with the on
url.setUserInfo(QString());
channels[i].request.setUrl(url);
}
createAuthorization(socket, channels[i].request);
#ifndef QT_NO_NETWORKPROXY
QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request,
(networkProxy.type() != QNetworkProxy::NoProxy));
#else
QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request,
false);
#endif
socket->write(header);
QIODevice *da
QHttpNetworkReply *reply = channels[i].reply;
if (reply && reply->d_func()->requestDataBuffer.size())
da
if (da
if (da
channels[i].bytesTotal = -1;
QObject::connect(da
QObject::connect(da
} else {
channels[i].bytesTotal = da
}
} else {
channels[i].state = WaitingState;
break;
}
// write the initial chunk together with the headers
// fall through
}
case WritingState: { // write the da
QIODevice *da
if (channels[i].reply->d_func()->requestDataBuffer.size())
da
if (!da
channels[i].state = WaitingState; // now wait for response
break;
}
QByteArray chunk;
chunk.resize(ChunkSize);
qint64 readSize = da
if (readSize == -1) {
// source has reached EOF
channels[i].state = WaitingState; // now wait for response
} else if (readSize > 0) {
// source gave us something useful
channels[i].written += socket->write(chunk.da
if (channels[i].reply)
emit channels[i].reply->dataSendProgress(channels[i].written, channels[i].bytesTotal);
}
break;
}
case WaitingState:
case ReadingState:
case Wait4AuthState:
// ignore _q_bytesWritten in these states
// fall through
default:
break;
}
return true;
}
跟QTcpSocket有关的调用总算出现了。分析到此结束。
http://blog.163.com/net_worm/blog/static/12770241920101513548567/