Qt深度分析之《网络编程详解(1)》

Qt深度分析之《网络编程详解(1)》

首先对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调用有关。

C/C++学习资料

各位同学们有需要提高编程技术水平、编程思维能力和动手开发实战能力。比如:C语言期末考试、C语言课程设计、参考全国计算机等级考试(二级C语言)、计算机考研C语言和数据结构、、计算机竞赛、蓝桥杯竞赛、【Windows C/C++高级工程师开发岗位技术栈】等等。现已推出最新课程,大家可以根据自己情况,选择适合自己的课程哦!

全套精品课程24小时在线学习,有需要的各位同学们可以点击下面链接即可:

【C语言与数据结构算法】精品课程链接

【C语言经典算法编程110道实战题】精品课程链接

【C++语言入门到精通】精品课程链接

【MFC C/C++工程师】精品课程链接

【QT C++开发工程师】精品课程链接

以上课程由浅入深、通俗易懂、融会贯通、理论与实战结合、大型企业级项目实战技术等。

各位朋友需要【C/C++高级工程师】技术栈全套学习资料请加QQ:726920220。

一、C语言与数据结构算法【视频教程150集及配套源码】

二、C语言经典算法110道实战题【视频教程110集及配套源码】

三、C++语言入门到精通【视频教程45集及配套源码】

四、MFC C/C++工程师【视频教程57集及配套源码】

备注:【MFC C/C++工程师课程】:本课程技术建立在C/C++编程基础之上,从事Windows开发岗位的技术栈(Win32编程、Windows消息机制、Windows常用开发高级控件、网络编程及多线程编程、进程间通讯技术、数据库与Windows程序编程技术结合应用实战,免费赠送18个企业项目源码,8个专业文档)。

需要【MFC C/C++工程师】开发岗位技术栈全套学习资料请加QQ:726920220。

【全套教学视频及配套源码,永久观看,定期升级更新】

【免费赠送18个项目及8个专业文档】

五、Qt C++开发工程师【视频教程75集及配套源码】

备注:【Qt C++工程师课程】:本课程技术建立在C/C++编程基础之上,从事Windows开发岗位的技术栈(本课程所覆盖的内容:1、C++编程语言;2、数据结构算法;3、Qt开发岗位技术(从入门到精通)全栈;4、MySQL数据库技术;5、SQLite数据库技术;6、Quick及QSS编程技术;7、综合实现【企业级项目实战开发】)。

【Qt C++开发工程师】开发岗位技术栈全套学习资料请加QQ:726920220。

【C/C++开发技术专家必读书籍】

按照这个思路,果然在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 = 0;
    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 = 0;
    d->peerPort = 0;
    d->localAddress.clear();
    d->peerAddress.clear();
    d->peerName = hostName;
    if (d->hostLookupId != -1) {
        QHostInfo::abortHostLookup(d->hostLookupId);
        d->hostLookupId = -1;
#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);
        // 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()),
                                 q, SLOT(_q_abortConnectionAttempt()),
                                 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 *)0;
#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 0;
#endif
    QMutexLocker locker(&socketHandlers()->mutex);
    for (int i = 0; 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, 1)) {
        d->setError(QAbstractSocket::UnsupportedSocketOperationError,
                    QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString);
        close();
        return false;
    // Make sure we receive out-of-band data
    if (socketType == QAbstractSocket::TcpSocket
        && !setOption(ReceiveOutOfBandData, 1)) {
        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(49152);
    setSendBufferSize(49152);
    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 -1;          // no socket engine is probably EOF
        qint64 readBytes = d->socketEngine->read(data, maxSize);
        if (readBytes < 0) {
            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(0) : qint64(-1);
    // If readFromSocket() read data, copy it to its destination.
    if (maxSize == 1) {
        *data = d->readBuffer.getChar();
        return 1;
    qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize);
    qint64 readSoFar = 0;
    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(), -1);
    Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1);
    qint64 readBytes = d->nativeRead(data, maxSize);
    // Handle remote close
    if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) {
        d->setError(QAbstractSocket::RemoteHostClosedError,
                    QNativeSocketEnginePrivate::RemoteHostClosedErrorString);
        close();
        return -1;
    return readBytes;
}

除了一些相关的检查,就是调用QNativeSocketPrivate::nativeRead()

qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength)
    qint64 ret = -1;
    WSABUF buf;
    buf.buf = data;
    buf.len = maxLength;
    DWORD flags = 0;
    DWORD bytesRead = 0;
#if defined(Q_OS_WINCE)
    WSASetLastError(0);
