QT版发送邮件程序

news2024/12/25 9:22:44

简单的TCP邮箱程序

**教学与实践目的:**学会网络邮件发送的程序设计技术。

1.SMTP协议

  • 邮件传输协议包括 SMTP(简单邮件传输协议,RFC821)及其扩充协议 MIME

  • 邮件接收协议包括 POP3 和功能更强大的 IMAP 协议。 服务邮件发送的服务器其端口为 25(如果开启 ssl 一般使用 465 端口,目前QQ邮箱已经强制必须使用加密连接方式,所以以下实验使用 465 端口), 服务邮件接收的服务器端口为 110(如果开启 SSL 一般使用 995 端口)。

SMTP协议解读以及如何使用SMTP协议发送电子邮件 - 一只会铲史的猫 - 博客园

  1. SMTP(简单邮件传输协议)

    • 命令有序性:SMTP的命令需要按照特定的顺序执行,以完成邮件的发送任务。每个命令都有其特定的作用,并且必须按照正确的顺序组合使用。
    • 请求应答模式:SMTP遵循请求应答式协议,客户端发送命令后,服务器会返回相应的响应。这种模式确保了命令的执行和结果的确认。
    • 响应格式:SMTP的响应格式通常包括一个三位数字的响应码,后面跟着响应描述,这与HTTP协议的响应格式相似。
  2. POP3(邮局协议第三版)

    • 命令独立性:POP3的命令如LIST、STAT、UIDL、TOP、RETR、DELE等,都可以独立使用,每个命令都有其特定的功能,如查看邮件列表、获取邮件内容等。
    • 数据流处理:在接收邮件时,POP3需要以流的方式处理数据,因为邮件数据可能不是一次性完整发送,而是分批次到达。
  3. Socket编程中的差异

    • 发送数据的简单性:在socket编程中,发送数据(如SMTP)相对简单,因为发送方可以按照自己的节奏发送数据,不需要考虑接收方的状态。
    • 接收数据的复杂性:接收数据(如POP3)需要判断数据是否完全接收,处理数据流的完整性,这比发送数据要复杂。
  4. 请求应答式协议

    • SMTP和HTTP:SMTP和HTTP都是请求应答式协议,客户端发送请求后,服务器返回响应。
    • HTTP的Keep-Alive:HTTP协议在设置为Keep-Alive时,可以进行多次请求和响应的交互,否则通常只有一次交互机会。

2.第三方邮箱设置

