QT给端口扫描工程增加线程2
前面的步骤:用QT实现 端口扫描工具1-CSDN博客
框架分析
mainwindow.cpp中
有类的构造函数、析构函数,点击事件处理函数,接受信号的槽函数
扫描函数的点击事件中
MyThread新增加了线程mthread,并为其连接信号和槽函数(信号和槽是相对应的,所以要把它们连接起来)
MyThread中
的.h文件中声明函数,MyThread的.cpp文件中进行函数定义
--------------------------------------------------------------------------------------------------------------------------------
加一个button
给Stop和Scan加clicked槽。
增加线程步骤
右键,添加
mythread.h文件
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QTcpSocket>
#include <QDebug>
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QString, int);
void closeThread(); //用于结束线程
signals:
void send_scan_signal(int, bool); //发送线程扫描结果
protected:
virtual void run();
private:
volatile bool isStop; // 线程是否结束,volatile 表示不优化此变量
QString m_strIP; //属性:目标 IP
int m_intPort; //属性:目标端口
};
#endif // MYTHREAD_H
mythread.cpp
#include "mythread.h"
MyThread::MyThread(QString strIP, int intPort)
{
isStop = false; //初始化
m_strIP = strIP; //给属性赋值
m_intPort = intPort; //给属性赋值
}
void MyThread::closeThread()
{
isStop = true; //给属性赋值,true 表示结束线程
}
void MyThread::run()
{
QTcpSocket socket(0);
for ( int i = m_intPort; i<=1024; i++ )//从 i 开始扫描,最大扫描到 1024
{
if(isStop)
return;
socket.abort();
socket.connectToHost(m_strIP, i);
if(socket.waitForConnected(1000))
{
emit (send_scan_signal(i, true)); // 发送信号
qDebug("%d: %s",i,"opened"); // 调试信息
}
else
{
emit (send_scan_signal(i, false));
qDebug("%d: %s",i,"closed");
}
msleep(100);
}
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
//添加头文件的引用
#include <QMessageBox>
#include <QTreeWidget>
#include "mythread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_Scan_clicked();
void on_stopButton_clicked();
void on_pushButton_Quit_clicked();
void recv_result(int, bool); //自定义槽函数,用于接收线程发送的消息;
private:
Ui::MainWindow *ui;
QTreeWidgetItem *itemRoot; //树形控件的条目,用于表示目标 IP
QTreeWidgetItem *itemLeaf; //树形控件的条目,用于表示目标端口
MyThread *myThread = 0; //线程类
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
// MainWindow 类的构造函数 执行清理操作
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) // 调用基类 QMainWindow 的构造函数
, ui(new Ui::MainWindow) // 创建 UI 对象
{
ui->setupUi(this); // 设置用户界面
// 给 QTreeWidget 初始化表头
QStringList head; // 创建一个字符串列表用于存储表头
head << "扫描结果"; // 添加表头项
ui->treeWidget->setHeaderLabels(head); // 设置 QTreeWidget 的表头
}
// MainWindow 类的析构函数
MainWindow::~MainWindow()
{
delete ui; // 删除 UI 对象以释放内存
}
// 扫描按钮点击事件处理函数
void MainWindow::on_pushButton_Scan_clicked()
{
ui->pushButton_Scan->setEnabled(false); // 正在扫描时,禁用扫描按钮
QString strIP = ui->lineEdit_IP->text(); // 获取用户输入的 IP 地址
int intPort = ui->spinBox_Port->value(); // 获取用户输入的端口号
// 验证 IP 地址是否为空
if(strIP.isEmpty()){
QMessageBox::information(this, "Error", "请输入 IP", QMessageBox::Ok); // 显示错误信息
return; // 退出函数
}
// 验证端口号是否为 0
if(intPort == 0){
QMessageBox::information(this, "Error", "请输入 port", QMessageBox::Ok); // 显示错误信息
return; // 退出函数
}
ui->treeWidget->clear(); // 清空 QTreeWidget 中的先前结果
itemRoot = new QTreeWidgetItem(ui->treeWidget, QStringList(strIP)); // 创建根节点,显示 IP 地址
myThread = new MyThread(strIP, intPort); // 创建新的扫描线程,传入 IP 和端口
// 连接 myThread 的信号 send_scan_signal(int, bool) 和槽函数 recv_result(int, bool)
connect(myThread, SIGNAL(send_scan_signal(int, bool)), this, SLOT(recv_result(int, bool))); // 连接信号和槽
myThread->start(); // 启动扫描线程
}
// 接收扫描结果的槽函数
void MainWindow::recv_result(int port, bool isOpen)
{
QString strPort = QString::number(port); // 将端口号转换为字符串
// 根据端口状态创建叶子节点
if(isOpen)
itemLeaf = new QTreeWidgetItem(itemRoot, QStringList(strPort + " opened")); // 端口打开
else
itemLeaf = new QTreeWidgetItem(itemRoot, QStringList(strPort + " closed")); // 端口关闭
}
// 停止按钮点击事件处理函数
void MainWindow::on_stopButton_clicked()
{
// 检查是否有正在运行的线程
if(myThread == 0)
{
QMessageBox::information(this, "Error", "还没有开始", QMessageBox::Ok); // 显示错误信息
return; // 退出函数
}
myThread->closeThread(); // 调用线程类的自定义成员函数以关闭线程
myThread->wait(); // 等待线程结束
delete myThread; // 删除线程对象以释放内存
myThread = 0; // 将线程指针置为 nullptr
ui->pushButton_Scan->setEnabled(true); // 重新启用扫描按钮
}
// 退出按钮点击事件处理函数
void MainWindow::on_pushButton_Quit_clicked()
{
QApplication::quit(); // 退出应用程序
}
在扫描线程结束后在主窗口得到结束信号并将 Scan 按钮设置为可用
在 MyThread
类中定义一个信号,例如 finished()
,并在扫描线程完成时发出该信号。然后,在 MainWindow
类中连接这个信号到一个槽函数,该槽函数将重新启用“Scan”按钮。
### 步骤
1. 在 MyThread
类的头文件中,添加一个结束信号:
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QTcpSocket>
#include <QDebug>
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QString, int);
void closeThread(); //用于结束线程
signals:
void send_scan_signal(int, bool); //发送线程扫描结果
void finished();
protected:
virtual void run();
private:
volatile bool isStop; // 线程是否结束,volatile 表示不优化此变量
QString m_strIP; //属性:目标 IP
int m_intPort; //属性:目标端口
};
#endif // MYTHREAD_H
2. 在 `MyThread` 的 `run()` 方法中发射信号
在扫描完成后,调用 `emit finished();` 来发射这个信号。
MyThread.cpp
#include "MyThread.h"
MyThread::MyThread(QString strIP, int intPort)
{
isStop = false; //初始化
m_strIP = strIP; //给属性赋值
m_intPort = intPort; //给属性赋值
}
void MyThread::closeThread()
{
isStop = true; //给属性赋值,true 表示结束线程
}
void MyThread::run()
{
QTcpSocket socket(0);
for ( int i = m_intPort; i<=1024; i++ )//从 i 开始扫描,最大扫描到 1024
{
if(isStop)
return;
socket.abort();
socket.connectToHost(m_strIP, i);
if(socket.waitForConnected(1000))
{
emit (send_scan_signal(i, true)); // 发送信号
qDebug("%d: %s",i,"opened"); // 调试信息
}
else
{
emit (send_scan_signal(i, false));
qDebug("%d: %s",i,"closed");
}
msleep(100);
}
emit finished(); // 在扫描完成后发出信号
}
3.在mainwindow的.h文件中声明槽函数
4. 实现槽函数
在 MainWindow
中实现 onScanFinished()
槽函数,重新启用“Scan”按钮:
//接受结束信号的槽函数
void MainWindow::onScanFinished()
{
ui->pushButton_Scan->setEnabled(true); // 重新启用扫描按钮
}
5. 在 Scan按钮的点击事件`void MainWindow::on_pushButton_Scan_clicked()` 中连接信号和槽:
这样可以确保每次点击“Scan”按钮时,都会正确连接信号和槽。
// 扫描按钮点击事件处理函数
void MainWindow::on_pushButton_Scan_clicked()
{
ui->pushButton_Scan->setEnabled(false); // 正在扫描时,禁用扫描按钮
QString strIP = ui->lineEdit_IP->text(); // 获取用户输入的 IP 地址
int intPort = ui->spinBox_Port->value(); // 获取用户输入的端口号
// 验证 IP 地址是否为空
if(strIP.isEmpty()){
QMessageBox::information(this, "Error", "请输入 IP", QMessageBox::Ok); // 显示错误信息
return; // 退出函数
}
// 验证端口号是否为 0
if(intPort == 0){
QMessageBox::information(this, "Error", "请输入 port", QMessageBox::Ok); // 显示错误信息
return; // 退出函数
}
ui->treeWidget->clear(); // 清空 QTreeWidget 中的先前结果
itemRoot = new QTreeWidgetItem(ui->treeWidget, QStringList(strIP)); // 创建根节点,显示 IP 地址
myThread = new MyThread(strIP, intPort); // 创建新的扫描线程,传入 IP 和端口
// 连接 myThread 的信号 send_scan_signal(int, bool) 和槽函数 recv_result(int, bool)
connect(myThread, SIGNAL(send_scan_signal(int, bool)), this, SLOT(recv_result(int, bool))); // 连接信号和槽
connect(myThread, &MyThread::finished, this, &MainWindow::onScanFinished); // 连接线程结束信号
myThread->start(); // 启动扫描线程
}
在主窗口设置扫描结束端口
为finished增加两个参数int port, bool isOpen
1.在 MyThread.h
中,修改 finished()
信号的定义,以便它可以接收端口号和状态:
signals:
void send_scan_signal(int, bool); // 发送线程扫描结果
void finished(int port, bool isOpen); // 扫描完成的信号,带有端口号和状态
2.在 MyThread.cpp
中,修改 run()
方法,以便在扫描结束时发出 finished(int port, bool isOpen)
信号:
void MyThread::run()
{
QTcpSocket socket(0);
bool lastPortStatus = false; // 用于存储最后一个端口的状态
int lastPort = m_intPort; // 用于存储最后一个端口号
for (int i = m_intPort; i <= 1024; i++) // 从 m_intPort 开始扫描,最大扫描到 1024
{
if (isStop)
return;
socket.abort();
socket.connectToHost(m_strIP, i);
if (socket.waitForConnected(1000))
{
emit send_scan_signal(i, true); // 发送信号
qDebug("%d: %s", i, "opened"); // 调试信息
lastPortStatus = true; // 更新最后一个端口状态
lastPort = i; // 更新最后一个端口号
}
else
{
emit send_scan_signal(i, false);
qDebug("%d: %s", i, "closed");
lastPortStatus = false; // 更新最后一个端口状态
lastPort = i; // 更新最后一个端口号
}
msleep(100);
}
emit finished(lastPort, lastPortStatus); // 在扫描完成后发出信号,传递最后的端口号和状态
}
3.更新 MainWindow.h
private slots:
void onScanFinished(int port, bool isOpen); // 声明 onScanFinished 槽函数
4.更新 MainWindow.cpp
// 连接信号和槽
connect(myThread, &MyThread::send_scan_signal, this, &MainWindow::recv_result); // 连接扫描结果信号
connect(myThread, &MyThread::finished, this, &MainWindow::onScanFinished); // 连接扫描结束信号
5.更新槽函数的实现
在 MainWindow
中实现 onScanFinished(int port, bool isOpen)
槽函数,以更新 UI:
void MainWindow::onScanFinished(int port, bool isOpen) {
QString strPort = QString::number(port); // 将端口号转换为字符串
QString status = isOpen ? "opened" : "closed"; // 根据状态设置字符串
// 在 UI 中显示端口状态
QMessageBox::information(this, "Scan Finished", "Port " + strPort + " is " + status, QMessageBox::Ok);
ui->pushButton_Scan->setEnabled(true); // 重新启用扫描按钮
}
添加一点中文播报
则实现了结束扫描的提示
补充概念和技巧
线程的概念
“线程”是计算机科学中的一个重要概念,通常指的是程序执行的最小单位。线程可以被视为轻量级的进程,它们共享同一进程的资源(如内存和文件句柄),但每个线程都有自己的执行栈和程序计数器。
槽函数
槽函数用于响应信号。
调大qt creater的字体:
ctrl+shift++