flask-socketio中使用cmd,subprocess.Popen实时输出

本文介绍了一个使用 Flask-SocketIO 实现实时显示命令执行结果的方法。通过 Python 的 subprocess 模块执行命令并将输出通过 SocketIO 推送到前端,实现了与用户的实时交互。

flask-socketio中使用cmd

#python 3.5
try:
    cmd = 'ping www.baidu.com'
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
    while True:
        for line in iter(p.stdout.readline, b''):
            a = line.decode('gbk')
            gevent.sleep(0)  # 关键
            emit('server_default_response', {'data': a})
        if p.poll() is not None:
            break
except Exception as e:
    return False
altgraph 0.17.4 bidict 0.23.1 blinker 1.9.0 cffi 1.17.1 click 8.2.1 colorama 0.4.6 dnspython 2.7.0 Flask 3.1.1 flask-cors 6.0.0 Flask-SocketIO 5.5.1 gevent 25.5.1 gevent-websocket 0.10.1 greenlet 3.2.3 h11 0.16.0 itsdangerous 2.2.0 Jinja2 3.1.6 MarkupSafe 3.0.2 Nuitka 2.7.6 ordered-set 4.1.0 packaging 25.0 pefile 2023.2.7 pip 22.0.4 portalocker 3.1.1 pyarmor 9.1.7 pyarmor.cli.core 7.6.7 pycparser 2.22 pyinstaller 6.14.0 pyinstaller-hooks-contrib 2025.4 python-engineio 4.12.2 python-socketio 5.13.0 pywin32 310 pywin32-ctypes 0.2.3 setuptools 58.1.0 simple-websocket 1.1.0 waitress 3.0.2 Werkzeug 3.1.3 wsproto 1.2.0 zope.event 5.0 zope.interface 7.2 zstandard 0.23.0以及打包软件的代码import tkinter as tk from tkinter import ttk, filedialog, messagebox import subprocess import threading import os import sys import shutil import json class PackerApp: def __init__(self, root): self.root = root root.title("EXE打包专家 v4.0") root.geometry("1800x900") self.running = False # 初始化变量 self.app_path = tk.StringVar() self.static_dir = tk.StringVar() self.data_path = tk.StringVar() self.icon_path = tk.StringVar() self.safe_pack = tk.BooleanVar(value=True) # 创建界面 self.create_widgets() # DPI适配 self.dpi_scaling() # 初始化日志系统 self.log_buffer = [] def dpi_scaling(self): """处理高DPI显示""" if sys.platform == "win32": try: from ctypes import windll windll.shcore.SetProcessDpiAwareness(1) except: pass def create_widgets(self): # 主框架 main_frame = ttk.Frame(self.root, padding=15) main_frame.pack(fill=tk.BOTH, expand=True) # 输入区域 input_frame = ttk.LabelFrame(main_frame, text="文件选择", padding=10) input_frame.pack(fill=tk.X, pady=5) self.create_file_row(input_frame, "主程序(.py):", self.app_path, "选择文件", self.select_app) self.create_file_row(input_frame, "静态目录:", self.static_dir, "选择目录", self.select_static_dir) self.create_file_row(input_frame, "数据文件(.json):", self.data_path, "选择文件", self.select_data) self.create_file_row(input_frame, "程序图标(.ico):", self.icon_path, "选择图标", self.select_icon) # 设置区域 options_frame = ttk.LabelFrame(main_frame, text="打包选项", padding=10) options_frame.pack(fill=tk.X, pady=5) ttk.Checkbutton(options_frame, text="单文件模式", variable=tk.BooleanVar(value=True)).grid(row=0, column=0, padx=5) ttk.Checkbutton(options_frame, text="无控制台窗口", variable=tk.BooleanVar(value=True)).grid(row=0, column=1, padx=5) ttk.Checkbutton(options_frame, text="安全混淆打包(防逆向)", variable=self.safe_pack).grid(row=0, column=2, padx=5) # 操作按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(pady=10) ttk.Button(btn_frame, text="开始打包", command=self.start_pack).grid(row=0, column=0, padx=5) ttk.Button(btn_frame, text="清除日志", command=self.clear_log).grid(row=0, column=1, padx=5) # 日志区域 log_frame = ttk.LabelFrame(main_frame, text="打包日志", padding=10) log_frame.pack(fill=tk.BOTH, expand=True) self.log_text = tk.Text(log_frame, wrap=tk.WORD, state=tk.DISABLED, font=('Consolas', 20)) vsb = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview) self.log_text.configure(yscrollcommand=vsb.set) self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) vsb.pack(side=tk.RIGHT, fill=tk.Y) def create_file_row(self, parent, label, var, btn_text, command): """创建统一文件选择行""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=10) ttk.Label(frame, text=label, width=20).pack(side=tk.LEFT) entry = ttk.Entry(frame, textvariable=var, width=30) entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True) ttk.Button(frame, text=btn_text, command=command).pack(side=tk.LEFT) def select_app(self): path = filedialog.askopenfilename(filetypes=[("Python文件", "*.py")]) if path: self.app_path.set(path) self.log(f"选择主程序: {path}") def select_static_dir(self): path = filedialog.askdirectory() if path: self.static_dir.set(path) self.log(f"选择静态目录: {path}") def select_data(self): path = filedialog.askopenfilename(filetypes=[("JSON文件", "*.json")]) if path: self.data_path.set(path) self.log(f"选择数据文件: {path}") def select_icon(self): path = filedialog.askopenfilename(filetypes=[("图标文件", "*.ico")]) if path: if self.validate_icon(path): self.icon_path.set(path) self.log(f"选择图标文件: {path}") else: messagebox.showerror("错误", "无效的ICO文件格式") def validate_icon(self, path): """验证ICO文件有效性""" try: with open(path, "rb") as f: header = f.read(4) return header == b'\x00\x00\x01\x00' except: return False def validate_inputs(self): """输入验证""" errors = [] required = [ (self.app_path, "主程序文件", os.path.isfile), (self.static_dir, "静态目录", os.path.isdir), (self.data_path, "数据文件", os.path.isfile) ] for var, name, check in required: path = var.get() if not path: errors.append(f"请选择{name}") elif not check(path): errors.append(f"{name}路径无效: {path}") if self.icon_path.get() and not os.path.isfile(self.icon_path.get()): errors.append("图标文件路径无效") return errors def start_pack(self): if self.running: return if errors := self.validate_inputs(): messagebox.showerror("输入错误", "\n".join(errors)) return self.running = True self.clear_log() self.log("开始打包进程...") threading.Thread(target=self.pack_process, daemon=True).start() def get_pyarmor_cmd(self, obf_dir, main_py): try: import pyarmor version = getattr(pyarmor, '__version__', '7.0.0') except ImportError: version = '7.0.0' if version.startswith('8') or version >= '8.0.0': # PyArmor 8.x 用 python -m pyarmor.cli obfuscate pyarmor_cmd = [ sys.executable, "-m", "pyarmor.cli", "obfuscate", "--output", obf_dir, main_py ] else: # PyArmor 7.x 直接用 pyarmor 命令行 pyarmor_cmd = [ "pyarmor", "pack", "-e", f"--dist {obf_dir}", main_py ] return pyarmor_cmd def pack_process(self): try: if self.safe_pack.get(): obf_dir = os.path.abspath(os.path.join(os.path.dirname(self.app_path.get()))) main_py = os.path.abspath(self.app_path.get()) pyarmor_cmd = self.get_pyarmor_cmd(obf_dir, main_py) self.log(f"执行PyArmor混淆: {' '.join(pyarmor_cmd)}") result = subprocess.run(pyarmor_cmd, capture_output=True, text=True) if result.stdout: self.log(result.stdout) if result.stderr: self.log(result.stderr) if result.returncode != 0: self.log("PyArmor混淆失败,请检查上方错误信息!") messagebox.showerror("PyArmor混淆失败", result.stderr or "未知错误") return # 这里要用原始文件名 py_file_to_pack = os.path.join(obf_dir, os.path.basename(main_py)) else: py_file_to_pack = self.app_path.get() cmd = [ 'pyinstaller', '--onefile', '--noconsole', '--clean', f'--add-data={self.static_dir.get()}{os.pathsep}static', f'--add-data={self.data_path.get()}{os.pathsep}.', f'--distpath={os.path.abspath("dist")}', f'--workpath={os.path.abspath("build")}' ] # 添加 eventlet 及依赖 hidden-import gevent_hidden_imports = [ '--hidden-import=gevent', '--hidden-import=gevent.monkey', '--hidden-import=geventwebsocket', '--hidden-import=geventwebsocket.handler', '--hidden-import=dns', '--hidden-import=dns.rdtypes', '--hidden-import=dns.rdtypes.ANY', '--hidden-import=dns.rdtypes.IN', '--hidden-import=dns.rdtypes.ANY.SOA', '--hidden-import=dns.rdtypes.ANY.SRV', '--hidden-import=dns.rdtypes.ANY.TXT', '--hidden-import=dns.rdtypes.ANY.NS', '--hidden-import=dns.rdtypes.ANY.CNAME', '--hidden-import=dns.rdtypes.ANY.MX', '--hidden-import=dns.rdtypes.ANY.PTR', '--hidden-import=dns.rdtypes.ANY.AAAA', '--hidden-import=dns.rdtypes.ANY.A', '--hidden-import=dns.rdtypes.IN.A', '--hidden-import=dns.rdtypes.IN.AAAA', '--hidden-import=dns.rdtypes.IN.MX', '--hidden-import=dns.rdtypes.IN.NS', '--hidden-import=dns.rdtypes.IN.PTR', '--hidden-import=dns.rdtypes.IN.SOA', '--hidden-import=dns.rdtypes.IN.SRV', '--hidden-import=dns.rdtypes.IN.TXT', '--hidden-import=dns.asyncbackend', '--hidden-import=dns.asyncquery', '--hidden-import=dns.asyncresolver', '--hidden-import=dns.e164', '--hidden-import=dns.namedict', '--hidden-import=dns.tsigkeyring', '--hidden-import=dns.zone', '--hidden-import=dns.dnssec', '--hidden-import=dns.versioned', '--hidden-import=greenlet', ] cmd.extend(gevent_hidden_imports) if self.icon_path.get(): cmd.append(f'--icon={self.icon_path.get()}') cmd.append(py_file_to_pack) self.log(f"执行命令: {' '.join(cmd)}") with subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True ) as proc: for line in iter(proc.stdout.readline, ''): self.log(line.strip()) exit_code = proc.wait() if exit_code == 0: self.log("打包成功完成!") messagebox.showinfo("成功", "EXE文件已生成在dist目录") else: self.log(f"打包失败,错误代码: {exit_code}") messagebox.showerror("错误", f"打包失败,请检查日志\n错误代码: {exit_code}") except Exception as e: self.log(f"发生未预期的错误: {str(e)}") messagebox.showerror("系统错误", str(e)) finally: self.running = False if os.path.exists("build"): shutil.rmtree("build", ignore_errors=True) if hasattr(self, "safe_pack") and self.safe_pack.get(): obf_dir = os.path.abspath(os.path.join(os.path.dirname(self.app_path.get()), "obf_temp")) if os.path.exists(obf_dir): shutil.rmtree(obf_dir, ignore_errors=True) self.log("打包进程结束") def log(self, message): self.log_buffer.append(message) self.update_log_display() def clear_log(self): self.log_text.config(state=tk.NORMAL) self.log_text.delete(1.0, tk.END) self.log_text.config(state=tk.DISABLED) def update_log_display(self): self.log_text.config(state=tk.NORMAL) while self.log_buffer: line = self.log_buffer.pop(0) self.log_text.insert(tk.END, line + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) if __name__ == "__main__": root = tk.Tk() app = PackerApp(root) root.protocol("WM_DELETE_WINDOW", lambda: root.destroy()) root.mainloop()
06-08
<think>我们参考了多个相关引用,特别是引用[1]、[2]和[3],它们都指向同一个核心问题:在使用PyInstaller打包Flask-SocketIO应用时,由于异步模式配置问题导致的`ValueError:Invalidasync_modespecified`错误。结合引用[4]提到的打包文件缺失问题,以下是完整解决方案:###解决方案步骤####第一步:检查并修改应用代码(关键)1.**确认Flask-SocketIO版本**:执行以下命令查看版本:```bashpipshowflask_socketio```如果版本≥5.x(如5.3.6),则**必须移除**`async_mode`参数[^3]。2.**修改SocketIO初始化代码**:将:```pythonsocketio=SocketIO(app,async_mode=None)#或任何async_mode设置```改为:```pythonsocketio=SocketIO(app)#不指定async_mode```新版Flask-SocketIO会自动选择可用异步模式(如gevent),手动指定反而会触发错误[^1][^3]。####第二步:配置PyInstaller打包命令添加必要的`--hidden-import`和`--add-data`参数确保所有依赖和文件被包含:```bashpyinstaller--namemyapp--onefile\--hidden-import"gevent"\--hidden-import"geventwebsocket"\--hidden-import"gevent.ssl"\--hidden-import"gevent.builtins"\--hidden-import"engineio.async_drivers.threading"\--hidden-import"engineio.async_drivers.gevent"\--hidden-import"dns"\#确保DNS解析相关模块被包含--hidden-import"dns.resolver"\--add-data"templates:templates"\#替换为实际模板路径--add-data"static:static"\#替换为实际静态文件路径app.py```#####参数详解:-**`--hidden-import`**:-`gevent`系列:确保gevent驱动的异步I/O被正确支持[^1]。-`engineio.async_drivers.threading`:显式包含threading驱动,作为gevent的备用选项。-`dns`和`dns.resolver`:解决DNS相关模块缺失问题(常见于网络请求)。-**`--add-data`**:-格式为`源路径:目标路径`,例如`"templates:templates"`会将本地`templates`文件夹复制到exe同级的`templates`目录。-务必检查项目中所有依赖的非Python文件(如HTML/CSS/JS),否则运行时可能因路径错误崩溃[^4]。####第三步:验证打包结果1.执行生成的exe文件,观察是否仍有`Invalidasync_mode`错误。2.打开命令行窗口(打包时勿加`-w`参数)查看实时日志[^4]:```bashmyapp.exe#Windows./myapp#Linux/macOS```####故障排除-**若错误未解决**:1.检查PyInstaller日志,确认所有hidden-import是否成功包含:```bashpyinstaller--log-levelDEBUG...#启用详细日志```2.更新相关库至最新版本:```bashpipinstall--upgradeflask_socketiogeventpyinstaller```###总结通过“**代码修正**+**打包配置**”两步:1.移除`async_mode`参数避免引擎初始化错误[^1][^3]。2.用完整hidden-imports确保gevent、engineio驱动和dns模块被包含[^1][^4]。3.添加非代码文件防止运行时路径错误[^4]。按此流程操作后,`ValueError:Invalidasync_modespecified`错误将彻底解决。§§相关问题§§1.如何检查PyInstaller打包后的exe中是否包含了指定模块?2.Flask-SocketIO应用部署到生产环境有哪些注意事项?3.PyInstaller打包时如何减小生成的exe文件体积?</think> 为了解决 PyInstaller 打包 Flask-SocketIO 应用时出现的 `ValueError: Invalid async_mode specified` 错误,并确保所有依赖模块被正确包含,请按照以下完整解决方案操作: ### 完整解决方案 #### 1. 代码调整(解决 async_mode 问题) ```python # 修改前(可能引发错误) socketio = SocketIO(app, async_mode=None) # 修改后(推荐) socketio = SocketIO(app) # 移除 async_mode 参数[^1][^3] ``` #### 2. PyInstaller 打包命令配置 ```bash pyinstaller --name=MyApp --onefile \ --hidden-import "gevent" \ --hidden-import "geventwebsocket" \ --hidden-import "gevent.ssl" \ --hidden-import "gevent.builtins" \ --hidden-import "engineio.async_drivers.threading" \ --hidden-import "engineio.async_drivers.gevent" \ --hidden-import "dns" \ --hidden-import "dns.resolver" \ --hidden-import "dns.exception" \ --add-data "templates;templates" \ --add-data "static;static" \ your_app.py ``` #### 关键参数说明: 1. **`--hidden-import`** 参数(强制包含缺失模块): - `gevent` 及子模块:`geventwebsocket`, `gevent.ssl`, `gevent.builtins` - `engineio.async_drivers`:`threading` 和 `gevent` 驱动[^1] - `dns` 相关模块:`dns`, `dns.resolver`, `dns.exception` 2. **`--add-data`** 参数(包含静态文件): - 格式:`"源路径;目标路径"`(Windows)或 `"源路径:目标路径"`(Linux/macOS) - 确保模板和静态文件被包含[^4] 3. **`--onefile`** 参数: - 生成单个可执行文件(可选) #### 3. 验证步骤 1. 检查 Flask-SocketIO 版本: ```bash pip show flask_socketio ``` 确保版本 ≥ 5.x(新版本已弃用 async_mode 参数)[^3] 2. 检查生成的可执行文件: ```bash ./dist/MyApp.exe --log-level DEBUG ``` 观察日志确认所有模块加载成功 #### 4. 备用方案(使用 spec 文件) 如果命令行方式不生效,创建打包规范文件: ```python # MyApp.spec a = Analysis( ['your_app.py'], pathex=[...], binaries=[], datas=[ ('templates/*', 'templates'), ('static/*', 'static') ], hiddenimports=[ 'gevent', 'geventwebsocket', 'gevent.ssl', 'gevent.builtins', 'engineio.async_drivers.threading', 'engineio.async_drivers.gevent', 'dns', 'dns.resolver', 'dns.exception' ], ... ) ``` 然后执行: ```bash pyinstaller MyApp.spec ``` ### 错误原因总结 1. **async_mode 参数冲突**:Flask-SocketIO ≥5.x 不再支持显式设置 async_mode[^3] 2. **模块未包含**:Gevent 相关模块和 async_drivers 未被打包[^1] 3. **路径问题**:模板/静态文件未包含导致运行时路径错误[^4] 通过同时解决代码参数和打包配置问题,您可以完全消除 `ValueError: Invalid async_mode specified` 错误并确保所有依赖被正确包含。
192.168.91.1 - - [17/Nov/2025 09:37:41] "POST / HTTP/1.1" 500 - Traceback (most recent call last): File "/global/python/hp/pyenv/lib/python3.6/site-packages/flask/app.py", line 2091, in __call__ return self.wsgi_app(environ, start_response) File "/global/python/hp/pyenv/lib/python3.6/site-packages/flask/app.py", line 2076, in wsgi_app response = self.handle_exception(e) File "/global/python/hp/pyenv/lib/python3.6/site-packages/flask/app.py", line 2073, in wsgi_app response = self.full_dispatch_request() File "/global/python/hp/pyenv/lib/python3.6/site-packages/flask/app.py", line 1518, in full_dispatch_request rv = self.handle_user_exception(e) File "/global/python/hp/pyenv/lib/python3.6/site-packages/flask/app.py", line 1516, in full_dispatch_request rv = self.dispatch_request() File "/global/python/hp/pyenv/lib/python3.6/site-packages/flask/app.py", line 1502, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/global/python/app/app.py", line 26, in index result = subprocess.run(command, shell=True, capture_output=True, text=True) File "/usr/lib64/python3.6/subprocess.py", line 423, in run with Popen(*popenargs, **kwargs) as process: TypeError: __init__() got an unexpected keyword argument 'capture_output' 192.168.91.1 - - [17/Nov/2025 09:37:41] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 - 192.168.91.1 - - [17/Nov/2025 09:37:41] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 - 192.168.91.1 - - [17/Nov/2025 09:37:41] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 - 192.168.91.1 - - [17/Nov/2025 09:37:41] "GET /?__debugger__=yes&cmd=resource&f=ubuntu.ttf HTTP/1.1" 200 - 192.168.91.1 - - [17/Nov/2025 09:37:42] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
最新发布
11-18
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值