邮箱设置一定要开启 smtp/pop3 服务(以 QQ邮箱为例,在[邮箱设置]中 的[账户]中开启相关服务(获取授权码)

在这里插入图片描述

3.本机设置telnet支持

  • 首先打开电脑的控制面板
  • 点击程序–>启用或关闭windows程序
  • 启用Telnet客户端
  • 将telent这个服务勾选上然后点击确定。
  • 测试telent是否可用。打开cmd命令窗口,输入telnet

smtp.h

#ifndef SMTP_H
#define SMTP_H

#include<QByteArray>
#include<QString>
#include<QTcpSocket>
class Smtp
{
public:
    Smtp(QByteArray username,QByteArray password);
    ~Smtp();
    void SendData(QByteArray sendIp,QByteArray s_Title,QByteArray s_Content);
    QString WaitAndReadData();
private:
    QByteArray m_UserName="";
    QByteArray m_Password="";
    QTcpSocket * m_pSocket=nullptr;
    QString m_ReceiverData="";

};

#endif // SMTP_H

smtp.cpp

#include "smtp.h"
#include<QDebug>
Smtp::Smtp(QByteArray username,QByteArray password)
{
    if(username.contains("@163"))
    {
        m_UserName= username;
        m_Password = password;
    }
    else
    {
        qDebug()<<"Error";
    }

}
void Smtp::SendData(QByteArray sendIp, QByteArray s_Title, QByteArray s_Content)
{
    m_pSocket=new QTcpSocket();
    m_pSocket->connectToHost("smtp.163.com",25,QTcpSocket::ReadWrite);  //连接163邮箱
    m_pSocket->waitForConnected(1000);
    WaitAndReadData();
    m_pSocket->write("helo localhost\r\n");
    WaitAndReadData();
    m_pSocket->write("auth login\r\n");
    WaitAndReadData();
    m_pSocket->write(m_UserName.toBase64()+"\r\n");  //写入用户名
    WaitAndReadData();
    m_pSocket->write(m_Password.toBase64()+"\r\n");  //写入密码
    WaitAndReadData();
    m_pSocket->write("mail from: <"+m_UserName+">\r\n"); //发送的邮箱
    WaitAndReadData();
    m_pSocket->write("rcpt to: <"+sendIp+">\r\n"); //接收的邮箱
    WaitAndReadData();
    m_pSocket->write("data\r\n");  //开始写入
    WaitAndReadData();
    m_pSocket->write("from:<"+m_UserName+">\r\n");  //发送名称
    WaitAndReadData();
    m_pSocket->write("to:<"+sendIp+">");  //接受名称
    WaitAndReadData();
    m_pSocket->write("data\r\n");
    WaitAndReadData();
    m_pSocket->write("Subject:"+s_Title+"\r\n");  //标题
    m_pSocket->write("\r\n");
    m_pSocket->write(s_Content.append("\r\n")); //内容
    m_pSocket->write(".\r\n");
    WaitAndReadData();
    m_pSocket->write("quit\r\n");
    m_pSocket->disconnect();

}

QString Smtp::WaitAndReadData()
{
    m_pSocket->waitForReadyRead(1000);
    m_ReceiverData = m_pSocket->readAll();
    return m_ReceiverData;
}
Smtp::~Smtp()
{
    delete m_pSocket;
}

4.测试

Smtp smtp("邮箱名称","授权码"); //邮箱和密码都要用自己的  //注意是授权码,不是你登录邮箱的密码
smtp.SendData("aaa@qq.com","你好","这是一个测试程序");

安全的SSL邮箱程序

1. 使用QSslSocket设置参数

#include <QSslSocket>
#include <QSslCertificate>
#include <QSslKey>
#include <QTcpSocket>
#include <QHostAddress>
#include <QIODevice>
#include <QApplication>
#include <QDebug>

class SSLClient : public QObject {
    Q_OBJECT
public:
    SSLClient(const QString &host, quint16 port, QObject *parent = nullptr) : QObject(parent) {
        QSslSocket *socket = new QSslSocket(QSsl::SslClientMode, this);
        connect(socket, &QSslSocket::encrypted, this, &SSLClient::onEncrypted);
        connect(socket, &QSslSocket::readyRead, this, &SSLClient::onReadyRead);
        connect(socket, &QSslSocket::sslErrors, this, &SSLClient::onSslErrors);
        connect(socket, &QSslSocket::connected, this, &SSLClient::onConnected);
        socket->connectToHostEncrypted(host, port);
    }

private slots:
    void onConnected() {
        qDebug() << "Connected to the server";
    }

    void onEncrypted() {
        QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
        if (socket) {
            QSslConfiguration config = socket->sslConfiguration();
            // 设置SSL/TLS协议
            config.setProtocol(QSsl::TlsV1_2);
            // 设置验证模式
            config.setPeerVerifyMode(QSslSocket::VerifyPeer);
            // 设置验证深度
            config.setPeerVerifyDepth(2);
            // 设置加密套件
            config.setCiphers(QSslConfiguration::supportedCiphers());
            // 加载本地证书和私钥(如果需要)
            // config.setLocalCertificate(QSslCertificate("path/to/certificate.pem", QSsl::Pem));
            // config.setPrivateKey(QSslKey("path/to/private_key.pem", QSsl::Rsa, QSsl::Pem));
            socket->setSslConfiguration(config);
        }
    }

    void onReadyRead() {
        QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
        if (socket) {
            QByteArray data = socket->readAll();
            qDebug() << "Received:" << data;
        }
    }

    void onSslErrors(const QList<QSslError> &errors) {
        QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
        if (socket) {
            foreach (const QSslError &error, errors) {
                qDebug() << "SSL Error:" << error.errorString();
            }
            socket->ignoreSslErrors();
        }
    }
};

2. 检查是否连接成功

在Qt中,使用QSslSocket类时,你可以通过几种方式来检查SSL连接是否已经成功建立:

  1. 使用信号
    QSslSocket提供了几个信号,可以用来确定连接的状态。

    • encrypted():当连接被加密时发出,表示SSL握手已经完成,并且数据传输现在是加密的。
    • connected():当底层的TCP连接建立时发出,但此时SSL握手可能还没有完成。
    • sslErrors():当SSL握手过程中出现错误时发出,你可以通过这个信号来检查是否有错误发生。
  2. 检查状态
    使用QSslSocketstate()方法可以获取当前的连接状态。QAbstractSocket::SocketState枚举值可以告诉你连接是否已经连接、正在连接、关闭等。

  3. 检查错误
    使用QSslSocketerror()方法可以获取最后一个错误。如果error()返回QAbstractSocket::NoError,则表示没有错误发生。

下面是一个简单的例子,展示了如何使用这些方法来检查SSL连接是否成功:

#include <QSslSocket>
#include <QDebug>

// 假设你已经有了一个QSslSocket对象叫做sslSocket

// 连接信号
connect(sslSocket, &QSslSocket::encrypted, this, []() {
    qDebug() << "SSL connection established";
});

connect(sslSocket, &QSslSocket::sslErrors, this, [](QSslSocket *socket, const QList<QSslError> &errors) {
    foreach (const QSslError &error, errors) {
        qDebug() << "SSL error:" << error.errorString();
    }
});

// 检查状态
if (sslSocket->state() == QAbstractSocket::ConnectedState) {
    if (sslSocket->error() == QAbstractSocket::NoError) {
        qDebug() << "SSL connection is up and running without errors";
    } else {
        qDebug() << "SSL connection has errors";
    }
}

// 检查是否加密
if (sslSocket->isEncrypted()) {
    qDebug() << "The connection is encrypted";
} else {
    qDebug() << "The connection is not encrypted";
}

在这个例子中:

  • 我们连接了encrypted()信号,当SSL连接建立时,会在控制台输出消息。
  • 我们连接了sslErrors()信号,如果有SSL错误发生,会在控制台输出错误信息。
  • 我们使用state()方法检查当前的连接状态,并且使用error()方法检查是否有错误发生。
  • 我们使用isEncrypted()方法检查连接是否已经加密。

界面布局

layoutstretch

在Qt Creator中,layoutStretch是用于控制布局中各个元素(小部件或子布局)的拉伸系数(stretch factor)的属性。拉伸系数决定了元素在父布局中分配多余空间的比例。如果拉伸系数为0,则元素将保持其最小大小,而大于0的拉伸系数会让元素能够拉伸占据更多空间。

具体来说,layoutStretch属性可以在Qt Designer中直接设置,或者通过代码来调整。在Qt Designer中,当你选中一个布局器(比如水平布局器QHBoxLayout或垂直布局器QVBoxLayout),你可以在属性编辑栏中找到layoutStretch属性。这个属性允许你为布局中的每个元素设置一个拉伸系数,这些系数决定了在布局中的元素如何随着父容器大小的变化而变化。

例如,如果你有一个水平布局器中包含三个按钮,并且你将layoutStretch设置为1,2,3,那么当布局中的总空间需要分配时,第一个按钮将获得1份空间,第二个按钮获得2份空间,第三个按钮获得3份空间。这样,第三个按钮将占据比第一个和第二个按钮更多的空间。

在代码中,你可以使用QBoxLayoutsetStretchsetStretchFactor方法来设置拉伸系数。例如:

QHBoxLayout *layout = new QHBoxLayout;
QPushButton *button1 = new QPushButton("Button 1");
QPushButton *button2 = new QPushButton("Button 2");
QPushButton *button3 = new QPushButton("Button 3");

layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);

