FTP上传下载实战:从协议原理到lftp/curl脚本避坑指南

发布时间:2026/10/21 2:47:14
FTP上传下载实战:从协议原理到lftp/curl脚本避坑指南 简介这份资源面向需要在Java项目中实现FTP文件传输的开发者尤其是刚接触网络编程、希望快速跑通上传下载流程的初中级程序员。包内共2个文件包含1个java源码与1个jar依赖包压缩包约106KB体积轻量便于直接导入工程参考。源码围绕Apache Commons Net库展开覆盖FTPClient连接与登录、主动与被动模式切换、二进制传输模式设置、retrieveFile下载与storeFile上传、目录切换与列表获取、断开连接及FTPException、IOException异常处理等关键环节并附有可直接运行的示例代码帮助读者理解各API的调用顺序与参数含义。目前已有251人学习下载适合作为FTP功能模块的入门模板也可在此基础上扩展超时设置、重试机制与SSL/TLS安全传输等进阶配置。1. 从一台老服务器说起FTP 上传下载到底还能不能打上周帮朋友处理一个遗留系统对方甩过来一台 Windows Server 2008 的机器上面跑着一个内部文件交换服务协议是 FTP。他问我“这年头还有人用 FTP 吗我是不是该换成对象存储”我没直接回答而是先连上去跑了一遍上传下载确认带宽、权限、被动模式端口都正常然后告诉他能用而且在这个场景下比重新搭一套 HTTP 接口快得多。FTP 从 1971 年活到现在靠的不是先进而是简单直接——客户端连上服务端登录、切目录、传文件四步完事。它适合内网文件交换、设备日志回传、老旧系统对接以及那些不想引入额外依赖的批量传输任务。如果你手头正好有一台 FTP 服务器要对接或者想自己搭一个来传文件这篇笔记把上传下载的完整链路、参数配置和踩坑点都拆开讲一遍。2. FTP 协议核心机制与连接模式选型2.1 控制连接与数据连接两条 TCP 各干各的FTP 跟 HTTP 最大的不同在于它用两条 TCP 连接。一条叫控制连接默认走 21 端口负责发命令、收响应码比如USER、PASS、CWD、RETR、STOR都走这条线。另一条叫数据连接专门传文件内容或目录列表端口不固定取决于主动还是被动模式。控制连接在整个会话期间一直保持数据连接每传一次文件就新建一次传完就关。这个设计带来的直接后果是防火墙只开 21 端口往往不够数据连接那一条如果被挡住就会出现“登录成功但列目录卡死”的经典现象。我一般会在客户端先开debug或verbose模式看它卡在LIST还是PASV那一步基本就能定位是哪条连接出了问题。2.2 主动模式与被动模式选错了就是连不上主动模式PORT的逻辑是客户端告诉服务端“你来连我”服务端从自己的 20 端口主动连客户端指定的一个高位端口。问题在于客户端如果在内网或防火墙后面服务端根本连不进来。被动模式PASV反过来客户端问服务端“你开哪个端口等我”服务端返回一个 IP 和端口客户端主动去连。现在绝大多数场景默认走被动模式因为客户端通常在内网服务端在公网或 DMZ。但被动模式也有坑服务端返回的 IP 如果是内网地址客户端拿到后连不上需要在服务端配置里强制指定公网 IP 或端口范围。我一般会在服务端固定一个被动端口区间比如 50000-50100然后在防火墙放行这个区间客户端侧不用改任何东西。2.3 明文传输与 FTPS/SFTP 的边界FTP 本身是明文协议账号密码和文件内容在网络上裸奔。如果传输链路经过不可信网络必须上 FTPSFTP over TLS或 SFTPSSH File Transfer Protocol。注意这两个不是一回事FTPS 是在 FTP 基础上加 TLS 层端口还是 21 和被动端口SFTP 是 SSH 协议的一个子系统通常走 22 端口跟 FTP 命令完全不兼容。选型上如果服务端已经跑着 SSH直接用 SFTP 最省事如果服务端是纯 FTP 服务且不想动架构就开 FTPS。我一般优先推 SFTP因为配置简单、穿透防火墙容易而且客户端工具支持度也高。2.4 客户端工具选型命令行还是图形化命令行工具里ftp是最原始的功能少、不支持被动模式切换有些系统可以passive命令lftp功能强很多支持多线程、断点续传、镜像目录curl适合脚本里单次上传下载。图形化工具里FileZilla 跨平台、支持 FTPS/SFTP、有站点管理器WinSCP 在 Windows 上跟资源管理器集成好。我一般写脚本用lftp或curl手动操作就用 FileZilla。下面这张表是我常用的几个工具对比工具协议支持断点续传脚本友好度典型场景ftp (系统自带)FTP部分低临时调试lftpFTP/FTPS/SFTP支持高批量镜像、定时任务curlFTP/FTPS/SFTP支持高单文件上传下载FileZillaFTP/FTPS/SFTP支持低手动拖拽操作WinSCPFTP/FTPS/SFTP支持中Windows 图形化3. 上传下载实操命令行与脚本两条路3.1 用 lftp 做批量上传镜像目录与排除规则lftp是我最常用的批量上传工具因为它支持mirror命令可以把本地目录整个同步到服务端而且支持排除规则和断点续传。下面是一个典型的上传脚本#!/bin/bash # 批量上传本地目录到 FTP 服务端 # 使用 lftp 的 mirror -R 反向镜像本地 - 远程 HOSTftp.example.com USERdeploy PASSyour_password LOCAL_DIR/data/release/ REMOTE_DIR/upload/release/ lftp -c set ftp:ssl-allow no; # 如果服务端不支持 FTPS显式关闭 set ftp:passive-mode on; # 强制被动模式避免主动模式连不上 set net:timeout 10; # 网络超时 10 秒 set net:max-retries 3; # 失败重试 3 次 open -u $USER,$PASS $HOST; mirror -R \ --parallel4 \ # 4 个并发传输 --exclude-glob *.tmp \ # 排除临时文件 --exclude-glob .git/ \ # 排除 .git 目录 --only-newer \ # 只传比服务端新的文件 $LOCAL_DIR $REMOTE_DIR; bye 这段脚本的逻辑是先设置被动模式和超时参数然后用mirror -R把本地目录反向镜像到远程。--parallel4开 4 个并发适合大量小文件--only-newer避免重复传已存在的文件--exclude-glob排除掉不需要上传的临时文件和版本控制目录。参数上net:timeout和net:max-retries根据网络质量调整内网可以设短一点公网建议 15 秒以上。如果服务端支持 FTPS把ftp:ssl-allow改成yes并加上证书验证参数即可。3.2 用 curl 做单文件上传下载一行命令搞定如果只是传一两个文件curl比lftp更直接。上传# 上传单个文件到 FTP 服务端 # -T 指定本地文件-u 指定用户名密码 curl -T /data/report.csv \ -u deploy:your_password \ ftp://ftp.example.com/upload/report.csv \ --ftp-pasv \ # 被动模式 --ftp-create-dirs # 自动创建远程目录下载# 下载单个文件-o 指定保存路径 curl -o /data/backup.zip \ -u deploy:your_password \ ftp://ftp.example.com/backup/backup.zip \ --ftp-pasv \ -C - # 断点续传从已下载位置继续-C -是断点续传的关键如果下载中断重新执行同一条命令会从断点继续不用重头来。--ftp-create-dirs在上传时如果远程目录不存在会自动创建省掉手动建目录的步骤。注意curl默认不验证 FTPS 证书如果服务端是自签证书需要加-k跳过验证但生产环境建议配好 CA 证书。3.3 Python 脚本实现上传下载ftplib 与 paramiko 的选择如果要在 Python 里集成 FTP 操作标准库ftplib够用但只支持 FTP 和 FTPS不支持 SFTP。SFTP 要用paramiko。下面是一个ftplib上传下载的示例import ftplib import os def ftp_upload(host, user, passwd, local_path, remote_dir): 上传文件到 FTP 服务端 try: # 连接并登录timeout 防止卡死 ftp ftplib.FTP(host, timeout15) ftp.login(user, passwd) # 切换到被动模式避免主动模式被防火墙拦截 ftp.set_pasv(True) # 切换远程目录不存在则创建 try: ftp.cwd(remote_dir) except ftplib.error_perm: ftp.mkd(remote_dir) ftp.cwd(remote_dir) # 以二进制模式上传避免文件损坏 with open(local_path, rb) as f: ftp.storbinary(fSTOR {os.path.basename(local_path)}, f) ftp.quit() print(f上传成功: {local_path}) except Exception as e: print(f上传失败: {e}) def ftp_download(host, user, passwd, remote_path, local_path): 从 FTP 服务端下载文件 try: ftp ftplib.FTP(host, timeout15) ftp.login(user, passwd) ftp.set_pasv(True) with open(local_path, wb) as f: ftp.retrbinary(fRETR {remote_path}, f.write) ftp.quit() print(f下载成功: {local_path}) except Exception as e: print(f下载失败: {e}) # 调用示例 ftp_upload(ftp.example.com, deploy, your_password, /data/report.csv, /upload) ftp_download(ftp.example.com, deploy, your_password, /backup/backup.zip, /data/backup.zip)关键点set_pasv(True)强制被动模式storbinary和retrbinary用二进制模式传输避免文本模式下换行符被转换导致文件损坏timeout参数防止网络异常时脚本永久挂起。如果服务端是 SFTP把ftplib换成paramiko的SFTPClient连接端口改成 22其他逻辑类似。3.4 定时任务与日志记录让传输可追溯生产环境里 FTP 传输通常要定时跑并且记录日志。我一般用cron加 shell 脚本脚本里把lftp的输出重定向到日志文件同时记录时间戳和退出码#!/bin/bash # /etc/cron.daily/ftp_sync.sh LOG/var/log/ftp_sync_$(date %Y%m%d).log echo [$(date %Y-%m-%d %H:%M:%S)] 开始同步 $LOG lftp -c set ftp:passive-mode on; set net:timeout 15; set net:max-retries 3; open -u deploy,your_password ftp.example.com; mirror -R --parallel4 --only-newer /data/release/ /upload/release/; bye $LOG 21 echo [$(date %Y-%m-%d %H:%M:%S)] 同步结束退出码: $? $LOG日志里能看到每次同步的开始结束时间、传输了哪些文件、有没有报错。如果退出码非 0可以配合监控告警发通知。注意日志文件要定期清理避免磁盘写满。4. 避坑指南FTP 上传下载的五个血泪教训4.1 列目录卡死但登录正常现象客户端能登录但执行ls或dir时卡住最后超时断开。原因数据连接被防火墙拦截通常是主动模式下服务端连不回来或者被动模式下服务端返回的 IP 是内网地址。解决客户端强制被动模式lftp里set ftp:passive-mode onftplib里set_pasv(True)服务端配置里指定公网 IP 和被动端口范围并在防火墙放行。4.2 上传后文件大小对不上现象本地文件 10MB上传到服务端只有 9.8MB或者下载回来打不开。原因用了文本模式ASCII传输二进制文件换行符被转换。解决始终用二进制模式ftplib用storbinary/retrbinarylftp默认二进制curl加--ftp-file不需要额外设置但注意不要用-B以外的模式。4.3 中文文件名乱码现象上传中文名文件后服务端显示乱码或者下载时找不到文件。原因客户端和服务端编码不一致常见是客户端 UTF-8、服务端 GBK。解决统一用 UTF-8lftp里set ftp:charset utf-8ftplib在 Python 3 里默认 UTF-8 但需要服务端支持OPTS UTF8 ON。如果服务端不支持只能避免中文文件名。4.4 大文件传输中途断开现象传几百 MB 以上的文件时传到一半连接断开重传又从零开始。原因网络不稳定或服务端超时设置太短。解决用支持断点续传的工具lftp的mirror默认支持续传curl加-C -ftplib需要自己实现REST命令。同时调大服务端和客户端的超时参数。4.5 权限拒绝但账号密码没错现象登录成功但上传时报550 Permission denied。原因服务端用户对目标目录没有写权限或者被动模式端口被 SELinux 拦截。解决检查服务端目录权限和属主确认 FTP 用户有写权限如果是 Linux检查 SELinux 是否允许 FTP 写操作setsebool -P ftpd_full_access on如果是 vsftpd检查write_enableYES和local_umask设置。5. 进阶技巧用 lftp 做增量同步与校验5.1 增量同步只传变化的文件lftp的mirror命令自带增量能力核心参数是--only-newer和--ignore-time。--only-newer只传比服务端新的文件适合发布场景--ignore-time忽略时间戳只比大小适合服务端时间不准的情况。我一般还会加--delete删除服务端多余的文件保持两端一致lftp -c set ftp:passive-mode on; open -u deploy,your_password ftp.example.com; mirror -R --parallel4 --only-newer --delete /data/release/ /upload/release/; bye 注意--delete有风险如果本地目录被误清空服务端也会被清空。我一般先跑一次--dry-run看会删哪些文件确认无误再去掉--dry-run正式执行。5.2 传输后校验比对文件大小和哈希FTP 协议本身不提供哈希校验但可以用SIZE命令比对文件大小或者下载回来算 MD5。lftp里可以用ls列出远程文件大小跟本地比对# 本地文件大小 local_size$(stat -c %s /data/release/report.csv) # 远程文件大小 remote_size$(lftp -c set ftp:passive-mode on; open -u deploy,your_password ftp.example.com; ls /upload/release/report.csv; bye | awk {print $5}) if [ $local_size -eq $remote_size ]; then echo 大小一致传输成功 else echo 大小不一致本地 $local_size 远程 $remote_size fi如果对完整性要求更高可以在上传前算本地 MD5上传后从服务端下载回来再算一次 MD5 比对。虽然多一次下载但对于关键文件值得。5.3 连接复用与并发控制lftp支持在一个会话里连续执行多条命令避免反复登录。--parallel控制并发数但不要设太大一般 4 到 8 就够了设太大反而会因为服务端连接数限制导致失败。如果服务端有连接数限制可以在lftp里设set net:limit-total-rate限制总带宽避免把带宽跑满影响其他服务。从那以后我每次配 FTP 传输都强制先跑一遍被动模式加二进制模式再拿一个小文件试传试下确认没问题才上批量脚本。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询