#endif
    if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) ==  SOCKET_ERROR) {
        int err = WSAGetLastError();
        WS_ERROR_DEBUG(err);
        switch (err) {
        case WSAEWOULDBLOCK:
            ret = -2;
            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 = 0;
            break;
        default:
            break;
    } else {
        if (WSAGetLastError() == WSAEWOULDBLOCK)
            ret = -2;
            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 -1;
    if (!d->isBuffered) {
        qint64 written = d->socketEngine->write(data, size);
        if (written < 0) {
            d->socketError = d->socketEngine->error();
            setErrorString(d->socketEngine->errorString());
        } else if (!d->writeBuffer.isEmpty()) {
            d->socketEngine->setWriteNotificationEnabled(true);
        if (written >= 0)
            emit bytesWritten(written);
        return written;
    char *ptr = d->writeBuffer.reserve(size);
    if (size == 1)
        *ptr = *data;
        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(), -1);
    Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -1);
    return d->nativeWrite(data, size);
qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len)
    Q_Q(QNativeSocketEngine);
    qint64 ret = 0;
    // don't send more than 49152 per call to WSASendTo to avoid getting a WSAENOBUFS
    for (;;) {
        qint64 bytesToSend = qMin<qint64>(49152, len - ret);
        WSABUF buf;
        buf.buf = (char*)data + ret;
        buf.len = bytesToSend;
        DWORD flags = 0;
        DWORD bytesWritten = 0;
        int socketRet = ::WSASend(socketDescriptor, &buf, 1, &bytesWritten, flags, 0,0);
        ret += qint64(bytesWritten);
        if (socketRet != SOCKET_ERROR) {
            if (ret == len)
                break;
                continue;
        } else if (WSAGetLastError() == WSAEWOULDBLOCK) {
            break;
        } else {
            int err = WSAGetLastError();
            WS_ERROR_DEBUG(err);
            switch (err) {
            case WSAECONNRESET:
            case WSAECONNABORTED:
                ret = -1;
                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 < 0)
        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[3] = { &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的介绍看了一遍。其使用方法大致是:

1 QNetworkAccessManager * manager = new QNetworkAccessManager(this);
2 QNetworkRequest request;
3 request.setUrl(QUrl("http://www.baidu.com"));
4 QNetworkReply * reply = manager->get(request);
5 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方法的实现。

1 QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
3     return d_func()->postProcess(createRequest(QNetworkAccessManager::GetOperation, request));
4 }

上面的一行程序中有两个调用:

1 QNetworkAccessManager::createRequest()
2 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());
                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 = 0;
            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();
            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 = 0;
    qint64 bytesToBuffer = 0;
    // 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 <= 0)
            bytesToBuffer = 2*1024;
        char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
        bytesBuffered = outgoingData->read(dst, bytesToBuffer);
        if (bytesBuffered == -1) {
            // EOF has been reached.
            outgoingDataBuffer->chop(bytesToBuffer);
            _q_bufferOutgoingDataFinished();
            break;
        } else if (bytesBuffered == 0) {
            // 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();
                backend->downstreamReadyWrite();
            break;
        case NotifyCloseDownstreamChannel:
            backend->closeDownstreamChannel();
            break;
        case NotifyCopyFinished: {
            QIODevice *dev = copyDevice;
            copyDevice = 0;
            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(),
                                                           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 0;
}

这段代码有一点复杂,先看红色标记的第一句,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衍生出来的。

一共有哪些子类呢?继续全局查找

1 class QNetworkAccessDataBackendFactory: public QNetworkAccessBackendFactory
2 class QNetworkAccessDebugPipeBackendFactory: public QNetworkAccessBackendFactory
3 class QNetworkAccessFileBackendFactory: public QNetworkAccessBackendFactory
4 class QNetworkAccessFtpBackendFactory: public QNetworkAccessBackendFactory
5 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 0;
    QUrl url = request.url();
    QString scheme = url.scheme().toLower();
    if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
        return new QNetworkAccessHttpBackend;
    return 0;
}

如果是能够处理的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之美丽。

1 void QNetworkAccessHttpBackend::open()
3     postRequest();
4 }

open函数仅仅是调用postRequest()

etworkAccessHttpBackend::postRequest()
    QThread *thread = 0;
    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 != 0) {
        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(6);
            int index = requestRange.indexOf('-');
            quint64 requestStartOffset = requestRange.left(index).toULongLong();
            quint64 requestEndOffset = requestRange.mid(index + 1).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(5000);
        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 ? 443 : 80));
#ifndef QT_NO_NETWORKPROXY
    if (transparentProxy.type() != QNetworkProxy::NoProxy)
        cacheKey = makeCacheKey(urlCopy, &transparentProxy);
    else if (cacheProxy.type() != QNetworkProxy::NoProxy)
        cacheKey = makeCacheKey(urlCopy, &cacheProxy);
#endif
        cacheKey = makeCacheKey(urlCopy, 0);
    // the http object is actually a QHttpNetworkConnection
    httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
    if (httpConnection == 0) {
        // 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:

1 QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request)
3     Q_D(QHttpNetworkConnection);
4     return d->queueRequest(request);
5 }

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[0]; // 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 = 0; 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 = 0; 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 = 0; 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 = 0; i < channelCount; ++i) {
        if (channels[i].socket->state() == QAbstractSocket::ConnectingState)
            queuedRequest--;
        if ( queuedRequest <=0 )
            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() != -1 && uploadByteDevice->size() != -1) {
            // both values known, take the smaller one.
            request.setContentLength(qMin(uploadByteDevice->size(), request.contentLength()));
        } else if (request.contentLength() == -1 && uploadByteDevice->size() != -1) {
            // content length not supplied by user, but the upload device knows it
            request.setContentLength(uploadByteDevice->size());
        } else if (request.contentLength() != -1 && uploadByteDevice->size() == -1) {
            // everything OK, the user supplied us the contentLength
        } else if (request.contentLength() == -1 && uploadByteDevice->size() == -1) {
            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);
            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 != -1) {
            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 = 0; // excluding the header
        bytesTotal = 0;
        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 = 32*1024;
        const qint64 socketWriteMaxSize = 16*1024;
#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() : 0))
                <= socketBufferFill && bytesTotal != written)
#else
        while (socket->bytesToWrite() <= socketBufferFill
               && bytesTotal != written)
#endif
            // get pointer to upload data
            qint64 currentReadSize = 0;
            qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written);
            const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
            if (currentReadSize == -1) {
                // premature eof happened
                connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
                return false;
                break;
            } else if (readPointer == 0 || currentReadSize == 0) {
                // nothing to read currently, break the loop
                break;
            } else {
                qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
                if (currentWriteSize == -1 || 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 = 0; 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;
        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);