layout->setStretchFactor(button1, 1);
layout->setStretchFactor(button2, 2);
layout->setStretchFactor(button3, 3);

这段代码将创建一个水平布局,其中包含三个按钮,并且设置了不同的拉伸系数,从而影响它们在布局中的空间分配。

设置QSS

C++ Qt开发:PushButton按钮组件 - lyshark - 博客园

最终界面如图

在这里插入图片描述

QT中的多线程

在Qt中,有几种方式可以创建和使用多线程。以下是一些常见的方法:

使用QThread

最直接的方法是使用QThread类。你可以将需要在后台执行的任务移到一个新的线程中。以下是如何使用QThread的基本示例:

#include <QThread>
#include <QDebug>

class Worker : public QObject {
    Q_OBJECT
public slots:
    void doWork() {
        qDebug() << "Work is being done in thread" << QThread::currentThreadId();
        // 执行一些耗时的操作
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QThread *thread = new QThread();
    Worker *worker = new Worker();
    worker->moveToThread(thread);

    // 当线程启动时,Worker对象的doWork()方法会被调用
    QObject::connect(thread, &QThread::started, worker, &Worker::doWork);
    // 当Worker对象的doWork()方法完成后,退出线程
    QObject::connect(worker, &Worker::finished, thread, &QThread::quit);

    // 确保线程结束时删除Worker对象
    QObject::connect(thread, &QThread::finished, worker, &QObject::deleteLater);
    // 同样,确保线程结束时删除线程对象本身
    QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);

