python再封装frpc内网穿透可视化客户端(by:Qoder-AI)
最近重新部署了frp,使用的是最新的0.67.0版本,之前有写过一篇frp部署的文章但是太老了,有需要中文frp介绍的可以去这个地址看下:https://gofrp.org/zh-cn/docs/overview/ 我的程序是在github上的地址下载的 https://github.com/fatedier/frp/tags(注意服务端、客户端版本用同一个版本)
本次借助Qoder直接在windows下写了个frpc的可视化配置客户端,直接看成品吧:

先附上一些所需文件/代码
frpc_manager_gui.py
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import subprocess
import threading
import os
import sys
import configparser
class FRPCClientGUI:
def __init__(self, root):
self.root = root
self.root.title("FRP 客户端管理器")
self.root.geometry("700x600")
# 进程对象
self.process = None
self.output_thread = None
self.is_running = False
self.startup_status = "starting" # starting, success, failed
# 配置文件路径
self.config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "frpc_manager.ini")
self.success_config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "frpc_last_success.ini")
# 创建主框架
self.create_widgets()
# 加载配置
self.load_config()
# 程序启动后自动触发启动
self.root.after(500, self.auto_start_frpc)
def create_widgets(self):
"""创建界面组件"""
# 标题
title_label = ttk.Label(self.root, text="FRP 客户端配置", font=("Arial", 16, "bold"))
title_label.pack(pady=5)
# 配置框架(默认先创建,后面根据配置决定是否隐藏)
self.config_frame = ttk.LabelFrame(self.root, text="配置参数", padding=10)
self.config_frame.pack(fill="x", padx=20, pady=5)
# 第一行:FRP_SERVER_ADDR 和 FRP_SERVER_PORT
ttk.Label(self.config_frame, text="FRP 服务器地址:").grid(row=0, column=0, sticky="w", pady=5, padx=5)
self.server_addr_entry = ttk.Entry(self.config_frame, width=20)
self.server_addr_entry.grid(row=0, column=1, padx=5, pady=5)
self.server_addr_entry.bind("<KeyRelease>", lambda e: self.update_config_description())
ttk.Label(self.config_frame, text="FRP 服务器端口:").grid(row=0, column=2, sticky="w", pady=5, padx=5)
self.server_port_entry = ttk.Entry(self.config_frame, width=20)
self.server_port_entry.grid(row=0, column=3, padx=5, pady=5)
# 第二行:FRP_TOKEN 和 FRP_NAME
ttk.Label(self.config_frame, text="FRP Token:").grid(row=1, column=0, sticky="w", pady=5, padx=5)
self.token_entry = ttk.Entry(self.config_frame, width=20, show="*")
self.token_entry.grid(row=1, column=1, padx=5, pady=5)
ttk.Label(self.config_frame, text="FRP 代理名称:").grid(row=1, column=2, sticky="w", pady=5, padx=5)
self.name_entry = ttk.Entry(self.config_frame, width=20)
self.name_entry.grid(row=1, column=3, padx=5, pady=5)
# 第三行:LOCAL_IP 和 LOCAL_PORT
ttk.Label(self.config_frame, text="局域网主机 IP:").grid(row=2, column=0, sticky="w", pady=5, padx=5)
self.local_ip_entry = ttk.Entry(self.config_frame, width=20)
self.local_ip_entry.grid(row=2, column=1, padx=5, pady=5)
self.local_ip_entry.bind("<KeyRelease>", lambda e: self.update_config_description())
ttk.Label(self.config_frame, text="局域网主机端口:").grid(row=2, column=2, sticky="w", pady=5, padx=5)
self.local_port_entry = ttk.Entry(self.config_frame, width=20)
self.local_port_entry.grid(row=2, column=3, padx=5, pady=5)
self.local_port_entry.bind("<KeyRelease>", lambda e: self.update_config_description())
# 第四行:FRP_REMOTE_PORT 和 FRP_USER (可选)
ttk.Label(self.config_frame, text="FRP 远程端口:").grid(row=3, column=0, sticky="w", pady=5, padx=5)
self.remote_port_entry = ttk.Entry(self.config_frame, width=20)
self.remote_port_entry.grid(row=3, column=1, padx=5, pady=5)
self.remote_port_entry.bind("<KeyRelease>", lambda e: self.update_config_description())
ttk.Label(self.config_frame, text="FRP 用户 (可选):").grid(row=3, column=2, sticky="w", pady=5, padx=5)
self.user_entry = ttk.Entry(self.config_frame, width=20)
self.user_entry.grid(row=3, column=3, padx=5, pady=5)
# 第五行:配置说明标签
self.desc_label = ttk.Label(self.config_frame, text="", foreground="gray")
self.desc_label.grid(row=4, column=0, columnspan=4, sticky="w", padx=10, pady=5)
# 按钮框架
button_frame = ttk.Frame(self.root)
button_frame.pack(fill="x", padx=20, pady=10)
# 启动按钮
self.start_button = ttk.Button(button_frame, text="▶ 启动 FRPC", command=self.start_frpc)
self.start_button.pack(side="left", padx=5)
# 停止按钮
self.stop_button = ttk.Button(button_frame, text="◼ 停止 FRPC", command=self.stop_frpc, state="disabled")
self.stop_button.pack(side="left", padx=5)
# 状态标签
self.status_label = ttk.Label(button_frame, text="状态:已停止", foreground="red")
self.status_label.pack(side="left", padx=20)
# 日志框架
log_frame = ttk.LabelFrame(self.root, text="FRPC 控制台输出", padding=10)
log_frame.pack(fill="both", expand=True, padx=20, pady=5)
# 日志文本区域
self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20, bg="black", fg="green", font=("Consolas", 9))
self.log_text.pack(fill="both", expand=True)
# 清空日志按钮
clear_button = ttk.Button(self.root, text="清空日志", command=self.clear_log)
clear_button.pack(pady=5)
def start_frpc(self):
"""启动 FRPC 客户端"""
if self.is_running:
messagebox.showwarning("警告", "FRPC 已经在运行中!")
return
# 先保存配置
self.save_config()
# 获取配置值
server_addr = self.server_addr_entry.get().strip()
server_port = self.server_port_entry.get().strip()
token = self.token_entry.get().strip()
remote_port = self.remote_port_entry.get().strip()
name = self.name_entry.get().strip()
local_ip = self.local_ip_entry.get().strip()
local_port = self.local_port_entry.get().strip()
user = self.user_entry.get().strip()
# 验证必填字段
if not server_addr or not server_port or not token or not remote_port or not name or not local_ip or not local_port:
messagebox.showerror("错误", "请填写所有必填字段!")
return
# 设置环境变量
env = os.environ.copy()
env["FRP_SERVER_ADDR"] = server_addr
env["FRP_SERVER_PORT"] = server_port
env["FRP_TOKEN"] = token
env["FRP_REMOTE_PORT"] = remote_port
env["FRP_NAME"] = name
env["LOCAL_IP"] = local_ip
env["LOCAL_PORT"] = local_port
if user:
env["FRP_USER"] = user
# 获取 frpc.exe 路径
frpc_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "frpc", "frpc.exe")
if not os.path.exists(frpc_path):
messagebox.showerror("错误", f"未找到 frpc.exe 文件:n{frpc_path}")
return
# 配置文件路径
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "frpc", "frpc.env.toml")
try:
# 启动进程
self.process = subprocess.Popen(
[frpc_path, "-c", config_path],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE,
env=env,
cwd=os.path.dirname(frpc_path),
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
)
self.is_running = True
self.start_button.config(state="disabled")
self.stop_button.config(state="normal")
self.status_label.config(text="状态:运行中", foreground="green")
self.append_log(f"=== FRPC 已启动 ===")
self.append_log(f"服务器地址:{server_addr}:{server_port}")
self.append_log(f"远程端口:{remote_port}")
self.append_log(f"代理名称:{name}")
self.append_log(f"本地地址:{local_ip}:{local_port}")
self.append_log(f"进程 ID: {self.process.pid}")
self.append_log("=" * 50)
# 重置启动状态
self.startup_status = "starting"
# 启动读取线程
self.output_thread = threading.Thread(target=self.read_output, daemon=True)
self.output_thread.start()
except Exception as e:
messagebox.showerror("错误", f"启动 FRPC 失败:n{str(e)}")
self.is_running = False
self.start_button.config(state="normal")
self.stop_button.config(state="disabled")
def stop_frpc(self):
"""停止 FRPC 客户端"""
if not self.is_running:
messagebox.showwarning("警告", "FRPC 未运行!")
return
confirm = messagebox.askyesno("确认", "确定要停止 FRPC 吗?")
if not confirm:
return
try:
if self.process:
self.process.terminate()
try:
self.process.wait(timeout=5)
except subprocess.TimeoutExpired:
self.process.kill()
self.append_log("n=== FRPC 已停止 ===")
self.is_running = False
self.process = None
except Exception as e:
self.append_log(f"停止时出错:{str(e)}")
finally:
self.is_running = False
self.start_button.config(state="normal")
self.stop_button.config(state="disabled")
self.status_label.config(text="状态:已停止", foreground="red")
def read_output(self):
"""读取 FRPC 输出并判断启动状态"""
if self.process and self.process.stdout:
for line in iter(self.process.stdout.readline, b''):
try:
decoded_line = line.decode('utf-8', errors='ignore').strip()
if decoded_line:
# 使用 after 方法在主线程中更新 UI
self.root.after(0, lambda l=decoded_line: self.append_log(l))
# 判断启动状态
self.root.after(0, lambda l=decoded_line: self.check_startup_status(l))
except Exception as e:
self.root.after(0, lambda: self.append_log(f"读取输出时出错:{str(e)}"))
def append_log(self, message):
"""添加日志消息"""
self.log_text.insert(tk.END, message + "n")
self.log_text.see(tk.END) # 自动滚动到底部
def check_startup_status(self, line):
"""检查启动状态"""
if self.startup_status == "starting":
# 检测是否启动成功
if "start proxy success" in line.lower():
self.startup_status = "success"
self.append_log("n✓ FRPC 启动成功!")
self.status_label.config(text="状态:已启动", foreground="#00aa00")
# 保存成功的配置
self.save_success_config()
# 检测是否启动失败(代理已存在)
elif "already exists" in line.lower() or "error" in line.lower():
self.startup_status = "failed"
self.append_log(f"n✗ FRPC 启动失败:代理可能已存在/网络异常/鉴权失败")
self.status_label.config(text="状态:启动失败", foreground="red")
# 延迟后自动停止进程
self.root.after(1000, self.auto_stop_on_failure)
def auto_stop_on_failure(self):
"""启动失败后自动停止进程"""
if self.startup_status == "failed" and self.is_running:
self.append_log("正在停止 FRPC 进程...")
try:
if self.process:
self.process.terminate()
try:
self.process.wait(timeout=5)
except subprocess.TimeoutExpired:
self.process.kill()
self.process = None
except Exception as e:
self.append_log(f"停止进程时出错:{str(e)}")
finally:
self.is_running = False
self.start_button.config(state="normal")
self.stop_button.config(state="disabled")
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
def update_config_description(self):
"""更新配置说明文字"""
server_addr = self.server_addr_entry.get().strip() or "{FRP 服务器地址}"
remote_port = self.remote_port_entry.get().strip() or "{FRP 远程端口}"
local_ip = self.local_ip_entry.get().strip() or "{局域网主机 IP}"
local_port = self.local_port_entry.get().strip() or "{局域网主机端口}"
desc_text = f"访问【{server_addr}:{remote_port}】> 将会转发到本机可访问的地址 > 【{local_ip}:{local_port}】"
self.desc_label.config(text=desc_text)
def auto_start_frpc(self):
"""程序启动时自动启动 FRPC"""
# 检查配置文件是否存在且有值
if not os.path.exists(self.config_file):
return
config = configparser.ConfigParser()
try:
config.read(self.config_file, encoding='utf-8')
# 检查是否有必填字段
if 'FRP' in config:
frp = config['FRP']
has_required = (
frp.get('server_addr', '').strip() and
frp.get('server_port', '').strip() and
frp.get('token', '').strip() and
frp.get('remote_port', '').strip() and
frp.get('name', '').strip() and
frp.get('local_ip', '').strip() and
frp.get('local_port', '').strip()
)
if has_required:
self.append_log("=== 自动启动 FRPC ===")
self.start_frpc()
except Exception as e:
self.append_log(f"自动启动检查失败:{str(e)}")
def load_config(self):
"""从 INI 配置文件加载配置"""
if not os.path.exists(self.config_file):
# 如果配置文件不存在,创建空配置文件
self.create_default_config()
return
config = configparser.ConfigParser()
try:
config.read(self.config_file, encoding='utf-8')
# 读取 UI 显示配置(默认不显示)
show_config = False # 默认不显示配置区域
if 'UI' in config:
ui = config['UI']
show_config_str = ui.get('show_config', '0').strip()
# 只有明确设置为 1 才显示
show_config = (show_config_str == '1')
# 根据配置显示/隐藏配置区域
if show_config:
self.config_frame.pack(fill="x", padx=20, pady=5)
else:
self.config_frame.pack_forget()
# 读取 FRP 配置
if 'FRP' in config:
frp = config['FRP']
self.server_addr_entry.insert(0, frp.get('server_addr', ''))
self.server_port_entry.insert(0, frp.get('server_port', ''))
self.token_entry.insert(0, frp.get('token', ''))
self.remote_port_entry.insert(0, frp.get('remote_port', ''))
self.name_entry.insert(0, frp.get('name', ''))
self.local_ip_entry.insert(0, frp.get('local_ip', ''))
self.local_port_entry.insert(0, frp.get('local_port', ''))
self.user_entry.insert(0, frp.get('user', ''))
# 更新配置说明
self.update_config_description()
except Exception as e:
messagebox.showerror("错误", f"读取配置文件失败:n{str(e)}")
def create_default_config(self):
"""创建默认配置文件"""
config = configparser.ConfigParser()
config['FRP'] = {
'server_addr': '',
'server_port': '',
'token': '',
'remote_port': '',
'name': '',
'local_ip': '',
'local_port': '',
'user': ''
}
config['UI'] = {
'show_config': '0' # 默认不显示配置区域
}
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
config.write(f)
messagebox.showinfo("提示", "已创建配置文件:n" + self.config_file + "nn请在配置文件中填写参数后重新启动程序。")
except Exception as e:
messagebox.showerror("错误", f"创建配置文件失败:n{str(e)}")
def save_config(self):
"""保存配置到 INI 文件"""
config = configparser.ConfigParser()
config['FRP'] = {
'server_addr': self.server_addr_entry.get().strip(),
'server_port': self.server_port_entry.get().strip(),
'token': self.token_entry.get().strip(),
'remote_port': self.remote_port_entry.get().strip(),
'name': self.name_entry.get().strip(),
'local_ip': self.local_ip_entry.get().strip(),
'local_port': self.local_port_entry.get().strip(),
'user': self.user_entry.get().strip()
}
# 保留原有的 UI 配置,不修改用户的设置
if os.path.exists(self.config_file):
old_config = configparser.ConfigParser()
try:
old_config.read(self.config_file, encoding='utf-8')
# 只复制 UI 节,不做任何修改
if 'UI' in old_config:
config['UI'] = {}
for key, value in old_config['UI'].items():
config['UI'][key] = value
except:
pass
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
config.write(f)
except Exception as e:
self.append_log(f"保存配置文件失败:{str(e)}")
def save_success_config(self):
"""保存最后一次成功启动的配置"""
config = configparser.ConfigParser()
config['FRP'] = {
'server_addr': self.server_addr_entry.get().strip(),
'server_port': self.server_port_entry.get().strip(),
'token': self.token_entry.get().strip(),
'remote_port': self.remote_port_entry.get().strip(),
'name': self.name_entry.get().strip(),
'local_ip': self.local_ip_entry.get().strip(),
'local_port': self.local_port_entry.get().strip(),
'user': self.user_entry.get().strip()
}
try:
with open(self.success_config_file, 'w', encoding='utf-8') as f:
config.write(f)
self.append_log(f"✓ 成功配置已保存到:{self.success_config_file}")
except Exception as e:
self.append_log(f"保存成功配置失败:{str(e)}")
def main():
root = tk.Tk()
app = FRPCClientGUI(root)
root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, app))
root.mainloop()
def on_closing(root, app):
"""窗口关闭时的处理"""
if app.is_running:
confirm = messagebox.askyesno("确认", "FRPC 正在运行,确定要退出程序吗?")
if confirm:
app.stop_frpc()
app.save_config() # 保存配置
root.destroy()
else:
app.save_config() # 保存配置
root.destroy()
if __name__ == "__main__":
main()
然后是配置文件frpc_manager.ini(这个是给python程序加载使用了,最终会注入环境变量后用来执行frpc程序)
[FRP]
server_addr = xxx.xxx.xxx.xxx
server_port = 7000
token = xxxxxxxxxx
remote_port = 7999
name = rdp_3389
local_ip = 127.0.0.1
local_port = 3389
user = test1
[UI]
show_config = 1
另外需要创建frpc文件夹,并放入frpc程序和配置文件frpc.env.toml(使用的是环境变量方式)
serverAddr = "{{ .Envs.FRP_SERVER_ADDR }}"
serverPort = {{ .Envs.FRP_SERVER_PORT }}
auth.token = "{{ .Envs.FRP_TOKEN }}"
user = "{{ .Envs.FRP_USER }}"
[[proxies]]
name = "{{ .Envs.FRP_NAME }}"
type = "tcp"
localIP = "{{ .Envs.LOCAL_IP }}"
localPort = {{ .Envs.LOCAL_PORT }}
remotePort = {{ .Envs.FRP_REMOTE_PORT }}

