This commit is contained in:
2025-10-31 17:10:24 +08:00
parent 0a646bb017
commit 28298e7952
10 changed files with 382 additions and 1 deletions
+22
View File
@@ -269,3 +269,25 @@
2025-10-31 15:57:02,030 [INFO] 开始由192.168.10.1 批量 Ping 192.168.1.1 - 192.168.1.255 2025-10-31 15:57:02,030 [INFO] 开始由192.168.10.1 批量 Ping 192.168.1.1 - 192.168.1.255
2025-10-31 15:57:05,860 [INFO] 开始由192.168.10.1 批量 Ping 192.168.1.1 - 192.168.1.255 2025-10-31 15:57:05,860 [INFO] 开始由192.168.10.1 批量 Ping 192.168.1.1 - 192.168.1.255
2025-10-31 15:57:06,885 [INFO] 停止由192.168.10.1 批量 Ping 192.168.1.1 - 192.168.1.255 2025-10-31 15:57:06,885 [INFO] 停止由192.168.10.1 批量 Ping 192.168.1.1 - 192.168.1.255
2025-10-31 15:59:58,363 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:02:26,037 [INFO] 开始由 Ping www.baidu.com
2025-10-31 16:02:30,715 [INFO] 停止由 Ping www.baidu.com
2025-10-31 16:10:57,005 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:10:58,442 [INFO] 开始批量 Ping 192.168.1.1 - 192.168.1.255
2025-10-31 16:13:23,888 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:13:35,360 [INFO] 开始批量 Ping 192.168.1.1 - 192.168.1.255
2025-10-31 16:14:22,199 [INFO] 停止批量 Ping 192.168.1.1 - 192.168.1.255
2025-10-31 16:26:02,682 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:26:18,252 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:37:44,993 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:38:08,211 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:41:18,553 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:58:02,766 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 16:58:26,946 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 17:00:32,403 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 17:01:12,536 [INFO] 日志系统初始化完成,日志文件路径:E:\Git\Network-tools\app.log
2025-10-31 17:01:18,984 [INFO] 开始测试202.89.233.100 的 100 到 1000 端口连接情况
2025-10-31 17:01:50,516 [INFO] 开始测试202.89.233.100 的 100 到 1000 端口连接情况
2025-10-31 17:03:14,076 [INFO] 开始测试202.89.233.100 的 1 到 1000 端口连接情况
2025-10-31 17:09:53,765 [INFO] 开始测试202.89.233.100 的 1 到 1000 端口连接情况
2025-10-31 17:09:55,506 [INFO] 开始测试202.89.233.100 的 1 到 1000 端口连接情况
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -100,7 +100,7 @@ class PingFun:
if self.stop_flag: if self.stop_flag:
return None return None
command = ['ping', ip, '-n', '1', '-w', '1000'] command = ['ping', ip, '-n', '1', '-w', '2000']
if local_ip: if local_ip:
command += ['-S', local_ip] command += ['-S', local_ip]
try: try:
+251
View File
@@ -0,0 +1,251 @@
import socket
import threading
import concurrent.futures
import tkinter as tk
from tkinter import scrolledtext, messagebox
from typing import Iterable, Optional, List
class PortScanner:
"""
PortScanner: 在 Tkinter 的 ScrolledText 中显示端口检测结果的工具类。
构造:
scanner = PortScanner(result_box)
方法:
test_connect(ip, port, timeout=1.0)
- 直接测试单个 ip:port 是否可连通(立即返回 bool,并在结果框显示一行结果)
start_range_scan(ip, start_port, end_port, timeout=1.0, max_workers=100)
- 并发扫描端口范围 [start_port, end_port](包含端口边界)
- 扫描结果会实时写入 result_box
start_list_scan(ip, ports: Iterable[int], timeout=1.0, max_workers=100)
- 并发扫描给定端口列表
stop_scan()
- 尝试中止正在进行的扫描(设置停止标志,后续任务检测到后会停止提交或返回)
注意:
- 使用 TCP 连接测试(socket.connect),适合服务端口检测。
- GUI 写入通过 self._append_text(...) 调度到主线程,保证线程安全。
"""
def __init__(self, result_box: scrolledtext.ScrolledText):
self.result_box = result_box
# 扫描控制状态
self._stop_flag = False # 外部调用 stop_scan() 会把此标志设为 True
self._scan_thread: Optional[threading.Thread] = None
self._executor: Optional[concurrent.futures.ThreadPoolExecutor] = None
# 用于统计(可选)
self._total = 0
self._done = 0
self._open_ports: List[int] = []
# -------------------------
# 辅助方法:线程安全地向结果框写文本
# -------------------------
def _append_text(self, text: str):
"""
把文本插入到 result_box。因为可能从子线程调用,所以用 after 调度到主线程执行。
"""
try:
# schedule on main thread immediately
self.result_box.after(0, lambda: (self.result_box.insert(tk.END, text), self.result_box.see(tk.END)))
except Exception:
# 在极少数情况下(例如 result_box 已销毁),捕获异常避免崩溃
pass
# -------------------------
# 单端口测试
# -------------------------
def test_connect(self, ip: str, port: int, timeout: float = 1.0) -> bool:
"""
立即测试单个 ip:port 是否可以 TCP 连接。
- 返回 True(可连通)或 False(不可连通)
- 同时把结果写入 result_box(通过主线程调度)
"""
addr = (ip, int(port))
status = False
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect(addr)
status = True
sock.close()
except Exception:
status = False
# 输出结果(在 GUI 中显示)
self._append_text(f"{ip}:{port} {'✅ 开放' if status else '❌ 关闭/不可达'}\n")
return status
# -------------------------
# 并发单端口任务(内部使用)
# -------------------------
def _scan_single_port(self, ip: str, port: int, timeout: float) -> str:
"""
线程池中运行的单个端口检测任务,返回一行结果字符串。
任务必须尽量短小(快速返回),并在开始前检查 stop_flag。
"""
if self._stop_flag:
return "" # 为空表示不输出
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((ip, port))
sock.close()
result = f"{ip}:{port} ✅ 开放\n"
# 记录到本地开放端口列表(线程安全地追加)
self._open_ports.append(port)
except Exception:
result = f"{ip}:{port} ❌ 关闭/不可达\n"
# 更新进度计数(最好在主线程更新显示,这里在子线程更新计数)
self._done += 1
return result
# -------------------------
# 并发扫描(范围或列表)
# -------------------------
def start_range_scan(self, ip: str, start_port: int, end_port: int, timeout: float = 0.8, max_workers: int = 200):
"""
并发扫描端口范围 [start_port, end_port](含两端)。
结果实时写入 result_box。单次扫描在后台线程中运行(不会阻塞主线程)。
"""
# 参数校验(基本)
try:
start_port = int(start_port); end_port = int(end_port)
except Exception:
messagebox.showwarning("输入错误", "起始端口和结束端口必须为整数")
return
if start_port < 1 or end_port > 65535 or start_port > end_port:
messagebox.showwarning("输入错误", "端口范围不合法(1-65535 且 起始<=结束)")
return
ports = list(range(start_port, end_port + 1))
self.start_list_scan(ip, ports, timeout=timeout, max_workers=max_workers)
def start_list_scan(self, ip: str, ports: Iterable[int], timeout: float = 0.8, max_workers: int = 200):
"""
并发扫描指定的端口列表。
- ip: 目标 IP(字符串)
- ports: 可迭代的端口集合(如 list、range 等)
- timeout: 单端口连接超时(秒)
- max_workers: 最大并发数(线程池大小)
"""
# 防止重复启动
if self._scan_thread and self._scan_thread.is_alive():
messagebox.showinfo("提示", "已有扫描任务在运行,请先停止后再启动新的扫描。")
return
# 将 ports 转为列表并进行基本校验
try:
ports_list = [int(p) for p in ports]
except Exception:
messagebox.showwarning("输入错误", "端口列表包含非法值")
return
if not ports_list:
messagebox.showwarning("输入错误", "端口列表为空")
return
for p in ports_list:
if p < 1 or p > 65535:
messagebox.showwarning("输入错误", f"端口 {p} 不在合法范围 1-65535")
return
# 重置状态
self._stop_flag = False
self._open_ports = []
self._total = len(ports_list)
self._done = 0
# 清空 result_box 并输出起始信息(主线程调度)
self._append_text(f"开始并发端口扫描:目标 {ip},共 {self._total} 个端口\n")
# 后台线程用于管理线程池与结果收集,确保 GUI 不阻塞
def manager():
# 根据任务数自适应限制并发数
actual_workers = max(1, min(max_workers, self._total))
self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=actual_workers)
# 提交任务
futures = {self._executor.submit(self._scan_single_port, ip, port, timeout): port for port in ports_list}
try:
# as_completed 会在每个 future 完成时迭代返回
for fut in concurrent.futures.as_completed(futures):
if self._stop_flag:
# 如果外部发出停止信号,尽量取消未开始的 future(cancel 返回 True 表示取消成功)
# 线程池中的任务可能已在运行, cancel 只能取消未开始的任务
break
try:
line = fut.result()
except Exception as e:
line = f"{ip}:{futures.get(fut)} 错误: {e}\n"
if line:
# 把结果写回 GUI(通过 _append_text 安全调度)
self._append_text(line)
# 可选:显示进度(例如:done/total)
self._append_text(f"进度: {self._done}/{self._total}\n")
# 如果 stop_flag 已设,尝试取消尚未开始的任务
if self._stop_flag:
for f in futures:
f.cancel()
finally:
# 关闭线程池
if self._executor:
self._executor.shutdown(wait=False)
self._executor = None
# 最终输出总结信息(开放端口列表)
if not self._stop_flag:
self._append_text("\n端口扫描完成。\n")
else:
self._append_text("\n端口扫描已停止。\n")
if self._open_ports:
self._append_text(f"开放端口: {sorted(self._open_ports)}\n")
else:
self._append_text("未发现开放端口。\n")
# 启动后台管理线程
self._scan_thread = threading.Thread(target=manager, daemon=True)
self._scan_thread.start()
# -------------------------
# 停止扫描
# -------------------------
def stop_scan(self):
"""
请求停止正在进行的扫描:设置停止标志并尝试关闭线程池。
- 已提交并正在运行的 socket.connect 调用无法被立即中断(但后续任务会被取消)
- stop_scan 尽快返回,实际终止需要等待正在运行的任务完成或超时
"""
if not (self._scan_thread and self._scan_thread.is_alive()):
messagebox.showinfo("提示", "当前没有正在运行的扫描任务。")
return
self._append_text("\n正在停止扫描,请稍候...\n")
self._stop_flag = True
# 尝试立即关闭线程池(不等待正在运行任务)
if self._executor:
try:
self._executor.shutdown(wait=False)
except Exception:
pass
# -------------------------
# 可选:返回当前扫描状态(供外部查询)
# -------------------------
def is_scanning(self) -> bool:
return bool(self._scan_thread and self._scan_thread.is_alive())
Binary file not shown.
Binary file not shown.
Binary file not shown.
+106
View File
@@ -0,0 +1,106 @@
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from core.ui.basic_ui import BasicUI
from core.Function.telnet_fun import PortScanner
import logging
logger = logging.getLogger(__name__)
class TelnetTab(ttk.Frame, BasicUI):
def __init__(self, parent):
super().__init__(parent)
self.telnet_ui()
self.telnet_fun = PortScanner(self.result_box)
def telnet_ui(self):
"""端口扫描界面布局"""
self.create_assigntelnet_section()
self.create_batchtelnet_section()
self.create_outputping_section()
# --------------------------------------UI界面布局函数--------------------------------------
def create_assigntelnet_section(self):
# 区域标签
frame = ttk.LabelFrame(self, text="指定端口连接测试")
frame.pack(side='top', fill='x', padx=10, pady=5)
#self.entry_assignIP_A = self.add_input(frame, "本地IP", row=0, col=0)
self.assignTelnet_IP = self.add_input(frame, "目标IP", row=0, col=0, inivar="202.89.233.100")
self.assignTelnet_port = self.add_input(frame, "目标端口", row=0, col=1, inivar="443", entry_width=10)
self.assignTelnet_test = self.add_button(frame, "测试", row=0, col=2, command=self.btn_assignTelnet_test)
def create_batchtelnet_section(self):
# 区域标签
frame = ttk.LabelFrame(self, text="批量端口连接测试")
frame.pack(side='top', fill='x', padx=10, pady=5)
self.batchTelnet_IP = self.add_input(frame, "目标IP", row=0, col=0, inivar="202.89.233.100")
self.batchTelnet_port_begin = self.add_input(frame, "起始端口", row=0, col=1, inivar="100", entry_width=10)
self.batchTelnet_port_end = self.add_input(frame, "结束端口", row=0, col=2, inivar="1000", entry_width=10)
self.batchTelnet_start = self.add_button(frame, "开始", row=0, col=3, command=self.btn_batchTelnet_start)
self.batchTelnet_stop = self.add_button(frame, "停止", row=0, col=4, command=self.btn_batchTelnet_stop)
def create_outputping_section(self):
# 区域标签
frame = ttk.LabelFrame(self, text="端口扫描结果输出")
frame.pack(side='top', fill='x', padx=10, pady=5)
self.result_box = scrolledtext.ScrolledText(frame, width=100, height=20)
self.result_box.pack(pady=10)
# --------------------------------------按钮回调函数--------------------------------------
def btn_assignTelnet_test(self):
self.IP = self.assignTelnet_IP['var'].get()
self.port = self.assignTelnet_port['var'].get()
if not self.IP:
messagebox.showwarning("输入错误", "请输入目标IP或域名!")
return
if not self.port:
messagebox.showwarning("输入错误", "请输入目标端口!")
return
try:
port = int(self.port)
except ValueError:
messagebox.showwarning("输入错误", "端口必须是整数!")
return
if port < 1 or port > 65535:
messagebox.showwarning("输入错误", "起始端口最小为1,结束端口最大为65535!")
return
logger.info(f"测试{self.IP}{port} 端口连接情况")
self.telnet_fun.test_connect(self.IP, port)
def btn_batchTelnet_start(self):
self.IP = self.batchTelnet_IP['var'].get()
self.port_begin = self.batchTelnet_port_begin['var'].get()
self.port_end = self.batchTelnet_port_end['var'].get()
if not self.IP:
messagebox.showwarning("输入错误", "请输入目标IP或域名!")
return
if not self.port_begin or not self.port_end:
messagebox.showwarning("输入错误", "请输入目标端口!")
return
try:
port_begin = int(self.port_begin)
port_end = int(self.port_end)
except ValueError:
messagebox.showwarning("输入错误", "端口必须是整数!")
return
if port_begin < 1 or port_end > 65535:
messagebox.showwarning("输入错误", "起始端口最小为1,结束端口最大为65535!")
return
logger.info(f"开始测试{self.IP}{port_begin}{port_end} 端口连接情况")
self.batchTelnet_start['btn'].config(state='disabled')
self.telnet_fun.start_range_scan(self.IP, port_begin, port_end)
def btn_batchTelnet_stop(self):
logger.info(f"停止测试{self.IP} 的端口连接情况")
self.telnet_fun.stop_scan()
self.batchTelnet_start['btn'].config(state='normal')
def batchTelnet_callback(self):
self.batchTelnet_start['btn'].config(state='normal')
+2
View File
@@ -3,6 +3,7 @@ from tkinter import ttk, filedialog, messagebox
from core.ui.basic_ui import BasicUI from core.ui.basic_ui import BasicUI
from core.ui.tab_ping import PingTab from core.ui.tab_ping import PingTab
from core.ui.tab_telnet import TelnetTab
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -19,6 +20,7 @@ class MainUI(BasicUI):
# 实例化各个功能页 # 实例化各个功能页
self.tabs = { self.tabs = {
"Ping测试": PingTab(self.tab_control), "Ping测试": PingTab(self.tab_control),
"端口扫描服务端": TelnetTab(self.tab_control),
} }
# 添加到 Notebook # 添加到 Notebook
for name, tab in self.tabs.items(): for name, tab in self.tabs.items():