    thread->start(); // 启动线程

    return app.exec(); // 进入Qt事件循环
}

使用QtConcurrent

Qt提供了QtConcurrent模块,它提供了一个高级的并发编程框架。你可以使用QtConcurrent::run()函数来简单地在后台线程中运行函数或成员函数。

#include <QtConcurrent>
#include <QDebug>
#include <QCoreApplication>

void doWork() {
    qDebug() << "Work is being done in thread" << QThread::currentThreadId();
    // 执行一些耗时的操作
}

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    // 这将在一个新的线程中异步执行doWork函数
    QtConcurrent::run(doWork);

    return app.exec(); // 进入Qt事件循环
}

使用QThreadPoolQRunnable

你可以创建一个QRunnable对象来表示一个任务,并将其提交给QThreadPool,这样它就可以在某个线程中异步执行。

#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class WorkerRunnable : public QRunnable {
public:
    WorkerRunnable() {}

    void run() override {
        qDebug() << "Work is being done in thread" << QThread::currentThreadId();
        // 执行一些耗时的操作
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    WorkerRunnable *task = new WorkerRunnable();
    QThreadPool::globalInstance()->start(task); // 任务将被添加到全局线程池并执行

    return app.exec(); // 进入Qt事件循环
}

注意事项

  • 当使用多线程时,你需要确保对共享数据的访问是线程安全的。这通常意味着使用互斥锁(QMutex)或其他同步机制。
  • 避免在子线程中直接操作GUI,因为Qt的GUI工具包不是线程安全的。如果需要更新GUI,可以使用QObject::moveToThread()将对象移动到GUI线程,或者使用QMetaObject::invokeMethod()QSignalMapper来安全地从后台线程发出信号到GUI线程。
  • 使用QThread时,确保适当地管理线程的生命周期,避免内存泄漏。通常,当线程完成其任务后,你应该调用quit()来结束线程的事件循环,然后删除线程对象。

在多线程编程中,正确地管理资源和同步是至关重要的,以避免数据竞争、死锁和其他并发问题。

子线程更新GUI

Thread子类版

在Qt中,由于GUI组件不是线程安全的,你不能直接从子线程更新GUI。相反,你需要使用信号和槽机制来安全地从子线程发出信号,并在主线程(GUI线程)中接收这些信号并更新GUI。

以下是使用子线程更新GUI的步骤:

步骤 1: 创建一个继承自QObject的类,并在其中定义信号
// Worker.h
#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject {
    Q_OBJECT

public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent) {}

signals:
    void updateGUI(const QString &data); // 自定义信号

public slots:
    void doWork();
};

#endif // WORKER_H
步骤 2: 实现Worker类的工作方法
// Worker.cpp
#include "Worker.h"

void Worker::doWork() {
    // 执行一些耗时的操作
    QString result = "Done"; // 假设这是耗时操作的结果
    emit updateGUI(result); // 发送信号,而不是直接更新GUI
}
步骤 3: 在主线程中创建Worker对象,并将其移动到子线程
// main.cpp
#include <QApplication>
#include <QThread>
#include <QPushButton>
#include "Worker.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QThread *thread = new QThread();
    Worker *worker = new Worker();
    worker->moveToThread(thread);

    // 当线程启动时,Worker对象的doWork()方法会被调用
    QObject::connect(thread, &QThread::started, worker, &Worker::doWork);

    // 从子线程接收信号,并在主线程中更新GUI
    QObject::connect(worker, &Worker::updateGUI, [](const QString &data) {
        qDebug() << "Update GUI in main thread:" << data;
        // 在这里更新GUI,例如设置文本到一个标签
    });

    // 当Worker对象的doWork()方法完成后,退出线程
    QObject::connect(worker, &Worker::updateGUI, thread, &QThread::quit);

    // 确保线程结束时删除Worker对象
    QObject::connect(thread, &QThread::finished, worker, &QObject::deleteLater);
    // 同样,确保线程结束时删除线程对象本身
    QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);

    // 创建一个简单的窗口来测试
    QPushButton button("Start Work");
    QObject::connect(&button, &QPushButton::clicked, thread, &QThread::start);
    button.show();

    return app.exec(); // 进入Qt事件循环
}

