使用Paramiko/sshd-mina的SFTP上传远程服务器失败,重试却成功?
SFTP上传大小不匹配问题排查
问题背景
以下是用于SFTP文件上传的代码:
# 检查文件大小 def check_file_size(path): try: size = os.path.getsize(path) print(f"本地文件大小: {size} 字节") return size except OSError as e: print(f"获取{path}大小失败: {e}") return None # 创建SFTP会话 def create_sftp_session(hostname, port, username, password): transport = paramiko.Transport((hostname, port)) transport.connect(username=username, password=password) sftp = paramiko.SFTPClient.from_transport(transport) return sftp, transport # 上传文件到远程服务器,存在则覆盖 def upload_file(sftp, local_path, remote_path, retries=3): try: # 创建临时文件并复制内容 with tempfile.NamedTemporaryFile(delete=False) as temp_file: with open(local_path, 'r') as file: temp_file.write(file.read().encode()) temp_file_path = temp_file.name local_size = os.path.getsize(temp_file_path) sftp.put(temp_file_path, remote_path) remote_size = sftp.stat(remote_path).st_size print(f"上传后远程文件大小: {remote_size} 字节") if local_size != remote_size: print(f"警告: 上传后大小不匹配!") # 清理临时文件 os.remove(temp_file_path) except IOError as e: print(f"上传{local_path}到{remote_path}失败: {e}") # 删除本地文件 def delete_local_file(path): try: os.remove(path) print(f"本地文件{path}已删除。") except OSError as e: print(f"删除{path}失败: {e}") # 主执行逻辑 def main(): # 创建本地文件 create_local_file(local_file_path) # 建立SFTP会话并上传文件 sftp, transport = create_sftp_session(hostname, port, username, password) try: # 上传并覆盖文件 upload_file(sftp, local_file_path, remote_file_path) finally: sftp.close() transport.close() # 删除本地文件 delete_local_file(local_file_path) if __name__ == "__main__": main()
针对远程服务器运行时,代码执行失败,报错:
Failed to upload localfile.txt to /dir/removefile.csv: size mismatch in put! 94 != 141
但本地Docker搭建的SFTP服务器、sshd-mina环境下均可正常运行,请问远程服务器为何无法正常工作?
另外,将upload_file函数修改为带重试逻辑的版本后:
# 上传文件到远程服务器,存在则覆盖 def upload_file(sftp, local_path, remote_path, retries=3): for attempt in range(retries): try: # 创建临时文件并复制内容 with tempfile.NamedTemporaryFile(delete=False) as temp_file: with open(local_path, 'r') as file: temp_file.write(file.read().encode()) temp_file_path = temp_file.name local_size = os.path.getsize(temp_file_path) sftp.put(temp_file_path, remote_path) remote_size = sftp.stat(remote_path).st_size print(f"上传后远程文件大小: {remote_size} 字节") if local_size != remote_size: print(f"警告: 上传后大小不匹配!") # 清理临时文件 os.remove(temp_file_path) break # 上传成功则退出循环 except IOError as e: print(f"第{attempt + 1}次上传{local_path}到{remote_path}失败: {e}") if attempt < retries - 1: print("重试中...") time.sleep(5) # 等待后重试
第二次尝试即可成功执行,日志如下:
Local file localfile.txt created with content. Attempt 1 failed to
upload localfile.txt to /flex-selections/Portfolio.csv: size mismatch
in put! 94 != 141 Retrying... Remote file size after upload: 141
bytes Local file localfile.txt deleted.
请问这一现象为何会发生?
问题分析与解答
1. 远程服务器首次上传失败的原因
- 网络传输不完整:远程网络存在延迟或丢包,
sftp.put返回成功信号时,服务器并未完全接收所有数据,此时调用sftp.stat读取的是未写完的文件大小,和本地文件不一致。 - 文件系统写缓存延迟:部分远程服务器的文件系统会用内存缓存暂存写入数据,并未立即刷到磁盘。这时
stat读取的是缓存中的部分数据大小,不是最终完整文件的大小。 - SFTP服务器额外处理延迟:企业级SFTP服务器可能对上传文件做病毒扫描、权限校验等额外处理,导致文件写入磁盘的过程被延迟,首次读取大小还未完成全量写入。
- 旧文件残留数据:如果远程路径原本有同名文件且更大,覆盖时服务器文件系统未及时截断旧文件,
stat返回的是旧文件的残留大小。
2. 重试后成功的原因
- 等待时间让服务器完成写入:重试逻辑中的
time.sleep(5)给了服务器足够时间,将缓存数据刷到磁盘,或是完成文件后续的扫描、旧文件截断等处理。 - 第二次传输的完整性:重试时网络环境可能恢复稳定,数据完整传输;同时服务器已完成旧文件清理,新文件能完整写入并刷新到磁盘,此时
stat读取的是正确的文件大小。
内容的提问来源于stack exchange,提问作者SGiux
相关产品推荐
相关产品推荐

