从源码角度透视QTcpServer:解构QTcpServer的底层原理与技术细节

深入了解QTcpServer的底层原理和技术细节

  • 一、背景
  • 二、QTcpServer的基本原理
    • 2.1、TCP协议简介
    • 2.2、QTcpServer的概念
  • 三、QTcpServer源码解析
    • 3.1、QTcpServer的构造函数
    • 3.2、调用listen函数启动tcpserver
    • 3.3、QSocketNotifier的实现
  • 总结

一、背景

QTcpServer是Qt网络模块中的一个网络通信类,用于创建TCP服务器,允许应用程序监听并处理传入的TCP连接请求。QTcpServer的作用:

  1. QTcpServer提供了一个简单而强大的方式来实现服务器端的网络通信,轻松地创建TCP服务器应用程序。

  2. QTcpServer能够处理多个客户端同时连接,通过多线程或事件循环等机制实现并发处理,提高服务器端的性能和效率。

  3. QTcpServer封装了TCP协议的复杂细节,提供了更高级别的接口,简化了网络编程的复杂性。

  4. 通过QTcpServer可以构建稳定可靠的网络服务,如实时通讯、远程监控、数据传输等涉及网络通信的应用场景。

示例:使用QTcpServer实现一个基本的TCP服务器。

// main.cpp
#include <QCoreApplication>
#include "mytcpserver.h"

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    MyTcpServer server;
    server.startServer();

    return a.exec();
}
// mytcpserver.h
#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>

class MyTcpServer : public QTcpServer {
    Q_OBJECT

public:
    MyTcpServer(QObject *parent = nullptr);
    void startServer();

protected:
    void incomingConnection(qintptr socketDescriptor) override;

private slots:
    void onNewConnection();
    void onReadyRead();
    void onDisconnected();

private:
    QTcpSocket *clientSocket;
};

#endif // MYTCPSERVER_H
// mytcpserver.cpp
#include "mytcpserver.h"

MyTcpServer::MyTcpServer(QObject *parent) : QTcpServer(parent), clientSocket(nullptr) {
    connect(this, &MyTcpServer::newConnection, this, &MyTcpServer::onNewConnection);
}

void MyTcpServer::startServer() {
    if (!this->listen(QHostAddress::Any, 1234)) {
        qDebug() << "Server could not start!";
    } else {
        qDebug() << "Server started!";
    }
}

void MyTcpServer::incomingConnection(qintptr socketDescriptor) {
    clientSocket = new QTcpSocket(this);
    if (!clientSocket->setSocketDescriptor(socketDescriptor)) {
        qDebug() << "Error in setting socket descriptor";
        return;
    }

    connect(clientSocket, &QTcpSocket::readyRead, this, &MyTcpServer::onReadyRead);
    connect(clientSocket, &QTcpSocket::disconnected, this, &MyTcpServer::onDisconnected);

    qDebug() << "Client connected";
}

void MyTcpServer::onNewConnection() {
    qDebug() << "New connection available";
}

void MyTcpServer::onReadyRead() {
    QByteArray data = clientSocket->readAll();
    qDebug() << "Data received: " << data;
}

void MyTcpServer::onDisconnected() {
    qDebug() << "Client disconnected";
}

使用Qt的构建工具qmake和make(或者使用Qt Creator集成开发环境)编译。在项目文件夹中创建一个.pro文件(例如:mytcpserver.pro),内容如下:

QT       += core network

CONFIG   += c++11

TARGET = mytcpserver
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp \
    mytcpserver.cpp

HEADERS += mytcpserver.h

执行编译命令:

qmake mytcpserver.pro
make

可以看到,使用QTcpServer很容易就实现了一个TCP服务器,而且它使用异步事件的方式处理TCP客户端的连接,那么它是如何实现的异步机制呢?想了解QT的socket是基于什么模型来实现的,博主同样非常的感兴趣,所以看了QT关于TcpServer实现的相关源码,现在将所了解的内容记录下来。

二、QTcpServer的基本原理

2.1、TCP协议简介

TCP(Transmission Control Protocol,传输控制协议)是因特网协议套件中的一部分,它位于传输层,提供可靠的、面向连接的数据传输服务。

TCP协议的特点:

  1. 面向连接:在进行数据传输之前,TCP在通信双方之间建立连接,之后才会开始数据的传输。连接建立包括三步握手,以确保通信的正常进行。

  2. 可靠性:TCP协议通过序号、确认和重传等机制来确保数据的可靠传输。如果一个数据包未能正确传输,TCP会进行重传以保证数据的完整性。

  3. 流控制:TCP协议通过滑动窗口机制来进行流控制,确保发送方和接收方之间的数据传输速率相匹配,避免数据包的过载和丢失。

  4. 拥塞控制:TCP通过拥塞窗口和拥塞避免等机制来控制网络拥塞,避免过多的数据流量导致的网络拥堵,从而保证网络的稳定性和可靠性。

  5. 面向字节流:TCP是面向字节流的协议,它不会将数据分割成固定大小的数据包,而是按照应用程序传送的字节流来进行数据传输。

