企业英语网站,平面设计人才网,百度品牌,珠宝首饰商城网站建设在日常的网络管理、安全测试或家庭网络排查中#xff0c;我们常常需要快速了解当前局域网中有哪些设备在线。虽然命令行工具#xff08;如 nmap、arp-scan#xff09;功能强大#xff0c;但对于非技术人员来说门槛较高。本文将带你从零开始#xff0c;使用 Python Tkinte…在日常的网络管理、安全测试或家庭网络排查中我们常常需要快速了解当前局域网中有哪些设备在线。虽然命令行工具如nmap、arp-scan功能强大但对于非技术人员来说门槛较高。本文将带你从零开始使用Python Tkinter 多线程 系统命令调用构建一个图形化局域网扫描器具备 IP 扫描、主机名解析、MAC 地址获取和响应时间显示等核心功能。一、项目目标与功能预览我们的局域网扫描器将实现以下功能✅ 图形用户界面GUI操作直观✅ 支持自定义 IP 范围输入如192.168.1.1-254✅ 并发 Ping 扫描快速检测在线设备✅ 自动解析主机名Hostname✅ 通过 ARP 表获取 MAC 地址✅ 显示 Ping 响应时间ms✅ 实时进度条与状态提示✅ 支持随时停止扫描✅ 跨平台兼容Windows / Linux / macOS。最终效果如下图所示顶部输入框可设置扫描范围点击“开始扫描”后下方表格实时列出在线设备的 IP、主机名、MAC 和延迟底部状态栏显示进度信息。二、技术选型与核心模块1. GUI 框架tkinterPython 内置无需额外安装提供ttk主题控件界面更现代支持Treeview表格、Progressbar进度条、ScrolledText等组件。2. 网络探测subprocess 系统ping命令利用操作系统原生命令进行 ICMP 探测通过正则表达式解析响应时间兼容 Windows (ping -n) 与 Unix-like (ping -c) 参数差异。3. 主机信息获取主机名socket.gethostbyaddr(ip)MAC 地址先ping触发 ARP 缓存再调用arp -a或arp -n解析。4. 并发控制threading为每个 IP 启动独立线程提升扫描速度限制最大并发数如 50 线程避免系统资源耗尽使用daemonTrue确保主线程退出时子线程自动终止。三、代码结构详解3.1 主类NetworkScannerclassNetworkScanner:def__init__(self,root):self.rootroot self.root.title(局域网扫描器)self.root.geometry(800x600)self.create_widgets()初始化主窗口并创建 UI 组件。3.2 创建图形界面create_widgets()输入区域IP 范围输入框 “开始/停止”按钮进度条ttk.Progressbar显示扫描进度结果表格ttk.Treeview展示四列数据IP、主机名、MAC、延迟状态栏底部Label实时反馈操作状态线程控制标志self.scanning False用于优雅停止。 技巧使用grid和pack布局管理器组合实现灵活排版。3.3 核心扫描逻辑1Ping 探测ping(ip)defping(self,ip):# 根据平台选择 ping 命令ifWindows:ping-n1-w500ipelse:ping-c1-W0.5ip# 正则提取响应时间time_matchre.search(r时间[](\d)ms|time[](\d\.?\d*)\s*ms,output)超时设为 500ms避免卡顿成功条件输出中包含TTLWindows或ttlLinux/macOS。2获取主机名get_hostname(ip)try:returnsocket.gethostbyaddr(ip)[0]except:return未知反向 DNS 查询失败则返回“未知”。3获取 MAC 地址get_mac_address(ip)# 先 ping 一次确保 ARP 表有记录subprocess.call([ping,-c,1,ip],...)# 执行 arp -a (Win) 或 arp -n (Unix)outputsubprocess.check_output([arp,...])# 正则匹配 MAC([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})⚠️ 注意此方法依赖本地 ARP 缓存若目标未通信过可能无法获取。3.4 多线程扫描控制启动扫描start_scan()解析 IP 范围支持192.168.1.1-254或192.168.1清空旧结果禁用按钮启动后台线程。扫描主循环scan_range(base_ip, start, end)foriinrange(start,end1):ipf{base_ip}.{i}threadthreading.Thread(targetself.scan_ip,args(ip,self.update_tree))thread.start()threads.append(thread)# 控制并发数 ≤ 50iflen(threads)50:fortinthreads:t.join()threads[]每次批量启动最多 50 个线程避免资源爆炸扫描完成后恢复 UI 状态。安全停止stop_scan()self.scanningFalse# 设置标志位# 后台线程检查该标志后自动退出无需强制 kill 线程实现优雅终止。四、跨平台兼容性处理功能WindowsLinux / macOSPing 命令ping -n 1 -w 500ping -c 1 -W 0.5ARP 查询arp -a iparp -n ipTTL 关键字TTLttl通过platform.system().lower()动态判断系统类型确保命令正确执行。五、运行效果与优化建议示例输入192.168.1.1-254输出结果表格IP地址主机名MAC地址响应时间(ms)192.168.1.1router.homeaa:bb:cc:dd:ee:ff2192.168.1.105DESKTOP-ABC11:22:33:44:55:668优化方向增加端口扫描结合socket.connect_ex()检测开放端口厂商识别根据 MAC 前缀查询设备厂商需 OUI 数据库导出结果支持 CSV/Excel 导出图标美化为不同设备类型手机、PC、IoT添加图标性能提升改用asyncioaioping实现异步 Ping更高效。六、总结本文通过一个完整的局域网扫描器项目展示了如何结合 Python 的多种能力GUI 开发Tkinter系统交互subprocess网络编程socket多线程并发正则解析跨平台适配该项目不仅实用更是学习 Python 综合应用的绝佳案例。你可以将其作为网络工具箱的一部分或在此基础上扩展更高级的功能如漏洞扫描、设备画像等。源码已完整提供复制即可运行注意部分功能如 MAC 获取在虚拟机或受限网络中可能受限建议在真实局域网环境测试。附运行要求Python 3.6无第三方依赖仅标准库完整代码importtkinterastkfromtkinterimportttk,scrolledtextimportsocketimportthreadingimportplatformimportsubprocessimportrefromdatetimeimportdatetimeclassNetworkScanner:def__init__(self,root):self.rootroot self.root.title(局域网扫描器)self.root.geometry(800x600)# 创建界面元素self.create_widgets()defcreate_widgets(self):# 顶部框架 - 输入区域top_framettk.Frame(self.root,padding10)top_frame.pack(filltk.X)# IP范围输入ttk.Label(top_frame,textIP范围:).grid(row0,column0,padx5,pady5)self.ip_entryttk.Entry(top_frame,width20)self.ip_entry.grid(row0,column1,padx5,pady5)self.ip_entry.insert(0,192.168.1.1-254)# 扫描按钮self.scan_buttonttk.Button(top_frame,text开始扫描,commandself.start_scan)self.scan_button.grid(row0,column2,padx5,pady5)# 停止按钮self.stop_buttonttk.Button(top_frame,text停止扫描,commandself.stop_scan,statetk.DISABLED)self.stop_button.grid(row0,column3,padx5,pady5)# 进度条self.progressttk.Progressbar(self.root,orienthorizontal,modedeterminate)self.progress.pack(filltk.X,padx10,pady5)# 结果显示区域result_framettk.Frame(self.root)result_frame.pack(filltk.BOTH,expandTrue,padx10,pady5)# 创建Treeview显示结果columns(IP地址,主机名,MAC地址,响应时间(ms))self.treettk.Treeview(result_frame,columnscolumns,showheadings)# 设置列标题forcolincolumns:self.tree.heading(col,textcol)self.tree.column(col,width150,anchortk.CENTER)# 添加滚动条scrollbarttk.Scrollbar(result_frame,orienttk.VERTICAL,commandself.tree.yview)self.tree.configure(yscrollscrollbar.set)self.tree.pack(sidetk.LEFT,filltk.BOTH,expandTrue)scrollbar.pack(sidetk.RIGHT,filltk.Y)# 底部状态栏self.status_vartk.StringVar()self.status_var.set(就绪)status_barttk.Label(self.root,textvariableself.status_var,relieftk.SUNKEN)status_bar.pack(sidetk.BOTTOM,filltk.X)# 扫描标志self.scanningFalsedefget_local_ip(self):获取本机IP地址try:ssocket.socket(socket.AF_INET,socket.SOCK_DGRAM)s.connect((8.8.8.8,80))ips.getsockname()[0]s.close()returnipexcept:return127.0.0.1defping(self,ip):Ping指定IP检查是否在线try:# 根据操作系统选择ping命令ifplatform.system().lower()windows:outputsubprocess.check_output([ping,-n,1,-w,500,ip],stderrsubprocess.STDOUT,universal_newlinesTrue)else:outputsubprocess.check_output([ping,-c,1,-W,0.5,ip],stderrsubprocess.STDOUT,universal_newlinesTrue)# 检查ping结果ifTTLinoutputorttlinoutput:# 提取响应时间time_matchre.search(r时间[](\d)ms|time[](\d\.?\d*)\s*ms,output)iftime_match:time_mstime_match.group(1)iftime_match.group(1)elsetime_match.group(2)else:time_msN/AreturnTrue,time_msreturnFalse,N/Aexcept:returnFalse,N/Adefget_hostname(self,ip):获取IP对应的主机名try:hostnamesocket.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],stdoutsubprocess.DEVNULL,stderrsubprocess.DEVNULL)# 查询ARP表outputsubprocess.check_output([arp,-a,ip],stderrsubprocess.STDOUT,universal_newlinesTrue)# 提取MAC地址mac_matchre.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],stdoutsubprocess.DEVNULL,stderrsubprocess.DEVNULL)outputsubprocess.check_output([arp,-n,ip],stderrsubprocess.STDOUT,universal_newlinesTrue)mac_matchre.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):扫描单个IPifnotself.scanning:returnis_online,response_timeself.ping(ip)ifis_online:hostnameself.get_hostname(ip)mac_addressself.get_mac_address(ip)update_callback(ip,hostname,mac_address,response_time)defupdate_tree(self,ip,hostname,mac_address,response_time):更新Treeviewself.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_rangeself.ip_entry.get().strip()ifnotip_range:self.status_var.set(请输入IP范围)returntry:if-inip_range:base_ip,range_partip_range.split(-)last_octet_base..join(base_ip.split(.)[:-1])startint(base_ip.split(.)[-1])endint(range_part)else:# 如果只输入了网络段扫描1-254last_octet_baseip_range start1end254except:self.status_var.set(IP范围格式错误应为如 192.168.1.1-254)return# 更新UI状态self.scanningTrueself.scan_button.config(statetk.DISABLED)self.stop_button.config(statetk.NORMAL)self.progress.config(maximumend-start1)self.progress.config(value0)# 启动扫描线程self.scan_threadthreading.Thread(targetself.scan_range,args(last_octet_base,start,end))self.scan_thread.daemonTrueself.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[]count0foriinrange(start,end1):ifnotself.scanning:breakipf{base_ip}.{i}# 创建并启动线程threadthreading.Thread(targetself.scan_ip,args(ip,self.update_tree))thread.daemonTruethread.start()threads.append(thread)# 限制并发线程数iflen(threads)50:fortinthreads:t.join()threads[]# 更新进度条count1self.progress.config(valuecount)# 等待剩余线程完成fortinthreads:t.join()# 更新UI状态self.scanningFalseself.scan_button.config(statetk.NORMAL)self.stop_button.config(statetk.DISABLED)self.status_var.set(f扫描完成发现{len(self.tree.get_children())}个在线设备)defstop_scan(self):停止扫描self.scanningFalseself.status_var.set(正在停止扫描...)self.scan_button.config(statetk.NORMAL)self.stop_button.config(statetk.DISABLED)if__name____main__:roottk.Tk()appNetworkScanner(root)root.mainloop()