qt udp和线程 理解

要点

涉及的线程,有一个问题就是,函数在那个线程运行?有种

1.在发送信号的线程运行

2.在指定的线程运行,绑定的时候可以指定是在发信号的线程还是槽函数所在的线程

3.系统保证函数在合理的线程运行,就比如一个例子,我在子线程种发信号,其和这个信号绑定的槽函数没有指定线程,那么槽函数会运行在发送信号的线程;但是如果这个槽函数是更新UI的,而更新UI的函数是不允许运行在子线程中的;所以这里会自动完成线程的切换。

上面三种实际上是两种情况,一个是主动控制,如上面的1、2;一个是系统自动切换保护,如上面的3.

调查过程

1.qt 6.7 udp 通信

在 Qt 6.7 中实现 UDP 通信,你可以使用 Qt 的网络模块,特别是 QUdpSocket 类。这个类提供了发送和接收 UDP 数据报的功能。

下面是一个简单的例子,展示了如何使用 QUdpSocket 来发送和接收 UDP 数据。

1. 创建一个 UDP Socket

首先,你需要创建一个 QUdpSocket 对象,并连接它的信号到你的槽函数以处理接收到的数据。

#include <QUdpSocket>  
#include <QCoreApplication>  
#include <QDebug>  
  
class UdpReceiver : public QObject {  
    Q_OBJECT  
public:  
    UdpReceiver() {  
        socket = new QUdpSocket(this);  
        connect(socket, &QUdpSocket::readyRead, this, &UdpReceiver::readPendingDatagrams);  
        socket->bind(QHostAddress::Any, 1234);  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
    UdpReceiver receiver;  
    return a.exec();  
}  
  
#include "main.moc"

2. 发送 UDP 数据

你可以使用相同的 QUdpSocket 对象来发送数据。

void sendDatagram(const QByteArray &data, const QHostAddress &host, quint16 port) {  
    socket->writeDatagram(data, host, port);  
}

确保在调用 sendDatagram 之前,socket 已经正确配置(不需要再次绑定,除非你需要改变端口)。

注意事项

  • 确保你的防火墙设置允许 UDP 通信。
  • UDP 是一种无连接的协议,发送的数据可能丢失、重复或乱序到达。
  • QUdpSocket 不保证数据报的到达,如果你需要可靠的数据传输,考虑使用 TCP。

这就是在 Qt 6.7 中使用 QUdpSocket 进行 UDP 通信的基本方法。根据你的具体需求,你可能需要对这个基本示例进行扩展或修改。

2.qt 6.7 udp 通信,在子线程运行 

在 Qt 6.7 中,如果你想要让 UDP 通信在子线程中运行,你需要将 QUdpSocket 移动到一个新的线程中。这通常涉及到以下几个步骤:

  1. 创建一个新的类,该类包含 QUdpSocket 作为成员变量。
  2. 在这个类中实现 UDP 通信的逻辑。
  3. 创建一个 QThread 对象,并将你的类实例移动到这个线程中。
  4. 启动线程,并在需要时停止它。

下面是一个简单的例子,展示了如何在子线程中运行 UDP 通信:

#include <QCoreApplication>  
#include <QDebug>  
#include <QUdpSocket>  
#include <QThread>  
  
class UdpWorker : public QObject {  
    Q_OBJECT  
public:  
    UdpWorker() {  
        socket = new QUdpSocket(this);  
        connect(socket, &QUdpSocket::readyRead, this, &UdpWorker::readPendingDatagrams);  
    }  
  
    void start(int port) {  
        socket->bind(QHostAddress::Any, port);  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QThread *thread = new QThread();  
    UdpWorker *worker = new UdpWorker();  
    worker->moveToThread(thread);  
  
    QObject::connect(thread, &QThread::started, worker, [worker]() {  
        worker->start(1234);  
    });  
  
    QObject::connect(worker, &UdpWorker::destroyed, thread, &QThread::quit);  
    QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);  
    QObject::connect(worker, &UdpWorker::destroyed, worker, &UdpWorker::deleteLater);  
  