在这个例子中,我们创建了一个Worker对象,并将其移动到了一个新的QThread对象中。当线程启动时,Worker对象的doWork()方法会被调用。这个方法执行一些耗时的操作,并通过updateGUI信号发送结果。这个信号被连接到一个槽函数,该槽函数在主线程中更新GUI。

注意事项
  • 确保在子线程中不要直接操作GUI组件。
  • 使用QThread::quit()来优雅地退出线程。
  • 使用QObject::moveToThread()将对象移动到正确的线程。
  • 使用QObject::deleteLater()来确保线程和对象被适当地清理。

QConcurrent::run方法

在Qt中,尝试从非GUI线程直接更新GUI元素(如ui->textBrowser)会导致未定义行为,因为Qt的GUI组件并不是线程安全的。因此,你需要使用信号和槽来安全地从子线程更新GUI。

下面是如何正确使用QtConcurrent::run来更新GUI的步骤:

步骤 1: 定义一个信号

首先,在你的窗口类或任何适当的类中定义一个信号,用于传递数据到GUI线程。

// MyMainWindow.h
#include <QMainWindow>
#include <QString>

namespace Ui {
class MyMainWindow;
}

class MyMainWindow : public QMainWindow {
    Q_OBJECT

public:
    explicit MyMainWindow(QWidget *parent = nullptr);
    ~MyMainWindow();

signals:
    void updateTextBrowser(const QString &text); // 定义一个信号

private:
    Ui::MyMainWindow *ui;
};
步骤 2: 连接信号和槽

在你的窗口类的构造函数中,连接这个信号到一个槽,这个槽会更新GUI。

// MyMainWindow.cpp
#include "MyMainWindow.h"
#include "ui_MyMainWindow.h"

MyMainWindow::MyMainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MyMainWindow),
    // 初始化ui组件等
{
    ui->setupUi(this);

    // 连接信号到槽
    connect(this, &MyMainWindow::updateTextBrowser, [this](const QString &text) {
        ui->textBrowser->append(text);
    });
}

MyMainWindow::~MyMainWindow() {
    delete ui;
}
步骤 3: 使用QtConcurrent::run在后台线程中运行任务

现在,你可以使用QtConcurrent::run来在后台线程中运行你的任务,并通过信号将数据发送回GUI线程。

#include <QtConcurrent>
#include <QDebug>

void startBackgroundTask(MyMainWindow *window) {
    QtConcurrent::run([=]() {
        while (true) {
            QString text = "nihao1";
            emit window->updateTextBrowser(text); // 发送信号
            QThread::sleep(1); // 避免过度占用CPU
        }
    });
    // 返回对象是QFuture<模板>,若lambda表达式没返回值,则是QFuture<void>型
}
步骤 4: 启动后台任务

在你的窗口类或其他适当的地方,调用startBackgroundTask函数来启动后台任务。

// 例如,在某个按钮的点击事件中
startBackgroundTask(this);
注意事项
  • 使用QThread::sleep或其他同步机制来控制循环的频率,以避免过度占用CPU。
  • 确保在适当的时候停止后台任务,例如在窗口关闭时。这可以通过设置一个控制变量来实现,当需要停止时,改变这个变量的值,并在循环中检查这个变量。
  • 在实际应用中,你可能需要更复杂的逻辑来确保线程安全地停止后台任务。

通过这种方式,你可以安全地从后台线程更新GUI,而不违反Qt的线程安全规则。

Socket跨线程调用的问题

Qt笔记-QTcpSocket跨线程调用(官方推荐方法,非百度烂大街方法)_setsocketdescriptor-CSDN博客

错误信息 "QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"

