Qt邮箱程序改良版(信号和槽)
上一版代码可以正常使用,但是会报错
上一篇文章
错误信息 "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_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
signals:
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);
}
原文地址:https://blog.csdn.net/x1343676/article/details/143854728
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!