Qt源码分析--QAbstractSocket类

QAbstractSocket类提供了网络通信的基础接口,包括connectToHost(), writeData(), readData()和disconnectFromHost()等关键函数。它定义了连接状态如ConnectedState, UnconnectedState等,并使用信号和槽机制处理网络事件。该类作为QTcpSocket和QUdpSocket的基类,用于处理网络数据传输和错误管理。connectToHost()会检查套接字状态并启动连接流程,writeData()和readData()分别负责数据写入和读取,而disconnectFromHost()则用于断开连接。" 8175395,674828,深入理解MyBatis <dynamic> 标签,"['MyBatis框架', 'SQL动态生成', '数据库交互']

QAbstractSocket类的头文件通常包含了一些重要的成员函数、信号和槽以及一些常量定义。

  1. 成员函数:
  • connectToHost():用于与指定的主机和端口建立连接。
  • writeData():向套接字写入数据。
  • readData():从套接字读取数据。
  • disconnectFromHost():关闭套接字连接。
  • error():返回套接字的错误状态。
  1. 信号和槽:
  • connected():在套接字成功连接到主机时触发的信号。
  • readyRead():当套接字有可读数据时触发的信号。
  • error():在套接字发生错误时触发的信号。
  1. 常量定义:
  • ConnectedState:表示套接字已连接的状态。
  • UnconnectedState:表示套接字未连接的状态。
  • HostLookupState:表示套接字正在进行主机查找的状态。

QAbstractSocket类是一个抽象类,不能直接实例化,而是通过其具体子类(如QTcpSocket和QUdpSocket)来使用。它提供了一组通用的接口和功能,用于处理网络通信和数据传输。

1.connectToHost

设置套接字的参数并发起与指定主机和端口的连接操作。

void QAbstractSocket::connectToHost(const QString &hostName, quint16 port,
                                    OpenMode openMode,
                                    NetworkLayerProtocol protocol)
{
    Q_D(QAbstractSocket);
#if defined(QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::connectToHost(\"%s\", %i, %i)...", qPrintable(hostName), port,
           (int) openMode);
#endif
    if (d->state == ConnectedState || d->state == ConnectingState
        || d->state == ClosingState || d->state == HostLookupState) {
        qWarning("QAbstractSocket::connectToHost() called when already looking up or connecting/connected to \"%s\"", qPrintable(hostName));
        d->setErrorAndEmit(OperationError, tr("Trying to connect while connection is in progress"));
        return;
    }
    d->preferredNetworkLayerProtocol = protocol;
    d->hostName = hostName;
    d->port = port;
    d->setReadChannelCount(0);
    d->setWriteChannelCount(0);
    d->abortCalled = false;
    d->pendingClose = false;
    if (d->state != BoundState) {
        d->state = UnconnectedState;
        d->localPort = 0;
        d->localAddress.clear();
    }
    d->peerPort = 0;
    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->setErrorAndEmit(UnsupportedSocketOperationError,
                           tr("Operation on socket is not supported"));
        return;
    }
#endif
    // Sync up with error string, which open() shall clear.
    d->socketError = UnknownSocketError;
    if (openMode & QIODevice::Unbuffered)
        d->isBuffered = false;
    else if (!d_func()->isBuffered)
        openMode |= QAbstractSocket::Unbuffered;
    QIODevice::open(openMode);
    d->readChannelCount = d->writeChannelCount = 0;
#ifndef Q_OS_WINRT
    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.loadRelaxed()->hasEventDispatcher()) {
            // this internal API for QHostInfo either immediately gives us the desired
            // QHostInfo from cache or later calls the _q_startConnecting slot.
            bool immediateResultValid = false;
            QHostInfo hostInfo = qt_qhostinfo_lookup(hostName,
                                                     this,
                                                     SLOT(_q_startConnecting(QHostInfo)),
                                                     &immediateResultValid,
                                                     &d->hostLookupId);
            if (immediateResultValid) {
                d->hostLookupId = -1;
                d->_q_startConnecting(hostInfo);
            }
        }
    }
#if defined(QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::connectToHost(\"%s\", %i) == %s%s", hostName.toLatin1().constData(), port,
           (d->state == ConnectedState) ? "true" : "false",
           (d->state == ConnectingState || d->state == HostLookupState)
           ? " (connection in progress)" : "");