指出 QSocketNotifier 不能从另一个线程被启用或禁用。这是因为 QSocketNotifier 需要在创建它的线程中被注册和取消注册。当你尝试在不同的线程中操作 QSocketNotifier 时,就会遇到这个问题。

  1. 确保 QTcpSocketQSocketNotifier 在同一个线程中:如果你在子线程中使用 QTcpSocket,那么 QSocketNotifier 也应该在这个子线程中创建和管理。这意味着你需要避免跨线程调用 QObject 及其子类对象。

  2. 使用 moveToThread 方法:如果你有一个 QTcpSocket 对象,你可以使用 moveToThread 方法将其移动到新的线程中。对于 QSocketNotifier,在连接前确保它已经在正确的线程上。

  3. 避免在子线程中直接操作 QTcpSocket:如果你在子线程中直接操作 QTcpSocket,可能会导致 QSocketNotifier 相关的问题。你可以通过信号和槽机制,将数据从一个线程安全地传递到另一个线程。

  4. 在子线程中创建 QTcpSocket:如果你在子线程的构造函数或 run 函数中创建 QTcpSocket,那么所有的操作都应该在这个子线程中进行,以避免跨线程操作 QSocketNotifier

  5. 使用 Qt::QueuedConnection:在连接信号和槽时,使用 Qt::QueuedConnection 可以确保信号安全地传递给主线程中的槽函数,这样可以避免在子线程中直接操作 QSocketNotifier

  6. 避免跨线程调用 QObject:当你在主线程中创建 QObject 及其子类对象时,不要尝试在子线程中对其进行操作。相反,你应该在子线程中创建这些对象,以避免跨线程调用。

最终代码

main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

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();

    bool CanReadLine();

private:
    QSslSocket* ssl;
    bool isentrcyed = false;

signals:
};

#endif // TCPMAILCLIENT_H

tcpmailclient.hpp

#include "tcpmailclient.h"

TCPMailClient::TCPMailClient(const QString &host, quint16 port, QObject *parent)
    : QObject{parent} {
  QSslSocket *socket = new QSslSocket(this);
  this->ssl = socket;

  QObject::connect(ssl, &QSslSocket::encrypted, [=]() {
    this->isentrcyed = true;
    qDebug() << "连接成功";
  });

  QObject::connect(ssl, &QSslSocket::connected, this,[]() {
        qDebug() << "已连接到SMTP服务器";
    });

  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) {
    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();
}

bool TCPMailClient::CanReadLine() { return ssl->canReadLine(); }

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include "tcpmailclient.h"
#include <QWidget>
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QCoreApplication>
#include <QFuture>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

signals:
    void updateTextBrowser(const QString &text); // 定义一个更新响应内容的信号
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void startReciveThread();

    void sendRequest();


private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;

    TCPMailClient *mailclient;
    QFuture <void> future;
    bool recieveFlag = true;
};
#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);
    mailclient = new TCPMailClient("smtp.163.com", 465);

    // 连接信号到槽
    connect(this, &Widget::updateTextBrowser, [this](const QString &text) {
        // 通过触发信号回到ui线程更新
        ui->textBrowser->append(text);
    });
    startReciveThread();
}

Widget::~Widget() {
    on_pushButton_2_clicked();
    delete ui;
}

void Widget::startReciveThread() {
    future = QtConcurrent::run([=]() {
        recieveFlag = true;
        while (recieveFlag) {
            QString text = this->mailclient->recieve();
            if (text.isEmpty()){
                continue;
            }
            emit this->updateTextBrowser(text); // 发送信号
            QThread::msleep(500);               // 避免过度占用CPU
        }
    });
}

