项目上需要实现一个功能,利用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))