    thread->start();  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个例子中,UdpWorker 类负责 UDP 通信。它有一个 QUdpSocket 成员变量,并在构造函数中连接了 readyRead 信号到 readPendingDatagrams 槽函数。start 方法用于绑定 socket 到指定的端口。

在 main 函数中,我们创建了一个 QThread 对象和一个 UdpWorker 对象。我们将 UdpWorker 对象移动到新创建的线程中,并连接了一些信号和槽来启动线程和清理资源。最后,我们调用 thread->start() 来启动线程。

这样,UDP 通信就会在子线程中运行,而不会阻塞主线程。

3.qt 6.7 对象所在的线程,是创建对象的时候的线程吗 

在 Qt 6.7 中,对象所在的线程并不是由创建对象时的线程决定的,而是由对象被分配到的线程决定的。Qt 使用了对象树(object tree)来管理对象的生命周期和线程关联。当你创建一个 QObject 或其子类的实例时,这个实例默认与其父对象在同一个线程中。如果你没有指定父对象(即父对象为 nullptr),则对象将属于创建它的那个线程。

然而,你可以使用 moveToThread() 方法来改变对象所在的线程。当你调用这个方法并将对象移动到一个新的线程时,Qt 会确保对象的所有事件和信号/槽连接都在新的线程中处理。

因此,对象所在的线程是由你如何管理对象的生命周期和如何调用 moveToThread() 方法来决定的,而不是仅仅由创建对象时的线程决定的。

4.qt 6.7 udp 通信,在子线程运行,udp 通信对象继承QThread 

在 Qt 中,通常不建议让通信对象(如 QUdpSocket)直接继承 QThread。更好的做法是使用 Qt 的信号和槽机制,在一个单独的类中封装 QUdpSocket,然后将该类的一个实例移动到一个 QThread 中运行。

下面是一个示例,展示了如何在 Qt 6.7 中创建一个在子线程中运行的 UDP 通信对象:

 
#include <QCoreApplication>  
#include <QDebug>  
#include <QUdpSocket>  
#include <QThread>  
  
class UdpCommunicator : public QObject {  
    Q_OBJECT  
public:  
    UdpCommunicator() {  
        socket = new QUdpSocket(this);  
        connect(socket, &QUdpSocket::readyRead, this, &UdpCommunicator::readPendingDatagrams);  
    }  
  
    void start(int port) {  
        if (socket->bind(QHostAddress::Any, port)) {  
            qDebug() << "UDP server started on port" << port;  
        } else {  
            qDebug() << "UDP server failed to start on port" << port;  
        }  
    }  
  
    void stop() {  
        socket->close();  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QThread *thread = new QThread();  
    UdpCommunicator *communicator = new UdpCommunicator();  
    communicator->moveToThread(thread);  
  
    QObject::connect(thread, &QThread::started, communicator, &UdpCommunicator::start);  
    QObject::connect(communicator, &UdpCommunicator::destroyed, thread, &QThread::quit);  
    QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);  
    QObject::connect(communicator, &UdpCommunicator::destroyed, communicator, &UdpCommunicator::deleteLater);  
  
    thread->start();  
  
    // Keep the application running for a while to receive datagrams  
    QTimer::singleShot(10000, &a, &QCoreApplication::quit);  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个例子中,UdpCommunicator 类封装了 QUdpSocket,并提供了 start 和 stop 方法来控制 UDP 通信。我们将 UdpCommunicator 的一个实例移动到了一个单独的 QThread 中,并通过信号和槽来启动和停止它。

注意,我们在 main 函数中使用了一个 QTimer::singleShot 来在一段时间后退出应用程序,以便有足够的时间接收 UDP 数据报。在实际应用中,你可能需要实现更复杂的逻辑来控制应用程序的退出。

5.上面的例子,修改成让UdpCommunicator直接继承QThread 

虽然通常不建议让通信对象直接继承 QThread,但如果你确实想要这样做,下面是一个修改后的例子,其中 UdpCommunicator 直接继承自 QThread

 
#include <QCoreApplication>  
#include <QDebug>  
#include <QUdpSocket>  
#include <QThread>  
  
class UdpCommunicator : public QThread {  
    Q_OBJECT  
public:  
    UdpCommunicator(QObject *parent = nullptr) : QThread(parent) {  
        socket = new QUdpSocket(this);  
        connect(socket, &QUdpSocket::readyRead, this, &UdpCommunicator::readPendingDatagrams);  
    }  
  
    ~UdpCommunicator() {  
        socket->deleteLater();  
    }  
  
    void run() override {  
        int port = 1234; // 示例端口号  
        if (socket->bind(QHostAddress::Any, port)) {  
            qDebug() << "UDP server started on port" << port;  
        } else {  
            qDebug() << "UDP server failed to start on port" << port;  
            return;  
        }  
  
        exec(); // 进入事件循环  
    }  
  
