本章目标
加上这章,下一章应该就能结束了。
大家是否还记得GUI上的一个“复制全文”按钮和已经实现功能的“复制原文”按钮。
之所以有“复制全文”按钮,是因为有些服务器开着端口,但并不会输出该云端运行的服务,所以可以提供端口参照。
所以这一章就是实现:
1、当端口是开启状态,但无运行服务的说明输出时,匹配一个常见服务参照并跟随输出;
2、不影响 复制原文 按钮,实现复制全文按钮功能。
步骤实施
我找到了一篇博文:
端口与服务对照表
在目录下新建文件 PortText.py ,将博文里的对照表复制下来后,替换掉了里面的“端口”两字,保存下来的数据格式如下:
检查了下中间的分割符号“:”,发现服务文字里没有这个符号,可以安心使用。
我需要实现一个函数,输入端口号,就能输出参照服务内容的文字说明。
def port_text(port):
port_string = str(port)
if str(port_string) not in TEXT:
return ''
text_list = TEXT.split('\n') # 先根据 换行符 分割字符串
for ele in text_list:
ele_list = ele.split(':')
if port_string == ele_list[0]:
return ele_list[1]
return ''
试着调用一下:
print(port_text(65301))
成功。
到PortSearchGUI中引入该模块:
import PortText
再修改 logic 函数:
# 函数B 负责调用逻辑
def logic(ip, thread_line, port_start, port_end):
search = PortSearch(ip=ip, thread_line=thread_line, port_start=port_start, port_end=port_end)
result = search.run()
string = ''
string1 = ''
str_len = len(result)
report_box_edit.setPlainText('')
if str_len > 0: # 处理返回数据,输出到编辑框内
for ele in result:
string = string + '端口:' + str(ele[1]) + ' ,状态:开启, 输出信息:' + str(ele[3]) + "\n"
if ele[3] == b'': # 如果没有端口说明,则输出参考端口服务说明
str_temp = '端口:' + str(ele[1]) + ' ,状态:开启, 输出信息:' + str(ele[3]) + \
' ,参考端口服务说明:' + PortText.port_text(ele[1])
string1 = string1 + str_temp + "\n"
report_box_edit.appendPlainText(str_temp) # 逐条添加信息
else:
str_temp = '端口:' + str(ele[1]) + ' ,状态:开启, 输出信息:' + str(ele[3])
string1 = string1 + str_temp + "\n"
report_box_edit.appendPlainText(str_temp) # 逐条添加信息
else:
string = '所有端口均未开启'
report_box_edit.setPlainText(string)
G_LIST[0] = string # 保存原文结果
G_LIST[1] = string1 # 保存全文结果
start_btn.setEnabled(True)
window.setWindowTitle('端口嗅探器 v1.0')
运行之后效果如下:
写一下 复制全文 按钮的响应函数和提示语:
MESSAGE = (
'ip或者网址不得为空',
'并发数不得小于1',
'开始端口号不得小于0',
'结束端口号需要大于开始端口号',
'原文复制成功',
'全文复制成功'
)
@Slot()
def copy_all():
pyperclip.copy(G_LIST[1])
show_tip(MESSAGE[5])
试运行了下,发现一个以前隐藏的性能问题,程序会崩溃。
崩溃时,输出内容是:
退出代码-1073741819 (0xC0000005)
这个问题困扰了我很久,查了很多资料,发现都不能解决问题。
问题基本上锁定为内存栈溢出导致。
经过将代码封装为类,逐步定位,终于工具运行比较稳定了,方法是将之前使用的
self.report_box_edit.appendPlainText()
方法,替换成了
self.report_box_edit.setPlainText()
可能是因为减少了对其内存的访问次数,所以就稳定了许多,当然速度也变快了。
完整代码贴出来:
# coding=utf-8
from PySide2.QtWidgets import QApplication, QLineEdit, QLabel, QPlainTextEdit, QPushButton, QWidget # 引入模块
from PySide2.QtWidgets import QMessageBox
from PySide2.QtWidgets import QGroupBox, QVBoxLayout, QHBoxLayout # 布局容器
import sys
from PySide2.QtCore import Slot # 插槽模块
from PortSearch import PortSearch
import PortText
import threading
import pyperclip
import time
class PortSearchGUI(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(600, 480) # 主窗体尺寸
self.setWindowTitle('端口嗅探器 v1.0') # 窗体名称
self.ip_line_edit = QLineEdit() # 修改父类
self.ip_line_edit.setPlaceholderText('输入ip或者网址')
self.ip_line_edit.setMinimumSize(180, 22)
self.thread_label = QLabel('并发数:')
self.thread_line_edit = QLineEdit() # 修改父类
self.thread_line_edit.setText('300')
self.thread_line_edit.setMinimumSize(40, 22)
self.port_label = QLabel('端口范围:')
self.port_line_edit1 = QLineEdit() # 修改父类
self.port_line_edit1.setText('0')
self.port_label2 = QLabel('~')
self.port_line_edit2 = QLineEdit() # 修改父类
self.port_line_edit2.setText('65535')
self.report_box_edit = QPlainTextEdit() # 修改父类
self.report_box_edit.setReadOnly(True)
self.start_btn = QPushButton() # 修改父类
self.start_btn.setText('启动')
self.start_btn.clicked.connect(self.collect_data) # 建立连接
self.copy_all_btn = QPushButton() # 修改父类
self.copy_all_btn.setText('复制全文')
self.copy_all_btn.clicked.connect(self.copy_all) # 建立连接
self.copy_ori_btn = QPushButton() # 修改父类
self.copy_ori_btn.setText('复制原文')
self.copy_ori_btn.clicked.connect(self.copy) # 建立连接
self.clear_btn = QPushButton() # 修改父类
self.clear_btn.setText('清空')
self.clear_btn.clicked.connect(self.clear) # 建立连接
self.first_group_box = QGroupBox() # 第一个分组框组控件
self.first_group_box.setTitle('参数设置')
self.second_group_box = QGroupBox() # 第二个分组框组控件
self.second_group_box.setTitle('端口开放情况')
self.first_h_layout = QHBoxLayout() # 第一个横向布局容器,属于第一个分组框控件
self.second_h_layout = QHBoxLayout() # 第二个横向布局容器,属于第一个分组框控件
self.third_h_layout = QHBoxLayout() # 第三个横向布局容器,属于第一个分组框控件
self.first_v_layout = QVBoxLayout() # 第一个纵向布局容器,属于第二个横向布局容器
self.second_v_layout = QVBoxLayout() # 第二个纵向布局容器,属于第二个横向布局容器
self.first_h_layout.addWidget(self.ip_line_edit)
self.first_h_layout.addWidget(self.thread_label)
self.first_h_layout.addWidget(self.thread_line_edit)
self.first_h_layout.addWidget(self.port_label)
self.first_h_layout.addWidget(self.port_line_edit1)
self.first_h_layout.addWidget(self.port_label2)
self.first_h_layout.addWidget(self.port_line_edit2)
self.first_h_layout.addWidget(self.start_btn)
self.first_group_box.setLayout(self.first_h_layout)
self.first_v_layout.addWidget(self.report_box_edit)
self.second_v_layout.addWidget(self.copy_all_btn)
self.second_v_layout.addWidget(self.copy_ori_btn)
self.second_v_layout.addWidget(self.clear_btn)
self.second_h_layout.addItem(self.first_v_layout)
self.second_h_layout.addItem(self.second_v_layout)
self.second_group_box.setLayout(self.second_h_layout)
self.layout = QVBoxLayout()
self.layout.addWidget(self.first_group_box)
self.layout.addWidget(self.second_group_box)
self.setLayout(self.layout)
self.MESSAGE = (
'ip或者网址不得为空',
'并发数不得小于1',
'开始端口号不得小于0',
'结束端口号需要大于开始端口号',
'原文复制成功',
'全文复制成功'
)
self.G_LIST = ['', '']
self.ip = ''
self.thread_line = 0
self.port_start = 0
self.port_end = 0
def show_tip(self, message): # 遇到问题,则丢到这里,抛到界面上
tip = QMessageBox(self)
tip.setWindowTitle('提示')
tip.setText(message)
tip.show()
# 函数B 负责调用逻辑
def logic(self):
search = PortSearch(ip=self.ip, thread_line=self.thread_line, port_start=self.port_start, port_end=self.port_end)
result = search.run()
string = ''
string1 = ''
str_len = len(result)
self.report_box_edit.setPlainText('')
if str_len > 0: # 处理返回数据,输出到编辑框内
for ele in result:
string = string + '端口:' + str(ele[1]) + ' ,状态:开启, 输出信息:' + str(ele[3]) + "\n"
time.sleep(1)
if ele[3] == b'': # 如果没有端口说明,则输出参考端口服务说明
string1 = string1 + '端口:' + str(ele[1]) + ',状态:开启,输出信息:无' + ',参考端口服务说明:' + \
PortText.port_text(ele[1]) + "\n"
else:
string1 = string1 + '端口:' + str(ele[1]) + ',状态:开启,输出信息:' + str(ele[3]) + "\n"
else:
string1 = '所有端口均未开启'
self.G_LIST[0] = string # 保存原文结果
self.G_LIST[1] = string1 # 保存全文结果
self.report_box_edit.setPlainText(string1)
self.start_btn.setEnabled(True)
self.setWindowTitle('端口嗅探器 v1.0')
@Slot()
def collect_data(self):
self.ip = self.ip_line_edit.text()
self.thread_line = int(self.thread_line_edit.text())
self.port_start = int(self.port_line_edit1.text())
self.port_end = int(self.port_line_edit2.text())
if not self.ip:
self.show_tip(self.MESSAGE[0])
return
if self.thread_line < 1:
self.show_tip(self.MESSAGE[1])
return
if self.port_start < 0:
self.show_tip(self.MESSAGE[2])
return
if self.port_end <= self.port_start:
self.show_tip(self.MESSAGE[3])
return
thread = threading.Thread(target=self.logic)
thread.start() # 异步调用,避免工具卡死
self.start_btn.setEnabled(False) # 限制按钮,避免重复调用
self.setWindowTitle('端口嗅探器 v1.0 ---- 执行中')
@Slot()
def copy(self):
pyperclip.copy(self.G_LIST[0])
self.show_tip(self.MESSAGE[4])
@Slot()
def copy_all(self):
pyperclip.copy(self.G_LIST[1])
self.show_tip(self.MESSAGE[5])
@Slot()
def clear(self):
self.report_box_edit.setPlainText('')
app = QApplication(sys.argv) # 创建app
window = PortSearchGUI() # 创建主窗体
window.show() # 显示窗体
app.exec_() # 启动app
sys.exit()
章节总结
本章目标完成。
下一章节还需要实现是工具打包成exe可执行文件,下一章应该就能结束本系列的文章了。