程序在单击按钮后停止响应

问题描述 投票:1回答:1

我正在尝试制作我的第一个程序,一个端口扫描程序,它显示远程服务器上所有打开的端口,我已经使其可以在CLI中工作(感谢Internet),但是决定制作一个GUI(Qt5 ) 为了它。我希望textbox2在输入IP地址并单击“扫描!”后输出所有打开的端口,显然,该程序单击后不会崩溃。这是复制问题的相关代码

from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QAction, QLineEdit, QMessageBox, QPlainTextEdit, QVBoxLayout, QLabel
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSlot, Qt
import socket
import time
import sys

class App(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = 'PPort'
        self.left = 10
        self.top = 10
        self.width = 800
        self.height = 400
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.label = QLabel('Enter Target Address:', self)
        self.label.move(50, -110)
        self.label.resize(300, 300)

        self.label2 = QLabel('Output:', self)
        self.label2.move(50, 80)
        self.label2.resize(300, 300)

        self.textbox = QLineEdit(self)
        self.textbox.move(50, 60)
        self.textbox.resize(540, 30)

        self.textbox2 = QPlainTextEdit(self)
        self.textbox2.move(50, 250)
        self.textbox2.resize(700, 100)
        self.textbox2.setReadOnly(True)

        self.button = QPushButton('Scan!', self)
        self.button.move(620, 60)
        self.button.clicked.connect(self.on_click)
        self.show()

    @pyqtSlot()

    def on_click(self):
        textboxValue = self.textbox.text()
        socket.gethostbyname(textboxValue)
        try:
            for port in range(1, 1025):
                socketprofile = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                result = socketprofile.connect_ex((textboxValue, port))
                if result == 0:
                    self.textbox2.appendPlainText('Port {} Is open'.format(port))
                socketprofile.close()
        except socket.gaierror:
            self.textbox2.appendPlainText('Hostname could not be resolved')
            time.sleep(5)
            sys.exit()
        except socket.error:
            self.textbox2.appendPlainText("Couldn't connect to server")
            time.sleep(5)
            sys.exit()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

textbox2中未显示错误,对我来说很奇怪的是,即使当我用print()替换self.textbox2.appendPlainText时,它在vscode终端中仍然不会输出任何错误消息。但是,输入无效的IP地址会显示gaierror(无法解析主机),而不是在textbox2中,而是在终端中,这与输入有效IP地址(8.8.8.8、192.168.0.1)时始终会崩溃的情况相比。我怀疑我在错误地使用if / for / try使其循环,但是我几乎看不到自己在做错什么,因为我几乎不知道自己作为新手在做什么。

python sockets pyqt pyqt5
1个回答
1
投票

诸如for循环或sleep之类的耗时任务会阻塞GUI事件循环,从而导致窗口冻结。在这些情况下,解决方案是在另一个线程中执行该任务,并通过信号在线程之间发送信息。

import sys
import time
import socket
from functools import partial

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QThread, QTimer
from PyQt5.QtWidgets import (
    QApplication,
    QGridLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QPlainTextEdit,
    QPushButton,
    QWidget,
)


class SocketWorker(QObject):
    messageChanged = pyqtSignal(str)

    @pyqtSlot(str)
    def start_task(self, ip):
        socket.gethostbyname(ip)
        try:
            for port in range(0, 65536):
                socketprofile = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                result = socketprofile.connect_ex((ip, port))
                if result == 0:
                    self.messageChanged.emit("Port {} Is open".format(port))
                socketprofile.close()
        except socket.gaierror:
            self.messageChanged.emit("Hostname could not be resolved")
            time.sleep(5)
            sys.exit()
        except socket.error:
            self.messageChanged.emit("Couldn't connect to server")
            time.sleep(5)
            sys.exit()


class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = "PPort"
        self.left = 10
        self.top = 10
        self.width = 800
        self.height = 400
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.textbox = QLineEdit()
        self.button = QPushButton("Scan!")
        self.textbox2 = QPlainTextEdit(readOnly=True)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        grid_layout = QGridLayout(central_widget)
        grid_layout.addWidget(QLabel("Enter Target Address:"), 0, 0)
        grid_layout.addWidget(self.textbox, 1, 0)
        grid_layout.addWidget(self.button, 1, 1)
        grid_layout.addWidget(QLabel("Output:"), 2, 0)
        grid_layout.addWidget(self.textbox2, 3, 0, 1, 2)

        self.button.clicked.connect(self.on_click)

        thread = QThread(self)
        thread.start()
        self.socker_worker = SocketWorker()
        self.socker_worker.moveToThread(thread)
        self.socker_worker.messageChanged.connect(self.textbox2.appendPlainText)

    @pyqtSlot()
    def on_click(self):
        textboxValue = self.textbox.text()
        wrapper = partial(self.socker_worker.start_task, textboxValue)
        QTimer.singleShot(0, wrapper)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = App()
    ex.show()
    sys.exit(app.exec_())
© www.soinside.com 2019 - 2024. All rights reserved.