TCP协议提供了一种高可靠性的数据传输方式,适用于要求数据传输可靠性和顺序性的应用场景,如文件传输、电子邮件发送等。但是,与UDP相比,TCP在数据传输过程中有较高的开销。

2.2、QTcpServer的概念

QTcpServer是Qt框架中用于实现TCP服务器的类。它提供了一种简单而高效的方式,用于监听传入的TCP连接请求,从而可以与客户端建立连接并实现数据交换。

QTcpServer的主要作用:

  1. QTcpServer可以通过调用listen()方法,在指定的IP地址和端口上开始监听传入的连接请求。
  2. 监听到连接请求后通过nextPendingConnection()方法接受这些请求,获得一个QTcpSocket对象,用于与客户端进行数据交换。
  3. 通过重写incomingConnection()方法,在建立新连接时执行自定义的处理操作。
  4. QTcpServer可以管理多个TCP连接,并在需要的时候进行数据交换或断开连接。
  5. 通过信号和槽机制,QTcpServer可以处理连接建立、断开、数据到达等事件,实现灵活的连接管理和数据处理。

QTcpSocket同样是Qt框架中用于实现TCP网络通信的重要类。QTcpServer与QTcpSocket的关系:

  1. QTcpServer是用于实现TCP服务器的类,它负责监听传入的TCP连接请求,并与客户端建立连接;QTcpSocket则是用于实现TCP客户端的类,它负责与服务器建立连接并进行数据交换。

  2. 当QTcpServer监听到传入的连接请求时,它会返回一个QTcpSocket对象,该对象用于与客户端进行数据交换。这个QTcpSocket对象是表示与客户端建立的连接的句柄,通过它可以实现数据的发送和接收。

  3. QTcpServer可以管理多个QTcpSocket连接,接受多个客户端的连接请求,每个连接都有一个对应的QTcpSocket对象。

  4. QTcpSocket对象在与服务器建立连接后,可以向服务器发送数据,也可以接收来自服务器的数据。服务器端的QTcpServer则可以接受来自客户端的数据,并向客户端发送数据。

三、QTcpServer源码解析

Qt源码下载:

git clone https://code.qt.io/qt/qt5.git                     # cloning the repo
cd qt5
git checkout 5.14.2                                         # checking out the specific release or branch
perl init-repository

3.1、QTcpServer的构造函数

先从QTcpServer的构造函数来看,下面是QTcpServer的构造函数原型:

QTcpServer::QTcpServer(QObject *parent)
    : QObject(*new QTcpServerPrivate, parent)
{
    Q_D(QTcpServer);
#if defined(QTCPSERVER_DEBUG)
    qDebug("QTcpServer::QTcpServer(%p)", parent);
#endif
    d->socketType = QAbstractSocket::TcpSocket;
}

首先创建了一个QTcpServerPrivate的参数类。在QT源码中,每个类都有一个参数类,类名就是原类名加上Private。这个类主要放着QTcpServer类会用到的一些成员对象,而QTcpServer类里面只会定义方法,不会有成员对象。QTcpServerPrivate类的定义:

// qtcpserver_p.h
class Q_NETWORK_EXPORT QTcpServerPrivate : public QObjectPrivate,
                                           public QAbstractSocketEngineReceiver
{
    Q_DECLARE_PUBLIC(QTcpServer)
public:
    QTcpServerPrivate();
    ~QTcpServerPrivate();

    QList<QTcpSocket *> pendingConnections;

    quint16 port;
    QHostAddress address;

    QAbstractSocket::SocketType socketType;
    QAbstractSocket::SocketState state;
    QAbstractSocketEngine *socketEngine;

    QAbstractSocket::SocketError serverSocketError;
    QString serverSocketErrorString;

    int maxConnections;

#ifndef QT_NO_NETWORKPROXY
    QNetworkProxy proxy;
    QNetworkProxy resolveProxy(const QHostAddress &address, quint16 port);
#endif

    virtual void configureCreatedSocket();

    // from QAbstractSocketEngineReceiver
    void readNotification() override;
    void closeNotification() override { readNotification(); }
    void writeNotification() override {}
    void exceptionNotification() override {}
    void connectionNotification() override {}
#ifndef QT_NO_NETWORKPROXY
    void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *) override {}
#endif

};

