diff --git a/app.log b/app.log index ab268c5..6a6373f 100644 --- a/app.log +++ b/app.log @@ -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: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: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 端口连接情况 diff --git a/core/Function/__pycache__/ping_fun.cpython-313.pyc b/core/Function/__pycache__/ping_fun.cpython-313.pyc index 0794e8c..61d72c1 100644 Binary files a/core/Function/__pycache__/ping_fun.cpython-313.pyc and b/core/Function/__pycache__/ping_fun.cpython-313.pyc differ diff --git a/core/Function/__pycache__/telnet_fun.cpython-313.pyc b/core/Function/__pycache__/telnet_fun.cpython-313.pyc new file mode 100644 index 0000000..45d2ce1 Binary files /dev/null and b/core/Function/__pycache__/telnet_fun.cpython-313.pyc differ diff --git a/core/Function/ping_fun.py b/core/Function/ping_fun.py index 4ebb2dc..f35fcb4 100644 --- a/core/Function/ping_fun.py +++ b/core/Function/ping_fun.py @@ -100,7 +100,7 @@ class PingFun: if self.stop_flag: return None - command = ['ping', ip, '-n', '1', '-w', '1000'] + command = ['ping', ip, '-n', '1', '-w', '2000'] if local_ip: command += ['-S', local_ip] try: diff --git a/core/Function/telnet_fun.py b/core/Function/telnet_fun.py new file mode 100644 index 0000000..837bf27 --- /dev/null +++ b/core/Function/telnet_fun.py @@ -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()) diff --git a/core/ui/__pycache__/tab_ping.cpython-313.pyc b/core/ui/__pycache__/tab_ping.cpython-313.pyc index 7a18b90..c417260 100644 Binary files a/core/ui/__pycache__/tab_ping.cpython-313.pyc and b/core/ui/__pycache__/tab_ping.cpython-313.pyc differ diff --git a/core/ui/__pycache__/tab_telnet.cpython-313.pyc b/core/ui/__pycache__/tab_telnet.cpython-313.pyc new file mode 100644 index 0000000..eaedbbe Binary files /dev/null and b/core/ui/__pycache__/tab_telnet.cpython-313.pyc differ diff --git a/core/ui/__pycache__/ui_main.cpython-313.pyc b/core/ui/__pycache__/ui_main.cpython-313.pyc index ed6c542..2f1b5a3 100644 Binary files a/core/ui/__pycache__/ui_main.cpython-313.pyc and b/core/ui/__pycache__/ui_main.cpython-313.pyc differ diff --git a/core/ui/tab_telnet.py b/core/ui/tab_telnet.py new file mode 100644 index 0000000..c1fe778 --- /dev/null +++ b/core/ui/tab_telnet.py @@ -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') + + + + + + \ No newline at end of file diff --git a/core/ui/ui_main.py b/core/ui/ui_main.py index b57f31d..dae6307 100644 --- a/core/ui/ui_main.py +++ b/core/ui/ui_main.py @@ -3,6 +3,7 @@ from tkinter import ttk, filedialog, messagebox from core.ui.basic_ui import BasicUI from core.ui.tab_ping import PingTab +from core.ui.tab_telnet import TelnetTab import logging logger = logging.getLogger(__name__) @@ -19,6 +20,7 @@ class MainUI(BasicUI): # 实例化各个功能页 self.tabs = { "Ping测试": PingTab(self.tab_control), + "端口扫描服务端": TelnetTab(self.tab_control), } # 添加到 Notebook for name, tab in self.tabs.items():