Qt Socket多线程绑定解绑,QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

问题描述:

项目上需要实现一个功能,利用UI界面点击实现Qt UdpSocket解除绑定和重新绑定的功能。由于Udp接收数据之后要执行复杂的计算逻辑,为了避免阻塞UI界面,需要在子线程执行UDP数据的接收和处理工作。然而,在UI界面所在的线程调用udpsocket->abort();会出现下面问题:

QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 0x0x1ccdc5a0. Receiver '' (of type 'QNativeSocketEngine') was created in thread 0x0x1e9f23e8", file kernel\qcoreapplication.cpp, line 576

问题分析:

经过查阅发现Qt的UdpSocket的abort方法是线程不安全的,我们在主线程使用UdpSocket的引用并调用abort方法是在UI线程当中执行的,由于UdpSocket是在继承QThread类的run函数当中构造的,二者属于不同的线程,导致问题的发生。

解决思路

由于UdpSocket的abort方法不是一个槽函数,无法通过信号槽的第五个参数来控制在哪个线程当中执行。继承QThread的类除了run函数内构造的东西,其他都属于主线程。为此,考虑修改或自定义一个类MyUdpSocket,构造一个槽函数,即可完成对于这种线程不安全函数的调用。
通过继承或者是通过成员变量的方法都可以实现上述方法,但是由于继承方式需要了解被继承类的具体结构,成本较高。这里使用成员变量的方式来实现封装。

代码如下

myudpsocket.h

#ifndef MYUDPSOCKET_H
#define MYUDPSOCKET_H

#include 
#include 
#include 

class MyUDPSocket : public QObject
{
    Q_OBJECT
public:
    explicit MyUDPSocket(QUdpSocket *newudpsocket,QObject *parent = nullptr);

    //定义成员变量udpsocket
    QUdpSocket *udpsocket;

signals:

public slots:
    //自定义一个槽函数,完成端口的解除绑定和重新绑定。
    void unbind();
};

#endif // MYUDPSOCKET_H

myudpsocket.cpp

#include "myudpsocket.h"
#include "QDebug"

//第一个参数是构造时传入的newudpsocket
MyUDPSocket::MyUDPSocket(QUdpSocket *newudpsocket,QObject *parent) : QObject(parent)
{
    udpsocket = newudpsocket;
}

void MyUDPSocket::unbind(){
    udpsocket->abort();
    qDebug()<<"Unbind";
    qDebug()<<"Rebind"<bind(QHostAddress::AnyIPv4,9998);
}

在mythread当中使用自定义的socket类。
mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include 
#include 
#include 
#include "myudpsocket.h"

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    QUdpSocket *udpSocketReceiver;
    MyUDPSocket *myUdpSocket;


signals:

public slots:

    void onSocketReadyRead(); //read data from udpsocket;

protected:
    void run(); // 新线程入口
};

#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"
#include "QDebug"

MyThread::MyThread(QObject *parent) : QThread(parent)
{

}
void MyThread::onSocketReadyRead(){
    qDebug()<<"Recfunc";
    QByteArray array;
    QHostAddress address;
    quint16 port;
    array.resize(myUdpSocket->udpsocket->bytesAvailable());//根据可读数据来设置空间大小
    myUdpSocket->udpsocket->readDatagram(array.data(),array.size(),&address,&port); //读取数据

    //得到当前收到的数据并转换为字符串输出
    qDebug()<<"Receive data"<thread();
    qDebug()<<"myUdpSocket所属线程"<thread();
    connect(myUdpSocket->udpsocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()),Qt::DirectConnection);

    //设置默认绑定端口
    myUdpSocket->udpsocket->bind(QHostAddress::AnyIPv4,9999);
    exec();
}

UI线程调用,此处按需取用效果更佳。
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_StartThread_clicked();

    void on_pushButton_clicked();

    void on_pushButton_2_clicked();


signals:
    void abortmyudp();


private:
    Ui::MainWindow *ui;

    MyThread *mythread;

};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include 

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    mythread = new MyThread();
    mythread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_StartThread_clicked()
{

}

void MainWindow::on_pushButton_clicked()
{
    connect(this,SIGNAL(abortmyudp()),mythread->myUdpSocket,SLOT(unbind()));
    qDebug()<<"clicked";
    emit abortmyudp();
}

void MainWindow::on_pushButton_2_clicked()
{
    mythread->myUdpSocket->udpsocket->abort();
}

在线程启动后可发现QUdpSocket和myUdpSocket所属的线程id相同。

QUdpSocket的所属线程 MyThread(0x1e9f23e8)
myUdpSocket所属线程 MyThread(0x1e9f23e8)

可以利用上述的Python脚本进行测试:

#不需要建立连接
import socket
import time
#创建socket对象中文
#SOCK_DGRAM    udp模式
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#发送数据-字节
s.sendto("Good luck!".encode(),("192.168.1.109",9999))

你可能感兴趣的:(Qt,Python)