然后QTcpServer构造函数内部实现就很简单了。Q_D(QTcpServer)宏实际上就是取到QTcpServerPrivate对象的指针赋给变量d:

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

d->socketType = QAbstractSocket::TcpSocket把套接字类型设置为Tcp。

至此,QTcpServer构造函数的工作结束。

3.2、调用listen函数启动tcpserver

一旦调用listen函数,tcpserver就开始运行了。接下来,连接、接收数据和发送数据的完成都可以通过信号来接收。那么,QT具体是如何实现等待连接和等待接收数据的呢?而且对于不同的平台又是如何实现的呢?我们来分析一下listen函数究竟都做了些什么工作。

(1)首先判断是否已是监听状态,是的话就直接返回。

Q_D(QTcpServer);
if (d->state == QAbstractSocket::ListeningState) {
    qWarning("QTcpServer::listen() called when already listening");
    return false;
}

(2)接着设置协议类型,IP地址、端口号。

    QAbstractSocket::NetworkLayerProtocol proto = address.protocol();
    QHostAddress addr = address;

#ifdef QT_NO_NETWORKPROXY
    static const QNetworkProxy &proxy = *(QNetworkProxy *)0;
#else
    QNetworkProxy proxy = d->resolveProxy(addr, port);
#endif

(3)然后创建了一个socketEngine对象,它的类型是QAbstractSocketEngine。QAbstractSocketEngine定义了很多与原始套接字机制相似的函数,比如bind、listen、accept等方法,还实现了waitForRead、writeDatagram、read等函数。所以当我们调用QSocket的读写方法时,实际上是由QAbstractSocketEngine类来实现的。不过,QAbstractSocketEngine本身是一个抽象类,不能直接实例化。在listen函数中,我们调用了QAbstractSocketEngine类的静态函数createSocketEngine来创建对象。

delete d->socketEngine;
d->socketEngine = QAbstractSocketEngine::createSocketEngine(d->socketType, proxy, this);
if (!d->socketEngine) {
    d->serverSocketError = QAbstractSocket::UnsupportedSocketOperationError;
    d->serverSocketErrorString = tr("Operation on socket is not supported");
    return false;
}

重点看一下createSocketEngine具体是怎么实现的:

QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(qintptr socketDescripter, QObject *parent)
{
    QMutexLocker locker(&socketHandlers()->mutex);
    for (int i = 0; i < socketHandlers()->size(); i++) {
        if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketDescripter, parent))
            return ret;
    }
    return new QNativeSocketEngine(parent);
}

在类似递归的所有条件判断之后,最终返回一个QNativeSocketEngine对象。QNativeSocketEngine继承了QAbstractSocketEngine类,并实现了QAbstractSocketEngine的所有功能。在这个类的具体代码中可以看到一些做平台判断的代码,以及与平台相关的套接字函数。QNativeSocketEngine的实现并不只是一个文件,它包括qnativesocketengine_unix.cpp、qnativesocketengine_win.cpp和qnativesocketengine_winrt.cpp。因此,当在Windows平台编译程序时,编译器会包含qnativesocketengine_win.cpp文件,在Linux下编译时会包含qnativesocketengine_unix.cpp文件。QT通过一个抽象类和不同平台的子类来实现跨平台的套接字机制。

(4)继续回到TcpServer的listen函数,创建了一个socketEngine对象后开始调用bind、listen等函数来完成最终的socket设置。

#ifndef QT_NO_BEARERMANAGEMENT
    //copy network session down to the socket engine (if it has been set)
    d->socketEngine->setProperty("_q_networksession", property("_q_networksession"));
#endif
    if (!d->socketEngine->initialize(d->socketType, proto)) {
        d->serverSocketError = d->socketEngine->error();
        d->serverSocketErrorString = d->socketEngine->errorString();
        return false;
    }
    proto = d->socketEngine->protocol();
    if (addr.protocol() == QAbstractSocket::AnyIPProtocol && proto == QAbstractSocket::IPv4Protocol)
        addr = QHostAddress::AnyIPv4;

    d->configureCreatedSocket();

    if (!d->socketEngine->bind(addr, port)) {
        d->serverSocketError = d->socketEngine->error();
        d->serverSocketErrorString = d->socketEngine->errorString();
        return false;
    }

    if (!d->socketEngine->listen()) {
        d->serverSocketError = d->socketEngine->error();
        d->serverSocketErrorString = d->socketEngine->errorString();
        return false;
    }

(5)接着开始设置信号接收,setReceiver传入TcpServerPrivate对象,从函数名可以看出是设置一个接收信息的对象,所以当套接字有新信息时,就会回调TcpServerPrivate对象的相关函数来实现消息通知。设置完消息接收对象以后,调用setReadNotificationEnabled(true)来启动消息监听。

