上一版代码可以正常使用,但是会报错
上一篇文章
错误信息 "QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"
指出了一个问题,即在非主线程中尝试启用或禁用套接字通知器(QSocketNotifier)。在Qt中,与GUI相关的操作,包括网络操作,通常需要在主线程中进行,因为Qt的GUI不是线程安全的。
在你的代码中,你使用了 QtConcurrent::run
来在后台线程中运行网络操作,这是不正确的,因为Qt的网络类(如QTcpSocket)不是线程安全的。尝试在非主线程中使用它们会导致这个错误。
要解决这个问题,你需要确保所有的网络操作都在主线程中进行。这里有几种方法可以解决这个问题:
-
不使用QtConcurrent::run:将网络操作放在主线程中,不使用QtConcurrent::run。这可能不是最佳解决方案,因为它会导致UI线程阻塞,用户体验不佳。
-
使用QThread和QObject::moveToThread:创建一个新的QThread,并将网络操作的QObject移动到这个新线程中。这样可以确保网络操作在新线程中运行,而不会干扰主线程。
-
使用Qt的网络线程:如果你使用的是QTcpSocket等网络类,可以考虑使用Qt自己的网络线程,例如通过QNetworkAccessManager来处理网络请求。
改进版本(信号与槽机制)
所以,为了改善代码,使用信号与槽机制对网络操作进行改善,通过在主线程触发信号,并传递参数到新线程workerThread
的networkhandler
对象进行数据处理,在触发信号返回主线程更新主线程的响应信息,达到自动化发送邮件的功能
效果展示
networkhandler.h
#ifndef NETWORKHANDLER_H
#define NETWORKHANDLER_H#include <QObject>class NetworkHandler : public QObject
{Q_OBJECT
public:explicit NetworkHandler(QObject *parent = nullptr);void handleData(const QString &data);signals:void updateTextBrowser(const QString &msg);
};#endif // NETWORKHANDLER_H
networkhandler.cpp
#include "networkhandler.h"NetworkHandler::NetworkHandler(QObject *parent): QObject{parent}
{}void NetworkHandler::handleData(const QString &data){if (data.isEmpty()){return;}emit this->updateTextBrowser(data);
}
tcpmailclient.h
#ifndef TCPMAILCLIENT_H
#define TCPMAILCLIENT_H#include <QObject>
#include <QObject>
#include <QtNetwork>
#include <QSslSocket>
#include <QSslCertificate>
#include <QSslKey>
#include <QTcpSocket>
#include <QHostAddress>
#include <QIODevice>
#include <QApplication>
#include <QDebug>class TCPMailClient : public QObject
{Q_OBJECT
public:explicit TCPMailClient(const QString &host, quint16 port,QObject *parent = nullptr);void send(QString msg);QString recieve();void close();private:QSslSocket* ssl;bool isentrcyed = false;signals:void Connected();void readyRead();
};#endif // TCPMAILCLIENT_H
tcpmailclient.cpp
#include "tcpmailclient.h"TCPMailClient::TCPMailClient(const QString &host, quint16 port, QObject *parent): QObject{parent} {this->ssl = new QSslSocket(this);QObject::connect(ssl, &QSslSocket::encrypted, [=]() {this->isentrcyed = true;qDebug() << "连接成功";});QObject::connect(ssl, &QSslSocket::connected, this,[this]() {qDebug() << "已连接到SMTP服务器";emit this->Connected();});// = 是复制要传递的变量, &是引用QObject::connect(ssl,&QSslSocket::readyRead,[&](){emit this->readyRead();});QObject::connect(ssl, &QSslSocket::errorOccurred, this,[](QAbstractSocket::SocketError socketError){qDebug() << "发生错误:" << socketError;});ssl->connectToHostEncrypted(host, port);// 可以连接信号,以确认数据已经发送connect(ssl, &QSslSocket::bytesWritten, this, [](qint64 bytes) {qDebug() << bytes << "bytes were written to the socket.";});
}void TCPMailClient::send(QString msg) {if (ssl->state() == QAbstractSocket::ConnectedState) {qDebug() << "发送" << msg;this->ssl->write(msg.toUtf8());} else {qDebug() << "SMTP连接未建立";}
}QString TCPMailClient::recieve() {// 等待并读取响应// if (ssl->waitForReadyRead()) {// QByteArray data = ssl->readAll(); // 读取所有可用数据// qDebug() << "响应内容:" << data;// return QString(data);// }// return QString();QByteArray data = ssl->readAll(); // 读取所有可用数据qDebug() << "响应内容:" << QString::fromUtf8(data);// return QString(data);return QString::fromUtf8(data); // 编码成字符串
}void TCPMailClient::close()
{ssl->disconnectFromHost();this->ssl->close();
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include "networkhandler.h"
#include "tcpmailclient.h"
#include <QWidget>
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QCoreApplication>
#include <QFuture>
#include <QMessageBox>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTsignals:void dataReceived(const QString& data);public:Widget(QWidget *parent = nullptr);~Widget();void startWork();void dealWithResponse(const QString& response);private slots:void on_pushButton_clicked();void on_pushButton_2_clicked();private:Ui::Widget *ui;TCPMailClient *mailclient;QThread *workerThread;NetworkHandler * networkhandler;int sendState = 0;bool hassendUsername = false;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QFuture>
#include <QtConcurrent/QtConcurrent>Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(600, 400);this->setWindowIcon(QIcon(":/icon/src/mail_icon.png"));
}Widget::~Widget() {on_pushButton_2_clicked();delete ui;
}void Widget::startWork()
{this->workerThread = new QThread;this->networkhandler = new NetworkHandler();this->networkhandler->moveToThread(workerThread);// 连接信号和槽connect(this, &Widget::dataReceived, networkhandler, &NetworkHandler::handleData);// connect(worker, &Worker::finished, workerThread, &QThread::quit);// connect(worker, &Worker::finished, worker, &Worker::deleteLater);connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);// 有新的数据可读时触发readyRead信号connect(mailclient,&TCPMailClient::readyRead,[this](){qDebug() << "触发readyRead信号";QString data = this->mailclient->recieve();emit this->dataReceived(data);});// 通过触发信号回到ui线程更新connect(networkhandler, &NetworkHandler::updateTextBrowser, this,[this](const QString &msg) {// 使用 Qt::QueuedConnection 确保在主线程中执行ui->textBrowser->append(msg);// 更新之后发送对应信息dealWithResponse(msg);}, Qt::QueuedConnection);// 启动线程workerThread->start();}
void Widget::dealWithResponse(const QString& response) {if (response.startsWith("220") && sendState == 0) {// 服务器已准备好接收命令this->mailclient->send("HELO xxx\r\n");sendState = 1; // 移动到下一个状态} else if (response.startsWith("250")) {// 如果服务器回复250,通常表示前一个命令成功执行switch (sendState) {case 1:this->mailclient->send("AUTH LOGIN\r\n");sendState = 2; // 准备发送用户名break;case 5:this->mailclient->send(QString("RCPT TO:<%1>\r\n").arg(ui->lineEdit_4->text()));sendState = 6; // 准备发送邮件数据break;case 6:// 服务器准备接收邮件数据this->mailclient->send("DATA\r\n");sendState = 7; // 邮件数据发送状态case 8:// 已发送退出QMessageBox::information(this,"提示信息","发送成功");break;default:qDebug() << "Unexpected 250 response in state" << sendState;break;}} else if (response.startsWith("334")) {// 服务器要求认证信息if (sendState == 2) {// 发送Base64编码的用户名QString username = QString::fromLatin1(QString("aaaa@163.com").toUtf8().toBase64());this->mailclient->send(username + "\r\n");qDebug() << username;sendState = 3; // 准备发送密码} else if (sendState == 3) {// 发送Base64编码的密码QString password = QString::fromLatin1(QString("MfjaiokgaaLN").toUtf8().toBase64());qDebug() << password;this->mailclient->send(password + "\r\n");sendState = 4; // 完成登录,准备发送MAIL FROM}} else if (response.startsWith("235")) {// 认证成功this->mailclient->send(QString("MAIL FROM:<%1>\r\n").arg(ui->lineEdit_3->text()));sendState = 5; // 准备发送RCPT TO} else if (response.startsWith("354") && sendState == 7) {// 发送邮件数据this->mailclient->send(QString("FROM:%1\r\n").arg(ui->lineEdit_3->text()));this->mailclient->send(QString("SUBJECT:%1\r\n").arg(ui->lineEdit_5->text()));this->mailclient->send(QString("TO:%1\r\n").arg(ui->lineEdit_4->text()));// 发送空行,隔开邮件正文和内容this->mailclient->send("\r\n");this->mailclient->send(ui->textEdit->toPlainText() + "\r\n");this->mailclient->send(".\r\n");this->mailclient->send("QUIT\r\n");// 退出状态sendState = 8;} else if (response.startsWith("5")) {// 5xx表示服务器端的错误qDebug() << "服务端报错" << response;} else {// 未识别的响应qDebug() << "Unrecognized SMTP response:" << response;}
}// 退出按钮
void Widget::on_pushButton_2_clicked() {QApplication::quit();
}// 发送按钮
void Widget::on_pushButton_clicked() {mailclient = new TCPMailClient("smtp.163.com", 465);// 连接之后触发槽函数处理接下来的步骤connect(mailclient,&TCPMailClient::Connected,this,&Widget::startWork);
}