上面准备好就可以python frpc_manager_gui.py启动啦(注意导入的一些包需要手动pip install安装下)
最后再附上操作说明说书吧(不用意外,这个也是让AI帮我生成的),如果你想继续按自己的需求修改这个程序的话,让AI先去读下这个文档,然后再把你的需求告诉它,让它继续开发功能吧!
# FRP 客户端管理器 - 软件操作说明书
## 一、软件简介
FRP 客户端管理器是一款基于 Python + Tkinter 开发的图形化管理工具,用于方便地配置和管理 FRP(Fast Reverse Proxy)客户端程序。该软件通过图形界面配置参数,自动注入环境变量启动 frpc,并实时监控运行状态和日志输出。
### 主要功能
- ✅ 图形化配置 FRP 参数,无需手动编辑配置文件
- ✅ 一键启动/停止 FRPC 客户端
- ✅ 实时显示 FRPC 控制台日志
- ✅ 智能判断启动状态(成功/失败)
- ✅ 配置参数持久化保存(INI 文件)
- ✅ 程序启动时自动加载配置并启动 FRPC
- ✅ Token 密码化显示,保护敏感信息
- ✅ 动态显示端口转发规则说明
---
## 二、系统要求
- **操作系统**:Windows 7/8/10/11
- **Python 版本**:Python 3.6+
- **依赖库**:Tkinter(Python 内置)
- **FRP 客户端**:frpc.exe 及 frpc.env.toml 配置文件
---
## 三、安装与部署
### 3.1 文件结构
```
frpc_client/
├── frpc_manager_gui.py # 主程序
├── frpc_manager.ini # 配置文件(首次运行自动生成)
├── frpc_last_success.ini # 最后一次成功配置(启动成功后生成)
└── frpc/
├── frpc.exe # FRP 客户端程序
└── frpc.env.toml # FRP 配置文件模板
```
### 3.2 运行方式
直接双击运行或在命令行执行:
```bash
python frpc_manager_gui.py
```
---
## 四、界面说明
### 4.1 配置参数区域
配置区域共 5 行,包含 8 个配置项:
**第 1 行:**
- **FRP 服务器地址**:FRP 服务器的 IP 地址或域名(必填)
- **FRP 服务器端口**:FRP 服务器的监听端口(必填,默认 7000)
**第 2 行:**
- **FRP Token**:FRP 认证令牌,以星号 `*` 隐藏显示(必填)
- **FRP 代理名称**:自定义代理标识名称(必填)
**第 3 行:**
- **局域网主机 IP**:本地要映射的服务 IP(必填,如 127.0.0.1)
- **局域网主机端口**:本地要映射的服务端口(必填,如 3389)
**第 4 行:**
- **FRP 远程端口**:FRP 服务器端对外开放的端口(必填)
- **FRP 用户**:可选的用户标识(可选)
**第 5 行:**
- **配置说明**:动态显示端口转发规则
```
访问【FRP 服务器地址:FRP 远程端口】> 将会转发到本机可访问的地址 > 【局域网主机 IP:局域网主机端口】
```
### 4.2 控制按钮
- **▶ 启动 FRPC**:启动 FRP 客户端
- **◼ 停止 FRPC**:停止 FRP 客户端
- **清空日志**:清除控制台输出日志
### 4.3 状态指示器
- **状态:已停止**(红色)- FRPC 未运行
- **状态:运行中**(绿色)- FRPC 正常启动
- **状态:启动失败**(红色)- 启动失败,自动停止进程
### 4.4 日志显示区域
黑色背景、绿色文字的仿终端样式,实时显示:
- 启动配置信息
- FRPC 运行日志
- 启动成功/失败提示
- 进程 ID 等信息
---
## 五、详细操作步骤
### 5.1 首次使用
1. **启动程序**
- 双击运行 `frpc_manager_gui.py`
- 程序自动创建空配置文件 `frpc_manager.ini`
- 弹出提示框提示填写配置
2. **配置参数**
- 在配置区域填写所有必填字段
- Token 输入时显示为星号,保护隐私
- 观察底部的配置说明,确认转发规则正确
3. **启动 FRPC**
- 点击"▶ 启动 FRPC"按钮
- 程序自动保存配置到 `frpc_manager.ini`
- 日志区域显示启动过程
4. **查看状态**
- 启动成功:状态变为"已启动"(绿色),显示"✓ FRPC 启动成功!"
- 启动失败:状态变为"启动失败"(红色),自动停止进程并提示原因
### 5.2 日常使用
#### 方式一:使用已有配置
1. 打开程序(自动加载上次配置)
2. 等待 500ms 后自动启动 FRPC
3. 查看日志确认启动成功
#### 方式二:修改配置后启动
1. 打开程序
2. 修改需要调整的参数
3. 点击"▶ 启动 FRPC"
4. 新配置会自动保存到 INI 文件
#### 方式三:隐藏配置区域(简洁模式)
1. 编辑 `frpc_manager.ini`
2. 设置 `show_config = 0`
3. 重启程序,只显示按钮和日志
4. 配置通过 INI 文件预先设置
### 5.3 停止 FRPC
1. 点击"◼ 停止 FRPC"按钮
2. 确认对话框点击"是"
3. 程序安全终止 FRPC 进程
4. 状态恢复为"已停止"
### 5.4 退出程序
1. 点击窗口关闭按钮
2. 如果 FRPC 正在运行,会弹出确认框
3. 确认后自动停止 FRPC 并保存配置
4. 程序退出
---
## 六、配置文件说明
### 6.1 主配置文件(frpc_manager.ini)
```ini
[FRP]
server_addr = xxx.xxx.xxx.xxx
server_port = 7000
token = xxxxxxx
remote_port = xxx
name = rdp_3389
local_ip = 127.0.0.1
local_port = 3389
user = test1
[UI]
# 是否在主程序显示配置参数区域(1=显示,0=隐藏)- 手动修改此值
show_config = 1
```
**配置项说明:**
- `server_addr`:FRP 服务器地址(必填)
- `server_port`:FRP 服务器端口(必填)
- `token`:认证令牌(必填)
- `remote_port`:远程映射端口(必填)
- `name`:代理名称(必填)
- `local_ip`:本地服务 IP(必填)
- `local_port`:本地服务端口(必填)
- `user`:用户标识(可选)
- `show_config`:是否显示配置区域(1=显示,0=隐藏)
### 6.2 成功配置文件(frpc_last_success.ini)
- **用途**:记录最后一次成功启动的配置
- **更新时机**:仅当启动成功时更新
- **格式**:与主配置文件相同
- **特点**:永不记录失败的配置
### 6.3 FRP 配置文件(frpc/frpc.env.toml)
这是 FRP 客户端的原始配置文件,使用环境变量占位符:
```toml
serverAddr = "{{ .Envs.FRP_SERVER_ADDR }}"
serverPort = {{ .Envs.FRP_SERVER_PORT }}
auth.token = "{{ .Envs.FRP_TOKEN }}"
user = "{{ .Envs.FRP_USER }}"
[[proxies]]
name = "{{ .Envs.FRP_NAME }}"
type = "tcp"
localIP = "{{ .Envs.LOCAL_IP }}"
localPort = {{ .Envs.LOCAL_PORT }}
remotePort = {{ .Envs.FRP_REMOTE_PORT }}
```
---
## 七、高级功能
### 7.1 自动启动
程序启动时会自动检查配置文件:
- ✓ 配置文件存在
- ✓ 所有必填字段都有值
- ✓ 满足条件则自动启动 FRPC
- ✓ 延迟 500ms 执行,确保界面完全加载
### 7.2 启动状态智能判断
程序实时监控 FRPC 控制台输出:
**成功检测:**
- 检测到关键词:`start proxy success`
- 状态变更为"已启动"(绿色)
- 保存配置到 `frpc_last_success.ini`
- 日志显示:✓ FRPC 启动成功!
**失败检测:**
- 检测到关键词:`already exists` / `error`
- 状态变更为"启动失败"(红色)
- 1 秒后自动停止进程
- 日志显示:✗ FRPC 启动失败:代理可能已存在/网络异常/鉴权失败
### 7.3 配置显示/隐藏切换
**显示配置区域(适合初次配置):**
```ini
[UI]
show_config = 1
```
**隐藏配置区域(适合日常使用):**
```ini
[UI]
show_config = 0
```
**注意事项:**
- ⚠️ 此配置只能手动在 INI 文件中修改
- ⚠️ 程序不会自动更改此值
- ⚠️ 默认值为 0(不显示)
- ⚠️ 新建配置文件时默认为 0
---
## 八、常见问题
### Q1: 启动失败提示"代理已存在"
**原因**:FRP 服务器上该代理名称已被占用
**解决**:
1. 修改"FRP 代理名称"为其他名称
2. 或在 FRP 服务器端删除旧配置
### Q2: 启动失败提示"网络异常"或"鉴权失败"
**原因**:服务器地址、端口或 Token 错误
**解决**:
1. 检查 FRP 服务器地址和端口是否正确
2. 检查 Token 是否与服务器配置一致
3. 确认 FRP 服务器正常运行
### Q3: 配置区域不显示
**原因**:`show_config` 设置为 0
**解决**:
1. 用文本编辑器打开 `frpc_manager.ini`
2. 设置 `show_config = 1`
3. 重启程序
### Q4: 如何修改已保存的配置?
**方法一**:直接在界面修改后点击启动
**方法二**:用文本编辑器打开 `frpc_manager.ini` 直接修改
### Q5: 在哪里查看 FRPC 的详细日志?
- 程序界面的日志区域会实时显示 FRPC 输出
- 支持自动滚动和清空功能
- 黑色背景绿色文字,仿终端样式
---
## 九、环境变量映射表
| 界面配置项 | 环境变量名 | TOML 配置对应 |
|-----------|-----------|--------------|
| FRP 服务器地址 | `FRP_SERVER_ADDR` | `serverAddr` |
| FRP 服务器端口 | `FRP_SERVER_PORT` | `serverPort` |
| FRP Token | `FRP_TOKEN` | `auth.token` |
| FRP 代理名称 | `FRP_NAME` | `proxies.name` |
| 局域网主机 IP | `LOCAL_IP` | `localIP` |
| 局域网主机端口 | `LOCAL_PORT` | `localPort` |
| FRP 远程端口 | `FRP_REMOTE_PORT` | `remotePort` |
| FRP 用户 | `FRP_USER` | `user` |
---
## 十、技术规格
### 10.1 开发技术
- **编程语言**:Python 3
- **GUI 框架**:Tkinter
- **配置文件**:INI 格式(configparser 模块)
- **进程管理**:subprocess 模块
### 10.2 核心特性
- ✅ 多线程读取 FRPC 输出,避免界面卡顿
- ✅ 使用 `root.after()` 安全更新 UI
- ✅ 进程终止超时处理(5 秒后强制结束)
- ✅ 配置文件 UTF-8 编码,支持中文注释
- ✅ 异常捕获和用户友好的错误提示
### 10.3 安全特性
- Token 密码化显示(星号遮挡)
- 配置文件不主动修改 UI 开关设置
- 窗口关闭前确认和配置保存
- 启动失败自动清理进程
---
## 十一、更新日志
### v1.0(初始版本)
- 基础 GUI 界面和 FRPC 启停功能
- 环境变量注入机制
- 实时日志显示
### v1.1(配置优化)
- 增加 INI 配置文件支持
- 配置持久化和自动加载
- 程序启动自动触发启动
### v1.2(状态监控)
- 智能判断启动状态
- 启动失败自动停止
- 成功配置备份功能
### v1.3(界面优化)
- 紧凑布局(每行 2 个配置项)
- 配置区域显示/隐藏开关
- Token 密码化显示
- 动态配置说明
### v1.4(当前版本)
- 新增 FRP 服务器端口配置
- 优化配置参数排列顺序
- 增强配置说明文字
---
## 十二、联系与支持
如有问题或建议,请参考:
1. 本操作说明书
2. FRP 官方文档
3. 查看程序日志中的错误信息
---
**最后更新**:2026 年 3 月 12 日
**软件版本**:v1.4
**适用系统**:Windows
发表评论