天天看點

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);

#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);

    } else if (d->proxyInUse.capabilities() & QNetworkProxy::HostNameLookupCapability) {

        // the proxy supports connection by name, so use it

        d->startConnectingByName(hostName);

    } 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;

        if (!initSocketLayer(host.protocol())) {

            // hope that the next address is better

        // 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();

        // 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) {

        // 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;

    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());

    if (threadData->eventDispatcher)

        socketEngine->setReceiver(this);

    return true;

QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &proxy, QObject *parent)

    // proxy type must have been resolved by now

    if (proxy.type() == QNetworkProxy::DefaultProxy)

        return 0;

    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 (protocol == QAbstractSocket::IPv6Protocol) {

        d->setError(QAbstractSocket::UnsupportedSocketOperationError,

                    QNativeSocketEnginePrivate::NoIpV6ErrorString);

    // Create the socket

    if (!d->createNewSocket(socketType, protocol)) {

    // Make the socket nonblocking.

    if (!setOption(NonBlockingSocketOption, 1)) {

                    QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString);

    // Set the broadcasting flag if it's a UDP socket.

    if (socketType == QAbstractSocket::UdpSocket

        && !setOption(BroadcastSocketOption, 1)) {

                    QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString);

    // 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;

至此,初始化過程完成,socket被設定為非阻塞模式(也就是Select會逾時方式)。

2、connect到遠端目标

bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port)

    Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false);

#if defined (QT_NO_IPV6)

    if (address.protocol() == QAbstractSocket::IPv6Protocol) {

    if (!d->checkProxy(address))

    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;

連接配接相對簡單。

繼續閱讀