void Widget::sendRequest() {
    QFuture<void> sendFuture = QtConcurrent::run([=]() {
        // 每发送一次会报错两行  QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
        // 因为套接字是在主线程创建的,所以套接字的QSocketNotifier也要在主线程
        this->mailclient->send("HELO myfriend\r\n");
        QThread::msleep(500);

        this->mailclient->send("AUTH LOGIN\r\n");
        QThread::msleep(500);

        // 发送用户名
        QString username = "xxx@163.com";
        QString encodeUsrName  = username.toUtf8().toBase64();
        this->mailclient->send(QString("%1\r\n").arg(encodeUsrName));
        QThread::msleep(500);

        // 发送授权码
        QString password = "xxxxxxxxxxxX";
        QString encodepwd = password.toUtf8().toBase64();
        this->mailclient->send(QString("%1\r\n").arg(encodepwd));
        QThread::msleep(500);

        this->mailclient->send("MAIL FROM:<xxxx@163.com>\r\n");
        QThread::msleep(500);

        this->mailclient->send("RCPT TO:<xxxx@163.com>\r\n");
        QThread::msleep(500);

        this->mailclient->send("DATA\r\n");
        QThread::msleep(500);

        this->mailclient->send("FROM:xxxx@163.com\r\n");
        QThread::msleep(500);

        this->mailclient->send("SUBJECT: 测试邮件\r\n");
        QThread::msleep(500);

        this->mailclient->send("TO:xxxx@163.com\r\n");
        QThread::msleep(500);

        this->mailclient->send("\r\n");
        QThread::msleep(500);

        this->mailclient->send("这是一封测试邮件\r\n");
        QThread::msleep(500);

        this->mailclient->send(".\r\n");
        QThread::msleep(500);

        this->mailclient->send("QUIT\r\n");
    });
    // 如果需要,可以在这里使用sendFuture
}

void Widget::on_pushButton_2_clicked() {
    recieveFlag = false;
    if (!future.isFinished()) {
        future.cancel();          // 请求取消任务
        future.waitForFinished(); // 等待任务完成
    }
    QApplication::quit();
}

