首页
最新活动
服务器租用
香港服务器租用
台湾服务器租用
美国服务器租用
日本服务器租用
新加坡服务器租用
高防服务器
香港高防服务器
台湾高防服务器
美国高防服务器
裸金属
香港裸金属服务器
台湾裸金属服务器
美国裸金属服务器
日本裸金属服务器
新加坡裸金属服务器
云服务器
香港云服务器
台湾云服务器
美国云服务器
日本云服务器
CDN
CDN节点
CDN带宽
CDN防御
CDN定制
行业新闻
官方公告
香港服务器资讯
帮助文档
wp博客
zb博客
服务器资讯
联系我们
关于我们
机房介绍
机房托管
登入
注册
帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
联系客服
服务器资讯
/
香港服务器租用
/
香港VPS租用
/
香港云服务器
/
美国服务器租用
/
台湾服务器租用
/
日本服务器租用
/
官方公告
/
帮助文档
从源码角度透视QTcpServer:解构QTcpServer的底层原理与技术细节
发布时间:2024-03-03 05:53:45 分类:帮助文档
从源码角度透视QTcpServer:解构QTcpServer的底层原理与技术细节 深入了解QTcpServer的底层原理和技术细节 一、背景二、QTcpServer的基本原理2.1、TCP协议简介2.2、QTcpServer的概念 三、QTcpServer源码解析3.1、QTcpServer的构造函数3.2、调用listen函数启动tcpserver3.3、QSocketNotifier的实现 总结 一、背景 QTcpServer是Qt网络模块中的一个网络通信类,用于创建TCP服务器,允许应用程序监听并处理传入的TCP连接请求。QTcpServer的作用: QTcpServer提供了一个简单而强大的方式来实现服务器端的网络通信,轻松地创建TCP服务器应用程序。 QTcpServer能够处理多个客户端同时连接,通过多线程或事件循环等机制实现并发处理,提高服务器端的性能和效率。 QTcpServer封装了TCP协议的复杂细节,提供了更高级别的接口,简化了网络编程的复杂性。 通过QTcpServer可以构建稳定可靠的网络服务,如实时通讯、远程监控、数据传输等涉及网络通信的应用场景。 示例:使用QTcpServer实现一个基本的TCP服务器。 // main.cpp #include
#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
#include
#include
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协议的特点: 面向连接:在进行数据传输之前,TCP在通信双方之间建立连接,之后才会开始数据的传输。连接建立包括三步握手,以确保通信的正常进行。 可靠性:TCP协议通过序号、确认和重传等机制来确保数据的可靠传输。如果一个数据包未能正确传输,TCP会进行重传以保证数据的完整性。 流控制:TCP协议通过滑动窗口机制来进行流控制,确保发送方和接收方之间的数据传输速率相匹配,避免数据包的过载和丢失。 拥塞控制:TCP通过拥塞窗口和拥塞避免等机制来控制网络拥塞,避免过多的数据流量导致的网络拥堵,从而保证网络的稳定性和可靠性。 面向字节流:TCP是面向字节流的协议,它不会将数据分割成固定大小的数据包,而是按照应用程序传送的字节流来进行数据传输。 TCP协议提供了一种高可靠性的数据传输方式,适用于要求数据传输可靠性和顺序性的应用场景,如文件传输、电子邮件发送等。但是,与UDP相比,TCP在数据传输过程中有较高的开销。 2.2、QTcpServer的概念 QTcpServer是Qt框架中用于实现TCP服务器的类。它提供了一种简单而高效的方式,用于监听传入的TCP连接请求,从而可以与客户端建立连接并实现数据交换。 QTcpServer的主要作用: QTcpServer可以通过调用listen()方法,在指定的IP地址和端口上开始监听传入的连接请求。监听到连接请求后通过nextPendingConnection()方法接受这些请求,获得一个QTcpSocket对象,用于与客户端进行数据交换。通过重写incomingConnection()方法,在建立新连接时执行自定义的处理操作。QTcpServer可以管理多个TCP连接,并在需要的时候进行数据交换或断开连接。通过信号和槽机制,QTcpServer可以处理连接建立、断开、数据到达等事件,实现灵活的连接管理和数据处理。 QTcpSocket同样是Qt框架中用于实现TCP网络通信的重要类。QTcpServer与QTcpSocket的关系: QTcpServer是用于实现TCP服务器的类,它负责监听传入的TCP连接请求,并与客户端建立连接;QTcpSocket则是用于实现TCP客户端的类,它负责与服务器建立连接并进行数据交换。 当QTcpServer监听到传入的连接请求时,它会返回一个QTcpSocket对象,该对象用于与客户端进行数据交换。这个QTcpSocket对象是表示与客户端建立的连接的句柄,通过它可以实现数据的发送和接收。 QTcpServer可以管理多个QTcpSocket连接,接受多个客户端的连接请求,每个连接都有一个对应的QTcpSocket对象。 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
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) ClassPrivate * 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
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
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的调用函数栈: #mermaid-svg-43JTaOPjtdykoNdJ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-43JTaOPjtdykoNdJ .error-icon{fill:#552222;}#mermaid-svg-43JTaOPjtdykoNdJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-43JTaOPjtdykoNdJ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-43JTaOPjtdykoNdJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-43JTaOPjtdykoNdJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-43JTaOPjtdykoNdJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-43JTaOPjtdykoNdJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-43JTaOPjtdykoNdJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-43JTaOPjtdykoNdJ .marker.cross{stroke:#333333;}#mermaid-svg-43JTaOPjtdykoNdJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-43JTaOPjtdykoNdJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-43JTaOPjtdykoNdJ .cluster-label text{fill:#333;}#mermaid-svg-43JTaOPjtdykoNdJ .cluster-label span{color:#333;}#mermaid-svg-43JTaOPjtdykoNdJ .label text,#mermaid-svg-43JTaOPjtdykoNdJ span{fill:#333;color:#333;}#mermaid-svg-43JTaOPjtdykoNdJ .node rect,#mermaid-svg-43JTaOPjtdykoNdJ .node circle,#mermaid-svg-43JTaOPjtdykoNdJ .node ellipse,#mermaid-svg-43JTaOPjtdykoNdJ .node polygon,#mermaid-svg-43JTaOPjtdykoNdJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-43JTaOPjtdykoNdJ .node .label{text-align:center;}#mermaid-svg-43JTaOPjtdykoNdJ .node.clickable{cursor:pointer;}#mermaid-svg-43JTaOPjtdykoNdJ .arrowheadPath{fill:#333333;}#mermaid-svg-43JTaOPjtdykoNdJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-43JTaOPjtdykoNdJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-43JTaOPjtdykoNdJ .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-43JTaOPjtdykoNdJ .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-43JTaOPjtdykoNdJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-43JTaOPjtdykoNdJ .cluster text{fill:#333;}#mermaid-svg-43JTaOPjtdykoNdJ .cluster span{color:#333;}#mermaid-svg-43JTaOPjtdykoNdJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-43JTaOPjtdykoNdJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建 设置 调用 触发 返回 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服务器的类,其底层原理和技术细节包括: 基于操作系统的套接字(socket):QTcpServer利用操作系统提供的套接字接口来实现TCP通信,包括创建、绑定、监听和接受连接等操作。 事件循环机制:QTcpServer通常结合QT框架的事件循环机制使用,通过在事件循环中监听新连接事件,实现异步处理客户端的连接请求。 信号与槽机制:QTcpServer通过信号与槽机制实现对新连接的处理,当有新连接时触发相应的信号,可以连接到相应的槽函数进行处理。 多线程支持:QTcpServer可以在多线程环境下使用,通过QThread或QtConcurrent等机制,在新的线程中处理连接,并确保线程安全。 处理并发连接:QTcpServer能够处理并发的客户端连接,可以通过多路复用技术(如select或poll)来实现高效的并发处理。在Linux下可能采用select或者poll来实现输入输出复用。 处理网络错误:QTcpServer能够处理网络错误和异常情况,通过QAbstractSocket提供的错误处理机制,能够及时响应并处理网络异常,保证服务器的稳定性和可靠性。
上一篇
打造专属多人游戏天地:如何自建幻兽帕鲁Palworld服务器
下一篇
初学littlefs文件系统
相关文章
Dockerfile简介+使用
百度统计怎么看ip
宝塔云数据库怎么打开
避免服务器租用失误,这些注意事项要了解!
【ZeroTier】ZeroTier内网穿透 + Moon节点部署,公网局域网
教你如何『SSH』远程连接『内网』服务器
帕鲁服务器价格多少?2024年幻兽帕鲁游戏服务器多少钱?
Python项目部署到服务器(pycharm简易版)
windows服务器限制特定ip访问指定端口(服务器ip白名单)
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价
7*24H在线售后
高可用资源,安全稳定
1v1专属客服对接
无忧退款试用保障
德讯电讯股份有限公司
电话:00886-982-263-666
台湾总部:台北市中山区建国北路一段29号3楼
香港分公司:九龙弥敦道625号雅兰商业二期906室
服务器租用
香港服务器
日本服务器
台湾服务器
美国服务器
高防服务器购买
香港高防服务器出租
台湾高防服务器租赁
美国高防服务器DDos
云服务器
香港云服务器
台湾云服务器
美国云服务器
日本云服务器
行业新闻
香港服务器租用
服务器资讯
香港云服务器
台湾服务器租用
zblog博客
香港VPS
关于我们
机房介绍
联系我们
Copyright © 1997-2024 www.hkstack.com All rights reserved.