QT分析之网络编程
原文地址:http://blog.163.com/net_worm/blog/static/127702419201002842553382/
首先对Windows下的网络编程总结一下:
如果是服务器,其WinSDK调用分别为:
WSAStartup() -> socket() -> htons() / htonl() -> bind() -> listen() -> accept() -> recv() / send() -> closesocket() -> WSACleanup()
如果是客户端程序,其调用序列为:
WSAStartup() -> socket() -> htons() / htonl() -> connect() -> recv() / send() -> closesocket() -> WSACleanup()
前面转贴的客户端(WinSocket温习)程序中,收到信息就在console打印出来然后退出了;在一般的应用中,通常是要一直等待收发消息的,直到程序确认退出才关闭socket。如果用一个轮询就会占用很多的CPU资源,所以很多嵌入式设计中会用一个WaitForMultiObject调用,等待退出命令或者超时,然后退出或进行下一轮信息接受。在Windows平台下也有一些比较高明的设计,使用异步socket,然后用异步选择的办法,实现多线程和事件的并发。在WinSocket中,同样也有一套异步Socket函数,那就是使用WSAAsyncSelect()及其配合函数。具体可以参考MSDN。QT在Windows平台上的实现,肯定也跟这些SDK调用有关。
按照这个思路,果然在QT代码里面找到了Qnativesocketengine_win.cpp,WSAStartup(),WSASocket()等序列WSA函数都有。QNativeSocketEnginePrivate类把这些SDK封装成:createNewSocket()、option()、setOption()、nativeConnect()、nativeBind()、nativeListen()、nativeAccept()、nativeWrite()、nativeRead()、nativeSelect()、nativeClose()等。按照QT的设计,QPrivate类是数据类;Q类应该是主类。接着看QNativeSocket类的继承:
QNativeSocketEngine : public QAbstractSocketEngine : public QObject
QAbstractSocketEngine类是使用了大量纯虚函数的定义。继续深入查看,发现大量有关的类:QAbstractSocket,SocketAsyncHandler,QTcpSocket,QUdpSocket等,看来我要先了解下QT网络编程体系再进一步分析之
之前没有看QT自带的文档,看了doc之后对QT的网络体系有一个大致的了解:
QNatvieSocketEnginePrivate是OS相关的API封装,和QNativeSocketEngine一起构成具体平台SOCKET实现;
QTcpSocket、QUdpSocket、QTcpServer构成底层的应用API;QSslSocket是SSL加密相关API;
QHttp、QFtp构成高层次应该API;
QNetworkAccessManager、QNetworkRequest、QNetworkReply是高度抽象的网络层。
分析TCP的例子fortuneclient,运行起来按了[Get
Fortune]按钮之后,调用的是Client::requestNewFortune()。
void Client::requestNewFortune()
{
getFortuneButton->setEnabled(false);
blockSize = ;
tcpSocket->abort();
tcpSocket->connectToHost(hostLineEdit->text(), portLineEdit->text().toInt());
}
具体看QTcpSocket::connectToHost()的代码
void QAbstractSocket::connectToHost(const QString &hostName, quint16 port,
OpenMode openMode)
{
QMetaObject::invokeMethod(this, "connectToHostImplementation",
Qt::DirectConnection,
Q_ARG(QString, hostName),
Q_ARG(quint16, port),
Q_ARG(OpenMode, openMode));
}
调用的是QAbstractSocket::connectToHostImplementation()。
void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint16 port,
OpenMode openMode)
{
Q_D(QAbstractSocket);
if (d->state == ConnectedState || d->state == ConnectingState || d->state == ClosingState) {
qWarning("QAbstractSocket::connectToHost() called when already connecting/connected to \"%s\"", qPrintable(hostName));
return;
} d->hostName = hostName;
d->port = port;
d->state = UnconnectedState;
d->readBuffer.clear();
d->writeBuffer.clear();
d->abortCalled = false;
d->closeCalled = false;
d->pendingClose = false;
d->localPort = ;
d->peerPort = ;
d->localAddress.clear();
d->peerAddress.clear();
d->peerName = hostName;
if (d->hostLookupId != -) {
QHostInfo::abortHostLookup(d->hostLookupId);
d->hostLookupId = -;
} #ifndef QT_NO_NETWORKPROXY
// Get the proxy information
d->resolveProxy(hostName, port);
if (d->proxyInUse.type() == QNetworkProxy::DefaultProxy) {
// failed to setup the proxy
d->socketError = QAbstractSocket::UnsupportedSocketOperationError;
setErrorString(QAbstractSocket::tr("Operation on socket is not supported"));
emit error(d->socketError);
return;
}
#endif if (!d_func()->isBuffered)
openMode |= QAbstractSocket::Unbuffered;
QIODevice::open(openMode); // ??
d->state = HostLookupState;
emit stateChanged(d->state); QHostAddress temp;
if (temp.setAddress(hostName)) {
QHostInfo info;
info.setAddresses(QList<QHostAddress>() << temp);
d->_q_startConnecting(info);
#ifndef QT_NO_NETWORKPROXY
} else if (d->proxyInUse.capabilities() & QNetworkProxy::HostNameLookupCapability) {
// the proxy supports connection by name, so use it
d->startConnectingByName(hostName);
return;
#endif
} else {
if (d->threadData->eventDispatcher)
d->hostLookupId = QHostInfo::lookupHost(hostName, this, SLOT(_q_startConnecting(QHostInfo)));
}
}
继续调用QAbstractSocket::_q_startConnecting(),是QAbstractSocket的私有信号。简单来说,_q_startConnecting()就是调用了_q_connectToNextAddress()而已。
void QAbstractSocketPrivate::_q_connectToNextAddress()
{
Q_Q(QAbstractSocket);
do {
// Check for more pending addresses
if (addresses.isEmpty()) {
state = QAbstractSocket::UnconnectedState;
if (socketEngine) {
if ((socketEngine->error() == QAbstractSocket::UnknownSocketError
) && socketEngine->state() == QAbstractSocket::ConnectingState) {
socketError = QAbstractSocket::ConnectionRefusedError;
q->setErrorString(QAbstractSocket::tr("Connection refused"));
} else {
socketError = socketEngine->error();
q->setErrorString(socketEngine->errorString());
}
} else {
// socketError = QAbstractSocket::ConnectionRefusedError;
// q->setErrorString(QAbstractSocket::tr("Connection refused"));
}
emit q->stateChanged(state);
emit q->error(socketError);
return;
} // Pick the first host address candidate
host = addresses.takeFirst(); #if defined(QT_NO_IPV6)
if (host.protocol() == QAbstractSocket::IPv6Protocol) {
// If we have no IPv6 support, then we will not be able to
// connect. So we just pretend we didn't see this address.
continue;
}
#endif if (!initSocketLayer(host.protocol())) {
// hope that the next address is better
continue;
} // Tries to connect to the address. If it succeeds immediately
// (localhost address on BSD or any UDP connect), emit
// connected() and return.
if (socketEngine->connectToHost(host, port)) {
//_q_testConnection();
fetchConnectionParameters();
return;
} // cache the socket descriptor even if we're not fully connected yet
cachedSocketDescriptor = socketEngine->socketDescriptor(); // Check that we're in delayed connection state. If not, try
// the next address
if (socketEngine->state() != QAbstractSocket::ConnectingState) {
continue;
} // Start the connect timer.
if (threadData->eventDispatcher) {
if (!connectTimer) {
connectTimer = new QTimer(q);
QObject::connect(connectTimer, SIGNAL(timeout()),
65 q, SLOT(_q_abortConnectionAttempt()),
66 Qt::DirectConnection);
}
connectTimer->start(QT_CONNECT_TIMEOUT);
} // Wait for a write notification that will eventually call
// _q_testConnection().
socketEngine->setWriteNotificationEnabled(true);
break;
} while (state != QAbstractSocket::ConnectedState);
}
上面关键的三句,实际是把WinSocket编程中的简单过程分成三个阶段:socket初始化;connect到远程目标;设定Timer定时查看并处理Select的情况(收发数据或者关闭socket)。这里主要看前面两个:初始化和连接,select的处理放到明天分析。
1、初始化
bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol)
{
#ifdef QT_NO_NETWORKPROXY
// this is here to avoid a duplication of the call to createSocketEngine below
static const QNetworkProxy &proxyInUse = *(QNetworkProxy *);
#endif Q_Q(QAbstractSocket); resetSocketLayer();
socketEngine = QAbstractSocketEngine::createSocketEngine(q->socketType(), proxyInUse, q);
if (!socketEngine) {
socketError = QAbstractSocket::UnsupportedSocketOperationError;
q->setErrorString(QAbstractSocket::tr("Operation on socket is not supported"));
return false;
}
if (!socketEngine->initialize(q->socketType(), protocol)) {
socketError = socketEngine->error();
q->setErrorString(socketEngine->errorString());
return false;
} if (threadData->eventDispatcher)
socketEngine->setReceiver(this); return true;
} QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &proxy, QObject *parent)
{
#ifndef QT_NO_NETWORKPROXY
// proxy type must have been resolved by now
if (proxy.type() == QNetworkProxy::DefaultProxy)
return ;
#endif QMutexLocker locker(&socketHandlers()->mutex);
for (int i = ; i < socketHandlers()->size(); i++) {
if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketType, proxy, parent))
return ret;
} return new QNativeSocketEngine(parent);
}
上面可以知道socketEngine->initialize()实际调用的是QNativeSocketEngine::initialize()
bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol)
{
Q_D(QNativeSocketEngine);
if (isValid())
close(); #if defined(QT_NO_IPV6)
if (protocol == QAbstractSocket::IPv6Protocol) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError,
QNativeSocketEnginePrivate::NoIpV6ErrorString);
return false;
}
#endif // Create the socket
if (!d->createNewSocket(socketType, protocol)) {
return false;
} // Make the socket nonblocking.
if (!setOption(NonBlockingSocketOption, 1)) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError,
QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString);
close();
return false;
} // Set the broadcasting flag if it's a UDP socket.
if (socketType == QAbstractSocket::UdpSocket
&& !setOption(BroadcastSocketOption, )) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError,
QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString);
close();
return false;
} // Make sure we receive out-of-band data
if (socketType == QAbstractSocket::TcpSocket
&& !setOption(ReceiveOutOfBandData, )) {
qWarning("QNativeSocketEngine::initialize unable to inline out-of-band data");
} // Set the send and receive buffer sizes to a magic size, found
// most optimal for our platforms.
setReceiveBufferSize();
setSendBufferSize(); d->socketType = socketType;
d->socketProtocol = protocol;
return true;
}
至此,初始化过程完成,socket被设定为非阻塞模式(也就是Select会超时方式)。
2、connect到远程目标
bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port)
{
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false); #if defined (QT_NO_IPV6)
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError,
QNativeSocketEnginePrivate::NoIpV6ErrorString);
return false;
}
#endif
if (!d->checkProxy(address))
return false; Q_CHECK_STATES(QNativeSocketEngine::connectToHost(),
QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false); d->peerAddress = address;
d->peerPort = port;
bool connected = d->nativeConnect(address, port);
if (connected)
d->fetchConnectionParameters(); return connected;
}
连接相对简单。
3、读取信息
在QAbstractSocket中,有两个成员是收发数据用的:readData()、writeData()
readData()有两种读取方式:有缓冲和无缓冲方式。基本原理是一致的,简单其见只分析无缓冲直接读取方式。
qint64 QAbstractSocket::readData(char *data, qint64 maxSize)
{
Q_D(QAbstractSocket);
if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid())
d->socketEngine->setReadNotificationEnabled(true); if (!d->isBuffered) {
if (!d->socketEngine)
return -; // no socket engine is probably EOF
qint64 readBytes = d->socketEngine->read(data, maxSize);
if (readBytes < ) {
d->socketError = d->socketEngine->error();
setErrorString(d->socketEngine->errorString());
}
if (!d->socketEngine->isReadNotificationEnabled())
d->socketEngine->setReadNotificationEnabled(true);
return readBytes;
} if (d->readBuffer.isEmpty())
// if we're still connected, return 0 indicating there may be more data in the future
// if we're not connected, return -1 indicating EOF
return d->state == QAbstractSocket::ConnectedState ? qint64() : qint64(-); // If readFromSocket() read data, copy it to its destination.
if (maxSize == ) {
*data = d->readBuffer.getChar();
return ;
} qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize);
qint64 readSoFar = ;
while (readSoFar < bytesToRead) {
const char *ptr = d->readBuffer.readPointer();
int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar),
d->readBuffer.nextDataBlockSize());
memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock);
readSoFar += bytesToReadFromThisBlock;
d->readBuffer.free(bytesToReadFromThisBlock);
} return readSoFar;
}
从前面(二)可以知道,socketEngine->read()实际调用的是QNativeSocketEngine::read()
qint64 QNativeSocketEngine::read(char *data, qint64 maxSize)
{
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -);
Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -); qint64 readBytes = d->nativeRead(data, maxSize); // Handle remote close
if (readBytes == && d->socketType == QAbstractSocket::TcpSocket) {
d->setError(QAbstractSocket::RemoteHostClosedError,
QNativeSocketEnginePrivate::RemoteHostClosedErrorString);
close();
return -;
}
return readBytes;
}
除了一些相关的检查,就是调用QNativeSocketPrivate::nativeRead()
qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength)
{
qint64 ret = -;
WSABUF buf;
buf.buf = data;
buf.len = maxLength;
DWORD flags = ;
DWORD bytesRead = ;
#if defined(Q_OS_WINCE)
WSASetLastError();
#endif
if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) {
int err = WSAGetLastError();
WS_ERROR_DEBUG(err);
switch (err) {
case WSAEWOULDBLOCK:
ret = -;
break;
case WSAEBADF:
case WSAEINVAL:
setError(QAbstractSocket::NetworkError, ReadErrorString);
break;
case WSAECONNRESET:
case WSAECONNABORTED:
// for tcp sockets this will be handled in QNativeSocketEngine::read
ret = ;
break;
default:
break;
}
} else {
if (WSAGetLastError() == WSAEWOULDBLOCK)
ret = -;
else
ret = qint64(bytesRead);
} return ret;
}
至此,调用Windows API读取数据。
4、发送数据
同样分有缓存与无缓存方式,对无缓存方式:
qint64 QAbstractSocket::writeData(const char *data, qint64 size)
{
Q_D(QAbstractSocket);
if (d->state == QAbstractSocket::UnconnectedState) {
d->socketError = QAbstractSocket::UnknownSocketError;
setErrorString(tr("Socket is not connected"));
return -;
} if (!d->isBuffered) {
qint64 written = d->socketEngine->write(data, size);
if (written < ) {
d->socketError = d->socketEngine->error();
setErrorString(d->socketEngine->errorString());
} else if (!d->writeBuffer.isEmpty()) {
d->socketEngine->setWriteNotificationEnabled(true);
}
if (written >= )
emit bytesWritten(written);
return written;
} char *ptr = d->writeBuffer.reserve(size);
if (size == )
*ptr = *data;
else
memcpy(ptr, data, size); qint64 written = size; if (d->socketEngine && !d->writeBuffer.isEmpty())
d->socketEngine->setWriteNotificationEnabled(true);
return written;
}
查看QNativeSocketEngine::write():
qint64 QNativeSocketEngine::write(const char *data, qint64 size)
{
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::write(), -);
Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -);
return d->nativeWrite(data, size);
} qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len)
{
Q_Q(QNativeSocketEngine);
qint64 ret = ;
// don't send more than 49152 per call to WSASendTo to avoid getting a WSAENOBUFS
for (;;) {
qint64 bytesToSend = qMin<qint64>(, len - ret);
WSABUF buf;
buf.buf = (char*)data + ret;
buf.len = bytesToSend;
DWORD flags = ;
DWORD bytesWritten = ; int socketRet = ::WSASend(socketDescriptor, &buf, 1, &bytesWritten, flags, 0,0); ret += qint64(bytesWritten); if (socketRet != SOCKET_ERROR) {
if (ret == len)
break;
else
continue;
} else if (WSAGetLastError() == WSAEWOULDBLOCK) {
break;
} else {
int err = WSAGetLastError();
WS_ERROR_DEBUG(err);
switch (err) {
case WSAECONNRESET:
case WSAECONNABORTED:
ret = -;
setError(QAbstractSocket::NetworkError, WriteErrorString);
q->close();
break;
default:
break;
}
break;
}
}
return ret;
}
至此分析完毕。
前面分析中,一个问题一直没有解决:新生成的SOCKET是什么时候加入WSASelect()的?另外还有一个不是很大的问题,close流程。
在
QEventDispatcherWin32Private::doWsaAsyncSelect()
中WSAAsyncSelect()设置一个断点,观察call stack:
QtCored4.dll!QEventDispatcherWin32Private::doWsaAsyncSelect(int socket=0x00001628) 行633 C++
QtCored4.dll!QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier * notifier=0x00c6f248) 行829 C++
QtCored4.dll!QSocketNotifier::QSocketNotifier(int socket=0x00001628, QSocketNotifier::Type type=Write, QObject * parent=0x00c66228) 行185 C++
QtNetworkd4.dll!QWriteNotifier::QWriteNotifier(int fd=0x00001628, QNativeSocketEngine * parent=0x00c66228) 行1053 + 0x1a 字节 C++
QtNetworkd4.dll!QNativeSocketEngine::setWriteNotificationEnabled(bool enable=true) 行1118 + 0x2d 字节 C++
QtNetworkd4.dll!QAbstractSocketPrivate::_q_connectToNextAddress() 行996 C++
QtNetworkd4.dll!QAbstractSocketPrivate::_q_startConnecting(const QHostInfo & hostInfo={...}) 行890 C++
QtNetworkd4.dll!QAbstractSocket::qt_metacall(QMetaObject::Call _c=InvokeMetaMethod, int _id=0x0000000a, void * * _a=0x00c6e510) 行104 + 0x16 字节 C++
QtNetworkd4.dll!QTcpSocket::qt_metacall(QMetaObject::Call _c=InvokeMetaMethod, int _id=0x00000012, void * * _a=0x00c6e510) 行58 + 0x14 字节 C++
QtCored4.dll!QMetaCallEvent::placeMetaCall(QObject * object=0x00c4f790) 行478 C++
QtCored4.dll!QObject::event(QEvent * e=0x00c4d8a0) 行1102 + 0x14 字节 C++
QtGuid4.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x00c4f790, QEvent * e=0x00c4d8a0) 行4065 + 0x11 字节 C++
QtGuid4.dll!QApplication::notify(QObject * receiver=0x00c4f790, QEvent * e=0x00c4d8a0) 行3605 + 0x10 字节 C++
QtCored4.dll!QCoreApplication::notifyInternal(QObject * receiver=0x00c4f790, QEvent * event=0x00c4d8a0) 行610 + 0x15 字节 C++
QtCored4.dll!QCoreApplication::sendEvent(QObject * receiver=0x00c4f790, QEvent * event=0x00c4d8a0) 行213 + 0x39 字节 C++
QtCored4.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000, QThreadData * data=0x00bc8890) 行1247 + 0xd 字节 C++
QtCored4.dll!QEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行679 + 0x10 字节 C++
QtGuid4.dll!QGuiEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行1182 + 0x15 字节 C++
QtCored4.dll!QEventLoop::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行150 C++
QtCored4.dll!QEventLoop::exec(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行201 + 0x2d 字节 C++
QtGuid4.dll!QDialog::exec() 行499 C++
fortuneclient.exe!main(int argc=0x00000001, char * * argv=0x00bc8750) 行51 + 0x9 字节 C++
fortuneclient.exe!WinMain(HINSTANCE__ * instance=0x00400000, HINSTANCE__ * prevInstance=0x00000000, char * __formal=0x001520e2, int cmdShow=0x00000001) 行137 + 0x12 字节 C++
fortuneclient.exe!__tmainCRTStartup() 行574 + 0x35 字节 C
fortuneclient.exe!WinMainCRTStartup() 行399 C
kernel32.dll!7c82f23b()
[下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]
看QNativeSocketEngine::setWriteNotificationEnabled()的代码实现:
void QNativeSocketEngine::setWriteNotificationEnabled(bool enable)
{
Q_D(QNativeSocketEngine);
if (d->writeNotifier) {
d->writeNotifier->setEnabled(enable);
} else if (enable && d->threadData->eventDispatcher) {
d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this);
d->writeNotifier->setEnabled(true);
}
}
在QWriteNotifier对象新建的时候,引起其父类的构建:QSocketNotifier
QSocketNotifier::QSocketNotifier(int socket, Type type, QObject *parent)
: QObject(parent)
{
if (socket < )
qWarning("QSocketNotifier: Invalid socket specified");
sockfd = socket;
sntype = type;
snenabled = true; Q_D(QObject);
if (!d->threadData->eventDispatcher) {
qWarning("QSocketNotifier: Can only be used with threads started with QThread");
} else {
d->threadData->eventDispatcher->registerSocketNotifier(this);
}
}
原来是通过获取当前线程数据得到Dispatcher的指针(QEventDispatcherWin32),通过其注册QNativeSocketEngine对象自己本身。
void QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier *notifier)
{
Q_ASSERT(notifier);
int sockfd = notifier->socket();
int type = notifier->type(); Q_D(QEventDispatcherWin32);
QSNDict *sn_vec[] = { &d->sn_read, &d->sn_write, &d->sn_except };
QSNDict *dict = sn_vec[type]; if (QCoreApplication::closingDown()) // ### d->exitloop?
return; // after sn_cleanup, don't reinitialize. if (dict->contains(sockfd)) {
const char *t[] = { "Read", "Write", "Exception" };
/* Variable "socket" below is a function pointer. */
qWarning("QSocketNotifier: Multiple socket notifiers for "
"same socket %d and type %s", sockfd, t[type]);
} QSockNot *sn = new QSockNot;
sn->obj = notifier;
sn->fd = sockfd;
dict->insert(sn->fd, sn); if (d->internalHwnd)
d->doWsaAsyncSelect(sockfd);
}
在这里跟前面分析的QEventDispatcherWin32消息处理搭上关系了,把QWriteNotifier对象加入到系统的列表中;在QApplication::exec()的消息循环中,就能够获取目标对象了。
今天分析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相关功能实现了吗?怎么实现的?
在动手分析前,简单介绍一下HTTP协议。HTTP协议是一种为分布式,合作式,超媒体信息系统。它是一种通用的,无状态(stateless)的协议,除了应用于超文本传输外,它也可以应用于诸如名称服务器和分布对象管理系统之类的系统,这可以通过扩展它的请求方法,错误代码和报头来实现。HTTP的一个特点是数据表现形式是可输入的和可协商性的,这就允许系统能被建立而独立于数据传输。HTTP在1990年WWW全球信息刚刚起步的时候就得到了应用。该规范定义的协议用“HTTP/1.1”表示,是对RFC2608[33]的更新。 HTTP协议是通过定义一序列的动作(协议文本中称为方法),来完成数据的传输通信。HTTP1.1版本中有这些方法:get、post、head、options、put、delete、trace、connect。
get方法用于获取URI资源,是最为常用的一种方法。
post方法用于向指定URI提交内容,服务器端响应其行为,该方法也极为常用。
head方法向URI发送请求,仅仅只需要获得响应的协议头。
put方法用于向URI发送请求,若URI不存在,则要求服务器端根据请求创建资源。当URI存在时,服务器端必须接受请求内容,将其作为URI资源的修改后版本。
delete方法用于删除URI标识的指定资源。
trace方法用于激活服务器端对请求的循环反馈,反馈作为http响应的正文内容被传输回客户端。
connect方法通常被用于使用代理连接。
更详细的内容请查看相关资料。
回到QT系统,manager->get()调用其实就是HTTP/1.1协议中get方法的实现。
QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
{
return d_func()->postProcess(createRequest(QNetworkAccessManager::GetOperation, request));
}
上面的一行程序中有两个调用
QNetworkAccessManager::createRequest()
QNetworkAccessManagerPrivate::postProcess()
先来看createRequest(),两个参数:第一个参数表示使用Get方法;第二个参数是目标网址。
QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op,
const QNetworkRequest &req,
QIODevice *outgoingData)
{
Q_D(QNetworkAccessManager); bool isLocalFile = req.url().isLocalFile();
QString scheme = req.url().scheme().toLower(); // fast path for GET on file:// URLs
// The QNetworkAccessFileBackend will right now only be used for PUT
if ((op == QNetworkAccessManager::GetOperation || op == QNetworkAccessManager::HeadOperation)
&& (isLocalFile || scheme == QLatin1String("qrc"))) {
return new QNetworkReplyFileImpl(this, req, op);
} if ((op == QNetworkAccessManager::GetOperation || op == QNetworkAccessManager::HeadOperation)
&& scheme == QLatin1String("data")) {
return new QNetworkReplyDataImpl(this, req, op);
} // A request with QNetworkRequest::AlwaysCache does not need any bearer management
QNetworkRequest::CacheLoadControl mode =
static_cast<QNetworkRequest::CacheLoadControl>(
req.attribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::PreferNetwork).toInt());
if (mode == QNetworkRequest::AlwaysCache
&& (op == QNetworkAccessManager::GetOperation
|| op == QNetworkAccessManager::HeadOperation)) {
// FIXME Implement a QNetworkReplyCacheImpl instead, see QTBUG-15106
QNetworkReplyImpl *reply = new QNetworkReplyImpl(this);
QNetworkReplyImplPrivate *priv = reply->d_func();
priv->manager = this;
priv->backend = new QNetworkAccessCacheBackend();
priv->backend->manager = this->d_func();
priv->backend->setParent(reply);
priv->backend->reply = priv;
priv->setup(op, req, outgoingData);
return reply;
} #ifndef QT_NO_BEARERMANAGEMENT
// Return a disabled network reply if network access is disabled.
// Except if the scheme is empty or file://.
if (!d->networkAccessible && !isLocalFile) {
return new QDisabledNetworkReply(this, req, op);
} if (!d->networkSessionStrongRef && (d->initializeSession || !d->networkConfiguration.isEmpty())) {
QNetworkConfigurationManager manager;
if (!d->networkConfiguration.isEmpty()) {
d->createSession(manager.configurationFromIdentifier(d->networkConfiguration));
} else {
if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired)
d->createSession(manager.defaultConfiguration());
else
d->initializeSession = false;
}
}
#endif QNetworkRequest request = req;
if (!request.header(QNetworkRequest::ContentLengthHeader).isValid() &&
outgoingData && !outgoingData->isSequential()) {
// request has no Content-Length
// but the data that is outgoing is random-access
request.setHeader(QNetworkRequest::ContentLengthHeader, outgoingData->size());
} if (static_cast<QNetworkRequest::LoadControl>
(request.attribute(QNetworkRequest::CookieLoadControlAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic) {
if (d->cookieJar) {
QList<QNetworkCookie> cookies = d->cookieJar->cookiesForUrl(request.url());
if (!cookies.isEmpty())
request.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookies));
}
} // first step: create the reply
QUrl url = request.url();
QNetworkReplyImpl *reply = new QNetworkReplyImpl(this);
#ifndef QT_NO_BEARERMANAGEMENT
if (!isLocalFile) {
connect(this, SIGNAL(networkSessionConnected()),
reply, SLOT(_q_networkSessionConnected()));
}
#endif
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;
} #ifndef QT_NO_OPENSSL
reply->setSslConfiguration(request.sslConfiguration());
#endif // fourth step: setup the reply
priv->setup(op, request, outgoingData); return reply;
}
代码比较长,主要做了这些事情:
1、设定HTTP请求的头信息(例如客户端请求内容的长度、Cookie等)
2、生成并初始化Reply对象(实际是QNetworkReplyImpl对象)
3、获取本地缓存的认证信息(如果有的话)
4、设定Reply
5、获取一个backend实体
6、如果支持OPENSSL的话,设定SSL的配置
暂时先放一边后面再对createRequest()做进一步的分析,再来看postProcess()
QNetworkReply *QNetworkAccessManagerPrivate::postProcess(QNetworkReply *reply)
{
Q_Q(QNetworkAccessManager);
QNetworkReplyPrivate::setManager(reply, q);
q->connect(reply, SIGNAL(finished()), SLOT(_q_replyFinished()));
#ifndef QT_NO_OPENSSL
/* In case we're compiled without SSL support, we don't have this signal and we need to
* avoid getting a connection error. */
q->connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(_q_replySslErrors(QList<QSslError>)));
#endif
#ifndef QT_NO_BEARERMANAGEMENT
activeReplyCount++;
#endif return reply;
}
简单来说就做了一件事情,把QNetworkReply的信号(finished、sslErrors)与QNetworkAccessManager的槽连接起来
接上面,进一步分析QNetworkAccessManager::createRequest()的实现。去除不重要的分支末节,看其调用的QNetworkReplyImplPrivate::setup()和QNetworkAccessManagerPrivate::findBackend()的代码
void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req,
QIODevice *data)
{
Q_Q(QNetworkReplyImpl); outgoingData = data; //outgoingData实际就是QNetworkRequest对象
request = req;
url = request.url();
operation = op; q->QIODevice::open(QIODevice::ReadOnly);
// Internal code that does a HTTP reply for the synchronous Ajax
// in QtWebKit.
QVariant synchronousHttpAttribute = req.attribute(
static_cast<QNetworkRequest::Attribute>(QNetworkRequest::SynchronousRequestAttribute));
// The synchronous HTTP is a corner case, we will put all upload data in one big QByteArray in the outgoingDataBuffer.
// Yes, this is not the most efficient thing to do, but on the other hand synchronous XHR needs to die anyway.
if (synchronousHttpAttribute.toBool() && outgoingData) {
outgoingDataBuffer = QSharedPointer<QRingBuffer>(new QRingBuffer());
qint64 previousDataSize = ;
do {
previousDataSize = outgoingDataBuffer->size();
outgoingDataBuffer->append(outgoingData->readAll());
} while (outgoingDataBuffer->size() != previousDataSize);
} if (backend)
backend->setSynchronous(synchronousHttpAttribute.toBool()); if (outgoingData && backend && !backend->isSynchronous()) {
// there is data to be uploaded, e.g. HTTP POST. if (!backend->needsResetableUploadData() || !outgoingData->isSequential()) {
// backend does not need upload buffering or
// fixed size non-sequential
// just start the operation
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
} else {
bool bufferingDisallowed =
req.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
false).toBool(); if (bufferingDisallowed) {
// if a valid content-length header for the request was supplied, we can disable buffering
// if not, we will buffer anyway
if (req.header(QNetworkRequest::ContentLengthHeader).isValid()) {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
} else {
state = Buffering;
QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
}
} else {
// _q_startOperation will be called when the buffering has finished.
state = Buffering;
QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
}
}
} else {
// for HTTP, we want to send out the request as fast as possible to the network, without
// invoking methods in a QueuedConnection
#ifndef QT_NO_HTTP
if (qobject_cast<QNetworkAccessHttpBackend *>(backend) || (backend && backend->isSynchronous())) {
_q_startOperation();
} else {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
}
#else
if (backend && backend->isSynchronous())
_q_startOperation();
else
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
#endif // QT_NO_HTTP
}
}
发现调用_q_startOperation函数和_q_bufferOutgoingData函数,代码如下
void QNetworkReplyImplPrivate::_q_startOperation()
{
// ensure this function is only being called once
if (state == Working || state == Finished) {
qDebug("QNetworkReplyImpl::_q_startOperation was called more than once");
return;
}
state = Working; // note: if that method is called directly, it cannot happen that the backend is 0,
// because we just checked via a qobject_cast that we got a http backend (see
// QNetworkReplyImplPrivate::setup())
if (!backend) {
error(QNetworkReplyImpl::ProtocolUnknownError,
QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(url.scheme())); // not really true!;
finished();
return;
} if (!backend->start()) {
#ifndef QT_NO_BEARERMANAGEMENT
// backend failed to start because the session state is not Connected.
// QNetworkAccessManager will call _q_startOperation again for us when the session
// state changes.
state = WaitingForSession; QSharedPointer<QNetworkSession> session(manager->d_func()->getNetworkSession()); if (session) {
Q_Q(QNetworkReplyImpl); QObject::connect(session.data(), SIGNAL(error(QNetworkSession::SessionError)),
q, SLOT(_q_networkSessionFailed()), Qt::QueuedConnection); if (!session->isOpen())
session->open();
} else {
qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
state = Working;
error(QNetworkReplyImpl::UnknownNetworkError,
QCoreApplication::translate("QNetworkReply", "Network session error."));
finished();
}
#else
qWarning("Backend start failed");
state = Working;
error(QNetworkReplyImpl::UnknownNetworkError,
QCoreApplication::translate("QNetworkReply", "backend start error."));
finished();
#endif
return;
} if (backend && backend->isSynchronous()) {
state = Finished;
q_func()->setFinished(true);
} else {
if (state != Finished) {
if (operation == QNetworkAccessManager::GetOperation)
pendingNotifications.append(NotifyDownstreamReadyWrite); handleNotifications();
}
}
}
void QNetworkReplyImplPrivate::_q_bufferOutgoingData()
{
Q_Q(QNetworkReplyImpl); if (!outgoingDataBuffer) {
// first call, create our buffer
outgoingDataBuffer = QSharedPointer<QRingBuffer>(new QRingBuffer()); QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
} qint64 bytesBuffered = ;
qint64 bytesToBuffer = ; // read data into our buffer
forever {
bytesToBuffer = outgoingData->bytesAvailable();
// unknown? just try 2 kB, this also ensures we always try to read the EOF
if (bytesToBuffer <= )
bytesToBuffer = *; char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
bytesBuffered = outgoingData->read(dst, bytesToBuffer); if (bytesBuffered == -) {
// EOF has been reached.
outgoingDataBuffer->chop(bytesToBuffer); _q_bufferOutgoingDataFinished();
break;
} else if (bytesBuffered == ) {
// nothing read right now, just wait until we get called again
outgoingDataBuffer->chop(bytesToBuffer); break;
} else {
// don't break, try to read() again
outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
}
}
}
连接两个信号与槽之后,是打开QIODevice,暂未深入分析。然后是呼叫q->_q_startOperation(),实际就是调用QNetworkReplyImpl::_q_startOperation(),使用的是队列等待方式(也就是发送一个消息进入系统消息队列,这个setup函数以及全部后续执行完毕,主动权交回给Windows后,再根据进入队列的消息来触发)。
_q_startOperation就是做了一些简单的判断,然后调用 handleNotifications
void QNetworkReplyImplPrivate::handleNotifications()
{
if (notificationHandlingPaused)
return; NotificationQueue current = pendingNotifications;
pendingNotifications.clear(); if (state != Working)
return; while (state == Working && !current.isEmpty()) {
InternalNotifications notification = current.dequeue();
switch (notification) {
case NotifyDownstreamReadyWrite:
if (copyDevice)
_q_copyReadyRead();
else
backend->downstreamReadyWrite();
break; case NotifyCloseDownstreamChannel:
backend->closeDownstreamChannel();
break; case NotifyCopyFinished: {
QIODevice *dev = copyDevice;
copyDevice = ;
backend->copyFinished(dev);
break;
}
}
}
}
该函数主要用于处理各种socket相关事件
因此我们先看QNetworkAccessManagerPrivate::findBackend()的代码实现
QNetworkAccessBackend *QNetworkAccessManagerPrivate::findBackend(QNetworkAccessManager::Operation op,
const QNetworkRequest &request)
{
if (QNetworkAccessBackendFactoryData::valid) {
QMutexLocker locker(&factoryData()->mutex);
QNetworkAccessBackendFactoryData::ConstIterator it = factoryData()->constBegin(),
7 end = factoryData()->constEnd();
while (it != end) {
QNetworkAccessBackend *backend = (*it)->create(op, request);
if (backend) {
backend->manager = this;
return backend; // found a factory that handled our request
}
++it;
}
}
return ;
}
这段代码有一点复杂,先看红色标记的第一句,factoryData()是用宏来定义的函数:
Q_GLOBAL_STATIC(QNetworkAccessBackendFactoryData, factoryData)
宏定义如下:
#define Q_GLOBAL_STATIC(TYPE, NAME) \
static TYPE *NAME() \
{ \
static TYPE thisVariable; \
static QGlobalStatic<TYPE > thisGlobalStatic(&thisVariable); \
return thisGlobalStatic.pointer; \
}
如果对STD比较熟悉,第一感觉这是一个模板List操作。在这里constBegin()和constEnd()组合起来是一个遍历,那么在什么地方设定值呢?良好代码的命名是很规范的,我试了试全局查找factoryData(),找到了我所希望看到的东西:
QNetworkAccessBackendFactory::QNetworkAccessBackendFactory()
{
QMutexLocker locker(&factoryData()->mutex);
factoryData()->append(this);
} QNetworkAccessBackendFactory::~QNetworkAccessBackendFactory()
{
if (QNetworkAccessBackendFactoryData::valid) {
QMutexLocker locker(&factoryData()->mutex);
factoryData()->removeAll(this);
}
}
里prepend()应该是把对象添加到列表;而removeAll()就是清空全部数据了。
factoryData()里面包含的对象序列,应该是从QNetworkAccessBackendFactory衍生出来的。
一共有哪些子类呢?继续全局查找
class QNetworkAccessDataBackendFactory: public QNetworkAccessBackendFactory
class QNetworkAccessDebugPipeBackendFactory: public QNetworkAccessBackendFactory
class QNetworkAccessFileBackendFactory: public QNetworkAccessBackendFactory
class QNetworkAccessFtpBackendFactory: public QNetworkAccessBackendFactory
class QNetworkAccessHttpBackendFactory : public QNetworkAccessBackendFactory
去除暂时不关心的DebugPipe,一共有四种:DataBackend、FileBackend、FtpBackend、HttpBackend。媒体的种类原来是在这里实现的。看其中QNetworkAccessHttpBackendFactory::create()
QNetworkAccessBackend *
QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op,
const QNetworkRequest &request) const
{
// check the operation
switch (op) {
case QNetworkAccessManager::GetOperation:
case QNetworkAccessManager::PostOperation:
case QNetworkAccessManager::HeadOperation:
case QNetworkAccessManager::PutOperation:
case QNetworkAccessManager::DeleteOperation:
case QNetworkAccessManager::CustomOperation:
break; default:
// no, we can't handle this request
return ;
} QUrl url = request.url();
QString scheme = url.scheme().toLower();
if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
return new QNetworkAccessHttpBackend; return ;
}
如果是能够处理的OP标记并且URL的前缀是http或者是https,则创建一个QNetworkAccessHttpBackend对象。
前面QNetworkAccessManager::get()代码中,调用的参数是QNetworkAccessManager::GetOperation,所以在我们分析的这个应用中,创建的是QNetworkAccessHttpBackend对象。
findBackend()到此分析完毕;由于factoryData()的具体实现跟我们分析网络通信的目标没有太大关系,未深入分析,有谁分析了的话请转告一声,值得一看。
回到前面暂停的QNetworkReplyImpl::_q_startOperation(),又实现了什么动作呢?
首先调用了刚刚创建的QNetworkAccessHttpBackend::start(),然后是添加通知消息、调用_q_sourceReadyRead()、最后处理通知消息
bool QNetworkAccessBackend::start()
{
#ifndef QT_NO_BEARERMANAGEMENT
// For bearer, check if session start is required
QSharedPointer<QNetworkSession> networkSession(manager->getNetworkSession());
if (networkSession) {
// session required
if (networkSession->isOpen() &&
networkSession->state() == QNetworkSession::Connected) {
// Session is already open and ready to use.
// copy network session down to the backend
setProperty("_q_networksession", QVariant::fromValue(networkSession));
} else {
// Session not ready, but can skip for loopback connections // This is not ideal.
const QString host = reply->url.host(); if (host == QLatin1String("localhost") ||
QHostAddress(host) == QHostAddress::LocalHost ||
QHostAddress(host) == QHostAddress::LocalHostIPv6) {
// Don't need an open session for localhost access.
} else {
// need to wait for session to be opened
return false;
}
}
}
#endif #ifndef QT_NO_NETWORKPROXY
#ifndef QT_NO_BEARERMANAGEMENT
// Get the proxy settings from the network session (in the case of service networks,
// the proxy settings change depending which AP was activated)
QNetworkSession *session = networkSession.data();
QNetworkConfiguration config;
if (session) {
QNetworkConfigurationManager configManager;
// The active configuration tells us what IAP is in use
QVariant v = session->sessionProperty(QLatin1String("ActiveConfiguration"));
if (v.isValid())
config = configManager.configurationFromIdentifier(qvariant_cast<QString>(v));
// Fallback to using the configuration if no active configuration
if (!config.isValid())
config = session->configuration();
// or unspecified configuration if that is no good either
if (!config.isValid())
config = QNetworkConfiguration();
}
reply->proxyList = manager->queryProxy(QNetworkProxyQuery(config, url()));
#else // QT_NO_BEARERMANAGEMENT
// Without bearer management, the proxy depends only on the url
reply->proxyList = manager->queryProxy(QNetworkProxyQuery(url()));
#endif
#endif // now start the request
open();
return true;
}
start函数很简单,主要是打开QNetworkAccessBackend
话说昨日走到QNetworkReplyImplPrivate::_q_startOperation(),勾引出QNetworkAccessHttpBackend::open(),今日接着欣赏QT之美丽
void QNetworkAccessHttpBackend::open()
{
postRequest();
}
open函数仅仅是调用postRequest()
etworkAccessHttpBackend::postRequest()
{
QThread *thread = ;
if (isSynchronous()) {
// A synchronous HTTP request uses its own thread
thread = new QThread();
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
} else if (!manager->httpThread) {
// We use the manager-global thread.
// At some point we could switch to having multiple threads if it makes sense.
manager->httpThread = new QThread();
QObject::connect(manager->httpThread, SIGNAL(finished()), manager->httpThread, SLOT(deleteLater()));
manager->httpThread->start();
#ifndef QT_NO_NETWORKPROXY
qRegisterMetaType<QNetworkProxy>("QNetworkProxy");
#endif
#ifndef QT_NO_OPENSSL
qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
qRegisterMetaType<QSslConfiguration>("QSslConfiguration");
#endif
qRegisterMetaType<QList<QPair<QByteArray,QByteArray> > >("QList<QPair<QByteArray,QByteArray> >");
qRegisterMetaType<QHttpNetworkRequest>("QHttpNetworkRequest");
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
qRegisterMetaType<QSharedPointer<char> >("QSharedPointer<char>"); thread = manager->httpThread;
} else {
// Asynchronous request, thread already exists
thread = manager->httpThread;
} QUrl url = request().url();
httpRequest.setUrl(url); bool ssl = url.scheme().toLower() == QLatin1String("https");
setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
httpRequest.setSsl(ssl); #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, only transparent proxies
if (!ssl
&& (p.capabilities() & QNetworkProxy::CachingCapability)
&& (p.type() == QNetworkProxy::HttpProxy ||
p.type() == QNetworkProxy::HttpCachingProxy)) {
cacheProxy = p;
transparentProxy = QNetworkProxy::NoProxy;
break;
}
if (p.isTransparentProxy()) {
transparentProxy = p;
cacheProxy = QNetworkProxy::NoProxy;
break;
}
} // check if at least one of the proxies
if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
cacheProxy.type() == QNetworkProxy::DefaultProxy) {
// unsuitable proxies
QMetaObject::invokeMethod(this, "error", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection,
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
Q_ARG(QString, tr("No suitable proxy found")));
QMetaObject::invokeMethod(this, "finished", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection);
return;
}
#endif bool loadedFromCache = false;
httpRequest.setPriority(convert(request().priority())); switch (operation()) {
case QNetworkAccessManager::GetOperation:
httpRequest.setOperation(QHttpNetworkRequest::Get);
loadedFromCache = loadFromCacheIfAllowed(httpRequest);
break; case QNetworkAccessManager::HeadOperation:
httpRequest.setOperation(QHttpNetworkRequest::Head);
loadedFromCache = loadFromCacheIfAllowed(httpRequest);
break; case QNetworkAccessManager::PostOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Post);
createUploadByteDevice();
break; case QNetworkAccessManager::PutOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Put);
createUploadByteDevice();
break; case QNetworkAccessManager::DeleteOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Delete);
break; case QNetworkAccessManager::CustomOperation:
invalidateCache(); // for safety reasons, we don't know what the operation does
httpRequest.setOperation(QHttpNetworkRequest::Custom);
createUploadByteDevice();
httpRequest.setCustomVerb(request().attribute(
QNetworkRequest::CustomVerbAttribute).toByteArray());
break; default:
break; // can't happen
} if (loadedFromCache) {
// commented this out since it will be called later anyway
// by copyFinished()
//QNetworkAccessBackend::finished();
return; // no need to send the request! :)
} QList<QByteArray> headers = request().rawHeaderList();
if (resumeOffset != ) {
if (headers.contains("Range")) {
// Need to adjust resume offset for user specified range headers.removeOne("Range"); // We've already verified that requestRange starts with "bytes=", see canResume.
QByteArray requestRange = request().rawHeader("Range").mid(); int index = requestRange.indexOf('-'); quint64 requestStartOffset = requestRange.left(index).toULongLong();
quint64 requestEndOffset = requestRange.mid(index + ).toULongLong(); requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
'-' + QByteArray::number(requestEndOffset); httpRequest.setHeaderField("Range", requestRange);
} else {
httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
}
} foreach (const QByteArray &header, headers)
httpRequest.setHeaderField(header, request().rawHeader(header)); if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
httpRequest.setPipeliningAllowed(true); if (static_cast<QNetworkRequest::LoadControl>
(request().attribute(QNetworkRequest::AuthenticationReuseAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
httpRequest.setWithCredentials(false); // Create the HTTP thread delegate
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
#ifndef QT_NO_BEARERMANAGEMENT
QVariant v(property("_q_networksession"));
if (v.isValid())
delegate->networkSession = qvariant_cast<QSharedPointer<QNetworkSession> >(v);
#endif // For the synchronous HTTP, this is the normal way the delegate gets deleted
// For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished
connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater())); // Set the properties it needs
delegate->httpRequest = httpRequest;
#ifndef QT_NO_NETWORKPROXY
delegate->cacheProxy = cacheProxy;
delegate->transparentProxy = transparentProxy;
#endif
delegate->ssl = ssl;
#ifndef QT_NO_OPENSSL
if (ssl)
delegate->incomingSslConfiguration = request().sslConfiguration();
#endif // Do we use synchronous HTTP?
delegate->synchronous = isSynchronous(); // The authentication manager is used to avoid the BlockingQueuedConnection communication
// from HTTP thread to user thread in some cases.
delegate->authenticationManager = manager->authenticationManager; if (!isSynchronous()) {
// Tell our zerocopy policy to the delegate
delegate->downloadBufferMaximumSize =
request().attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute).toLongLong(); // These atomic integers are used for signal compression
delegate->pendingDownloadData = pendingDownloadDataEmissions;
delegate->pendingDownloadProgress = pendingDownloadProgressEmissions; // Connect the signals of the delegate to us
connect(delegate, SIGNAL(downloadData(QByteArray)),
this, SLOT(replyDownloadData(QByteArray)),
Qt::QueuedConnection);
connect(delegate, SIGNAL(downloadFinished()),
this, SLOT(replyFinished()),
Qt::QueuedConnection);
connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
this, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
Qt::QueuedConnection);
connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
this, SLOT(replyDownloadProgressSlot(qint64,qint64)),
Qt::QueuedConnection);
connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
this, SLOT(httpError(QNetworkReply::NetworkError, const QString)),
Qt::QueuedConnection);
#ifndef QT_NO_OPENSSL
connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
this, SLOT(replySslConfigurationChanged(QSslConfiguration)),
Qt::QueuedConnection);
#endif
// Those need to report back, therefire BlockingQueuedConnection
connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
this, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
Qt::BlockingQueuedConnection);
#ifndef QT_NO_NETWORKPROXY
connect (delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
Qt::BlockingQueuedConnection);
#endif
#ifndef QT_NO_OPENSSL
connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
this, SLOT(replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *)),
Qt::BlockingQueuedConnection);
#endif
// This signal we will use to start the request.
connect(this, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
connect(this, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest())); // To throttle the connection.
QObject::connect(this, SIGNAL(readBufferSizeChanged(qint64)), delegate, SLOT(readBufferSizeChanged(qint64)));
QObject::connect(this, SIGNAL(readBufferFreed(qint64)), delegate, SLOT(readBufferFreed(qint64))); if (uploadByteDevice) {
QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice =
new QNonContiguousByteDeviceThreadForwardImpl(uploadByteDevice->atEnd(), uploadByteDevice->size());
if (uploadByteDevice->isResetDisabled())
forwardUploadDevice->disableReset();
forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
delegate->httpRequest.setUploadByteDevice(forwardUploadDevice); // From main thread to user thread:
QObject::connect(this, SIGNAL(haveUploadData(QByteArray, bool, qint64)),
forwardUploadDevice, SLOT(haveDataSlot(QByteArray, bool, qint64)), Qt::QueuedConnection);
QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
forwardUploadDevice, SIGNAL(readyRead()),
Qt::QueuedConnection); // From http thread to user thread:
QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
this, SLOT(wantUploadDataSlot(qint64)));
QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
this, SLOT(sentUploadDataSlot(qint64)));
connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
this, SLOT(resetUploadDataSlot(bool*)),
Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
}
} else if (isSynchronous()) {
connect(this, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection); if (uploadByteDevice) {
// For the synchronous HTTP use case the use thread (this one here) is blocked
// so we cannot use the asynchronous upload architecture.
// We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly
// use the uploadByteDevice provided to us by the QNetworkReplyImpl.
// The code that is in QNetworkReplyImplPrivate::setup() makes sure it is safe to use from a thread
// since it only wraps a QRingBuffer
delegate->httpRequest.setUploadByteDevice(uploadByteDevice.data());
}
} // Move the delegate to the http thread
delegate->moveToThread(thread);
// This call automatically moves the uploadDevice too for the asynchronous case. // Send an signal to the delegate so it starts working in the other thread
if (isSynchronous()) {
emit startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done if (delegate->incomingErrorCode != QNetworkReply::NoError) {
replyDownloadMetaData
(delegate->incomingHeaders,
delegate->incomingStatusCode,
delegate->incomingReasonPhrase,
delegate->isPipeliningUsed,
QSharedPointer<char>(),
delegate->incomingContentLength);
replyDownloadData(delegate->synchronousDownloadData);
httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
} else {
replyDownloadMetaData
(delegate->incomingHeaders,
delegate->incomingStatusCode,
delegate->incomingReasonPhrase,
delegate->isPipeliningUsed,
QSharedPointer<char>(),
delegate->incomingContentLength);
replyDownloadData(delegate->synchronousDownloadData);
} // End the thread. It will delete itself from the finished() signal
thread->quit();
thread->wait(); finished();
} else {
emit startHttpRequest(); // Signal to the HTTP thread and go back to user.
}
}
主要是链接槽函数,看槽函数代码startRequest
void QHttpThreadDelegate::startRequest()
{
#ifdef QHTTPTHREADDELEGATE_DEBUG
qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId();
#endif
// Check QThreadStorage for the QNetworkAccessCache
// If not there, create this connection cache
if (!connections.hasLocalData()) {
connections.setLocalData(new QNetworkAccessCache());
} // check if we have an open connection to this host
QUrl urlCopy = httpRequest.url();
urlCopy.setPort(urlCopy.port(ssl ? : )); #ifndef QT_NO_NETWORKPROXY
if (transparentProxy.type() != QNetworkProxy::NoProxy)
cacheKey = makeCacheKey(urlCopy, &transparentProxy);
else if (cacheProxy.type() != QNetworkProxy::NoProxy)
cacheKey = makeCacheKey(urlCopy, &cacheProxy);
else
#endif
cacheKey = makeCacheKey(urlCopy, ); // the http object is actually a QHttpNetworkConnection
httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
if (httpConnection == ) {
// no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection
#ifdef QT_NO_BEARERMANAGEMENT
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl);
#else
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, networkSession);
#endif
#ifndef QT_NO_OPENSSL
// Set the QSslConfiguration from this QNetworkRequest.
if (ssl && incomingSslConfiguration != QSslConfiguration::defaultConfiguration()) {
httpConnection->setSslConfiguration(incomingSslConfiguration);
}
#endif #ifndef QT_NO_NETWORKPROXY
httpConnection->setTransparentProxy(transparentProxy);
httpConnection->setCacheProxy(cacheProxy);
#endif // cache the QHttpNetworkConnection corresponding to this cache key
connections.localData()->addEntry(cacheKey, httpConnection);
} // Send the request to the connection
httpReply = httpConnection->sendRequest(httpRequest);
httpReply->setParent(this); // Connect the reply signals that we need to handle and then forward
if (synchronous) {
connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError, const QString)),
this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString))); connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*))); // Don't care about ignored SSL errors for now in the synchronous HTTP case.
} else if (!synchronous) {
connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError, const QString)),
this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
// some signals are only interesting when normal asynchronous style is used
connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
connect(httpReply,SIGNAL(dataReadProgress(int, int)), this, SLOT(dataReadProgressSlot(int,int)));
#ifndef QT_NO_OPENSSL
connect(httpReply,SIGNAL(sslErrors(const QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
#endif // In the asynchronous HTTP case we can just forward those signals
// Connect the reply signals that we can directly forward
connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
} connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
}
先查缓冲,没用的话新建连接,然后调用其sendRequest
QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request)
{
Q_D(QHttpNetworkConnection);
return d->queueRequest(request);
}
sendRequest()调用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;
reply->d_func()->connectionChannel = &channels[]; // will have the correct one set later
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;
} // this used to be called via invokeMethod and a QueuedConnection
// It is the only place _q_startNextRequest is called directly without going
// through the event loop using a QueuedConnection.
// This is dangerous because of recursion that might occur when emitting
// signals as DirectConnection from this code path. Therefore all signal
// emissions that can come out from this code path need to
// be QueuedConnection.
// We are currently trying to fine-tune this.
_q_startNextRequest(); return reply;
}
在这里整个消息处理(或者是初始化动作)完成之后,按消息序列调用_q_startNextRequest
循环多通道处理请求,类似于connect流程
void QHttpNetworkConnectionPrivate::_q_startNextRequest()
{
// If the QHttpNetworkConnection is currently paused then bail out immediately
if (state == PausedState)
return; //resend the necessary ones.
for (int i = ; i < channelCount; ++i) {
if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
channels[i].resendCurrent = false;
channels[i].state = QHttpNetworkConnectionChannel::IdleState; // if this is not possible, error will be emitted and connection terminated
if (!channels[i].resetUploadData())
continue;
channels[i].sendRequest();
}
} // dequeue new ones // return fast if there is nothing to do
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
// try to get a free AND connected socket
for (int i = ; i < channelCount; ++i) {
if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
}
} // try to push more into all sockets
// ### FIXME we should move this to the beginning of the function
// as soon as QtWebkit is properly using the pipelining
// (e.g. not for XMLHttpRequest or the first page load)
// ### FIXME we should also divide the requests more even
// on the connected sockets
//tryToFillPipeline(socket);
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
for (int i = ; i < channelCount; i++)
if (channels[i].socket->state() == QAbstractSocket::ConnectedState)
fillPipeline(channels[i].socket); // If there is not already any connected channels we need to connect a new one.
// We do not pair the channel with the request until we know if it is
// connected or not. This is to reuse connected channels before we connect new once.
int queuedRequest = highPriorityQueue.count() + lowPriorityQueue.count();
for (int i = ; i < channelCount; ++i) {
if (channels[i].socket->state() == QAbstractSocket::ConnectingState)
queuedRequest--;
if ( queuedRequest <= )
break;
if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) {
channels[i].ensureConnection();
queuedRequest--;
}
}
}
接着调用看代码
bool QHttpNetworkConnectionPrivate::dequeueRequest(QAbstractSocket *socket)
{
Q_ASSERT(socket); int i = indexOf(socket); if (!highPriorityQueue.isEmpty()) {
// remove from queue before sendRequest! else we might pipeline the same request again
HttpMessagePair messagePair = highPriorityQueue.takeLast();
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
channels[i].request = messagePair.first;
channels[i].reply = messagePair.second;
return true;
} if (!lowPriorityQueue.isEmpty()) {
// remove from queue before sendRequest! else we might pipeline the same request again
HttpMessagePair messagePair = lowPriorityQueue.takeLast();
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
channels[i].request = messagePair.first;
channels[i].reply = messagePair.second;
return true;
}
return false;
}
看看prepareReuest
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
{
QHttpNetworkRequest &request = messagePair.first;
QHttpNetworkReply *reply = messagePair.second; // add missing fields for the request
QByteArray value;
// check if Content-Length is provided
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
if (uploadByteDevice) {
if (request.contentLength() != - && uploadByteDevice->size() != -) {
// both values known, take the smaller one.
request.setContentLength(qMin(uploadByteDevice->size(), request.contentLength()));
} else if (request.contentLength() == - && uploadByteDevice->size() != -) {
// content length not supplied by user, but the upload device knows it
request.setContentLength(uploadByteDevice->size());
} else if (request.contentLength() != - && uploadByteDevice->size() == -) {
// everything OK, the user supplied us the contentLength
} else if (request.contentLength() == - && uploadByteDevice->size() == -) {
qFatal("QHttpNetworkConnectionPrivate: Neither content-length nor upload device size were given");
}
}
// set the Connection/Proxy-Connection: Keep-Alive headers
#ifndef QT_NO_NETWORKPROXY
if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) {
value = request.headerField("proxy-connection");
if (value.isEmpty())
request.setHeaderField("Proxy-Connection", "Keep-Alive");
} else {
#endif
value = request.headerField("connection");
if (value.isEmpty())
request.setHeaderField("Connection", "Keep-Alive");
#ifndef QT_NO_NETWORKPROXY
}
#endif // If the request had a accept-encoding set, we better not mess
// with it. If it was not set, we announce that we understand gzip
// and remember this fact in request.d->autoDecompress so that
// we can later decompress the HTTP reply if it has such an
// encoding.
value = request.headerField("accept-encoding");
if (value.isEmpty()) {
#ifndef QT_NO_COMPRESS
request.setHeaderField("Accept-Encoding", "gzip");
request.d->autoDecompress = true;
#else
// if zlib is not available set this to false always
request.d->autoDecompress = false;
#endif
} // some websites mandate an accept-language header and fail
// if it is not sent. This is a problem with the website and
// not with us, but we work around this by setting
// one always.
value = request.headerField("accept-language");
if (value.isEmpty()) {
QString systemLocale = QLocale::system().name().replace(QChar::fromAscii('_'),QChar::fromAscii('-'));
QString acceptLanguage;
if (systemLocale == QLatin1String("C"))
acceptLanguage = QString::fromAscii("en,*");
else if (systemLocale.startsWith(QLatin1String("en-")))
acceptLanguage = QString::fromAscii("%1,*").arg(systemLocale);
else
acceptLanguage = QString::fromAscii("%1,en,*").arg(systemLocale);
request.setHeaderField("Accept-Language", acceptLanguage.toAscii());
} // set the User Agent
value = request.headerField("user-agent");
if (value.isEmpty())
request.setHeaderField("User-Agent", "Mozilla/5.0");
// set the host
value = request.headerField("host");
if (value.isEmpty()) {
QHostAddress add;
QByteArray host;
if(add.setAddress(hostName)) {
if(add.protocol() == QAbstractSocket::IPv6Protocol) {
host = "[" + hostName.toAscii() + "]";//format the ipv6 in the standard way
} else {
host = QUrl::toAce(hostName);
}
} else {
host = QUrl::toAce(hostName);
} int port = request.url().port();
if (port != -) {
host += ':';
host += QByteArray::number(port);
} request.setHeaderField("Host", host);
} reply->d_func()->requestIsPrepared = true;
}
按优先级次序发送请求。prepareRequest()设定HTTP请求的Header信息;关键是sendRequest()
bool QHttpNetworkConnectionChannel::sendRequest()
{
if (!reply) {
// heh, how should that happen!
qWarning() << "QHttpNetworkConnectionChannel::sendRequest() called without QHttpNetworkReply";
state = QHttpNetworkConnectionChannel::IdleState;
return false;
} switch (state) {
case QHttpNetworkConnectionChannel::IdleState: { // write the header
if (!ensureConnection()) {
// wait for the connection (and encryption) to be done
// sendRequest will be called again from either
// _q_connected or _q_encrypted
return false;
}
written = ; // excluding the header
bytesTotal = ; QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
replyPrivate->clear();
replyPrivate->connection = connection;
replyPrivate->connectionChannel = this;
replyPrivate->autoDecompress = request.d->autoDecompress;
replyPrivate->pipeliningUsed = false; // if the url contains authentication parameters, use the new ones
// both channels will use the new authentication parameters
if (!request.url().userInfo().isEmpty() && request.withCredentials()) {
QUrl url = request.url();
QAuthenticator &auth = authenticator;
if (url.userName() != auth.user()
|| (!url.password().isEmpty() && url.password() != auth.password())) {
auth.setUser(url.userName());
auth.setPassword(url.password());
connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &auth, false);
}
// clear the userinfo, since we use the same request for resending
// userinfo in url can conflict with the one in the authenticator
url.setUserInfo(QString());
request.setUrl(url);
}
// Will only be false if QtWebKit is performing a cross-origin XMLHttpRequest
// and withCredentials has not been set to true.
if (request.withCredentials())
connection->d_func()->createAuthorization(socket, request);
#ifndef QT_NO_NETWORKPROXY
QByteArray header = QHttpNetworkRequestPrivate::header(request,
(connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
#else
QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
#endif
socket->write(header);
// flushing is dangerous (QSslSocket calls transmit which might read or error)
// socket->flush();
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
if (uploadByteDevice) {
// connect the signals so this function gets called again
QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead())); bytesTotal = request.contentLength(); state = QHttpNetworkConnectionChannel::WritingState; // start writing data
sendRequest(); //recurse
} else {
state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
sendRequest(); //recurse
} break;
}
case QHttpNetworkConnectionChannel::WritingState:
{
// write the data
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
if (!uploadByteDevice || bytesTotal == written) {
if (uploadByteDevice)
emit reply->dataSendProgress(written, bytesTotal);
state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
sendRequest(); // recurse
break;
} // only feed the QTcpSocket buffer when there is less than 32 kB in it
const qint64 socketBufferFill = *;
const qint64 socketWriteMaxSize = *; #ifndef QT_NO_OPENSSL
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
// if it is really an ssl socket, check more than just bytesToWrite()
while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : ))
<= socketBufferFill && bytesTotal != written)
#else
while (socket->bytesToWrite() <= socketBufferFill
&& bytesTotal != written)
#endif
{
// get pointer to upload data
qint64 currentReadSize = ;
qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written);
const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize); if (currentReadSize == -) {
// premature eof happened
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
return false;
break;
} else if (readPointer == || currentReadSize == ) {
// nothing to read currently, break the loop
break;
} else {
qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
if (currentWriteSize == - || currentWriteSize != currentReadSize) {
// socket broke down
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
return false;
} else {
written += currentWriteSize;
uploadByteDevice->advanceReadPointer(currentWriteSize); emit reply->dataSendProgress(written, bytesTotal); if (written == bytesTotal) {
// make sure this function is called once again
state = QHttpNetworkConnectionChannel::WaitingState;
sendRequest();
break;
}
}
}
}
break;
} case QHttpNetworkConnectionChannel::WaitingState:
{
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
if (uploadByteDevice) {
QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead()));
} // HTTP pipelining
//connection->d_func()->fillPipeline(socket);
//socket->flush(); // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called
// this is needed if the sends an reply before we have finished sending the request. In that
// case receiveReply had been called before but ignored the server reply
if (socket->bytesAvailable())
QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
break;
}
case QHttpNetworkConnectionChannel::ReadingState:
// ignore _q_bytesWritten in these states
// fall through
default:
break;
}
return true;
}
进行的底层的socket调用,不详细分析
QHttpNetworkConnection的构造中,有些我们感兴趣的东西:
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent)
{
Q_D(QHttpNetworkConnection);
d->networkSession = networkSession;
d->init();
}
继续跟进 init函数:
void QHttpNetworkConnectionPrivate::init()
{
for (int i = ; i < channelCount; i++) {
channels[i].setConnection(this->q_func());
channels[i].ssl = encrypt;
#ifndef QT_NO_BEARERMANAGEMENT
//push session down to channels
channels[i].networkSession = networkSession;
#endif
channels[i].init();
}
}
接下来看channels的init函数
void QHttpNetworkConnectionChannel::init()
{
#ifndef QT_NO_OPENSSL
if (connection->d_func()->encrypt)
socket = new QSslSocket;
else
socket = new QTcpSocket;
#else
socket = new QTcpSocket;
#endif
#ifndef QT_NO_BEARERMANAGEMENT
//push session down to socket
if (networkSession)
socket->setProperty("_q_networksession", QVariant::fromValue(networkSession));
#endif
#ifndef QT_NO_NETWORKPROXY
// Set by QNAM anyway, but let's be safe here
socket->setProxy(QNetworkProxy::NoProxy);
#endif QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
this, SLOT(_q_bytesWritten(qint64)),
Qt::DirectConnection);
QObject::connect(socket, SIGNAL(connected()),
this, SLOT(_q_connected()),
Qt::DirectConnection);
QObject::connect(socket, SIGNAL(readyRead()),
this, SLOT(_q_readyRead()),
Qt::DirectConnection); // The disconnected() and error() signals may already come
// while calling connectToHost().
// In case of a cached hostname or an IP this
// will then emit a signal to the user of QNetworkReply
// but cannot be caught because the user did not have a chance yet
// to connect to QNetworkReply's signals.
qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
QObject::connect(socket, SIGNAL(disconnected()),
this, SLOT(_q_disconnected()),
Qt::QueuedConnection);
QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(_q_error(QAbstractSocket::SocketError)),
Qt::QueuedConnection); #ifndef QT_NO_NETWORKPROXY
QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
Qt::DirectConnection);
#endif #ifndef QT_NO_OPENSSL
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
if (sslSocket) {
// won't be a sslSocket if encrypt is false
QObject::connect(sslSocket, SIGNAL(encrypted()),
this, SLOT(_q_encrypted()),
Qt::DirectConnection);
QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(_q_sslErrors(QList<QSslError>)),
Qt::DirectConnection);
QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
this, SLOT(_q_encryptedBytesWritten(qint64)),
Qt::DirectConnection);
}
#endif
}
看到了我们熟悉的QTcpSocket类,该类继承于QAbstractSocket,封装了平台socket
回到前面,继续看postRequst又做了哪些事情呢?再看代码
void QNetworkAccessHttpBackend::postRequest()
{
QThread *thread = ;
if (isSynchronous()) {
// A synchronous HTTP request uses its own thread
thread = new QThread();
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
} else if (!manager->httpThread) {
// We use the manager-global thread.
// At some point we could switch to having multiple threads if it makes sense.
manager->httpThread = new QThread();
QObject::connect(manager->httpThread, SIGNAL(finished()), manager->httpThread, SLOT(deleteLater()));
manager->httpThread->start();
#ifndef QT_NO_NETWORKPROXY
qRegisterMetaType<QNetworkProxy>("QNetworkProxy");
#endif
#ifndef QT_NO_OPENSSL
qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
qRegisterMetaType<QSslConfiguration>("QSslConfiguration");
#endif
qRegisterMetaType<QList<QPair<QByteArray,QByteArray> > >("QList<QPair<QByteArray,QByteArray> >");
qRegisterMetaType<QHttpNetworkRequest>("QHttpNetworkRequest");
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
qRegisterMetaType<QSharedPointer<char> >("QSharedPointer<char>"); thread = manager->httpThread;
} else {
// Asynchronous request, thread already exists
thread = manager->httpThread;
} QUrl url = request().url();
httpRequest.setUrl(url); bool ssl = url.scheme().toLower() == QLatin1String("https");
setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
httpRequest.setSsl(ssl); #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, only transparent proxies
if (!ssl
&& (p.capabilities() & QNetworkProxy::CachingCapability)
&& (p.type() == QNetworkProxy::HttpProxy ||
p.type() == QNetworkProxy::HttpCachingProxy)) {
cacheProxy = p;
transparentProxy = QNetworkProxy::NoProxy;
break;
}
if (p.isTransparentProxy()) {
transparentProxy = p;
cacheProxy = QNetworkProxy::NoProxy;
break;
}
} // check if at least one of the proxies
if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
cacheProxy.type() == QNetworkProxy::DefaultProxy) {
// unsuitable proxies
QMetaObject::invokeMethod(this, "error", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection,
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
Q_ARG(QString, tr("No suitable proxy found")));
QMetaObject::invokeMethod(this, "finished", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection);
return;
}
#endif bool loadedFromCache = false;
httpRequest.setPriority(convert(request().priority())); switch (operation()) {
case QNetworkAccessManager::GetOperation:
httpRequest.setOperation(QHttpNetworkRequest::Get);
loadedFromCache = loadFromCacheIfAllowed(httpRequest);
break; case QNetworkAccessManager::HeadOperation:
httpRequest.setOperation(QHttpNetworkRequest::Head);
loadedFromCache = loadFromCacheIfAllowed(httpRequest);
break; case QNetworkAccessManager::PostOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Post);
createUploadByteDevice();
break; case QNetworkAccessManager::PutOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Put);
createUploadByteDevice();
break; case QNetworkAccessManager::DeleteOperation:
invalidateCache();
httpRequest.setOperation(QHttpNetworkRequest::Delete);
break; case QNetworkAccessManager::CustomOperation:
invalidateCache(); // for safety reasons, we don't know what the operation does
httpRequest.setOperation(QHttpNetworkRequest::Custom);
createUploadByteDevice();
httpRequest.setCustomVerb(request().attribute(
QNetworkRequest::CustomVerbAttribute).toByteArray());
break; default:
break; // can't happen
} if (loadedFromCache) {
// commented this out since it will be called later anyway
// by copyFinished()
//QNetworkAccessBackend::finished();
return; // no need to send the request! :)
} QList<QByteArray> headers = request().rawHeaderList();
if (resumeOffset != ) {
if (headers.contains("Range")) {
// Need to adjust resume offset for user specified range headers.removeOne("Range"); // We've already verified that requestRange starts with "bytes=", see canResume.
QByteArray requestRange = request().rawHeader("Range").mid(); int index = requestRange.indexOf('-'); quint64 requestStartOffset = requestRange.left(index).toULongLong();
quint64 requestEndOffset = requestRange.mid(index + ).toULongLong(); requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
'-' + QByteArray::number(requestEndOffset); httpRequest.setHeaderField("Range", requestRange);
} else {
httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
}
} foreach (const QByteArray &header, headers)
httpRequest.setHeaderField(header, request().rawHeader(header)); if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
httpRequest.setPipeliningAllowed(true); if (static_cast<QNetworkRequest::LoadControl>
(request().attribute(QNetworkRequest::AuthenticationReuseAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
httpRequest.setWithCredentials(false); // Create the HTTP thread delegate
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
#ifndef QT_NO_BEARERMANAGEMENT
QVariant v(property("_q_networksession"));
if (v.isValid())
delegate->networkSession = qvariant_cast<QSharedPointer<QNetworkSession> >(v);
#endif // For the synchronous HTTP, this is the normal way the delegate gets deleted
// For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished
connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater())); // Set the properties it needs
delegate->httpRequest = httpRequest;
#ifndef QT_NO_NETWORKPROXY
delegate->cacheProxy = cacheProxy;
delegate->transparentProxy = transparentProxy;
#endif
delegate->ssl = ssl;
#ifndef QT_NO_OPENSSL
if (ssl)
delegate->incomingSslConfiguration = request().sslConfiguration();
#endif // Do we use synchronous HTTP?
delegate->synchronous = isSynchronous(); // The authentication manager is used to avoid the BlockingQueuedConnection communication
// from HTTP thread to user thread in some cases.
delegate->authenticationManager = manager->authenticationManager; if (!isSynchronous()) {
// Tell our zerocopy policy to the delegate
delegate->downloadBufferMaximumSize =
request().attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute).toLongLong(); // These atomic integers are used for signal compression
delegate->pendingDownloadData = pendingDownloadDataEmissions;
delegate->pendingDownloadProgress = pendingDownloadProgressEmissions; // Connect the signals of the delegate to us
connect(delegate, SIGNAL(downloadData(QByteArray)),
this, SLOT(replyDownloadData(QByteArray)),
Qt::QueuedConnection);
connect(delegate, SIGNAL(downloadFinished()),
this, SLOT(replyFinished()),
Qt::QueuedConnection);
connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
this, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
Qt::QueuedConnection);
connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
this, SLOT(replyDownloadProgressSlot(qint64,qint64)),
Qt::QueuedConnection);
connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
this, SLOT(httpError(QNetworkReply::NetworkError, const QString)),
Qt::QueuedConnection);
#ifndef QT_NO_OPENSSL
connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
this, SLOT(replySslConfigurationChanged(QSslConfiguration)),
Qt::QueuedConnection);
#endif
// Those need to report back, therefire BlockingQueuedConnection
connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
this, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
Qt::BlockingQueuedConnection);
#ifndef QT_NO_NETWORKPROXY
connect (delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
Qt::BlockingQueuedConnection);
#endif
#ifndef QT_NO_OPENSSL
connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
this, SLOT(replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *)),
Qt::BlockingQueuedConnection);
#endif
// This signal we will use to start the request.
connect(this, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
connect(this, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest())); // To throttle the connection.
QObject::connect(this, SIGNAL(readBufferSizeChanged(qint64)), delegate, SLOT(readBufferSizeChanged(qint64)));
QObject::connect(this, SIGNAL(readBufferFreed(qint64)), delegate, SLOT(readBufferFreed(qint64))); if (uploadByteDevice) {
QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice =
new QNonContiguousByteDeviceThreadForwardImpl(uploadByteDevice->atEnd(), uploadByteDevice->size());
if (uploadByteDevice->isResetDisabled())
forwardUploadDevice->disableReset();
forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
delegate->httpRequest.setUploadByteDevice(forwardUploadDevice); // From main thread to user thread:
QObject::connect(this, SIGNAL(haveUploadData(QByteArray, bool, qint64)),
forwardUploadDevice, SLOT(haveDataSlot(QByteArray, bool, qint64)), Qt::QueuedConnection);
QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
forwardUploadDevice, SIGNAL(readyRead()),
Qt::QueuedConnection); // From http thread to user thread:
QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
this, SLOT(wantUploadDataSlot(qint64)));
QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
this, SLOT(sentUploadDataSlot(qint64)));
connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
this, SLOT(resetUploadDataSlot(bool*)),
Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
}
} else if (isSynchronous()) {
connect(this, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection); if (uploadByteDevice) {
// For the synchronous HTTP use case the use thread (this one here) is blocked
// so we cannot use the asynchronous upload architecture.
// We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly
// use the uploadByteDevice provided to us by the QNetworkReplyImpl.
// The code that is in QNetworkReplyImplPrivate::setup() makes sure it is safe to use from a thread
// since it only wraps a QRingBuffer
delegate->httpRequest.setUploadByteDevice(uploadByteDevice.data());
}
} // Move the delegate to the http thread
delegate->moveToThread(thread);
// This call automatically moves the uploadDevice too for the asynchronous case. // Send an signal to the delegate so it starts working in the other thread
if (isSynchronous()) {
emit startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done if (delegate->incomingErrorCode != QNetworkReply::NoError) {
replyDownloadMetaData
(delegate->incomingHeaders,
delegate->incomingStatusCode,
delegate->incomingReasonPhrase,
delegate->isPipeliningUsed,
QSharedPointer<char>(),
delegate->incomingContentLength);
replyDownloadData(delegate->synchronousDownloadData);
httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
} else {
replyDownloadMetaData
(delegate->incomingHeaders,
delegate->incomingStatusCode,
delegate->incomingReasonPhrase,
delegate->isPipeliningUsed,
QSharedPointer<char>(),
delegate->incomingContentLength);
replyDownloadData(delegate->synchronousDownloadData);
} // End the thread. It will delete itself from the finished() signal
thread->quit();
thread->wait(); finished();
} else {
emit startHttpRequest(); // Signal to the HTTP thread and go back to user.
}
}
完了下面这些动作:
1、看Cache中是否保存有过去浏览的内容,如果有还要看是否超出生存时间(Expiration
Time);
2、设定Url、Header和数据内容(需要提交的数据);
3、调用QNetworkAccessHttpBackendCache::sendRequest()发送请求内容;
4、把QHttpNetworkReply的信号与QNetworkAccessHttpBackend的槽连接起来,完成后续处理。
分析QNetworkAccessManager的时候,有一段设定HTTP的请求包的Header,当时没进行深入的分析。
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
{
QHttpNetworkRequest &request = messagePair.first;
QHttpNetworkReply *reply = messagePair.second; // add missing fields for the request
QByteArray value;
// check if Content-Length is provided
QIODevice *data = request.data();
if (data && request.contentLength() == -) {
if (!data->isSequential())
request.setContentLength(data->size());
else
bufferData(messagePair); // ### or do chunked upload
}
// set the Connection/Proxy-Connection: Keep-Alive headers
#ifndef QT_NO_NETWORKPROXY
if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) {
value = request.headerField("proxy-connection");
if (value.isEmpty())
request.setHeaderField("Proxy-Connection", "Keep-Alive");
} else {
#endif
value = request.headerField("connection");
if (value.isEmpty())
request.setHeaderField("Connection", "Keep-Alive");
#ifndef QT_NO_NETWORKPROXY
}
#endif // If the request had a accept-encoding set, we better not mess
// with it. If it was not set, we announce that we understand gzip
// and remember this fact in request.d->autoDecompress so that
// we can later decompress the HTTP reply if it has such an
// encoding.
value = request.headerField("accept-encoding");
if (value.isEmpty()) {
#ifndef QT_NO_COMPRESS
request.setHeaderField("Accept-Encoding", "gzip");
request.d->autoDecompress = true;
#else
// if zlib is not available set this to false always
request.d->autoDecompress = false;
#endif
}
// set the User Agent
value = request.headerField("user-agent");
if (value.isEmpty())
request.setHeaderField("User-Agent", "Mozilla/5.0");
// set the host
value = request.headerField("host");
if (value.isEmpty()) {
QByteArray host = QUrl::toAce(hostName); int port = request.url().port();
if (port != -) {
host += ':';
host += QByteArray::number(port);
} request.setHeaderField("Host", host);
} reply->d_func()->requestIsPrepared = true;
}
如果想模拟IE浏览器,或者想修改成任何你希望的信息,就是在这里修改。
在设定了这些请求信息之后,发送的请求信息包是什么样子的呢?我把工具拦截的信息包具体情况贴出来
QT分析之网络编程的更多相关文章
- 5、QT分析之网络编程
原文地址:http://blog.163.com/net_worm/blog/static/127702419201002842553382/ 首先对Windows下的网络编程总结一下: 如果是服务器 ...
- Qt 多线程和网络编程学习
一,Qt多线程类学习 QThread类,开始一个新的线程就是开始执行重新实现QThread::run(),run()是默认现实调用exec(),QThread::start()开始线程的执行,run( ...
- Qt学习之网络编程(一)
一些说明 学了有一段时间的python了,小项目做了不少,最近由于项目需要,所以要回归老本行了,开始重点突击C++和qt.python的网络爬虫系列有时间就更吧. 获取本机网络信息 在网络应用中,经常 ...
- 5.关于QT中的网络编程,QTcpSocket,QUdpSocket
1 新建一个项目:TCPServer.pro A 修改TCPServer.pro,注意:如果是想使用网络库,需要加上network SOURCES += \ TcpServer.cpp \ T ...
- Qt学习之网络编程(二)
UDP协议 UDP协议(用户数据报协议)是一种简单轻量级.不可靠.面向数据报.无连接的传输层协议.之后我们会介绍TCP协议,相对于UDP,TCP是一种可靠的.有连接的协议:既然这样我们就用TCP不就好 ...
- UNIX网络编程——揭开网络编程常见API的面纱【上】
Linux网络编程API函数初步剖析 今天我们来分析一下前几篇博文中提到的网络编程中几个核心的API,探究一下当我们调用每个API时,内核中具体做了哪些准备和初始化工作. 1.socket(famil ...
- 揭开网络编程常见API的面纱【上】
Linux网络编程API函数初步剖析 今天我们来分析一下前几篇博文中提到的网络编程中几个核心的API,探究一下当我们调用每个API时,内核中具体做了哪些准备和初始化工作. 1.socket(famil ...
- Qt网络编程QTcpServer和QTcpSocket的理解
前一段时间通过调试Qt源码,大致了解了Qt的事件机制.信号槽机制.毕竟能力和时间有限.有些地方理解的并不是很清楚. 开发环境:Linux((fedora 17),Qt版本(qt-everywhere- ...
- 3、Qt Project之Socket网络编程
Socket网络编程 Step1:首先完成整个界面的设计 <?xml version="1.0" encoding="UTF-8"?> <u ...
随机推荐
- DMVPN的实验模拟与分析
此篇博客正在介绍的是下图中的DMVPN: 为什么会出现DMVPN这个技术呢? 在这篇博客中https://www.cnblogs.com/huwentao/p/9355240.html介绍过Dynam ...
- 简单复习一下ArrayList的扩容原理
刚刚跟几个好朋友喝完小酒回家,简单大概复习一下ArrayList的扩容原理,由于头有点小晕,就只大概说一下扩容的原理哈: 首先ArrayList实现了List接口,继承了AbstractList,大家 ...
- Dinic算法最大流入门
例题传送门 Dinic算法是网络流最大流的优化算法之一,每一步对原图进行分层,然后用DFS求增广路.时间复杂度是O(n^2*m),Dinic算法最多被分为n个阶段,每个阶段包括建层次网络和寻找增广路两 ...
- Mybatis简单入门
前言 之前一直有直接使用Mybatis,但是没有细致的整理出来.长时间没有使用,细致的内容都忘记了.因此借此机会,从头开始整理,以后可以直接查看此次记录的内容. Mybatis的介绍 MyBatis是 ...
- crontab执行PHP
在stackoverflow上看到一个问题:http://stackoverflow.com/questions/14015543/crontab-php-wget-or-curl 有三种通过cron ...
- 调用bash的时候出现curl command not found
调用bash的时候出现curl command not found 解决办法: apt-get install curl
- 内置方法(item系列)
class Foo: def __init__(self,name): self.name = name def __getitem__(self, item): # 获取时触发 print('get ...
- Fiddler - 拦截手机请求
1. 在电脑上安装Fillder. 安装好之后的Fiddler 打开是这样的: 2. 浏览器访问http://127.0.0.1:8888/fiddler,下载证书并安装 3. 打开抓取https请求 ...
- flex布局笔记
flex布局: 容器: 容器主轴方向: 项目的主轴对齐方式: space-between:两端对齐,项目之间的间隔都相等. space-around:每个项目两侧的间隔相等.所以,项目之间的间隔比项目 ...
- (原) MaterialEditor部- UmateriaEditor中 Node编译过程和使用(3)
@author: 白袍小道 转载说明原处 插件同步在GITHUB: DaoZhang_XDZ 说明 1.本篇是接着-----(原) MaterialEditor部- UmateriaE ...