void Widget::on_pushButton_clicked() {
    sendRequest();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2240503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于我重生到21世纪学C语言这件事——指针详解(1)

人无完人&#xff0c;持之以恒&#xff0c;方能见真我&#xff01;&#xff01;&#xff01; 共同进步&#xff01;&#xff01; 文章目录 1. 内存和地址2. 指针变量和地址3. 指针变量类型的意义4. const修饰指针5. 指针运算6. 野指针7. assert断⾔8. 指针的使⽤和传址调⽤ 1.…

PaoluGPT——窥视未知

上一题已经得到一个flag&#xff0c;还有一个flag 根据题目信息&#xff0c;说明还有一些聊天记录是没有公开的&#xff0c;另一个flag就在这些未公开的聊天记录中 下载题目附件看看&#xff0c;发现里面有个main.py&#xff1a; 可以看到有两条SQL查询语句&#xff0c;猜测应该…

WLAN消失或者已连接但是访问不了互联网

目录 1、WLAN已连接但是访问不了互联网 2、WLAN图标消失 今晚电脑突然连不上网了&#xff0c;重启试了好多种办法都没有用。 1、WLAN已连接但是访问不了互联网 这个的问题很多&#xff0c;建议直接网络重置&#xff0c;即将网络驱动全部删除&#xff0c;然后重新安装。 首先…

Python学习从0到1 day26 第三阶段 Spark ④ 数据输出

半山腰太挤了&#xff0c;你该去山顶看看 —— 24.11.10 一、输出为python对象 1.collect算子 功能: 将RDD各个分区内的数据&#xff0c;统一收集到Driver中&#xff0c;形成一个List对象 语法&#xff1a; rdd.collect() 返回值是一个list列表 示例&#xff1a; from …

机器学习在网络安全中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 机器学习在网络安全中的应用 机器学习在网络安全中的应用 机器学习在网络安全中的应用 引言 机器学习概述 定义与原理 发展历程 …

JMeter进阶篇

目录 上篇导航&#xff1a; 总目录&#xff1a; 一、逻辑控制器&#xff1a; 1.逻辑控制器和关联&#xff1a; 2.if逻辑控制器&#xff1a; 3.forEach控制器&#xff1a; 4.循环控制器&#xff1a; 二、关联&#xff1a; 1.xpath&#xff1a; 2.正则表达式提取器&…

O-RAN简介

O-RAN简介 概览 如今,全球蜂窝数据使用量持续增长,因此,电信系统必须随之进行革新,才能满足这一需求量。虽然5G标准能够满足更高的蜂窝吞吐量需求,且有望实现各种新的应用场景,但如果网络没有进行相应的改进,许多拟定的5G应用只能是纸上谈兵。以高可靠低延时通信(URLL…

Spring设计模式

设计模式 是一种软件开发中的解决方案&#xff0c;设计原则。目的是使代码具有扩展性&#xff0c;可维护性&#xff0c;可读性&#xff0c;如&#xff1a; 单例模式&#xff08;Singleton Pattern&#xff09; Spring IoC 容器默认会将 Bean 创建为单例&#xff0c;保证一个类…

安全的时钟启动

Note&#xff1a;文章内容以 Xilinx 系列 FPGA 进行讲解 1、什么是安全启动时钟 通常情况下&#xff0c;在MMCM/PLL的LOCKED信号抬高之后&#xff08;由0变为1&#xff09;&#xff0c;MMCM/PLL就处于锁定状态&#xff0c;输出时钟已保持稳定。但在此之前&#xff0c;输出时钟会…

【含开题报告+文档+PPT+源码】基于Springboot和vue的电影售票系统

开题报告 随着电影产业的快速发展和科技的不断进步而逐渐形成的。在早期&#xff0c;电影票的销售主要依赖于传统的实体售票窗口和人工售票员&#xff0c;这种方式虽然直接&#xff0c;但效率低下&#xff0c;容易出现错误&#xff0c;并且无法满足大规模、高流量的售票需求。…

5G的发展演进

5G发展的驱动力 什么是5G [远程会议&#xff0c;2020年7月10日] 在来自世界各地的政府主管部门、电信制造及运营企业、研究机构约200多名会议代表和专家们的共同见证下&#xff0c;ITU-R WP 5D#35e远程会议宣布3GPP 5G技术&#xff08;含NB-IoT&#xff09;满足IMT-2020 5G技…

Vue全栈开发旅游网项目(11)-用户管理前端接口联调

联调基本步骤 1.阅读接口文档 2.配置接口地址 3.使用axios获取数据 4.将数据设置到模型层 1.发送验证码联调 1.1 配置接口地址 文件地址&#xff1a;src\utils\apis.js //系统相关的接口 const SystemApis {sliderListUrl:apiHost"/system/slider/list/",//发送…

接口返回的结构体里包含图片(做图片预览)

摘要&#xff1a;有这么一种情况&#xff0c;页面上有一块儿内容是接口返回的&#xff0c;前端做渲染&#xff0c;比如 <div><p><img srcxxx /></p><p>测试</p> </div> 这是接口返回的字符串结构数据&#xff0c;这时候需要前端做…

免费,WPS Office教育考试专用版

WPS Office教育考试专用版&#xff0c;不仅满足了考试需求&#xff0c;更为教育信息化注入新动力。 https://pan.quark.cn/s/609ef85ae6d4

aws中AcmClient.describeCertificate返回值中没有ResourceRecord

我有一个需求&#xff0c;就是让用户自己把自己的域名绑定我们的提供的AWS服务器。 AWS需要验证证书 上一篇文章中我用php的AcmClient中的requestCertificate方法申请到了证书。 $acmClient new AcmClient([region > us-east-1,version > 2015-12-08,credentials>[/…

ctfshow-web入门-反序列化(web271-web278)

目录 1、web271 2、web272 3、web273 4、web274 5、web275 6、web276 7、web277 8、web278 laravel 反序列化漏洞 1、web271 laravel 5.7&#xff08;CVE-2019-9081&#xff09; poc <?php namespace Illuminate\Foundation\Testing{use Illuminate\Auth\Generic…

程序员的数学之进制与零

最近一年多发生了很多平凡的大事&#xff0c;应接不暇&#xff0c;一度断更。从今儿起再接上来。 先从数学开始吧&#xff0c;因为太枯燥了。 生活中有许多种进制在共同起作用&#xff0c;例如&#xff0c;数学上的十进制、计算机中的二进制、八进制和十六进制、计时的60进制、…

GPT-5 要来了:抢先了解其创新突破

Microsoft 的工程师计划于 2024 年 11 月在 Azure 上部署 Orion (GPT-5)。虽然这一版本不会向公众开放&#xff0c;但其上线被视为人工智能领域的一个重要里程碑&#xff0c;并将产生深远的影响。 文章目录 GPT-5 真的要来了GPT-4 的局限性GPT-5 的创新突破与遗留挑战GPT-5 预期…

01-Ajax入门与axios使用、URL知识

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

堆中的时间复杂度+TOP K问题

堆中的时间复杂度分析 回顾: 堆在物理上:数组 逻辑上:完全二叉树 1.堆排序是什么? // 排升序void HeapSort(int* a, int n){// 建大堆 -for (int i (n - 1 - 1) / 2; i > 0; --i){AdjustDown(a, n, i);}int end n - 1;while (end > 0){Swap(&a[0], &a[end]…