资讯详情 用 Trae 开发 Win10 端口占用检测工具:TaoToken 统一 Key 接入 Python+wxPython 实战
📅 2026/10/12 17:26:15
1. Win10 端口占用检测工具到底解决什么问题在 Windows 10 上跑 Python 服务、Node 项目或者本地调试接口时最常撞见的报错就是OSError: [WinError 10048] 通常每个套接字地址只允许使用一次或者Address already in use。翻译成人话就是你想用的端口被别的进程占着系统不让你再绑一次。Linux 下有lsof -i:端口号一条命令就能定位Windows 下虽然也能用netstat -ano | findstr :8080查到 PID但接下来还得手动tasklist查进程名、taskkill /F /PID杀进程三步下来对不熟悉命令行的同学很不友好。这篇要做的是一个基于 Python wxPython 的 Win10 端口占用检测 GUI 工具输入端口号点一下按钮列表里直接显示占用该端口的 PID、程序名称、连接状态、程序路径选中某一行还能一键终止进程。整个开发过程在 Trae 里完成同时把工具里可能用到的模型调用 endpoint 统一改到 TaoToken用一个 Key 走通所有模型请求省得在多个平台之间来回切。适合谁看正在学 Python GUI 的初学者、经常被端口占用卡住的 Windows 开发者、想用 Trae 快速生成可运行桌面工具的同学。工具本身不复杂但把「扫描端口 → 解析进程 → 可视化 → 一键释放」这条链路走通能顺手练到subprocess、正则解析、wxPython 布局和线程调度几个点。先说清楚技术选型。GUI 用 wxPython原因是它在 Windows 上原生控件观感好、ListCtrl做表格展示很省事进程查询用系统自带的netstat、tasklist、wmic不引入额外依赖打包用 Nuitka生成的是编译后的可执行文件比 PyInstaller 的单文件模式在启动速度和缓存行为上更干净。模型调用部分把 endpoint 指向 TaoToken 的 API 地址Key 统一管理后面不管是接对话模型还是接 coding 场景都能复用同一套配置。我试过直接在命令行里反复netstattaskkill短时间调试还行一旦要频繁切换端口就很烦。做成 GUI 之后输入端口、点检测、看结果、点终止四步在一个窗口里完成效率提升明显。下面从环境准备开始一步步把代码和配置贴出来。2. TaoToken 统一 Key 接入与 Trae 环境准备在 Trae 里开发这个工具模型调用不是必须的但如果你想让它帮你补全代码、解释报错、生成打包脚本就需要一个稳定的模型入口。TaoToken 的作用是把多个模型的调用收敛到一个 API 地址和一个 Key 上你不用为每个模型单独申请账号、记不同的 Base URL。对个人开发者来说最直接的好处是配置只写一份换模型只改 Model ID。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 请求地址是 https://taotoken.net/api 注意 API 地址后面不加 UTM 参数直接用于代码里的 Base URL。模型对话、Coding Plan、控制台、API Keys、接入文档、Claude Code 对应的 deep link 分别是模型对话https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteCoding Planhttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite控制台https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keyshttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaude Codehttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite拿到 Key 之后在 Trae 里配置模型调用核心就是三件套Base URL、API Key、Model ID。Base URL 填https://taotoken.net/apiKey 填你在控制台生成的密钥Model ID 按你实际要用的模型填。这三件套在后面的 JSON 配置片段里会完整给出路径和字段名保持一致直接复制就能用。Trae 本身是字节出的 AI 编程工具官网在 https://www.trae.com.cn/ 。它的定位是帮你从自然语言描述生成代码、补全函数、解释报错。这个端口检测工具的主体代码就是先在 Trae 里描述需求让它生成 wxPython 骨架和subprocess调用逻辑再手动调整正则和线程部分。Trae 生成代码快但涉及系统命令解析的地方需要你自己核对输出格式尤其是netstat -ano和tasklist在不同 Windows 版本上的列宽差异。环境准备清单Windows 1064 位版本 1809 以上Python 3.9 或以上安装时勾选 Add to PATHwxPythonpip install wxpythonNuitka可选用于打包pip install nuitka一个可用的 TaoToken API Key安装 wxPython 时如果遇到编译错误优先用预编译 wheelpip install wxpython --only-binary :all:。如果还是失败检查 Python 版本是否和 wheel 匹配3.12 的 wheel 覆盖已经比较全了。Nuitka 打包需要本机有 C 编译器Windows 上装 Visual Studio Build Tools 或者 MinGW 都行Trae 可以帮你生成对应的安装命令说明。配置模型调用时如果你用的是 OpenAI 兼容的 SDK代码里这样写from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_key你的_TaoToken_Key ) resp client.chat.completions.create( model你的_Model_ID, messages[{role: user, content: 帮我解释 netstat -ano 的输出格式}] ) print(resp.choices[0].message.content)这段代码里的base_url就是 TaoToken 的 API 地址api_key换成你自己的model填控制台里看到的 Model ID。跑通之后Trae 里的模型调用也可以指向同一个地址配置一次多处复用。3. 可复制配置端口扫描与 wxPython 界面骨架这一节把完整代码拆成三块端口扫描与进程查询、wxPython 界面、模型调用配置片段。每块都能单独复制运行最后拼成完整工具。先看端口扫描核心逻辑。Windows 下netstat -ano输出格式大致是TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 12345 TCP 127.0.0.1:8080 127.0.0.1:54321 ESTABLISHED 12345第一列协议第二列本地地址第三列外部地址第四列状态第五列 PID。我们要做的是按端口号过滤提取 PID再用tasklist查进程名用wmic查可执行文件路径。import subprocess import re def _startupinfo(): si subprocess.STARTUPINFO() si.dwFlags | subprocess.STARTF_USESHOWWINDOW return si def scan_port(port): 返回占用指定端口的进程列表 output subprocess.check_output( [netstat, -ano], textTrue, stderrsubprocess.STDOUT, startupinfo_startupinfo() ) pattern re.compile(rTCP\s.*?:{}\s.*?\s(\d).format(port)) pids set(pattern.findall(output)) results [] for pid in pids: task_info subprocess.check_output( [tasklist, /fi, fpid eq {pid}], textTrue, stderrsubprocess.STDOUT, startupinfo_startupinfo() ) name_match re.search(r(.?)\s\d\sConsole, task_info) proc_name name_match.group(1).strip() if name_match else 未知程序 detail re.compile( rTCP\s.*?:{}\s.*?\s(\S)\s({}).format(port, pid) ) status_match detail.search(output) status status_match.group(1) if status_match else UNKNOWN try: path_out subprocess.check_output( [wmic, process, where, fprocessid{pid}, get, executablepath], textTrue, stderrsubprocess.STDOUT, startupinfo_startupinfo() ) path_match re.search(rExecutablePath\s(.?)\s*$, path_out, re.MULTILINE) proc_path path_match.group(1).strip() if path_match else 未知路径 except subprocess.CalledProcessError: proc_path 未知路径 results.append({ pid: pid, name: proc_name, status: status, path: proc_path }) return results这段代码里_startupinfo()的作用是隐藏命令行窗口否则每次调用netstat都会闪一下黑框。scan_port返回一个字典列表每个字典包含 PID、程序名、状态、路径。注意wmic在新版 Windows 上可能被标记为弃用如果报错可以换成 PowerShell 的Get-Process但为了兼容性这里保留wmic。再看 wxPython 界面骨架。用wx.Frame做主窗口wx.BoxSizer做垂直布局顶部一行输入端口中间一行放检测和终止按钮底部用wx.ListCtrl展示结果。import wx import threading class PortMonitorFrame(wx.Frame): def __init__(self, parent, title): super().__init__(parent, titletitle, size(760, 460)) self.is_scanning False self.InitUI() self.Centre() def InitUI(self): panel wx.Panel(self) vbox wx.BoxSizer(wx.VERTICAL) hbox1 wx.BoxSizer(wx.HORIZONTAL) self.port_input wx.TextCtrl(panel) hbox1.Add(wx.StaticText(panel, label端口号), flagwx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border8) hbox1.Add(self.port_input, proportion1) hbox2 wx.BoxSizer(wx.HORIZONTAL) self.check_btn wx.Button(panel, label检测端口占用) self.kill_btn wx.Button(panel, label终止占用进程) self.check_btn.Bind(wx.EVT_BUTTON, self.on_check_port) self.kill_btn.Bind(wx.EVT_BUTTON, self.on_kill_process) hbox2.Add(self.check_btn, flagwx.RIGHT, border8) hbox2.Add(self.kill_btn) self.result_list wx.ListCtrl(panel, stylewx.LC_REPORT) self.result_list.InsertColumn(0, PID, width90) self.result_list.InsertColumn(1, 程序名称, width180) self.result_list.InsertColumn(2, 状态, width130) self.result_list.InsertColumn(3, 程序路径, width320) vbox.Add(hbox1, flagwx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border10) vbox.Add(hbox2, flagwx.EXPAND | wx.ALL, border10) vbox.Add(self.result_list, proportion1, flagwx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border10) panel.SetSizer(vbox) def on_check_port(self, event): port self.port_input.GetValue().strip() if not port.isdigit(): wx.MessageBox(请输入有效的端口号, 错误, wx.ICON_ERROR) return if self.is_scanning: wx.MessageBox(扫描正在进行中, 提示, wx.ICON_INFORMATION) return self.result_list.DeleteAllItems() self.is_scanning True self.check_btn.Enable(False) def task(): try: rows scan_port(port) if not rows: wx.CallAfter(wx.MessageBox, 端口未被占用, 提示, wx.ICON_INFORMATION) for row in rows: wx.CallAfter(self._append_row, row) except subprocess.CalledProcessError as e: wx.CallAfter(wx.MessageBox, f执行命令失败{e.output}, 错误, wx.ICON_ERROR) finally: self.is_scanning False wx.CallAfter(self.check_btn.Enable, True) threading.Thread(targettask, daemonTrue).start() def _append_row(self, row): idx self.result_list.InsertItem( self.result_list.GetItemCount(), row[pid]) self.result_list.SetItem(idx, 1, row[name]) self.result_list.SetItem(idx, 2, row[status]) self.result_list.SetItem(idx, 3, row[path]) if row[status] LISTENING: self.result_list.SetItemTextColour(idx, wx.RED) def on_kill_process(self, event): if self.is_scanning: wx.MessageBox(扫描未完成请稍后, 提示, wx.ICON_INFORMATION) return selected self.result_list.GetFirstSelected() if selected -1: wx.MessageBox(请选择要终止的进程, 提示, wx.ICON_INFORMATION) return pid self.result_list.GetItemText(selected) try: subprocess.check_output( [taskkill, /F, /PID, pid], textTrue, stderrsubprocess.STDOUT, startupinfo_startupinfo() ) self.result_list.DeleteItem(selected) wx.MessageBox(进程终止成功, 提示, wx.ICON_INFORMATION) except subprocess.CalledProcessError as e: wx.MessageBox(f终止进程失败{e.output}, 错误, wx.ICON_ERROR) if __name__ __main__: app wx.App() frame PortMonitorFrame(None, 端口监控工具) frame.Show() app.MainLoop()界面部分的关键点是扫描放在子线程里UI 更新用wx.CallAfter回到主线程否则会卡界面。LISTENING状态的行标红一眼就能看出哪个进程在监听。最后是模型调用配置片段。如果你在 Trae 里用配置文件管理模型可以写一个settings.json{ model_providers: { taotoken: { base_url: https://taotoken.net/api, api_key: 你的_TaoToken_Key, model_id: 你的_Model_ID } }, default_provider: taotoken }如果用的是 TOML 格式比如某些 CLI 工具对应写法[model_providers.taotoken] base_url https://taotoken.net/api api_key 你的_TaoToken_Key model_id 你的_Model_ID default_provider taotoken三件套就是 Base URL、API Key、Model ID路径和字段名按你实际使用的工具调整但值保持一致。配置好之后Trae 里的模型请求和工具里的模型请求都走同一个入口。4. 验证请求与本地运行成功结果代码写完之后先别急着打包直接用 Python 跑一遍确认扫描和终止都能正常工作。第一步保存文件为port_monitor.py在终端里执行python port_monitor.py窗口弹出后先制造一个端口占用。开一个终端跑python -m http.server 8080这会在 8080 端口起一个 HTTP 服务。回到 GUI输入8080点「检测端口占用」。正常情况下列表里会出现一行PID 是 Python 进程的 ID程序名称是python.exe状态是LISTENING路径是你 Python 解释器的位置整行标红。如果列表为空先确认http.server是否真的在跑再检查netstat -ano | findstr :8080在命令行里能不能查到。查不到说明端口没被占用GUI 显示「端口未被占用」也是对的。第二步验证终止功能。选中那一行点「终止占用进程」。如果成功会弹出「进程终止成功」列表里对应行消失。回到跑http.server的终端会发现进程已经被杀掉。这一步用的是taskkill /F /PID/F表示强制终止对大多数用户态进程有效。如果遇到系统关键进程taskkill会返回拒绝访问这是正常的权限保护不要强行绕过。第三步验证模型调用。在另一个终端里跑前面那段 OpenAI SDK 的代码把base_url指向https://taotoken.net/apiapi_key和model填你自己的。请求成功的话会返回模型对netstat -ano输出格式的解释。这一步验证的是 TaoToken 的 Key 和 endpoint 配置是否正确。如果返回 401说明 Key 有问题如果返回模型不存在说明 Model ID 填错了。第四步打包成 exe。用 Nuitkapip install nuitka nuitka --standalone --onefile --windows-console-modedisable port_monitor.py打包完成后dist目录下会生成port_monitor.exe。双击运行功能和 Python 脚本一致。--windows-console-modedisable的作用是运行时不弹黑框--onefile把所有依赖打成一个文件。Nuitka 首次打包会编译 C 代码耗时几分钟之后增量打包会快很多。实测下来打包后的 exe 在 Win10 上启动大约 1 到 2 秒比 PyInstaller 的单文件模式稍快而且不会在用户目录留下_MEI缓存文件夹。如果你更习惯 PyInstaller也可以用pyinstaller --onefile --noconsole port_monitor.py但要注意它每次运行会解压到临时目录杀毒软件偶尔会误报。验证清单验证项命令/操作预期结果端口扫描输入 8080 点检测列表显示 python.exe状态 LISTENING进程终止选中行点终止弹窗成功行消失服务停止模型调用跑 SDK 代码返回模型回复无 401exe 运行双击 port_monitor.exe窗口正常功能一致四步都通过说明工具链路完整。任何一步失败对照下一节的排查表定位。5. 本篇常见错排查401、local proxy failed、reading choices这一节把实际开发中容易撞到的报错列出来对照现象和原因逐个解决。报错一401 Unauthorized现象模型调用返回401提示invalid api key或authentication failed。原因API Key 填错、过期或者复制时带了空格。TaoToken 的 Key 在控制台生成复制时注意不要多选前后空白字符。解决重新在控制台生成一个 Key替换配置里的api_key字段。如果用的是环境变量检查变量名是否和代码里读取的一致。Base URL 确认是https://taotoken.net/api不要多加斜杠或路径。报错二local proxy failed现象请求发不出去提示local proxy failed或连接被拒绝。原因本机网络配置里有代理设置或者环境变量HTTP_PROXY、HTTPS_PROXY指向了一个不可用的地址。有些工具会读取系统代理导致请求被转发到错误的地方。解决检查环境变量临时清掉代理设置再试set HTTP_PROXY set HTTPS_PROXY然后在同一个终端里重新跑请求。如果用的是 Python也可以在代码里显式指定不使用代理import os os.environ.pop(HTTP_PROXY, None) os.environ.pop(HTTPS_PROXY, None)报错三reading choices 相关错误现象返回结果解析失败提示list index out of range或reading choices。原因请求返回的结构和预期不一致可能是模型名填错导致返回了错误信息也可能是返回体为空。常见于 Model ID 拼写错误或者请求参数里messages格式不对。解决先把原始返回打印出来看resp client.chat.completions.create(...) print(resp)如果返回的是错误对象里面会有error字段说明原因。确认 Model ID 和控制台里显示的一致messages是列表且每个元素有role和content。报错四OAuth 相关错误现象提示OAuth token expired或invalid_grant。原因如果你用的是需要 OAuth 的工具比如某些 CLItoken 过期了。TaoToken 的 API Key 方式不涉及 OAuth但如果你在 Trae 里配置了其他认证方式可能会撞到。解决重新走一遍授权流程或者改用 API Key 方式。在配置里把认证方式切到api_key填 TaoToken 的 Key。报错五taskkill 拒绝访问现象点终止进程弹窗提示拒绝访问或Access is denied。原因目标进程是系统进程或需要管理员权限。taskkill /F对普通用户态进程有效但对系统服务或受保护进程无效。解决不要强行终止系统进程。如果是自己的服务用管理员身份运行 GUI 再试。如果还是不行检查进程是否被其他机制保护。报错六netstat 输出解析不到 PID现象扫描后列表为空但命令行netstat -ano | findstr :端口能查到。原因正则没匹配上可能是netstat输出里协议列不是TCP而是TCPv6或者端口号前后有空格差异。解决把正则放宽同时匹配TCP和TCPv6pattern re.compile(rTCPv?6?\s.*?:{}\s.*?\s(\d).format(port))或者先把netstat -ano的原始输出打印出来对照实际格式调整正则。排查时记住一个原则先看原始输出再改解析逻辑。subprocess调用系统命令输出格式受系统版本和语言影响中文 Windows 的tasklist列名可能是中文正则里用\S比写死英文更稳。6. 从端口工具到日常开发把配置沉淀下来工具做完之后真正有价值的是把配置和代码沉淀成可复用的模板。端口检测这个场景本身不复杂但它串起了几个日常开发中反复出现的点系统命令调用、GUI 线程调度、模型 endpoint 统一管理、打包分发。我的做法是把scan_port和_startupinfo抽到一个独立的win_utils.py里以后写其他 Windows 小工具直接 import。wxPython 的界面骨架也存一份改改控件就能复用到别的检测工具上。模型配置单独放一个settings.json所有项目共用同一个 TaoToken Key 和 Base URL换模型只改 Model ID。如果你经常在 Trae 里生成代码建议把这段配置片段固定下来{ base_url: https://taotoken.net/api, api_key: 你的_TaoToken_Key, model_id: 你的_Model_ID }每次新建项目先把这三行填好再让 Trae 生成业务代码。这样模型调用不会因为配置散落各处而出问题。需要长期跑编码任务或者 Agent 场景的可以看 Coding Plan 对应的入口只是验证模型效果的用模型对话入口就行接入过程中遇到报错的去 API Keys 和接入文档页面找对应说明。最后留一个实用技巧打包 exe 之前先在port_monitor.py里加一个--debug参数运行时把netstat和tasklist的原始输出打到日志文件。这样在别人机器上出问题时让对方把日志发回来比远程猜快得多。日志用logging模块写输出到port_monitor.log打包时排除掉日志文件本身。工具代码不长但每个环节都值得自己跑一遍。端口扫描的正则、线程里的wx.CallAfter、Nuitka 的打包参数这些细节在真实项目里会反复用到。把这一套走通下次再遇到端口占用就不用对着命令行发呆了。