自学内容网 自学内容网

Qt邮箱程序改良版(信号和槽)

上一版代码可以正常使用,但是会报错

上一篇文章
错误信息 "QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread" 指出了一个问题,即在非主线程中尝试启用或禁用套接字通知器(QSocketNotifier)。在Qt中,与GUI相关的操作,包括网络操作,通常需要在主线程中进行,因为Qt的GUI不是线程安全的。

在你的代码中,你使用了 QtConcurrent::run 来在后台线程中运行网络操作,这是不正确的,因为Qt的网络类(如QTcpSocket)不是线程安全的。尝试在非主线程中使用它们会导致这个错误。

要解决这个问题,你需要确保所有的网络操作都在主线程中进行。这里有几种方法可以解决这个问题:

  1. 不使用QtConcurrent::run:将网络操作放在主线程中,不使用QtConcurrent::run。这可能不是最佳解决方案,因为它会导致UI线程阻塞,用户体验不佳。

  2. 使用QThread和QObject::moveToThread:创建一个新的QThread,并将网络操作的QObject移动到这个新线程中。这样可以确保网络操作在新线程中运行,而不会干扰主线程。

  3. 使用Qt的网络线程:如果你使用的是QTcpSocket等网络类,可以考虑使用Qt自己的网络线程,例如通过QNetworkAccessManager来处理网络请求。

改进版本(信号与槽机制)

所以,为了改善代码,使用信号与槽机制对网络操作进行改善,通过在主线程触发信号,并传递参数到新线程workerThreadnetworkhandler对象进行数据处理,在触发信号返回主线程更新主线程的响应信息,达到自动化发送邮件的功能

效果展示

效果

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)!