#endif
#else // !Q_OS_WINRT
    // On WinRT we should always connect by name. Lookup and proxy handling are done by the API.
    d->startConnectingByName(hostName);
#endif
}

函数的主要功能如下:

  1. 通过d->state检查当前套接字的状态,如果处于连接状态、连接中状态、关闭状态或主机查找状态,则输出警告信息并返回。
  2. 设置首选的网络层协议、主机名和端口,并重置读写通道的计数。
  3. 如果当前套接字的状态不是绑定状态(BoundState),将其设置为未连接状态,并清空本地地址和端口。
  4. 清空对等方地址和端口,并将对等方名称(peerName)设置为主机名(hostName)。
  5. 如果存在主机查找操作,中止它(abortHostLookup)。
  6. 如果启用了网络代理,解析代理信息d->resolveProxy(hostName, port)并检查代理设置是否成功。如果代理设置失败,设置错误信息并返回。
  7. 同步套接字错误状态(d->socketError)。
  8. 根据打开模式打开套接字。如果使用了QIODevice::Unbuffered模式,禁用缓冲。如果套接字本身未启用缓冲且未指定QIODevice::Unbuffered模式,则将其添加到打开模式中。
  9. 重置读写通道计数。(d->readChannelCount = d->writeChannelCount = 0;)
  10. 根据操作系统类型,设置套接字状态为主机查找状态,并发出stateChanged信号。如果主机名可以解析为IP地址,则创建QHostInfo对象,并调用_q_startConnecting槽函数开始连接。如果代理支持按名称连接,则使用代理进行连接。否则,如果具有事件分发器,使用qt_qhostinfo_lookup函数进行主机查找,如果立即返回结果,则直接调用_q_startConnecting槽函数。
  11. 输出调试信息。

2. writeData

根据套接字的状态和类型,将数据写入套接字的引擎或缓冲区,并处理相应的错误状态和通知机制。负责处理套接字数据的写入操作。

/*! \reimp
*/
qint64 QAbstractSocket::writeData(const char *data, qint64 size)
{
    Q_D(QAbstractSocket);
    if (d->state == QAbstractSocket::UnconnectedState
        || (!d->socketEngine && d->socketType != TcpSocket && !d->isBuffered)) {
        d->setError(UnknownSocketError, tr("Socket is not connected"));
        return -1;
    }
    if (!d->isBuffered && d->socketType == TcpSocket
        && d->socketEngine && d->writeBuffer.isEmpty()) {
        // This code is for the new Unbuffered QTcpSocket use case
        qint64 written = size ? d->socketEngine->write(data, size) : Q_INT64_C(0);
        if (written < 0) {
            d->setError(d->socketEngine->error(), d->socketEngine->errorString());
        } else if (written < size) {
            // Buffer what was not written yet
            d->writeBuffer.append(data + written, size - written);
            written = size;
            d->socketEngine->setWriteNotificationEnabled(true);
        }
#if defined (QABSTRACTSOCKET_DEBUG)
        qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data,
               qt_prettyDebug(data, qMin((int)size, 32), size).data(),
               size, written);
#endif
        return written; // written = actually written + what has been buffered
    } else if (!d->isBuffered && d->socketType != TcpSocket) {
        // This is for a QUdpSocket that was connect()ed
        qint64 written = d->socketEngine->write(data, size);
        if (written < 0)
            d->setError(d->socketEngine->error(), d->socketEngine->errorString());
#if defined (QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data,
           qt_prettyDebug(data, qMin((int)size, 32), size).data(),
           size, written);
#endif
        if (written >= 0)
            d->emitBytesWritten(written);
        return written;
    }
    // This is the code path for normal buffered QTcpSocket or
    // unbuffered QTcpSocket when there was already something in the
    // write buffer and therefore we could not do a direct engine write.
    // We just write to our write buffer and enable the write notifier
    // The write notifier then flush()es the buffer.
    d->writeBuffer.append(data, size);
    qint64 written = size;
    if (d->socketEngine && !d->writeBuffer.isEmpty())
        d->socketEngine->setWriteNotificationEnabled(true);
#if defined (QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data,
           qt_prettyDebug(data, qMin((int)size, 32), size).data(),
           size, written);