d->socketEngine->setReceiver(d);
d->socketEngine->setReadNotificationEnabled(true);

setReadNotificationEnabled函数的实现如下:

void QNativeSocketEngine::setReadNotificationEnabled(bool enable)
{
    Q_D(QNativeSocketEngine);
    if (d->readNotifier) {
        d->readNotifier->setEnabled(enable);
    } else if (enable && d->threadData->hasEventDispatcher()) {
        d->readNotifier = new QReadNotifier(d->socketDescriptor, this);
        d->readNotifier->setEnabled(true);
    }
}

这个函数是创建了一个QReadNotifier对象,QReadNotifier的定义如下:

class QReadNotifier : public QSocketNotifier
{
public:
    QReadNotifier(qintptr fd, QNativeSocketEngine *parent)
        : QSocketNotifier(fd, QSocketNotifier::Read, parent)
    { engine = parent; }

protected:
    bool event(QEvent *) override;

    QNativeSocketEngine *engine;
};

bool QReadNotifier::event(QEvent *e)
{
    if (e->type() == QEvent::SockAct) {
        engine->readNotification();
        return true;
    } else if (e->type() == QEvent::SockClose) {
        engine->closeNotification();
        return true;
    }
    return QSocketNotifier::event(e);
}

QReadNotifier其实就是继承了QSocketNotifier。QSocketNotifier是一个消息处理类,主要用来监听文件描述符的活动。也就是说,当文件描述符状态发生变化时,就会触发相应的信息。它可以监听三种状态:Read(读)、Write(写)、Exception(异常)。而我们这里用到的QReadNotifier主要是监听Read事件,也就是说当套接字句柄有可读消息时(连接信息也是可读信息的一种),就会调用event函数。在event函数中,我们回调了engine->readNotification()函数。readNotification函数的实现如下:

void QTcpServerPrivate::readNotification()
{
    Q_Q(QTcpServer);
    for (;;) {
        if (pendingConnections.count() >= maxConnections) {
#if defined (QTCPSERVER_DEBUG)
            qDebug("QTcpServerPrivate::_q_processIncomingConnection() too many connections");
#endif
            if (socketEngine->isReadNotificationEnabled())
                socketEngine->setReadNotificationEnabled(false);
            return;
        }

        int descriptor = socketEngine->accept();
        if (descriptor == -1) {
            if (socketEngine->error() != QAbstractSocket::TemporaryError) {
                q->pauseAccepting();
                serverSocketError = socketEngine->error();
                serverSocketErrorString = socketEngine->errorString();
                emit q->acceptError(serverSocketError);
            }
            break;
        }
#if defined (QTCPSERVER_DEBUG)
        qDebug("QTcpServerPrivate::_q_processIncomingConnection() accepted socket %i", descriptor);
#endif
        q->incomingConnection(descriptor);

        QPointer<QTcpServer> that = q;
        emit q->newConnection();
        if (!that || !q->isListening())
            return;
    }
}

在这个函数里面调用了socketEngine->accept()来获取套接字句柄,然后将其传递给q->incomingConnection(descriptor)来创建QTcpSocket对象。最后发送了emit q->newConnection()信号。如果使用过QTcpServer,那么对这个信号应该很熟悉。

因此,QT通过内部消息机制实现了套接字的异步通信。并且,对外提供的函数既支持同步机制,也支持异步机制。调用者可以选择通过信号槽机制来实现异步,也可以调用类似waitforread、waitforconnect等函数来实现同步等待。实际上,waitforread等同步函数是通过函数内部的循环来检查消息标志。当标志为可读或者函数超时时则返回。

3.3、QSocketNotifier的实现

在前面提到了使用QSocketNotifier,可以在套接字有可读或可写信号时调用event函数来实现异步通知。但是,QSocketNotifier又是如何知道套接字什么时候发生变化的呢?QSocketNotifier的实现和QT的消息处理机制是密切相关的。要完全讲清楚这一点,就必须涉及到QT的消息机制。

这里只把比较关键的代码抽取出来分析一下。首先,不同平台的消息处理机制都是不一样的,所以QSocketNotifier在不同平台下的实现也是不一样的。我们主要看一下Linux平台下是如何实现的。

(1)QSocketNotifier类的声明和定义,用于通知状况以支持异步 IO(输入/输出)操作。

class QSocketNotifierPrivate;
class Q_CORE_EXPORT QSocketNotifier : public QObject
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(QSocketNotifier)

public:
    enum Type { Read, Write, Exception };

    QSocketNotifier(qintptr socket, Type, QObject *parent = nullptr);
    ~QSocketNotifier();

    qintptr socket() const;
    Type type() const;

    bool isEnabled() const;

