实验任务
-
编写端口扫描程序
- 具有界面(使用QtCreator)
- 具有多线程处理能力
- 使用简单的
connect 确定端口是否开放即可 - 可以提前结束扫面,安全的结束线程
-
效果示例(源码)
-
界面 -
扫描局域网内开放的端口 -
扫描百度 -
多线程加速 -
提前结束扫描
实验步骤一、熟悉QtCreator编程
实验步骤二、设计界面
-
拖拉拽即可,注意一点就是控件的objectName 属性值,通过该值可以在代码中访问、更新UI,如 更新结果显示区内容时,使用ui->resultArea 访问该控件对象 void MainWindow::updateResult(QString info, bool opened) {
ui->resultArea->append(info + " " + (opened ? "opened" : "closed"));
}
也可以在代码中设置控件的一些属性,如 MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startIP_1->setMaxLength(3);
ui->startIP_2->setMaxLength(3);
ui->startIP_3->setMaxLength(3);
ui->startIP_4->setMaxLength(3);
ui->endIP_1->setMaxLength(3);
ui->endIP_2->setMaxLength(3);
ui->endIP_3->setMaxLength(3);
ui->endIP_4->setMaxLength(3);
}
将按钮的点击事件和处理函数绑定(Qt称为连接信号与槽) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
QObject::connect(ui->beginScan, &QPushButton::clicked, this, &MainWindow::scan);
QObject::connect(ui->stopScan, &QPushButton::clicked, this, &MainWindow::terminateScan);
}
实验步骤二、确定通信对象,声明信号和槽
-
为UI中的按钮创建对应的槽 mainwindow.h class MainWindow : public QMainWindow
{
public slots :
void scan();
void terminateScan();
void updateResult(QString info, bool open);
};
-
子线程扫描结束信号,及主线程对应的槽(上述第三个) scanthread.h class ScanThread : public QThread
{
signals:
void scanOver(QString info, bool opened);
};
实验步骤三、主线程之参数校验
-
合法IP地址、IP范围校验 -
合法端口校验 -
合理线程校验 qDebug() << "检查线程\n";
int numOfThread = ui->threadCount->text().toInt();
if(numOfThread == NULL || numOfThread < 0 || numOfThread > 1000) {
QMessageBox::warning(
this,
tr("提示"),
tr("请输入正确的线程数(max=1000)!"),
QMessageBox::Ok);
return ;
}
实验步骤四、主线程之任务分配
-
分配方式
-
待扫描的IP数量小于等于线程数: 每个线程只需扫描一个IP地址,按端口数量分配任务; 有可能出现刚好跨IP的情况(前一个IP的最后几个端口和下一个IP的前几个端口),方便起见,遇到这种情况时,前一个线程只需扫描完当前IP的最后几个端口即可;而原本在它工作范围之内的剩余IP均分给剩余线程 -
带扫描的IP数量大于线程数 每个线程要扫描多个IP地址,则按IP数量分配任务,每个线程完成分配到的IP的所有端口的扫描 -
每次给当线程分配完任务后都动态调整剩余任务的分配 -
分配实现 if(numOfThread >= ipCount) {
int taskForSingleThread = totCount / numOfThread, dispatchTask = 0;
for(int i = 0; i < numOfThread; i++) {
currentIP = netAddr + QString::number(hostAddr + scannedIP, 10);
currentPort = scannedPort % portCount + startPort;
if(i == numOfThread - 1) taskForSingleThread = totCount - scannedPort;
if(endPort - currentPort + 1 < taskForSingleThread) {
dispatchTask = endPort - currentPort + 1;
} else {
dispatchTask = taskForSingleThread;
}
ScanThread* thread = new ScanThread(currentIP, 1, currentPort, dispatchTask);
QObject::connect(thread, &ScanThread::scanOver, this, &MainWindow::updateResult);
threadPool[i] = thread;
thread->start();
scannedPort += dispatchTask;
scannedIP = scannedPort / portCount;
if((totCount - scannedPort) * 1.0 / (numOfThread - i - 1) > taskForSingleThread) {
taskForSingleThread++;
}
}
} else {
int taskForSingleThread = ipCount / numOfThread;
for(int i = 0; i < numOfThread; i++) {
currentIP = netAddr + QString::number(hostAddr + scannedIP, 10);
if(i == numOfThread - 1) {
taskForSingleThread = ipCount - scannedIP;
}
ScanThread* thread = new ScanThread(currentIP, taskForSingleThread, startPort, portCount);
QObject::connect(thread, &ScanThread::scanOver, this, &MainWindow::updateResult);
threadPool[i] = thread;
thread->start();
scannedIP += taskForSingleThread;
if((ipCount - scannedIP) * 1.0 / (numOfThread - i - 1) > taskForSingleThread) {
taskForSingleThread++;
}
}
}
}
实验步骤五、主线程之更新UI
-
收到子线程传递的扫描结果,将其显示在相应区域 void MainWindow::updateResult(QString info, bool opened) {
if(this->isOver) return ;
ui->resultArea->append(info + " " + (opened ? "opened" : "closed"));
if(opened) {
this->openedPort.append(info);
}
this->finTask++;
if(this->finTask == this->totTask) {
this->endTime = QTime::currentTime();
ui->resultArea->append("扫描结束,耗时: " + QString::number(this->startTime.msecsTo(this->endTime) / 1000.0));
this->showAllOpenedPorts();
}
}
-
最后显示所有开启的端口 void MainWindow::showAllOpenedPorts() {
ui->resultArea->append("-------------------------------------------\n开启的IP及端口号如下");
int numOfOpenedPort = this->openedPort.size();
for(int i = 0; i < numOfOpenedPort; i++) {
ui->resultArea->append(this->openedPort.at(i));
}
ui->resultArea->append("-------------------------------------------\n本次扫描完成\n");
qDebug() << "over";
}
实验步骤六、主线程之结束子线程
-
由于提供了提前终止扫描的按钮,需要适当的处理子线程 void MainWindow::terminateScan() {
int confirm = QMessageBox::warning(
this,
tr("提示"),
tr("您确定要提前结束扫描吗?"),
QMessageBox::Ok, QMessageBox::Cancel);
if(confirm != QMessageBox::Ok) {
return ;
}
this->isOver = true;
for(int i = 0; i < 64; i++) {
if(threadPool[i] != NULL) {
threadPool[i]->requestInterruption();
threadPool[i]->wait();
qDebug() << "stop" << threadPool[i] << "\n";
}
}
ui->resultArea->append("-------------------------------------------\n本次扫描已终止\n");
qDebug() << "stopped\n";
}
实验步骤七、子线程之扫描端口
-
主线程给子线程分配任务时,传递了以下参数
ip :待扫描的起始IPipCount :待扫描的IP数量port :待扫描的起始端口portCount :待扫描的端口数量 -
扫描实现 void ScanThread::run() {
QTcpSocket* conn = new QTcpSocket();
QString netAddr = ip.left(ip.lastIndexOf('.') + 1);
int hostAddr = ip.right(ip.length() - ip.lastIndexOf('.') - 1).toInt();
QString curIP;
int curPort;
for(int i = 0; !isInterruptionRequested() && i < ipCount; i++) {
curIP = netAddr + QString::number(hostAddr + i);
for(int j = 0; !isInterruptionRequested() && j < portCount; j++) {
qDebug() << "线程" << QThread::currentThreadId() << " 开始扫描 " << curIP << ":" << port << "\n";
curPort = port + j;
conn->connectToHost(curIP, curPort);
bool res = conn->waitForConnected(1000);
if(!isInterruptionRequested()) {
emit scanOver(curIP + ":" + QString::number(curPort), res);
}
if(res) {
qDebug() << "opened";
} else {
qDebug() << "closed";
}
conn->disconnectFromHost();
}
}
qDebug() << "线程" << QThread::currentThreadId() << " 扫描完成" << "\n";
}
总结
|