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. 结果展示
- 表格形式显示开放端口
- 显示端口号、协议、状态和服务名称
- 自动滚动到最新结果
使用说明
- 在"目标主机"字段输入要扫描的IP地址或域名
- 设置要扫描的端口范围(起始端口和结束端口)
- 选择要使用的协议(TCP或UDP)
- 根据需要调整线程数和超时时间
- 点击"开始扫描"按钮启动扫描
- 可以在扫描过程中点击"停止扫描"按钮停止扫描
- 使用"清除结果"按钮清除扫描结果和日志
注意事项
- 扫描他人计算机的端口可能违反法律或网络策略,请确保你有权限扫描目标主机