从源码角度透视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_OBJECTpublic: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 networkCONFIG   += c++11TARGET = mytcpserver
CONFIG += console
CONFIG -= app_bundleTEMPLATE = appSOURCES += main.cpp \mytcpserver.cppHEADERS += 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);
#endifd->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_NETWORKPROXYQNetworkProxy proxy;QNetworkProxy resolveProxy(const QHostAddress &address, quint16 port);
#endifvirtual void configureCreatedSocket();// from QAbstractSocketEngineReceivervoid readNotification() override;void closeNotification() override { readNotification(); }void writeNotification() override {}void exceptionNotification() override {}void connectionNotification() override {}
#ifndef QT_NO_NETWORKPROXYvoid 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_NETWORKPROXYstatic const QNetworkProxy &proxy = *(QNetworkProxy *)0;
#elseQNetworkProxy 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"));
#endifif (!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");
#endifif (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);
#endifq->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_OBJECTQ_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");elsed->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_DEBUGif (notifier->thread() != thread() || thread() != QThread::currentThread()) {qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");return;}
#endifQ_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 itemit 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 belowd->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 otherwisereturn (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 foreverint 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 neededforever {const int ret = qt_ppoll(fds, nfds, &timeout);if (ret != -1 || errno != EINTR)return ret;// recalculate the timeoutif (!time_update(&timeout, start, *timeout_ts)) {// timeout during update// or clock reset, fake timeout errorreturn 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 ourselvesfds[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 againn_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提供的错误处理机制,能够及时响应并处理网络异常,保证服务器的稳定性和可靠性。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker 容器指定主机网段

docker 容器指定主机网段。 直接连接到物理网络&#xff1a;使用macvlan技术可以让Docker容器直接连接到物理网络&#xff0c;而不需要通过NAT或端口映射的方式来访问它们。可以提高网络性能和稳定性&#xff0c;同时也可以使容器更加透明和易于管理。 1、首先需要查询网卡的…

vuex store,mutations,getters,actions

文章目录 1.vuex概述2.构建vuex【多组件数据共享】环境Son1.vueSon2.vueApp.vue 3.创建一个空仓库4.如何提供&访问vuex的数据①核心概念 - state状态1.通过store直接访问2.通过辅助函数简化代码 ②核心概念 - mutations&#xff08;粗略&#xff09; 5.核心概念 - mutation…

Leetcode29-最大频率元素计数(3005)

1、题目 给你一个由 正整数 组成的数组 nums 。 返回数组 nums 中所有具有 最大 频率的元素的 总频率 。 元素的 频率 是指该元素在数组中出现的次数。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,2,3,1,4] 输出&#xff1a;4 解释&#xff1a;元素 1 和 2 的频率为 …

python3基础学习一

打印print()函数 R 主要的原因是为防止转义&#xff0c;保证r后面的内容全文输出f “主要作用就是格式化字符串&#xff0c;加上f以后&#xff0c;{“变量/表达式”},花括号里的变量和表达式就可以使用了” def ptSubstr():msg "hello,world!"print(msg[0:-1]);prin…

在Django Admin添加快捷方式django-admin-shortcuts