    void stop() {  
        socket->close();  
        quit(); // 退出事件循环  
        wait(); // 等待线程结束  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    UdpCommunicator *communicator = new UdpCommunicator();  
    QObject::connect(&a, &QCoreApplication::aboutToQuit, communicator, &UdpCommunicator::stop);  
  
    communicator->start(); // 启动线程  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个例子中,UdpCommunicator 类直接继承自 QThread,并重写了 run 方法来启动 UDP 服务器。我们还添加了一个 stop 方法来关闭 socket 并退出线程的事件循环。在 main 函数中,我们创建了一个 UdpCommunicator 的实例,并在应用程序即将退出时连接了 aboutToQuit 信号到 stop 方法,以确保线程能够正确清理并退出。

 

6.为什么不建议直接继承呢 

不建议直接继承 QThread 来创建线程并处理业务逻辑,主要是基于以下几个原因:

1. 线程管理与业务逻辑耦合

当 QThread 被直接继承以执行特定任务时,线程的管理(如启动、停止、暂停等)和业务逻辑被紧密地耦合在一起。这导致代码结构不够清晰,维护和扩展变得困难。理想情况下,线程的管理和具体的业务逻辑应该分离,以提高代码的可读性和可维护性。

2. 事件循环的复杂性

QThread 的 run 方法默认并不启动事件循环(在早期的 Qt 版本中可能需要手动调用 exec())。如果业务逻辑依赖于 Qt 的信号和槽机制,或者需要处理定时器、网络事件等,那么直接继承 QThread 可能不足以满足需求。此外,手动管理事件循环也增加了代码的复杂性。

3. 更好的替代方案

Qt 提供了更灵活和强大的线程处理机制,如使用 QObject::moveToThread() 方法将对象移动到不同的线程中。这种方法允许你将业务逻辑封装在一个或多个 QObject 子类中,然后通过信号和槽机制在不同线程之间进行通信。这种方式不仅代码结构清晰,而且能够充分利用 Qt 的事件驱动特性。

4. 官方推荐

Qt 官方文档和许多经验丰富的开发者都推荐使用 QObject::moveToThread() 方法来处理多线程问题,而不是直接继承 QThread。这是因为这种方式更符合 Qt 的设计哲学,即利用信号和槽机制来简化跨线程通信。

5. 线程安全问题

直接继承 QThread 并在其中处理复杂的业务逻辑时,需要特别注意线程安全问题。例如,当多个线程访问共享资源时,必须使用互斥锁(如 QMutex)或其他同步机制来避免数据竞争和条件竞争等问题。而使用 QObject::moveToThread() 方法时,由于业务逻辑和线程管理是分离的,因此更容易实现线程安全。

综上所述,虽然直接继承 QThread 可以实现多线程功能,但出于代码结构、事件循环管理、替代方案的优越性、官方推荐以及线程安全等方面的考虑,通常不建议采用这种方式。相反,使用 QObject::moveToThread() 方法来处理多线程问题是一种更加优雅和强大的解决方案。

7.上面的例子中,将socket变成通过构造函数传入的对象,在主线程中创建 

为了将 QUdpSocket 对象通过构造函数传入 UdpCommunicator 类,并在主线程中创建该对象,我们需要对代码进行一些调整。以下是修改后的代码示例:

#include <QCoreApplication>  
#include <QDebug>  
#include <QUdpSocket>  
#include <QThread>  
  
class UdpCommunicator : public QThread {  
    Q_OBJECT  
public:  
    UdpCommunicator(QUdpSocket *socket, QObject *parent = nullptr)  
        : QThread(parent), m_socket(socket) {  
        connect(m_socket, &QUdpSocket::readyRead, this, &UdpCommunicator::readPendingDatagrams);  
    }  
  
    ~UdpCommunicator() {  
        // 注意:这里不再删除 socket,因为它是由外部传入的  
        m_socket = nullptr;  
    }  
  
    void run() override {  
        // 假设 socket 已经在外部绑定到了某个端口  
        exec(); // 进入事件循环  
    }  
  
    void stop() {  
        m_socket->close();  
        quit(); // 退出事件循环  
        wait(); // 等待线程结束  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (m_socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(m_socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            m_socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *m_socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QUdpSocket *socket = new QUdpSocket();  
    int port = 1234;  
    if (socket->bind(QHostAddress::Any, port)) {  
        qDebug() << "UDP server started on port" << port;  
    } else {  
        qDebug() << "UDP server failed to start on port" << port;  
        return 1;  
    }  
  
    UdpCommunicator *communicator = new UdpCommunicator(socket);  
    QObject::connect(&a, &QCoreApplication::aboutToQuit, communicator, &UdpCommunicator::stop);  
  
    communicator->start(); // 启动线程  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个修改后的例子中,QUdpSocket 对象是在 main 函数中创建的,并通过构造函数传入 UdpCommunicator 类。这样,UdpCommunicator 类就不再负责创建和管理 QUdpSocket 对象,而是只负责处理 UDP 数据报的读取和线程的事件循环。当应用程序即将退出时,aboutToQuit 信号会触发 stop 方法,从而关闭 socket 并退出线程的事件循环。

 上面的例子中,将socket变成通过构造函数传入的对象,在主线程中创建

为了将 QUdpSocket 对象通过构造函数传入 UdpCommunicator 类,并在主线程中创建该对象,我们需要对代码进行一些调整。以下是修改后的代码示例:

 
#include <QCoreApplication>  
#include <QDebug>  
#include <QUdpSocket>  
#include <QThread>  
  
class UdpCommunicator : public QThread {  
    Q_OBJECT  
public:  
    UdpCommunicator(QUdpSocket *socket, QObject *parent = nullptr)  
        : QThread(parent), m_socket(socket) {  
        connect(m_socket, &QUdpSocket::readyRead, this, &UdpCommunicator::readPendingDatagrams);  
    }  
  
    ~UdpCommunicator() {  
        // 注意:这里不再删除 socket,因为它是由外部传入的  
        m_socket = nullptr;  
    }  
  
    void run() override {  
        // 假设 socket 已经在外部绑定到了某个端口  
        exec(); // 进入事件循环  
    }  
  
    void stop() {  
        m_socket->close();  
        quit(); // 退出事件循环  
        wait(); // 等待线程结束  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (m_socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(m_socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            m_socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *m_socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QUdpSocket *socket = new QUdpSocket();  
    int port = 1234;  
    if (socket->bind(QHostAddress::Any, port)) {  
        qDebug() << "UDP server started on port" << port;  
    } else {  
        qDebug() << "UDP server failed to start on port" << port;  
        return 1;  
    }  
  
    UdpCommunicator *communicator = new UdpCommunicator(socket);  
    QObject::connect(&a, &QCoreApplication::aboutToQuit, communicator, &UdpCommunicator::stop);  
  
    communicator->start(); // 启动线程  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个修改后的例子中,QUdpSocket 对象是在 main 函数中创建的,并通过构造函数传入 UdpCommunicator 类。这样,UdpCommunicator 类就不再负责创建和管理 QUdpSocket 对象,而是只负责处理 UDP 数据报的读取和线程的事件循环。当应用程序即将退出时,aboutToQuit 信号会触发 stop 方法,从而关闭 socket 并退出线程的事件循环。

8.为UdpCommunicator 添加一个给m_socket写入数据的接口 

为了给 UdpCommunicator 类添加一个向 m_socket 写入数据的接口,我们可以在类中定义一个公共方法,该方法接受要发送的数据、目标地址和端口号作为参数,并使用 QUdpSocket 的 writeDatagram 方法来发送数据。以下是修改后的 UdpCommunicator 类,包含了新的 sendDatagram 方法:

#include <QCoreApplication>  
#include <QDebug>  
#include <QUdpSocket>  
#include <QThread>  
  
class UdpCommunicator : public QThread {  
    Q_OBJECT  
public:  
    UdpCommunicator(QUdpSocket *socket, QObject *parent = nullptr)  
        : QThread(parent), m_socket(socket) {  
        connect(m_socket, &QUdpSocket::readyRead, this, &UdpCommunicator::readPendingDatagrams);  
    }  
  
    ~UdpCommunicator() {  
        m_socket = nullptr;  
    }  
  
    void run() override {  
        exec(); // 进入事件循环  
    }  
  
    void stop() {  
        m_socket->close();  
        quit(); // 退出事件循环  
        wait(); // 等待线程结束  
    }  
  
    // 新增的发送数据接口  
    void sendDatagram(const QByteArray &data, const QHostAddress &address, quint16 port) {  
        m_socket->writeDatagram(data, address, port);  
    }  
  
private slots:  
    void readPendingDatagrams() {  
        while (m_socket->hasPendingDatagrams()) {  
            QByteArray datagram;  
            datagram.resize(m_socket->pendingDatagramSize());  
            QHostAddress sender;  
            quint16 senderPort;  
            m_socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
            qDebug() << "Received:" << datagram << "from" << sender.toString() << ":" << senderPort;  
        }  
    }  
  
private:  
    QUdpSocket *m_socket;  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QUdpSocket *socket = new QUdpSocket();  
    int port = 1234;  
    if (socket->bind(QHostAddress::Any, port)) {  
        qDebug() << "UDP server started on port" << port;  
    } else {  
        qDebug() << "UDP server failed to start on port" << port;  
        return 1;  
    }  
  
    UdpCommunicator *communicator = new UdpCommunicator(socket);  
    QObject::connect(&a, &QCoreApplication::aboutToQuit, communicator, &UdpCommunicator::stop);  
  
    communicator->start(); // 启动线程  
  
    // 示例:发送数据  
    QByteArray data = "Hello, UDP!";  
    QHostAddress address("127.0.0.1");  
    quint16 remotePort = 1234;  
    communicator->sendDatagram(data, address, remotePort);  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个例子中,sendDatagram 方法允许外部调用者向指定的地址和端口发送 UDP 数据报。在 main 函数中,我们添加了一个示例,展示了如何使用这个新接口来发送数据。

9.如果我在主线程中,调用sendDatagram接口,处理sendDatagram接口调用引发的回复数据的readPendingDatagrams接口在子线程运行,还是在主线程运行

在您的场景中,sendDatagram 接口是在 UdpCommunicator 类中定义的,而这个类是从 QThread 派生的。当您在主线程中调用 sendDatagram 接口时,实际上是在向 m_socket 发送数据,而这个 m_socket 是与 UdpCommunicator 线程的事件循环相关联的。

readPendingDatagrams 是一个槽函数,它连接到了 m_socket 的 readyRead 信号。当 m_socket 接收到数据时,readyRead 信号会在 m_socket 所属的线程(在这种情况下是 UdpCommunicator 线程)中被触发,因此 readPendingDatagrams 槽函数也会在 UdpCommunicator 线程中执行。

总结一下,即使您在主线程中调用 sendDatagram 接口,处理接收到的回复数据的 readPendingDatagrams 接口也会在 UdpCommunicator 线程(子线程)中运行,而不是在主线程中运行。这是因为 QUdpSocket 的信号和槽机制确保了相关的槽函数在正确的线程中被调用。

10.qt 允许在子线程中调用UI控件的接口吗 

在Qt中,直接在子线程中调用UI控件的接口是不允许的,也是不安全的。Qt的UI控件(如QWidget及其子类)不是线程安全的,它们只能在创建它们的线程(通常是主线程)中进行操作。

如果您需要在子线程中更新UI,您应该使用信号和槽机制,将需要更新的信息从子线程发送到主线程,然后在主线程中更新UI。这是Qt推荐的做法,也是线程间通信的常用方式。

例如,您可以在子线程中发射一个自定义信号,该信号携带需要更新的数据。然后,在主线程中连接这个信号到一个槽函数,该槽函数负责更新UI。这样,即使信号是在子线程中发射的,槽函数也会在主线程中被调用,从而安全地更新UI。

11.能对这种情况做一个实验,且提供一版代码吗 

 

当然可以。以下是一个简单的Qt实验,演示了如何从子线程发送信号以在主线程中更新UI。

首先,创建一个新的Qt Widgets Application项目,并在其中添加以下代码:

main.cpp

 
#include <QApplication>  
#include <QPushButton>  
#include <QVBoxLayout>  
#include <QWidget>  
#include <QThread>  
#include <QDebug>  
  
class Worker : public QObject {  
    Q_OBJECT  
public:  
    Worker() {}  
  
signals:  
    void updateButtonLabel(const QString &text);  
  
public slots:  
    void doWork() {  
        // 模拟一些耗时的工作  
        QThread::sleep(2);  
        emit updateButtonLabel("Updated from Worker");  
    }  
};  
  
class MainWindow : public QWidget {  
    Q_OBJECT  
public:  
    MainWindow() {  
        QPushButton *button = new QPushButton("Click me", this);  
        QVBoxLayout *layout = new QVBoxLayout(this);  
        layout->addWidget(button);  
  
        Worker *worker = new Worker();  
        QThread *thread = new QThread();  
        worker->moveToThread(thread);  
  
        connect(button, &QPushButton::clicked, worker, &Worker::doWork);  
        connect(worker, &Worker::updateButtonLabel, button, &QPushButton::setText);  
        connect(thread, &QThread::started, worker, &Worker::doWork);  
        connect(worker, &Worker::updateButtonLabel, thread, &QThread::quit);  
        connect(thread, &QThread::finished, thread, &QThread::deleteLater);  
        connect(worker, &Worker::updateButtonLabel, worker, &Worker::deleteLater);  
  
        thread->start();  
    }  
};  
  
int main(int argc, char *argv[]) {  
    QApplication app(argc, argv);  
    MainWindow window;  
    window.show();  
    return app.exec();  
}  
  
#include "main.moc"

在这个例子中,Worker 类是一个在子线程中执行的对象。它有一个 doWork 槽函数,该函数在调用时会休眠2秒,然后发射 updateButtonLabel 信号。

MainWindow 类是主窗口类,它包含一个按钮。当按钮被点击时,它会触发 Worker 的 doWork 槽函数。Worker 完成工作后,会发射 updateButtonLabel 信号,该信号连接到按钮的 setText 槽函数,从而更新按钮的文本。

注意,我们使用了 QThread 来创建一个子线程,并将 Worker 对象移动到了这个子线程中。我们还连接了 QThread 的 started 信号到 Worker 的 doWork 槽函数,以便在子线程启动时开始工作。

运行这个程序,点击按钮,你会看到按钮的文本在2秒后更新为 "Updated from Worker",这表明信号和槽机制正确地跨线程工作了。

12 请追加线程ID输出的逻辑,已确认函数当时执行所在的线程

为了确认函数当时执行所在的线程,我们可以在关键位置输出线程的ID。在Qt中,可以通过QThread::currentThreadId()来获取当前线程的ID。下面是在之前的示例代码中追加线程ID输出逻辑的版本:

main.cpp

 
#include <QApplication>  
#include <QPushButton>  
#include <QVBoxLayout>  
#include <QWidget>  
#include <QThread>  
#include <QDebug>  
  
class Worker : public QObject {  
    Q_OBJECT  
public:  
    Worker() {}  
  
signals:  
    void updateButtonLabel(const QString &text);  
  
public slots:  
    void doWork() {  
        // 输出当前线程的ID  
        qDebug() << "Worker thread ID:" << QThread::currentThreadId();  
  
        // 模拟一些耗时的工作  
        QThread::sleep(2);  
        emit updateButtonLabel("Updated from Worker");  
    }  
};  
  
class MainWindow : public QWidget {  
    Q_OBJECT  
public:  
    MainWindow() {  
        QPushButton *button = new QPushButton("Click me", this);  
        QVBoxLayout *layout = new QVBoxLayout(this);  
        layout->addWidget(button);  
  
        Worker *worker = new Worker();  
        QThread *thread = new QThread();  
        worker->moveToThread(thread);  
  
        connect(button, &QPushButton::clicked, worker, &Worker::doWork);  
        connect(worker, &Worker::updateButtonLabel, button, &QPushButton::setText);  
        connect(thread, &QThread::started, worker, &Worker::doWork);  
        connect(worker, &Worker::updateButtonLabel, thread, &QThread::quit);  
        connect(thread, &QThread::finished, thread, &QThread::deleteLater);  
        connect(worker, &Worker::updateButtonLabel, worker, &Worker::deleteLater);  
  
        // 输出主线程的ID  
        qDebug() << "Main thread ID:" << QThread::currentThreadId();  
  
        thread->start();  
    }  
};  
  
int main(int argc, char *argv[]) {  
    QApplication app(argc, argv);  
    MainWindow window;  
    window.show();  
    return app.exec();  
}  
  
#include "main.moc"

在这个修改后的版本中,我们在Worker类的doWork槽函数中输出了当前线程的ID,以确认这个函数是在子线程中执行的。同时,在MainWindow的构造函数中,我们也输出了主线程的ID,以便进行比较。

运行这个程序,并点击按钮,你将在控制台看到类似以下的输出:

 
Main thread ID: 0x1234abcd  
Worker thread ID: 0x5678efgh

这里的ID是示例性的,实际运行时你将看到不同的值。这个输出确认了doWork函数是在子线程中执行的,而主窗口的构造函数是在主线程中执行的。

13 QThread::quit 

在Qt中,QThread::quit()函数是一个槽函数,它的作用是告诉线程的事件循环结束运行。当你调用这个函数时,如果线程正在运行一个事件循环(即调用了exec()),那么这个事件循环将会退出,线程随之也会结束。

重要的是要理解,QThread::quit()并不会立即终止线程。它只是请求事件循环退出,然后线程会在处理完所有剩余的事件后优雅地结束。如果你需要立即终止线程,应该使用更激进的方法,比如QThread::terminate(),但这种方法是不安全的,因为它会立即停止线程,可能导致资源泄露或数据不一致。

在大多数情况下,你应该避免使用QThread::terminate(),而是使用QThread::quit()来请求线程优雅地退出。然后,你可以使用QThread::wait()来等待线程实际结束。

这里是一个使用QThread::quit()的例子:

 
class Worker : public QObject {  
    Q_OBJECT  
public slots:  
    void doWork() {  
        // ... 执行一些工作 ...  
  
        // 现在工作完成了,告诉线程可以退出了  
        QThread::currentThread()->quit();  
    }  
};  
  
// 在另一个地方  
QThread *thread = new QThread();  
Worker *worker = new Worker();  
worker->moveToThread(thread);  
  
// 连接信号和槽来开始工作  
connect(someSignalSource, &SomeClass::someSignal, worker, &Worker::doWork);  
  
// 连接线程的finished信号来清理  
connect(thread, &QThread::finished, worker, &Worker::deleteLater);  
connect(thread, &QThread::finished, thread, &QThread::deleteLater);  
  
// 开始线程的事件循环  
thread->start();  
  
// ... 在某个时刻,someSignal会被发射,导致worker的doWork槽函数被调用 ...  
  
// 当doWork槽函数调用QThread::currentThread()->quit()时,线程的事件循环会结束,  
// 然后发出finished信号,导致worker和thread对象被删除。

在这个例子中,当doWork槽函数完成时,它会调用QThread::currentThread()->quit()来请求线程的事件循环退出。然后,线程会发出finished信号,这个信号被用来清理workerthread对象。

14.QThread::deleteLater 

QThread::deleteLater() 是 Qt 框架中的一个成员函数,它属于 QObject 类,而 QThread 继承自 QObject。当你调用 deleteLater() 时,你并不是立即删除对象,而是将对象标记为“待删除”。这意味着在对象的线程的事件循环下一次迭代时,或者在对象所在的线程结束时(如果对象没有事件循环),对象将被删除。

在 QThread 的上下文中,deleteLater() 非常有用,因为你不应该直接从另一个线程删除 QThread 对象或其子对象。这样做可能会导致未定义的行为或程序崩溃,因为 Qt 的对象模型不是线程安全的。

使用 deleteLater() 可以确保对象在正确的线程中被删除,即对象所在的线程。这是通过内部的事件循环和事件处理机制来实现的。当对象被标记为待删除时,它会在适当的时候收到一个删除事件,然后在其线程上下文中被安全地删除。

这里是一个使用 QThread::deleteLater() 的例子:

 
class Worker : public QObject {  
    Q_OBJECT  
public slots:  
    void doWork() {  
        // ... 执行一些工作 ...  
  
        // 工作完成,准备退出线程  
        emit finished();  
    }  
  
signals:  
    void finished();  
};  
  
// 在另一个地方  
QThread *thread = new QThread();  
Worker *worker = new Worker();  
worker->moveToThread(thread);  
  
// 连接信号和槽来开始和结束工作  
connect(someSignalSource, &SomeClass::someSignal, worker, &Worker::doWork);  
connect(worker, &Worker::finished, thread, &QThread::quit);  
connect(worker, &Worker::finished, worker, &Worker::deleteLater);  
connect(thread, &QThread::finished, thread, &QThread::deleteLater);  
  
// 开始线程的事件循环  
thread->start();  
  
// ... 在某个时刻,someSignal 会被发射,导致 worker 的 doWork 槽函数被调用 ...  
  
// 当 doWork 槽函数发出 finished 信号时,它会导致线程退出(通过调用 QThread::quit)  
// 并且标记 worker 和 thread 对象为待删除(通过调用 deleteLater)  
// 当线程的事件循环结束时,这些对象将被删除

在这个例子中,当 Worker 类的 doWork 槽函数完成时,它会发出一个 finished 信号。这个信号被连接到 QThread::quit,以请求线程的事件循环退出,同时也被连接到 Worker 对象的 deleteLater,以标记该对象为待删除。最后,当线程的事件循环结束时,QThread 对象也会被删除。

15.QThread 常用接口

QThread 是 Qt 框架中用于管理线程的类。它提供了一系列常用的接口,使得在多线程编程中能够更加方便地创建、启动、停止和管理线程。以下是一些 QThread 的常用接口:

  1. 构造函数和析构函数
    • QThread(QObject *parent = nullptr):构造函数,创建一个新的线程对象。
    • ~QThread():析构函数,销毁线程对象。注意,在调用析构函数之前,应确保线程已经停止运行。
  2. 线程启动和终止
    • void start(Priority priority = InheritPriority):启动线程。可以通过参数指定线程的优先级。
    • void quit():请求线程的事件循环退出。如果线程正在运行事件循环,它将优雅地结束。
    • void terminate():强制终止线程。这是一个不安全的操作,因为它不会等待线程优雅地结束,可能会导致资源泄露或数据不一致。
  3. 线程状态查询
    • bool isRunning():返回线程是否正在运行。
    • bool isFinished():返回线程是否已经结束。
  4. 线程等待
    • bool wait(unsigned long time = ULONG_MAX):等待线程结束。可以通过参数指定等待的超时时间。
  5. 线程ID和优先级
    • QThread::currentThreadId():返回当前线程的ID。
    • QThread::currentThread():返回当前线程的指针。
    • Priority priority():返回线程的优先级。
    • void setPriority(Priority priority):设置线程的优先级。
  6. 事件循环
    • int exec():启动线程的事件循环。在调用此函数后,线程将开始处理事件,直到调用 quit() 或 terminate()
  7. 信号和槽
    • void started():当线程开始运行时发射此信号。
    • void finished():当线程结束时发射此信号。

使用 QThread 时,通常的做法是将工作类的实例移动到一个新的线程中,而不是直接重写 QThread 的 run() 方法。这样可以保持 QThread 的简洁性,并将线程逻辑与工作逻辑分离。工作类应该是一个继承自 QObject 的类,并且实现所需的信号和槽来进行线程间的通信。

16.QUdpSocket 常用接口 

QUdpSocket 是 Qt 框架中用于 UDP 通信的类,它继承自 QAbstractSocket,提供了发送和接收 UDP 数据报的功能。以下是 QUdpSocket 的一些常用接口及其简要介绍:

构造函数和析构函数

  • QUdpSocket(QObject *parent = nullptr):构造函数,创建一个新的 QUdpSocket 对象,可以选择性地指定父对象。
  • ~QUdpSocket():析构函数,销毁 QUdpSocket 对象。在析构过程中,如果套接字仍然打开,它会自动关闭。

绑定和解绑

  • bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform):绑定套接字到指定的端口上。如果端口为0,则系统会自动选择一个可用的端口。绑定成功后,套接字进入监听状态。
  • void abort():立即中止当前操作,并关闭套接字。这不同于 close()abort() 会丢弃写入缓冲区中的所有待发送数据。

发送数据报

  • qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port):向指定的主机和端口发送数据报。返回成功发送的字节数,如果发送失败则返回-1。
  • qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port):这是另一个重载版本,允许直接发送字符数组形式的数据报。

接收数据报

  • qint64 readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr):从套接字接收一个数据报,并将其存储在提供的缓冲区中。如果提供了 address 和 port 参数,它们将被填充为发送方的地址和端口。
  • QByteArray receiveDatagram(qint64 maxSize = INT_MAX, QHostAddress *address = nullptr, quint16 *port = nullptr):接收一个数据报,并将其作为 QByteArray 返回。如果提供了 address 和 port 参数,它们将被填充为发送方的地址和端口。

状态和错误处理

  • QAbstractSocket::SocketState state() const:返回套接字的当前状态,如未连接、监听、已连接等。
  • bool hasPendingDatagrams() const:检查是否有挂起的数据报等待读取。
  • qint64 pendingDatagramSize() const:返回第一个挂起的数据报的大小(以字节为单位)。
  • void readyRead():当套接字有数据可读时,会发出此信号。通常,在槽函数中连接此信号以处理接收到的数据。
  • void errorOccurred(QAbstractSocket::SocketError socketError):当发生错误时,会发出此信号,并携带错误类型的信息。

多播组管理

  • bool joinMulticastGroup(const QHostAddress &groupAddress):加入指定的多播组。成功加入后,该套接字将能够接收发送到该多播组的数据报。
  • bool leaveMulticastGroup(const QHostAddress &groupAddress):离开指定的多播组。离开后,该套接字将不再接收发送到该多播组的数据报。

这些接口提供了使用 QUdpSocket 进行 UDP 通信所需的基本功能。通过组合使用这些接口,可以实现复杂的网络通信逻辑。

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt中,实现UDP线程通信的方法有多种。其中一种方法是使用QtQThread类来创建多线程,并在每个线程中创建一个QUdpSocket对象来进行UDP数据的发送和接收。具体步骤如下: 1. 首先,在工程文件中添加`QT += core gui network`以引入Qt的网络模块。 2. 创建一个继承自QThread的自定义线程类,例如`MyThread`。 3. 在`MyThread`类中重写`run()`函数,在该函数中创建一个QUdpSocket对象,并实现UDP数据的发送和接收逻辑。 4. 在主线程中创建一个或多个`MyThread`对象,并调用`start()`函数启动线程。 通过以上步骤,你可以在Qt中实现UDP线程通信。具体的代码实现可以参考引用\[2\]中的示例代码。 #### 引用[.reference_title] - *1* [QT线程实现UDP数据的发送](https://blog.csdn.net/weixin_43552197/article/details/120882987)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Qt网络通信,多线程实现UDP通信](https://blog.csdn.net/qq_37636917/article/details/107644483)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Qt创建多线程接收惯导UDP数据](https://blog.csdn.net/zong596568821xp/article/details/78972105)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值