#endif
    return written;
}
  1. 代码开始的 Q_D(QAbstractSocket) 用于获取 QAbstractSocket 的私有实例指针,以便访问该类的私有成员。

  2. 首先,代码检查套接字的状态。如果套接字的状态是未连接状态(UnconnectedState),或者套接字没有有效的 socketEngine(套接字引擎),且套接字类型不是 TcpSocket 且没有缓冲,那么设置套接字的错误状态为 "Socket is not connected",并返回 -1 表示写入失败。

  3. 接下来,代码检查套接字的类型和缓冲状态。如果套接字不是缓冲的且是 TcpSocket 类型,且具有有效的 socketEngine,并且写缓冲区为空,则执行特定于新的未缓冲(Unbuffered)QTcpSocket 的代码。在这种情况下,调用 socketEngine 的 write() 函数将数据直接写入套接字引擎,返回实际写入的字节数。如果写入的字节数小于数据的大小,则将未写入的部分存储在 writeBuffer 中,并启用写通知以便后续写入。

  4. 如果套接字不是缓冲的且类型不是 TcpSocket,那么执行相应于已连接的 QUdpSocket 的代码。在这种情况下,调用 socketEngine 的 write() 函数将数据直接写入套接字引擎,返回实际写入的字节数。如果写入失败,则设置错误状态。如果写入成功,则通过 emitBytesWritten() 函数发送写入的字节数。

  5. 如果是普通的带缓冲的 QTcpSocket 或者是已经有数据存在于写缓冲区中的未缓冲 QTcpSocket,将数据追加到 writeBuffer 中,并启用套接字引擎的写通知以便后续刷新缓冲区。

  6. 最后,函数返回写入的字节数。

3.readData

该函数用于从套接字中读取数据。

qint64 QAbstractSocket::readData(char *data, qint64 maxSize)
{
    Q_D(QAbstractSocket);
    // if we're not connected, return -1 indicating EOF
    if (!d->socketEngine || !d->socketEngine->isValid() || d->state != QAbstractSocket::ConnectedState)
        return maxSize ? qint64(-1) : qint64(0);
    qint64 readBytes = (maxSize && !d->isBuffered) ? d->socketEngine->read(data, maxSize)
                                                   : qint64(0);
    if (readBytes == -2) {
        // -2 from the engine means no bytes available (EAGAIN) so read more later
        readBytes = 0;
    }
    if (readBytes < 0) {
        d->setError(d->socketEngine->error(), d->socketEngine->errorString());
        d->resetSocketLayer();
        d->state = QAbstractSocket::UnconnectedState;
    } else {
        // Only do this when there was no error
        d->hasPendingData = false;
        d->socketEngine->setReadNotificationEnabled(true);
    }
#if defined (QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [engine]",
           data, qt_prettyDebug(data, 32, readBytes).data(), maxSize,
           readBytes);
#endif
    return readBytes;
}
  1. 代码开始的 Q_D(QAbstractSocket) 用于获取 QAbstractSocket 的私有实例指针,以便访问该类的私有成员。

  2. 首先,代码检查套接字的状态。如果套接字引擎无效(socketEngine 为空或不是有效的套接字引擎)或套接字状态不是已连接状态(ConnectedState),则根据 maxSize 的值返回适当的结果。如果 maxSize 为零,则返回 0 表示没有可读取的数据;否则返回 -1 表示已到达文件末尾。

  3. 如果 maxSize 不为零且套接字未缓冲(isBuffered 为 false),则调用 socketEngine 的 read() 函数从套接字引擎中读取数据,并将读取的字节数赋值给 readBytes。否则,将 readBytes 设置为 0。

  4. 如果 readBytes 的值为 -2,表示套接字引擎中没有可用的字节(EAGAIN),因此将 readBytes 设置为 0。

  5. 如果 readBytes 小于 0,表示发生了读取错误,此时设置套接字的错误状态为套接字引擎的错误状态,并重置套接字的底层状态,并将套接字的状态设置为未连接状态(UnconnectedState)。

  6. 如果读取操作没有发生错误,则将 hasPendingData 设置为 false,表示没有待处理的数据,并启用套接字引擎的读通知。

  7. 最后,函数返回读取的字节数。

4.disconnectFromHost

该函数用于断开与当前主机的连接。

