PCL点云离群点处理:SOR与ROR原理、参数调优与工程实践
2026/10/17 8:47:46
在日常的网络管理、安全测试或家庭网络排查中,我们常常需要快速了解当前局域网中有哪些设备在线。虽然命令行工具(如nmap、arp-scan)功能强大,但对于非技术人员来说门槛较高。本文将带你从零开始,使用Python + Tkinter + 多线程 + 系统命令调用构建一个图形化局域网扫描器,具备 IP 扫描、主机名解析、MAC 地址获取和响应时间显示等核心功能。
我们的局域网扫描器将实现以下功能:
192.168.1.1-254);最终效果如下图所示:
顶部输入框可设置扫描范围,点击“开始扫描”后,下方表格实时列出在线设备的 IP、主机名、MAC 和延迟,底部状态栏显示进度信息。
tkinterttk主题控件,界面更现代;Treeview表格、Progressbar进度条、ScrolledText等组件。subprocess+ 系统ping命令ping -n) 与 Unix-like (ping -c) 参数差异。socket.gethostbyaddr(ip);ping触发 ARP 缓存,再调用arp -a或arp -n解析。threadingdaemon=True确保主线程退出时子线程自动终止。NetworkScannerclassNetworkScanner:def__init__(self,root):self.root=root self.root.title("局域网扫描器")self.root.geometry("800x600")self.create_widgets()初始化主窗口并创建 UI 组件。
create_widgets()ttk.Progressbar显示扫描进度;ttk.Treeview展示四列数据(IP、主机名、MAC、延迟);Label实时反馈操作状态;self.scanning = False用于优雅停止。💡 技巧:使用
grid和pack布局管理器组合,实现灵活排版。
ping(ip)defping(self,ip):# 根据平台选择 ping 命令ifWindows:ping-n1-w500ipelse:ping-c1-W0.5ip# 正则提取响应时间time_match=re.search(r"时间[=<](\d+)ms|time[=<](\d+\.?\d*)\s*ms",output)TTL=(Windows)或ttl=(Linux/macOS)。get_hostname(ip)try:returnsocket.gethostbyaddr(ip)[0]except:return"未知"get_mac_address(ip)# 先 ping 一次,确保 ARP 表有记录subprocess.call(["ping","-c","1",ip],...)# 执行 arp -a (Win) 或 arp -n (Unix)output=subprocess.check_output(["arp",...])# 正则匹配 MAC:([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})⚠️ 注意:此方法依赖本地 ARP 缓存,若目标未通信过可能无法获取。
start_scan()192.168.1.1-254或192.168.1);scan_range(base_ip, start, end)foriinrange(start,end+1):ip=f"{base_ip}.{i}"thread=threading.Thread(target=self.scan_ip,args=(ip,self.update_tree))thread.start()threads.append(thread)# 控制并发数 ≤ 50iflen(threads)>=50:fortinthreads:t.join()threads=[]stop_scan()self.scanning=False# 设置标志位# 后台线程检查该标志后自动退出| 功能 | Windows | Linux / macOS |
|---|---|---|
| Ping 命令 | ping -n 1 -w 500 | ping -c 1 -W 0.5 |
| ARP 查询 | arp -a ip | arp -n ip |
| TTL 关键字 | "TTL=" | "ttl=" |
通过platform.system().lower()动态判断系统类型,确保命令正确执行。
192.168.1.1-254| IP地址 | 主机名 | MAC地址 | 响应时间(ms) |
|---|---|---|---|
| 192.168.1.1 | router.home | aa:bb:cc:dd:ee:ff | 2 |
| 192.168.1.105 | DESKTOP-ABC | 11:22:33:44:55:66 | 8 |
socket.connect_ex()检测开放端口;asyncio+aioping实现异步 Ping(更高效)。本文通过一个完整的局域网扫描器项目,展示了如何结合 Python 的多种能力:
该项目不仅实用,更是学习 Python 综合应用的绝佳案例。你可以将其作为网络工具箱的一部分,或在此基础上扩展更高级的功能(如漏洞扫描、设备画像等)。
🔧源码已完整提供,复制即可运行!
📌注意:部分功能(如 MAC 获取)在虚拟机或受限网络中可能受限,建议在真实局域网环境测试。
附:运行要求
importtkinterastkfromtkinterimportttk,scrolledtextimportsocketimportthreadingimportplatformimportsubprocessimportrefromdatetimeimportdatetimeclassNetworkScanner:def__init__(self,root):self.root=root self.root.title("局域网扫描器")self.root.geometry("800x600")# 创建界面元素self.create_widgets()defcreate_widgets(self):# 顶部框架 - 输入区域top_frame=ttk.Frame(self.root,padding="10")top_frame.pack(fill=tk.X)# IP范围输入ttk.Label(top_frame,text="IP范围:").grid(row=0,column=0,padx=5,pady=5)self.ip_entry=ttk.Entry(top_frame,width=20)self.ip_entry.grid(row=0,column=1,padx=5,pady=5)self.ip_entry.insert(0,"192.168.1.1-254")# 扫描按钮self.scan_button=ttk.Button(top_frame,text="开始扫描",command=self.start_scan)self.scan_button.grid(row=0,column=2,padx=5,pady=5)# 停止按钮self.stop_button=ttk.Button(top_frame,text="停止扫描",command=self.stop_scan,state=tk.DISABLED)self.stop_button.grid(row=0,column=3,padx=5,pady=5)# 进度条self.progress=ttk.Progressbar(self.root,orient="horizontal",mode="determinate")self.progress.pack(fill=tk.X,padx=10,pady=5)# 结果显示区域result_frame=ttk.Frame(self.root)result_frame.pack(fill=tk.BOTH,expand=True,padx=10,pady=5)# 创建Treeview显示结果columns=("IP地址","主机名","MAC地址","响应时间(ms)")self.tree=ttk.Treeview(result_frame,columns=columns,show="headings")# 设置列标题forcolincolumns:self.tree.heading(col,text=col)self.tree.column(col,width=150,anchor=tk.CENTER)# 添加滚动条scrollbar=ttk.Scrollbar(result_frame,orient=tk.VERTICAL,command=self.tree.yview)self.tree.configure(yscroll=scrollbar.set)self.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)status_bar.pack(side=tk.BOTTOM,fill=tk.X)# 扫描标志self.scanning=Falsedefget_local_ip(self):"""获取本机IP地址"""try:s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)s.connect(("8.8.8.8",80))ip=s.getsockname()[0]s.close()returnipexcept:return"127.0.0.1"defping(self,ip):"""Ping指定IP,检查是否在线"""try:# 根据操作系统选择ping命令ifplatform.system().lower()=="windows":output=subprocess.check_output(["ping","-n","1","-w","500",ip],stderr=subprocess.STDOUT,universal_newlines=True)else:output=subprocess.check_output(["ping","-c","1","-W","0.5",ip],stderr=subprocess.STDOUT,universal_newlines=True)# 检查ping结果if"TTL="inoutputor"ttl="inoutput:# 提取响应时间time_match=re.search(r"时间[=<](\d+)ms|time[=<](\d+\.?\d*)\s*ms",output)iftime_match:time_ms=time_match.group(1)iftime_match.group(1)elsetime_match.group(2)else:time_ms="N/A"returnTrue,time_msreturnFalse,"N/A"except:returnFalse,"N/A"defget_hostname(self,ip):"""获取IP对应的主机名"""try:hostname=socket.gethostbyaddr(ip)[0]returnhostnameexcept:return"未知"defget_mac_address(self,ip):"""获取IP对应的MAC地址"""try:# 根据操作系统选择命令ifplatform.system().lower()=="windows":# 先ping一下确保ARP表中有该IPsubprocess.call(["ping","-n","1",ip],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)# 查询ARP表output=subprocess.check_output(["arp","-a",ip],stderr=subprocess.STDOUT,universal_newlines=True)# 提取MAC地址mac_match=re.search(r"([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})",output)ifmac_match:returnmac_match.group(0)else:# Linux/Mac系统subprocess.call(["ping","-c","1",ip],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)output=subprocess.check_output(["arp","-n",ip],stderr=subprocess.STDOUT,universal_newlines=True)mac_match=re.search(r"([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})",output)ifmac_match:returnmac_match.group(0)return"未知"except:return"未知"defscan_ip(self,ip,update_callback):"""扫描单个IP"""ifnotself.scanning:returnis_online,response_time=self.ping(ip)ifis_online:hostname=self.get_hostname(ip)mac_address=self.get_mac_address(ip)update_callback(ip,hostname,mac_address,response_time)defupdate_tree(self,ip,hostname,mac_address,response_time):"""更新Treeview"""self.tree.insert("",tk.END,values=(ip,hostname,mac_address,response_time))self.root.update_idletasks()defstart_scan(self):"""开始扫描"""# 清空结果foriteminself.tree.get_children():self.tree.delete(item)# 解析IP范围ip_range=self.ip_entry.get().strip()ifnotip_range:self.status_var.set("请输入IP范围")returntry:if"-"inip_range:base_ip,range_part=ip_range.split("-")last_octet_base=".".join(base_ip.split(".")[:-1])start=int(base_ip.split(".")[-1])end=int(range_part)else:# 如果只输入了网络段,扫描1-254last_octet_base=ip_range start=1end=254except:self.status_var.set("IP范围格式错误,应为如 192.168.1.1-254")return# 更新UI状态self.scanning=Trueself.scan_button.config(state=tk.DISABLED)self.stop_button.config(state=tk.NORMAL)self.progress.config(maximum=end-start+1)self.progress.config(value=0)# 启动扫描线程self.scan_thread=threading.Thread(target=self.scan_range,args=(last_octet_base,start,end))self.scan_thread.daemon=Trueself.scan_thread.start()# 更新状态栏self.status_var.set(f"正在扫描{last_octet_base}.{start}到{last_octet_base}.{end}...")defscan_range(self,base_ip,start,end):"""扫描IP范围"""threads=[]count=0foriinrange(start,end+1):ifnotself.scanning:breakip=f"{base_ip}.{i}"# 创建并启动线程thread=threading.Thread(target=self.scan_ip,args=(ip,self.update_tree))thread.daemon=Truethread.start()threads.append(thread)# 限制并发线程数iflen(threads)>=50:fortinthreads:t.join()threads=[]# 更新进度条count+=1self.progress.config(value=count)# 等待剩余线程完成fortinthreads:t.join()# 更新UI状态self.scanning=Falseself.scan_button.config(state=tk.NORMAL)self.stop_button.config(state=tk.DISABLED)self.status_var.set(f"扫描完成,发现{len(self.tree.get_children())}个在线设备")defstop_scan(self):"""停止扫描"""self.scanning=Falseself.status_var.set("正在停止扫描...")self.scan_button.config(state=tk.NORMAL)self.stop_button.config(state=tk.DISABLED)if__name__=="__main__":root=tk.Tk()app=NetworkScanner(root)root.mainloop()