public Q_SLOTS:
    void setEnabled(bool);

Q_SIGNALS:
    void activated(int socket, QPrivateSignal);

protected:
    bool event(QEvent *) override;

private:
    Q_DISABLE_COPY(QSocketNotifier)
};

QSocketNotifier类同时声明了一个嵌套类QSocketNotifierPrivate,这个类用来实现QSocketNotifier的私有方法和属性。QSocketNotifier类继承自QObject类,并且使用了Q_OBJECT宏来支持信号和槽机制以及元对象特性。

QSocketNotifier类还提供了一些函数,用于获取套接字句柄、设置套接字的类型(可读、可写、异常)、获取状态等。可以通过setEnable(bool)槽函数来设置通知器的状态(开启或关闭)。通过activated(int socket, QPrivateSignal)信号来通知有关套接字状态的改变。采用了Q_DISABLE_COPY宏来禁止类的复制构造函数和赋值运算符的使用。

(2)SocketNotifier构造函数:

QSocketNotifier::QSocketNotifier(qintptr socket, Type type, QObject *parent)
    : QObject(*new QSocketNotifierPrivate, parent)
{
    Q_D(QSocketNotifier);
    d->sockfd = socket;
    d->sntype = type;
    d->snenabled = true;

    if (socket < 0)
        qWarning("QSocketNotifier: Invalid socket specified");
    else if (!d->threadData->hasEventDispatcher())
        qWarning("QSocketNotifier: Can only be used with threads started with QThread");
    else
        d->threadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
}

QSocketNotifier的构造函数需要传入一个套接字句柄以及要监听的类型,比如读、写或错误。然后在构造函数里调用了QSocketNotifierPrivate的registerSocketNotifier函数,将自己注册进去。这样当有消息触发时,就能调用这个对象的event函数了。

(3)registerSocketNotifier函数:

/*****************************************************************************
 QEventDispatcher implementations for UNIX
 *****************************************************************************/

void QEventDispatcherUNIX::registerSocketNotifier(QSocketNotifier *notifier)
{
    Q_ASSERT(notifier);
    int sockfd = notifier->socket();
    QSocketNotifier::Type type = notifier->type();
#ifndef QT_NO_DEBUG
    if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
        qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");
        return;
    }
#endif

    Q_D(QEventDispatcherUNIX);
    QSocketNotifierSetUNIX &sn_set = d->socketNotifiers[sockfd];

    if (sn_set.notifiers[type] && sn_set.notifiers[type] != notifier)
        qWarning("%s: Multiple socket notifiers for same socket %d and type %s",
                 Q_FUNC_INFO, sockfd, socketType(type));

    sn_set.notifiers[type] = notifier;
}

在这个函数里面主要是将对象和套接字句柄sockfd作为映射放入socketNotifiers里面。

QHash<int, QSocketNotifierSetUNIX> socketNotifiers;

(4)processEvents函数处理所有消息,Linux平台的实现如下:

bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherUNIX);
    d->interrupt.storeRelaxed(0);

    // we are awake, broadcast it
    emit awake();
    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);

    const bool include_timers = (flags & QEventLoop::X11ExcludeTimers) == 0;
    const bool include_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0;
    const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents;

    const bool canWait = (d->threadData->canWaitLocked()
                          && !d->interrupt.loadRelaxed()
                          && wait_for_events);

    if (canWait)
        emit aboutToBlock();

    if (d->interrupt.loadRelaxed())
        return false;

    timespec *tm = nullptr;
    timespec wait_tm = { 0, 0 };

    if (!canWait || (include_timers && d->timerList.timerWait(wait_tm)))
        tm = &wait_tm;

    d->pollfds.clear();
    d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0));

    if (include_notifiers)
        for (auto it = d->socketNotifiers.cbegin(); it != d->socketNotifiers.cend(); ++it)
            d->pollfds.append(qt_make_pollfd(it.key(), it.value().events()));

    // This must be last, as it's popped off the end below
    d->pollfds.append(d->threadPipe.prepare());

    int nevents = 0;

    switch (qt_safe_poll(d->pollfds.data(), d->pollfds.size(), tm)) {
    case -1:
        perror("qt_safe_poll");
        break;
    case 0:
        break;
    default:
        nevents += d->threadPipe.check(d->pollfds.takeLast());
        if (include_notifiers)
            nevents += d->activateSocketNotifiers();
        break;
    }

    if (include_timers)
        nevents += d->activateTimers();

    // return true if we handled events, false otherwise
    return (nevents > 0);
}

可以看到一个处理套接字相关的函数qt_safe_poll。看看它的内部实现:

int qt_safe_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts)
{
    if (!timeout_ts) {
        // no timeout -> block forever
        int ret;
        EINTR_LOOP(ret, qt_ppoll(fds, nfds, nullptr));
        return ret;
    }

    timespec start = qt_gettime();
    timespec timeout = *timeout_ts;

    // loop and recalculate the timeout as needed
    forever {
        const int ret = qt_ppoll(fds, nfds, &timeout);
        if (ret != -1 || errno != EINTR)
            return ret;

        // recalculate the timeout
        if (!time_update(&timeout, start, *timeout_ts)) {
            // timeout during update
            // or clock reset, fake timeout error
            return 0;
        }
    }
}

qt_safe_poll调用了qt_ppoll,qt_ppoll的定义如下:

static inline int qt_ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts)
{
#if QT_CONFIG(poll_ppoll) || QT_CONFIG(poll_pollts)
    return ::ppoll(fds, nfds, timeout_ts, nullptr);
#elif QT_CONFIG(poll_poll)
    return ::poll(fds, nfds, timespecToMillisecs(timeout_ts));
#elif QT_CONFIG(poll_select)
    return qt_poll(fds, nfds, timeout_ts);
#else
    // configure.json reports an error when everything is not available
#endif
}

这里通过QT_CONFIG的标志来判断采用哪种实现。qt_poll是QT自己的函数,在早期的版本中可能都是用select模式。但是从QT5.7开始,采用了poll模式。博主使用的是QT5.14.2版本,也是采用的poll模式。选择poll模式的原因是因为select模式监听的套接字长度是用的定长数组,无法在运行时扩展。一旦套接字数量超过FD_SETSIZE就会返回错误,在Linux默认的设置中,FD_SETSIZE是1024。

qt_poll的实现如下,内部使用的是select:

int qt_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts)
{
    if (!fds && nfds) {
        errno = EFAULT;
        return -1;
    }

    fd_set read_fds, write_fds, except_fds;
    struct timeval tv, *ptv = 0;

    if (timeout_ts) {
        tv = timespecToTimeval(*timeout_ts);
        ptv = &tv;
    }

    int n_bad_fds = 0;

    for (nfds_t i = 0; i < nfds; i++) {
        fds[i].revents = 0;

        if (fds[i].fd < 0)
            continue;

        if (fds[i].events & QT_POLL_EVENTS_MASK)
            continue;

        if (qt_poll_is_bad_fd(fds[i].fd)) {
            // Mark bad file descriptors that have no event flags set
            // here, as we won't be passing them to select below and therefore
            // need to do the check ourselves
            fds[i].revents = POLLNVAL;
            n_bad_fds++;
        }
    }

    forever {
        const int max_fd = qt_poll_prepare(fds, nfds, &read_fds, &write_fds, &except_fds);

        if (max_fd < 0)
            return max_fd;

        if (n_bad_fds > 0) {
            tv.tv_sec = 0;
            tv.tv_usec = 0;
            ptv = &tv;
        }

        const int ret = ::select(max_fd, &read_fds, &write_fds, &except_fds, ptv);

        if (ret == 0)
            return n_bad_fds;

        if (ret > 0)
            return qt_poll_sweep(fds, nfds, &read_fds, &write_fds, &except_fds);

        if (errno != EBADF)
            return -1;

        // We have at least one bad file descriptor that we waited on, find out which and try again
        n_bad_fds += qt_poll_mark_bad_fds(fds, nfds);
    }
}

其实Linux还有更高效的IO多路复用器,叫做epoll。

(5)activateSocketNotifiers函数处理事件:

int QEventDispatcherUNIXPrivate::activateSocketNotifiers()
{
    markPendingSocketNotifiers();

    if (pendingNotifiers.isEmpty())
        return 0;

    int n_activated = 0;
    QEvent event(QEvent::SockAct);

    while (!pendingNotifiers.isEmpty()) {
        QSocketNotifier *notifier = pendingNotifiers.takeFirst();
        QCoreApplication::sendEvent(notifier, &event);
        ++n_activated;
    }

    return n_activated;
}

在processEvents函数中调用了qt_safe_poll来检查是否有套接字事件。如果有事件需要处理,就会调用activateSocketNotifiers函数,而在这个函数中会通过QCoreApplication::sendEvent(notifier, &event)将消息反馈给QSocketNotifier

通过这个过程,可以了解到Qt在Linux下使用select或者poll来实现输入输出复用的具体流程。但是具体采用哪种方式取决于使用的Qt版本。

总结

针对Qt 5.14.2的QTcpServer源码分析,QTcpServer的异步事件默认采用的poll模式(通过QT_CONFIG的标志来判断采用哪种实现,在早期的版本中可能都是用select模式。但是从QT5.7开始,采用了poll模式),poll模式解决了select模式监听的套接字长度是定长数组的问题,但是对事件的响应还是通过轮询的方式。