void QAbstractSocket::disconnectFromHost()
{
    Q_D(QAbstractSocket);
#if defined(QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::disconnectFromHost()");
#endif
    if (d->state == UnconnectedState) {
#if defined(QABSTRACTSOCKET_DEBUG)
        qDebug("QAbstractSocket::disconnectFromHost() was called on an unconnected socket");
#endif
        return;
    }
    if (!d->abortCalled && (d->state == ConnectingState || d->state == HostLookupState)) {
#if defined(QABSTRACTSOCKET_DEBUG)
        qDebug("QAbstractSocket::disconnectFromHost() but we're still connecting");
#endif
        d->pendingClose = true;
        return;
    }
    // Disable and delete read notification
    if (d->socketEngine)
        d->socketEngine->setReadNotificationEnabled(false);
    if (d->abortCalled) {
#if defined(QABSTRACTSOCKET_DEBUG)
        qDebug("QAbstractSocket::disconnectFromHost() aborting immediately");
#endif
        if (d->state == HostLookupState) {
            QHostInfo::abortHostLookup(d->hostLookupId);
            d->hostLookupId = -1;
        }
    } else {
        // Perhaps emit closing()
        if (d->state != ClosingState) {
            d->state = ClosingState;
#if defined(QABSTRACTSOCKET_DEBUG)
            qDebug("QAbstractSocket::disconnectFromHost() emits stateChanged()(ClosingState)");
#endif
            emit stateChanged(d->state);
        } else {
#if defined(QABSTRACTSOCKET_DEBUG)
            qDebug("QAbstractSocket::disconnectFromHost() return from delayed close");
#endif
        }
        // Wait for pending data to be written.
        if (d->socketEngine && d->socketEngine->isValid() && (!d->allWriteBuffersEmpty()
            || d->socketEngine->bytesToWrite() > 0)) {
            d->socketEngine->setWriteNotificationEnabled(true);
#if defined(QABSTRACTSOCKET_DEBUG)
            qDebug("QAbstractSocket::disconnectFromHost() delaying disconnect");
#endif
            return;
        } else {
#if defined(QABSTRACTSOCKET_DEBUG)
            qDebug("QAbstractSocket::disconnectFromHost() disconnecting immediately");
#endif
        }
    }
    SocketState previousState = d->state;
    d->resetSocketLayer();
    d->state = UnconnectedState;
    emit stateChanged(d->state);
    emit readChannelFinished();       // we got an EOF
    // only emit disconnected if we were connected before
    if (previousState == ConnectedState || previousState == ClosingState)
        emit disconnected();
    d->localPort = 0;
    d->peerPort = 0;
    d->localAddress.clear();
    d->peerAddress.clear();
    d->peerName.clear();
    d->setWriteChannelCount(0);
#if defined(QABSTRACTSOCKET_DEBUG)
        qDebug("QAbstractSocket::disconnectFromHost() disconnected!");
#endif
}
  1. 函数开始时,进行一些用于调试的输出。

  2. 检查套接字的当前状态。如果状态是UnconnectedState,表示套接字未连接到任何主机,函数直接返回。

  3. 如果套接字正在连接中(ConnectingState或HostLookupState),并且abortCalled标志未设置,则将pendingClose标志设置为true,表示需要延迟断开连接直到连接过程完成。然后函数返回。

  4. 如果套接字引擎可用,禁用读通知(setReadNotificationEnabled(false);)。这一步确保在断开连接后不会再接收到读通知。

  5. 如果abortCalled标志被设置,表示套接字将立即中止。如果套接字处于HostLookupState状态,使用QHostInfo::abortHostLookup()函数中止主机查找过程。然后继续下一步。

  6. 如果套接字不处于ClosingState状态,将状态设置为ClosingState,并发出stateChanged()信号。这一步表示套接字正在关闭的过程中。

  7. 接下来,检查是否有待写入的数据。如果写缓冲区中有数据待写入或还有待写的字节数,启用写通知并返回。这个延迟操作确保在断开连接之前将待写入的数据写入完成。

  8. 如果没有待写入的数据,立即进行断开连接的过程。

  9. 存储当前套接字的状态,并重置套接字层。然后将状态设置为UnconnectedState,并发出stateChanged()信号。

  10. 发出readChannelFinished()信号,表示没有更多数据可读取。

  11. 如果先前的状态是ConnectedState或ClosingState,发出disconnected()信号,表示套接字已断开连接。

  12. 重置与套接字的本地和远程地址、端口以及写通道计数等相关的成员变量。

  13. 最后,输出一条调试信息,表示断开连接过程完成。

5.error

返回套接字的错误状态。

QAbstractSocket::SocketError QAbstractSocket::error() const
{
    return d_func()->socketError;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值