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);
    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()),
                                 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;
}
连接相对简单。

时间: 2024-08-01 09:12:51

QT分析之网络编程(二)的相关文章

QT分析之网络编程(一)

首先对Windows下的网络编程总结一下: 如果是服务器,其WinSDK调用分别为: WSAStartup() -> socket() -> htons() / htonl() -> bind() -> listen() -> accept() -> recv() / send() -> closesocket() -> WSACleanup() 如果是客户端程序,其调用序列为: WSAStartup() -> socket() -> htons

QT分析之网络编程(七)

接上面,进一步分析QNetworkAccessManager::createRequest()的实现.去除不重要的分支末节,看其调用的QNetworkReplyImplPrivate::setup()和QNetworkAccessManagerPrivate::findBackend()的代码. void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req,

QT分析之网络编程(六)

在动手分析前,简单介绍一下HTTP协议.HTTP协议是一种为分布式,合作式,超媒体信息系统.它是一种通用的,无状态(stateless)的协议,除了应用于超文本传输外,它也可以应用于诸如名称服务器和分布对象管理系统之类的系统,这可以通过扩展它的请求方法,错误代码和报头来实现.HTTP的一个特点是数据表现形式是可输入的和可协商性的,这就允许系统能被建立而独立于数据传输.HTTP在1990年WWW全球信息刚刚起步的时候就得到了应用.该规范定义的协议用"HTTP/1.1"表示,是对RFC26

QT分析之网络编程(五)

今天分析QNetworkAccessManager.QNetworkRequest和QNetworkReply组成的高级抽象API序列.在动手之前,把doc中有关QNetworkAccessManager的介绍看了一遍.其使用方法大致是: QNetworkAccessManager * manager = new QNetworkAccessManager(this); QNetworkRequest request; request.setUrl(QUrl("http://www.baidu.

QT分析之网络编程(八)

话说昨日走到QNetworkReplyImplPrivate::_q_startOperation(),勾引出QNetworkAccessHttpBackend::open(),今日接着欣赏QT之美丽. void QNetworkAccessHttpBackend::open() {     QUrl url = request().url();     bool encrypt = url.scheme().toLower() == QLatin1String("https");  

QT分析之网络编程(三)

3.读取信息 在QAbstractSocket中,有两个成员是收发数据用的:readData().writeData() readData()有两种读取方式:有缓冲和无缓冲方式.基本原理是一致的,简单其见只分析无缓冲直接读取方式. qint64 QAbstractSocket::readData(char *data, qint64 maxSize) {     Q_D(QAbstractSocket);     if (d->socketEngine && !d->socke

QT分析之网络编程(四)

前面分析中,一个问题一直没有解决:新生成的SOCKET是什么时候加入WSASelect()的?另外还有一个不是很大的问题,close流程. 在QEventDispatcherWin32Private::doWsaAsyncSelect()中WSAAsyncSelect()设置一个断点,观察call stack: >    QtCored4.dll!QEventDispatcherWin32Private::doWsaAsyncSelect(int socket=0x00001628)  行633

Windows Socket网络编程(二) 套接字编程原理

一.客户机/服务器模式 在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Server model).该模式的建立基于以下两点:1.非对等作用:2.通信完全是异步的.客户机/服务器模式在操作过程中采取的是主动请示方式: 首先服务器方要先启动,并根据请示提供相应服务:(过程如下) 1.打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求. 2.等待客户请求到达该端口. 3.接收到重复服务请求,处理该请求并发送应答信号. 4.返回第二步,等待另一客户

实例分析J2ME网络编程的两种方法

编程|网络 本文描述了在J2me中开发主要使用的网络连接方法,分别详细介绍了使用http和socket两种方法. HttpConnection 首先我们先来看一个简单的例子吧: 主要用到的java包: javax.microedition.io.*; public String requestGET(String URLString,String URL) throws IOException{ // ===============================================