QTcpServer的调用函数栈:

创建
设置
调用
触发
返回
QTcpServer Constructor
Create QTcpServerPrivate object
Set socketType as TcpSocket
Call QTcpServerPrivate::configureCreatedSocket()
qt_safe_poll
Initialize socketEngine
Set socketEngine as readNotification enabled
SocketEngine's readNotification()
Handle incoming connections
Emit newConnection signal

QTcpServer是QT网络模块中用于实现TCP服务器的类,其底层原理和技术细节包括:

  1. 基于操作系统的套接字(socket):QTcpServer利用操作系统提供的套接字接口来实现TCP通信,包括创建、绑定、监听和接受连接等操作。

  2. 事件循环机制:QTcpServer通常结合QT框架的事件循环机制使用,通过在事件循环中监听新连接事件,实现异步处理客户端的连接请求。

  3. 信号与槽机制:QTcpServer通过信号与槽机制实现对新连接的处理,当有新连接时触发相应的信号,可以连接到相应的槽函数进行处理。

  4. 多线程支持:QTcpServer可以在多线程环境下使用,通过QThread或QtConcurrent等机制,在新的线程中处理连接,并确保线程安全。

  5. 处理并发连接:QTcpServer能够处理并发的客户端连接,可以通过多路复用技术(如select或poll)来实现高效的并发处理。在Linux下可能采用select或者poll来实现输入输出复用。

  6. 处理网络错误:QTcpServer能够处理网络错误和异常情况,通过QAbstractSocket提供的错误处理机制,能够及时响应并处理网络异常,保证服务器的稳定性和可靠性。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/364818.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

花瓣网美女图片爬取

爬虫基础案例01 花瓣网美女图片 网站url&#xff1a;https://huaban.com 图片爬取 import requests import json import os res requests.get(url "https://api.huaban.com/search/file?text%E7%BE%8E%E5%A5%B3&sortall&limit40&page1&positionsear…

『C++成长记』string使用指南

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、string类介绍 二、string类的常用接口说明 &#x1f4d2;2.1string类对象的常…

数据结构+算法(第10篇):叉堆“功夫熊猫”的速成之路

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

Elasticsearch:集群故障排除和优化综合指南

Elasticsearch 是一个强大的搜索和分析引擎&#xff0c;是许多数据驱动应用程序和服务的核心。 它实时处理、分析和存储大量数据的能力使其成为当今快节奏的数字世界中不可或缺的工具。 然而&#xff0c;与任何复杂的系统一样&#xff0c;Elasticsearch 可能会遇到影响其性能和…

【ACL 2023】Enhancing Document-level EAE with Contextual Clues and Role Relevance

【ACL 2023】Enhancing Document-level Event Argument Extraction with Contextual Clues and Role Relevance 论文&#xff1a;https://aclanthology.org/2023.findings-acl.817/ 代码&#xff1a;https://github.com/LWL-cpu/SCPRG-master Abstract 与句子级推理相比&…

linux中的静态库和共享库

库&#xff1a;库是二进制文件&#xff0c;是源代码文件的另一种表现形式&#xff0c;是加了密的源代码&#xff1b;是一些功能相近或者相似函数的集合体 库的使用&#xff1a; 头文件--包含了库函数的声明 库文件--包含了库函数的代码实现 注意&#xff1a;库不能单独使用…

应用智能家居领域中的低功耗蓝牙模块

智能家居&#xff08;smart home, home automation&#xff09;是以住宅为平台&#xff0c;利用综合布线技术、网络通信技术、 安全防范技术、自动控制技术、音视频技术将家居生活有关的设施集成&#xff0c;构建高效的住宅设施与家庭日程事务的管理系统&#xff0c;提升家居安…

某零售公司竞聘上岗项目成功案例纪实

——建立科学选人标准、评价方法&#xff0c;实现人岗匹配 【客户行业】零售业&#xff1b;销售行业 【问题类型】竞聘上岗 【客户背景】 半月&#xff08;化名&#xff09;有限公司成立于2008年&#xff0c;以母婴零售为基础&#xff0c;始终坚持以客户为导向&#xff0c;…

Stable diffusion使用和操作流程

Stable Diffusion是一个文本到图像的潜在扩散模型,由CompVis、Stability AI和LAION的研究人员和工程师创建。它使用来自LAION-5B数据库子集的512x512图像进行训练。使用这个模型,可以生成包括人脸在内的任何图像,因为有开源的预训练模型,所以我们也可以在自己的机器上运行它…

第1章 简单使用 Linux