在Django管理主页上添加简单漂亮的快捷方式。 1.安装 pip install django-admin-shortcuts 2在settings.py注册django-admin-shortcuts INSTALLED_APPS [admin_shortcuts,django.contrib.admin,....... ] 3.添加ADMIN_SHORTCUTS设置 ADMIN_SHORTCUTS [ { ti…

k8s二进制及负载均衡集群部署详解

目录 常见部署方式 二进制部署流程 环境准备 操作系统初始化配置 关闭防火墙 配置SELinux 关闭SWAP 根据规划设置主机名 在master添加hosts&#xff0c;便于主机名解析 调整内核参数 配置时间同步 部署docker引擎 在所有node节点部署docker引擎 部署etcd集群 签发…

合约短线高胜率策略-扭转乾坤指标使用说明

扭转乾坤指标使用说明 行情判断 双绿线 多趋势双红线 空趋势大绿线 小红线 多震荡大红线 小绿线 空震荡 进场条件 趋势行情进场 多趋势 多信号 底金叉 做多空趋势 空信号 顶死叉 做空 震荡行情进场 多震荡 多信号 底金叉 做多多震荡 空信号 顶死叉 做空空…

idea docker 镜像生成太慢太大问题

文章目录 前言一、更小的jdk基础镜像二、服务瘦包&#xff08;thin jar&#xff09;2.1 maven2.2 修改dockerfile2.3 container run options 三、 基础jdk镜像入手&#xff1f;总结 前言 idea docker 内网应用实践遗留问题 idea docker插件 build 服务镜像太慢服务镜像太大 …

【Java程序设计】【C00223】基于Springboot+vue的图书购物商城(论文)

基于Springbootvue的图书购物商城&#xff08;论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springbootvue的前后端分离的图书商城购物系统 本系统分为用户以及管理员2个角色。 用户&#xff1a;用户登录后、可以查看新上架的书籍和新闻等…

npm ERR! reason: certificate has expired(淘宝镜像过期)

npm ERR! request to https://registry.npm.taobao.org/yauzl/-/yauzl-2.4.1.tgz failed, reason: certificate has expired 今天在执行npm install命令时&#xff0c;报错百度了下是淘宝证书过期原因 解决方法一 执行下面两个命令再进行npm install即可 npm cache clean --…

【深度学习】从0完整讲透深度学习第2篇:TensorFlow介绍和基本操作(代码文档已分享)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论深度学习相关知识。可以让大家熟练掌握机器学习基础,如分类、回归&#xff08;含代码&#xff09;&#xff0c;熟练掌握numpy,pandas,sklearn等框架使用。在算法上&#xff0c;掌握神经网络的数学原理&#xff0c;手动实…

02链表:19、删除链表的倒数第N个节点

19、删除链表的倒数第N个节点 文章目录 19、删除链表的倒数第N个节点方法一&#xff1a;快慢指针 思路&#xff1a;使用虚拟头节点快慢指针&#xff0c;fast指针先走n1&#xff0c;直到为null&#xff0c;slow节点刚好在删除元素前一个位置&#xff0c;方便操作 重点&#xff1…

2024美赛数学建模B题思路源码

赛题目的 赛题目的&#xff1a; 问题描述&#xff1a; 解题的关键&#xff1a; 问题一. 问题分析 问题解答 问题二. 问题分析 问题解答 问题三. 问题分析 问题解答 问题四. 问题分析 问题解答 问题五. 问题分析 问题解答

云计算基础(云计算概述)

目录 一、云计算概述 1.1 云计算的概念 1.1.1 云计算解决的问题 1.1.2 云计算的概念 1.1.3 云计算的组成 1.2 云计算主要特征 1.2.1 按需自助服务 1.2.2 泛在接入 1.2.3 资源池化 1.2.4 快速伸缩性 1.2.5 服务可度量 1.3 云计算服务模式 1.3.1 软件即服务(Softwar…

老师罚学生钱违法吗

在教师岗位上耕耘了近十年&#xff0c;我遇到过无数的学生和无数的教学情境。其中&#xff0c;有一个问题始终困扰着我&#xff1a;在某些情况下&#xff0c;我能否用“罚钱”的方式来纠正学生的行为&#xff1f;当然&#xff0c;这还涉及到许多复杂的因素&#xff1a;学校的规…

Pandas快问快答1-15题

如果你是一名使用python进行过数据处理的程序员&#xff0c;那你对Pandas必然不陌生。pandas是一个开源的第三方Python库&#xff0c;旨在提供快速、灵活和富有表现力的数据结构&#xff0c;以便能够简单、直观地处理关系型和标记型数据。它的名字来源于面板数据&#xff08;Pa…

Linux项目的挂起与结束

后台挂起 nohup java -jar java_gobang.jar & 结束项目 先查询pid ps -ef | grep java_gobang 结束&#xff1a; kill -9 23183

Ruoyi-Cloud-Plus_Nacos配置服务漏洞CVE-2021-29441_官方解决方法以及_修改源码解决---SpringCloud工作笔记199

CVE-2021-29441 这个漏洞是Nacos的,通过使用postman,直接访问接口: 就可以直接添加nacos的用户 Nacos是Alibaba的一个动态服务发现、配置和服务管理平台。攻击者通过添加Nacos-Server的User-Agent头部将可绕过(nacos.core.auth.enabled=true)鉴权认证,从而进行API操作。 …

工业智能网关构建智慧污水处理远程监测及管理

污水处理厂是为了处理生活污水和工业废水而建立的设施。为了监测和控制污水处理过程&#xff0c;现代污水处理厂采用了智能工业网关物联网技术。智慧污水系统能够通过工业网关远程监测厂内各个环节的运行情况&#xff0c;提高处理效率和管理水平。 智能工业网关能够将不同设备…

C语言第十六弹---操作符(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 操作符 1、下标访问[]、函数调用() 1.1、[ ] 下标引用操作符 1.2、函数调用操作符 2、结构成员访问操作符 2.1、结构体 2.1.1、结构的声明 2.1.2、结构体变…