使用python 374行实现支持tcp和udp的带界面的端口扫描程序

Python 端口扫描程序

下面是一个功能完善的Python端口扫描程序,支持TCP和UDP端口扫描,包含图形用户界面(GUI)和多线程功能:

import socket
import threading
from queue import Queue
import time
import ipaddress
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
from datetime import datetime

class PortScanner:
    def __init__(self):
        # 创建GUI窗口
        self.root = tk.Tk()
        self.root.title("Python 端口扫描器")
        self.root.geometry("800x600")
        self.root.resizable(True, True)
        
        # 设置主题风格
        self.style = ttk.Style()
        self.style.theme_use('clam')
        
        # 创建GUI元素
        self.create_widgets()
        
        # 扫描状态变量
        self.scanning = False
        self.scan_queue = Queue()
        self.threads = []
        
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        
    def create_widgets(self):
        # 创建框架
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 输入区域
        input_frame = ttk.LabelFrame(main_frame, text="扫描设置", padding=10)
        input_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 目标主机输入
        ttk.Label(input_frame, text="目标主机:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        self.target_entry = ttk.Entry(input_frame, width=40)
        self.target_entry.grid(row=0, column=1, sticky=tk.W)
        self.target_entry.insert(0, "127.0.0.1")
        ttk.Label(input_frame, text="(IP地址或域名)").grid(row=0, column=2, sticky=tk.W, padx=(5, 10))
        
        # 端口范围输入
        ttk.Label(input_frame, text="端口范围:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5))
        self.start_port_entry = ttk.Entry(input_frame, width=8)
        self.start_port_entry.grid(row=1, column=1, sticky=tk.W)
        self.start_port_entry.insert(0, "1")
        ttk.Label(input_frame, text="-").grid(row=1, column=2, sticky=tk.W)
        self.end_port_entry = ttk.Entry(input_frame, width=8)
        self.end_port_entry.grid(row=1, column=3, sticky=tk.W)
        self.end_port_entry.insert(0, "100")
        ttk.Label(input_frame, text="(1-65535)").grid(row=1, column=4, sticky=tk.W, padx=(5, 10))
        
        # 扫描选项
        ttk.Label(input_frame, text="协议:").grid(row=2, column=0, sticky=tk.W, padx=(0, 5))
        self.protocol_var = tk.StringVar(value="TCP")
        ttk.Radiobutton(input_frame, text="TCP", variable=self.protocol_var, value="TCP").grid(row=2, column=1, sticky=tk.W)
        ttk.Radiobutton(input_frame, text="UDP", variable=self.protocol_var, value="UDP").grid(row=2, column=2, sticky=tk.W)
        
        ttk.Label(input_frame, text="线程数:").grid(row=3, column=0, sticky=tk.W, padx=(0, 5))
        self.thread_count = tk.IntVar(value=50)
        ttk.Spinbox(input_frame, from_=1, to=500, width=5, textvariable=self.thread_count).grid(row=3, column=1, sticky=tk.W)
        
        ttk.Label(input_frame, text="超时时间(秒):").grid(row=3, column=2, sticky=tk.W, padx=(10, 5))
        self.timeout = tk.DoubleVar(value=1.5)
        ttk.Spinbox(input_frame, from_=0.5, to=10, increment=0.5, width=5, textvariable=self.timeout).grid(row=3, column=3, sticky=tk.W)
        
        # 常用端口按钮
        common_frame = ttk.Frame(input_frame)
        common_frame.grid(row=4, column=0, columnspan=5, pady=(10, 0), sticky=tk.W)
        ttk.Label(common_frame, text="常用端口:").pack(side=tk.LEFT)
        ttk.Button(common_frame, text="常用端口", command=lambda: self.set_ports("common")).pack(side=tk.LEFT, padx=5)
        ttk.Button(common_frame, text="所有端口", command=lambda: self.set_ports("all")).pack(side=tk.LEFT, padx=5)
        ttk.Button(common_frame, text="Web服务", command=lambda: self.set_ports("web")).pack(side=tk.LEFT, padx=5)
        
        # 控制按钮
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.scan_btn = ttk.Button(btn_frame, text="开始扫描", command=self.start_scan)
        self.scan_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.stop_btn = ttk.Button(btn_frame, text="停止扫描", command=self.stop_scan, state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT)
        
        ttk.Button(btn_frame, text="清除结果", command=self.clear_results).pack(side=tk.RIGHT)
        
        # 结果区域
        result_frame = ttk.LabelFrame(main_frame, text="扫描结果", padding=10)
        result_frame.pack(fill=tk.BOTH, expand=True)
        
        # 结果表格
        columns = ("port", "protocol", "status", "service")
        self.result_tree = ttk.Treeview(result_frame, columns=columns, show="headings")
        
        # 设置列标题
        self.result_tree.heading("port", text="端口")
        self.result_tree.heading("protocol", text="协议")
        self.result_tree.heading("status", text="状态")
        self.result_tree.heading("service", text="服务")
        
        # 设置列宽
        self.result_tree.column("port", width=80, anchor=tk.CENTER)
        self.result_tree.column("protocol", width=80, anchor=tk.CENTER)
        self.result_tree.column("status", width=100, anchor=tk.CENTER)
        self.result_tree.column("service", width=150, anchor=tk.CENTER)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.result_tree.yview)
        self.result_tree.configure(yscroll=scrollbar.set)
        
        # 布局
        self.result_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="扫描日志", padding=10)
        log_frame.pack(fill=tk.BOTH, pady=(10, 0))
        
        self.log_text = scrolledtext.ScrolledText(log_frame, height=8, state=tk.DISABLED)
        self.log_text.pack(fill=tk.BOTH, expand=True)
    
    def log_message(self, message):
        """将消息添加到日志区域"""
        self.log_text.configure(state=tk.NORMAL)
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)  # 滚动到最后
        self.log_text.configure(state=tk.DISABLED)
    
    def set_ports(self, port_set):
        """设置常用的端口范围"""
        if port_set == "common":
            self.start_port_entry.delete(0, tk.END)
            self.start_port_entry.insert(0, "1")
            self.end_port_entry.delete(0, tk.END)
            self.end_port_entry.insert(0, "1024")
            self.log_message("已设置常用端口范围: 1-1024")
        elif port_set == "all":
            self.start_port_entry.delete(0, tk.END)
            self.start_port_entry.insert(0, "1")
            self.end_port_entry.delete(0, tk.END)
            self.end_port_entry.insert(0, "65535")
            self.log_message("已设置所有端口: 1-65535")
        elif port_set == "web":
            self.start_port_entry.delete(0, tk.END)
            self.start_port_entry.insert(0, "80")
            self.end_port_entry.delete(0, tk.END)
            self.end_port_entry.insert(0, "443")
            self.log_message("已设置Web服务端口: 80, 443")
    
    def validate_input(self):
        """验证用户输入是否有效"""
        target = self.target_entry.get().strip()
        start_port = self.start_port_entry.get().strip()
        end_port = self.end_port_entry.get().strip()
        
        if not target:
            messagebox.showerror("错误", "请输入目标主机")
            return False
        
        try:
            # 尝试解析目标地址
            ipaddress.ip_address(target)
        except ValueError:
            try:
                # 尝试解析域名
                socket.gethostbyname(target)
            except socket.gaierror:
                messagebox.showerror("错误", "无效的目标主机地址或域名")
                return False
        
        try:
            start_port = int(start_port)
            end_port = int(end_port)
            if not (1 <= start_port <= 65535) or not (1 <= end_port <= 65535):
                messagebox.showerror("错误", "端口必须在1-65535范围内")
                return False
            if start_port > end_port:
                messagebox.showerror("错误", "起始端口不能大于结束端口")
                return False
        except ValueError:
            messagebox.showerror("错误", "端口必须是数字")
            return False
        
        return True
    
    def scan_tcp_port(self, target, port):
        """扫描TCP端口"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(self.timeout.get())
            result = sock.connect_ex((target, port))
            sock.close()
            
            if result == 0:
                return "开放"
            return "关闭"
        except:
            return "错误"
    
    def scan_udp_port(self, target, port):
        """扫描UDP端口"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.settimeout(self.timeout.get())
            
            # 发送空数据包
            sock.sendto(b'', (target, port))
            
            # 尝试接收响应
            try:
                data, addr = sock.recvfrom(1024)
                return "开放"
            except socket.timeout:
                # 某些UDP服务可能不会响应
                pass
            
            # 查询已知服务
            service_name = self.get_service_name(port, "udp")
            if service_name != "未知":
                return "可能开放"
            
            return "关闭或过滤"
        except:
            return "错误"
        finally:
            sock.close()
    
    def get_service_name(self, port, protocol="tcp"):
        """获取端口对应的服务名称"""
        try:
            return socket.getservbyport(port, protocol)
        except:
            return "未知"
    
    def worker(self):
        """工作线程函数"""
        target = self.target_entry.get().strip()
        protocol = self.protocol_var.get().lower()
        
        while True:
            if self.scan_queue.empty() or not self.scanning:
                return
            
            port = self.scan_queue.get()
            
            if protocol == "tcp":
                status = self.scan_tcp_port(target, port)
            else:  # UDP
                status = self.scan_udp_port(target, port)
            
            service = self.get_service_name(port, protocol)
            
            # 只显示开放的端口
            if "开放" in status:
                self.root.after(0, self.add_result, port, protocol.upper(), status, service)
            
            self.scan_queue.task_done()
    
    def add_result(self, port, protocol, status, service):
        """向结果列表中添加结果"""
        self.result_tree.insert("", tk.END, values=(port, protocol, status, service))
    
    def start_scan(self):
        """开始扫描"""
        if self.scanning:
            return
        
        if not self.validate_input():
            return
        
        # 重置状态
        self.scanning = True
        self.scan_btn.configure(state=tk.DISABLED)
        self.stop_btn.configure(state=tk.NORMAL)
        self.result_tree.delete(*self.result_tree.get_children())
        self.log_message("开始扫描...")
        
        # 获取输入参数
        target = self.target_entry.get().strip()
        start_port = int(self.start_port_entry.get())
        end_port = int(self.end_port_entry.get())
        protocol = self.protocol_var.get()
        thread_count = self.thread_count.get()
        
        self.status_var.set(f"扫描中: {target} {start_port}-{end_port}/{protocol} 使用 {thread_count} 个线程")
        
        # 添加端口到队列
        for port in range(start_port, end_port + 1):
            self.scan_queue.put(port)
        
        # 创建工作线程
        self.threads = []
        for _ in range(thread_count):
            thread = threading.Thread(target=self.worker)
            thread.daemon = True
            thread.start()
            self.threads.append(thread)
        
        # 启动监控线程
        monitor_thread = threading.Thread(target=self.monitor_scan)
        monitor_thread.daemon = True
        monitor_thread.start()
    
    def monitor_scan(self):
        """监控扫描进度"""
        total_ports = int(self.end_port_entry.get()) - int(self.start_port_entry.get()) + 1
        
        while self.scanning and not self.scan_queue.empty():
            ports_done = total_ports - self.scan_queue.qsize()
            progress = (ports_done / total_ports) * 100
            self.root.after(100, self.status_var.set, 
                           f"扫描中: {progress:.1f}% 完成 ({ports_done}/{total_ports} 端口)")
            time.sleep(0.1)
        
        # 扫描完成
        if self.scanning:
            self.root.after(100, self.scan_completed)
    
    def scan_completed(self):
        """扫描完成处理"""
        self.scanning = False
        self.scan_btn.configure(state=tk.NORMAL)
        self.stop_btn.configure(state=tk.DISABLED)
        self.log_message("扫描完成")
        self.status_var.set(f"扫描完成: 找到 {self.result_tree.get_children().__len__()} 个开放端口")
    
    def stop_scan(self):
        """停止扫描"""
        if self.scanning:
            self.scanning = False
            self.log_message("扫描已停止")
            self.scan_btn.configure(state=tk.NORMAL)
            self.stop_btn.configure(state=tk.DISABLED)
            self.status_var.set("扫描已停止")
    
    def clear_results(self):
        """清除结果"""
        self.result_tree.delete(*self.result_tree.get_children())
        self.log_text.configure(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.configure(state=tk.DISABLED)
        self.status_var.set("就绪")
        self.log_message("已清除结果")
    
    def on_close(self):
        """关闭窗口时的处理"""
        if self.scanning:
            if messagebox.askokcancel("退出", "扫描仍在进行中,确定要退出吗?"):
                self.scanning = False
                self.root.destroy()
        else:
            self.root.destroy()
    
    def run(self):
        """运行程序"""
        self.root.mainloop()

if __name__ == "__main__":
    scanner = PortScanner()
    scanner.run()

功能说明

这个端口扫描程序具有以下功能:

1. 扫描功能

  • 支持TCP和UDP端口扫描
  • 可自定义扫描的端口范围
  • 多线程扫描提高效率
  • 可配置线程数量和超时时间

2. 用户界面

  • 直观的图形用户界面(GUI)
  • 实时显示扫描结果
  • 状态栏显示进度信息
  • 日志区域记录操作历史

3. 额外特性

  • 常用端口预设(常用端口、所有端口、Web服务端口)
  • 显示端口对应的服务名称
  • 扫描过程中可随时停止
  • 支持IP地址和域名作为目标
  • 输入验证确保参数正确

4. 结果展示

  • 表格形式显示开放端口
  • 显示端口号、协议、状态和服务名称
  • 自动滚动到最新结果

使用说明

  1. 在"目标主机"字段输入要扫描的IP地址或域名
  2. 设置要扫描的端口范围(起始端口和结束端口)
  3. 选择要使用的协议(TCP或UDP)
  4. 根据需要调整线程数和超时时间
  5. 点击"开始扫描"按钮启动扫描
  6. 可以在扫描过程中点击"停止扫描"按钮停止扫描
  7. 使用"清除结果"按钮清除扫描结果和日志

注意事项

  • 扫描他人计算机的端口可能违反法律或网络策略,请确保你有权限扫描目标主机
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值