第1章 简单使用 Linux 1.1 Linux 的组成 1.2 远程连接 首先以 root 用户登录到 Linux 系统&#xff0c;然后在 Terminal 终端上输入 ip add 命令&#xff0c;来查看 IP 地址。 上图中的 192.168.72.128 就是 IP 地址。 然后打开 XShell 远程连接工具。 然后在命令提示符下输…

C++ Webserver从零开始:基础知识(七)——多进程编程

前言 在学习操作系统时&#xff0c;我们知道现代计算机往往都是多进程多线程的&#xff0c;多进程和多线程技术能大大提高了CPU的利用率&#xff0c;因此在web服务器的设计中&#xff0c;不可避免地要涉及到多进程多线程技术。 这一章将简要讲解web服务器中的多进程编程&#x…

养猫家庭必备猫用空气净化器哪款牌子好?宠物空气净化器值得推荐的品牌

养宠家庭的朋友们都知道&#xff0c;猫咪的浮毛无处不在&#xff0c;每天都会在空气中飘荡。无论是沙发、地板还是衣服&#xff0c;都成了浮毛的重灾区。这些浮毛不仅难以清理&#xff0c;而且对于呼吸道敏感的人来说&#xff0c;可能会引发过敏反应。为了除去猫毛&#xff0c;…

Zoho Mail 2023:回顾过去,展望未来:不断进化的企业级邮箱解决方案

当我们告别又一个非凡的一年时&#xff0c;我们想回顾一下Zoho Mail如何融合传统与创新。我们迎来了成立15周年&#xff0c;这是一个由客户、合作伙伴和我们的敬业团队共同庆祝的里程碑。与我们一起回顾这段旅程&#xff0c;探索定义Zoho Mail历史篇章的敏捷性、精确性和创新性…

2024美赛预测算法 | 回归预测 | Matlab基于WOA-LSSVM鲸鱼算法优化最小二乘支持向量机的数据多输入单输出回归预测

2024美赛预测算法 | 回归预测 | Matlab基于WOA-LSSVM鲸鱼算法优化最小二乘支持向量机的数据多输入单输出回归预测 目录 2024美赛预测算法 | 回归预测 | Matlab基于WOA-LSSVM鲸鱼算法优化最小二乘支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果…

vit细粒度图像分类(八)SIM-Trans学习笔记

1.摘要 细粒度视觉分类(FGVC)旨在从相似的从属类别中识别物体&#xff0c;这对人类准确的自动识别需求具有挑战性和实用性。大多数FGVC方法侧重于判别区域挖掘的注意机制研究&#xff0c;而忽略了它们之间的相互依赖关系和组成的整体对象结构&#xff0c;而这些对模型的判别信…

防御保护---防火墙双机热备直路部署(上下三层接口)

防御保护---防火墙双机热备直路部署&#xff08;上下三层接口&#xff09; 一、根据网段划分配置IP地址和安全区域二、配置动态路由OSPF三、配置双机热备四、测试&#xff1a;4.1 测试一&#xff1a;查看状态和路由器路由表&#xff08;双机热备&#xff09;前后对比4.2 测试二…

2024年美赛数学建模B题思路分析 - 搜索潜水器

# 1 赛题 问题B&#xff1a;搜索潜水器 总部位于希腊的小型海上巡航潜艇&#xff08;MCMS&#xff09;公司&#xff0c;制造能够将人类运送到海洋最深处的潜水器。潜水器被移动到该位置&#xff0c;并不受主船的束缚。MCMS现在希望用他们的潜水器带游客在爱奥尼亚海底探险&…

2024年美赛美国大学生数学建模竞赛BCEF题思路解析+代码+论文

下文包含&#xff1a;2024年美国大学生数学建模竞赛&#xff08;美赛&#xff09;A- F题思路解析、选题建议、代码可视化及如何准备数学建模竞赛&#xff08;2号发&#xff09; 将会第一时间发布选题建议、所有题目的思路解析、相关代码、参考文献、参考论文等多项资料&#x…

配网故障预警定位装置_故障预警_故障定位_深圳恒峰

随着社会经济的快速发展&#xff0c;电力需求不断增长&#xff0c;电力系统的安全稳定运行对于国家经济发展和民生改善具有重要意义。然而&#xff0c;电力系统的复杂性和不确定性使得设备故障、线路跳闸等问题时有发生&#xff0c;给电力系统的正常运行带来极大隐患。为了解决…

前后端分离,RSA加密传输方案

1.原理 RSA是一种非对称加密算法。通过生成密钥对&#xff0c;用公钥加密&#xff0c;用私钥解密。对于前后端分离的项目&#xff0c;让前端获取到公钥对敏感数据加密&#xff0c;发送到后端&#xff0c;后端用私钥对加密后的数据进行解密即可。 2.实现 RSA工具类&#xff1…