Paramiko connect()挂起问题求助——EC2文件上传场景
使用paramiko向远程EC2服务器上传文件,项目结构为:tkinter GUI的app.py导入自定义lib.file_handler模块,该模块又导入lib.ssh_handler.py,其中ssh_Handler类处理SSH请求。在ssh_handler.py的if __name__ == '__main__':中测试类功能正常,但在项目中导入使用时,paramiko因模块导入阶段生成额外线程而挂起。日志提示不要在导入时调用connect(),但未直接在导入时调用,而是在file_handler中初始化类后使用。需求是仅在上传文件时建立连接,不保持长连接,且不想在app.py中直接调用paramiko.connect()。曾参考2012年的Issue 104,但其中涉及的py3compat已从paramiko中移除。
相关代码
file_handler.py
from lib.ssh_handler import ssh_Handler ssh = ssh_Handler() ssh = ssh.upload_files()
ssh_handler.py
import paramiko import lib.Secrets as Secrets class ssh_Handler(): """Handle interactions with a remote server""" def __init__(self, hostname=Secrets.hostname, user=Secrets.ssh_user, ssh_key=Secrets.ssh_key): self.ssh = paramiko.SSHClient() self.ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts"))) self.ssh.connect(hostname=hostname, port=22, username=user, key_filename=ssh_key, allow_agent=False) self.storage_folder = '/path/to/storage'
日志信息
03-18 14:02 paramiko.transport DEBUG starting thread (client mode): 0xe42de650 03-18 14:02 paramiko.transport DEBUG Local version/idstring: SSH-2.0-paramiko_3.4.0 03-18 14:02 paramiko.transport DEBUG Remote version/idstring: SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6 03-18 14:02 paramiko.transport INFO Connected (version 2.0, client OpenSSH_8.9p1)
核心问题在于:ssh_handler.py的ssh_Handler类在__init__方法中直接调用了paramiko.connect(),而file_handler.py在模块级别执行了ssh = ssh_Handler()。当app.py导入file_handler时,模块代码会立即执行这个初始化操作,导致在模块导入阶段就建立SSH连接并启动paramiko线程,这与tkinter GUI的主线程冲突,进而引发挂起。
1. 重构ssh_Handler类,分离连接逻辑
将connect()从__init__中移出,改为单独的方法,仅在需要上传时才建立连接,用完立即关闭:
import paramiko import os import lib.Secrets as Secrets class ssh_Handler(): """Handle interactions with a remote server""" def __init__(self, hostname=Secrets.hostname, user=Secrets.ssh_user, ssh_key=Secrets.ssh_key): self.hostname = hostname self.user = user self.ssh_key = ssh_key self.ssh = None self.storage_folder = '/path/to/storage' def connect(self): # 仅在未连接时建立连接 if not self.ssh: self.ssh = paramiko.SSHClient() self.ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts"))) self.ssh.connect(hostname=self.hostname, port=22, username=self.user, key_filename=self.ssh_key, allow_agent=False) def upload_files(self, local_files): # 上传前先连接,上传完成后关闭 self.connect() sftp = self.ssh.open_sftp() try: for local_file in local_files: remote_path = os.path.join(self.storage_folder, os.path.basename(local_file)) sftp.put(local_file, remote_path) finally: # 确保SFTP和SSH连接都关闭 sftp.close() self.disconnect() def disconnect(self): if self.ssh: self.ssh.close() self.ssh = None
2. 修改file_handler.py,避免模块级别初始化
将初始化和上传逻辑封装到函数中,仅在触发上传动作时才执行:
from lib.ssh_handler import ssh_Handler def upload_files_to_server(local_files): ssh = ssh_Handler() ssh.upload_files(local_files)
3. 在app.py中触发上传
在tkinter的按钮点击事件等用户交互逻辑中,调用file_handler.upload_files_to_server(),确保连接操作仅在用户触发时执行,而非模块导入阶段:
import tkinter as tk from tkinter import filedialog import lib.file_handler as file_handler def on_upload_click(): local_files = filedialog.askopenfilenames() if local_files: file_handler.upload_files_to_server(local_files) # GUI示例 root = tk.Tk() upload_btn = tk.Button(root, text="上传文件", command=on_upload_click) upload_btn.pack() root.mainloop()
如果仍遇到paramiko的线程兼容问题,可考虑以下工具:
- Fabric:基于paramiko封装,提供更简洁的API,专注于SSH任务自动化,文件上传逻辑更直观。
- scp:轻量级SCP库,依赖paramiko,API仅聚焦文件传输,减少额外线程开销。
- asyncssh:异步SSH实现,适合结合异步GUI框架使用,避免阻塞主线程,解决线程冲突问题。
内容的提问来源于stack exchange,提问作者Ryan

