FTP文件传输实战:命令行、脚本与自动化避坑指南 简介这份资源面向需要在Java项目中实现FTP文件传输的开发者尤其适合刚接触网络编程、希望快速跑通上传下载流程的初中级程序员。包内共2个文件包含1个Java源码与1个jar依赖包压缩包约106KB体积轻量便于直接引入工程或对照学习。源码围绕Apache Commons Net库展开覆盖FTPClient连接、登录验证、主动与被动模式切换、二进制传输模式设置、retrieveFile下载与storeFile上传、目录切换与列表获取、断开连接及异常处理等核心环节并配有可直接参考的示例代码帮助读者理解FTP交互的完整链路。目前已有251人学习下载可作为搭建FTP工具类、排查连接与传输问题的实用起点也便于在此基础上扩展超时控制、重试机制与SSL/TLS安全传输等进阶能力。1. FTP 文件传输的工程化落地从命令行到脚本的完整拆解很多人第一次接触 FTP 是在浏览器地址栏里敲ftp://开头的链接然后发现现代浏览器早就把这个协议砍掉了。于是转头去装 FileZilla 这类图形客户端拖拽几下文件觉得“会用 FTP 了”。但真到了服务器运维、批量数据同步、定时任务这些场景图形界面反而成了累赘——你没法在凌晨三点的 crontab 里拖拽文件。这个项目标题“ftp从服务器上传下载文件”看着朴素实际上覆盖的是文件传输里最基础也最容易翻车的一环用命令行和脚本把文件可靠地送进去、取出来。它适合需要做自动化部署、日志归集、数据备份的从业者也适合刚接触 Linux 服务器、想搞明白 FTP 到底怎么在终端里跑起来的新手。下面按“协议原理 → 命令行实操 → 脚本封装 → 排错”这条线走一遍。2. FTP 协议的工作模型两条连接与三种传输模式2.1 控制连接与数据连接为什么必须分开FTP 和 HTTP 最大的区别在于它用两条 TCP 连接干活。一条叫控制连接默认走 21 端口负责发指令、收状态码比如USER、PASS、RETR、STOR这些命令都在这条连接上跑。另一条叫数据连接专门搬文件内容端口不固定。这种设计在 1971 年刚提出来时没问题但放到今天有 NAT 和防火墙的环境里就成了玄学源头。控制连接在整个会话期间保持打开你登录一次后面所有操作复用这条连接。数据连接则是每次传文件时临时建立传完就关。理解这一点很关键当你看到“登录成功但传文件卡死”问题几乎一定出在数据连接上而不是账号密码。常见做法是先用ftp命令连上去敲ls看目录能不能列出来。如果ls卡住不动说明数据连接没建起来这时候要去看被动模式配置而不是反复检查密码。2.2 主动模式与被动模式的选型逻辑FTP 有两种数据连接建立方式。主动模式PORT下客户端告诉服务器“你来连我的某个端口”服务器主动发起数据连接。被动模式PASV下客户端问服务器“你开哪个端口等我”然后客户端去连服务器。在服务器有公网 IP、客户端在防火墙后面的老架构里主动模式能用。但现在绝大多数场景是客户端在内网、服务器也在云上主动模式基本废掉——服务器连不进你的内网端口。所以默认用被动模式命令是passive或p。提示如果你在脚本里用ftp -p参数注意不同实现的-p含义不一样。有的表示被动模式有的表示保留文件权限。稳妥做法是在脚本内部发passive命令而不是依赖命令行参数。2.3 二进制与 ASCII 传输模式的选择FTP 有 ASCII 和二进制两种传输模式。ASCII 模式会做换行符转换Linux 的\n和 Windows 的\r\n之间会互转。二进制模式原样传输不做任何转换。选错模式的后果很直接传压缩包、图片、可执行文件用 ASCII 模式文件会损坏解压报错或者图片打不开。传纯文本配置文件用二进制模式换行符不转换在跨平台场景下可能多出^M字符。我一般会脚本开头直接发binary命令除非明确知道要传的是需要换行转换的文本。这样最省心不会因为文件类型判断失误翻车。3. 命令行实操从登录到批量传输的完整流程3.1 交互式登录与基本目录操作先看最基础的手工操作。打开终端输入ftp加服务器地址# 连接 FTP 服务器默认 21 端口 ftp 192.168.1.100 # 如果端口不是 21用空格分隔端口号 ftp 192.168.1.100 2121连上之后会提示输入用户名和密码。登录成功后进入ftp提示符。这时候可以用的命令和 Linux shell 很像但要注意ls、cd、pwd操作的是远程服务器目录lcd、lls操作的是本地目录。这个区分是新手最容易搞混的地方。# 查看远程当前目录 ftp pwd # 切换远程目录 ftp cd /data/upload # 查看本地当前目录 ftp lcd /home/user/downloads # 列出远程文件 ftp ls # 列出本地文件 ftp lls逻辑说明pwd和cd作用于远程lcd和lls作用于本地。传输文件时源路径和目标路径分别由当前远程目录和本地目录决定。参数上cd支持绝对路径和相对路径lcd同理。3.2 单文件上传下载与传输模式设置上传用put下载用get。传之前先设二进制模式# 设置为二进制传输模式 ftp binary # 上传单个文件到远程当前目录 ftp put /home/user/data.tar.gz # 上传并重命名 ftp put /home/user/data.tar.gz /data/upload/backup.tar.gz # 下载单个文件到本地当前目录 ftp get remote_file.zip # 下载并重命名 ftp get remote_file.zip local_copy.zip逻辑说明put的第一个参数是本地路径第二个可选参数是远程目标路径。get反过来第一个是远程路径第二个是本地路径。不指定第二个参数时用原文件名存到当前目录。参数上binary是会话级设置设一次后面都生效。如果中途要传文本文件可以切回ascii传完再切回binary。但我的习惯是全程binary文本文件传过去也不影响使用。3.3 批量传输与 mget/mput 的通配符陷阱批量下载用mget批量上传用mput支持通配符# 下载所有 .log 文件 ftp mget *.log # 上传所有 .csv 文件 ftp mput *.csv这里有个坑mget和mput默认每个文件都会交互式问一次“确认吗”。批量文件多的时候你得一直按y脚本里直接卡死。解决办法是先关掉交互提示# 关闭交互式确认 ftp prompt # 然后再批量传输就不会逐个询问 ftp mget *.log逻辑说明prompt是个开关命令执行一次关闭确认再执行一次打开。在脚本里通常先prompt再mget。注意通配符展开是在远程服务器上做的不是本地 shell 展开所以mget *.log匹配的是远程目录下的文件。4. 脚本化封装用 here document 和 expect 做自动化4.1 here document 方式最轻量的非交互脚本手工敲命令只能应急真正落地要写成脚本。最简单的办法是用 here document 把命令喂给ftp#!/bin/bash # 定义连接参数 FTP_HOST192.168.1.100 FTP_USERdeploy FTP_PASSyour_password FTP_DIR/data/upload LOCAL_FILE/home/user/build/app.tar.gz # 用 here document 传入命令序列 ftp -n $FTP_HOST EOF user $FTP_USER $FTP_PASS binary cd $FTP_DIR put $LOCAL_FILE bye EOF逻辑说明-n参数禁止自动登录避免脚本卡在用户名提示上。user命令一次性传用户名和密码。binary设传输模式cd切远程目录put上传bye退出。参数上$FTP_HOST后面直接跟 here document所有命令按顺序执行。这种方式的局限是没法根据上一条命令的返回结果做分支判断比如上传失败了你也不知道。适合简单场景。4.2 expect 方式能判断结果、能重试的可靠方案需要判断成功失败、做重试的场景用expect#!/usr/bin/expect -f # 设置超时时间单位秒 set timeout 30 # 连接参数 set host 192.168.1.100 set user deploy set pass your_password set remote_dir /data/upload set local_file /home/user/build/app.tar.gz # 启动 ftp 进程 spawn ftp $host # 等待用户名提示并发送 expect Name* send $user\r # 等待密码提示并发送 expect Password:* send $pass\r # 等待登录成功提示 expect 230* # 发送后续命令 send binary\r send cd $remote_dir\r send put $local_file\r # 等待传输完成 expect 226* send bye\r # 等待进程结束 expect eof逻辑说明spawn启动子进程expect等待特定输出send发送输入。230是登录成功的 FTP 状态码226是传输完成的状态码。通过匹配这些状态码脚本能确认每一步是否成功。参数上set timeout 30表示等待 30 秒没匹配到就报错退出。生产环境建议设长一点比如 120 秒避免大文件传输时超时误判。\r是回车符FTP 交互需要回车确认。4.3 用 lftp 做镜像同步和断点续传如果服务器上装了lftp批量同步会舒服很多。lftp支持mirror命令做目录镜像还支持断点续传#!/bin/bash # 用 lftp 做目录镜像同步 lftp -u deploy,your_password 192.168.1.100 EOF set ftp:ssl-allow no mirror -R /home/user/build /data/upload bye EOF逻辑说明-u参数传用户名和密码。mirror -R表示反向镜像即把本地目录推到远程。不加-R则是从远程拉到本地。参数上set ftp:ssl-allow no在服务器不支持 FTPS 时关掉 SSL 尝试避免握手卡住。mirror支持--continue断点续传、--parallel4并发传输、--exclude排除文件。这些参数在传大量小文件时能明显提速。5. 避坑与排查FTP 传输中最容易翻车的五个点5.1 现象登录成功但 ls 卡住不动原因数据连接没建立起来通常是主动/被动模式不匹配。客户端默认主动模式但服务器在防火墙后面连不回来。解决登录后先发passive命令切被动模式。如果脚本里用ftp -p确认该实现的-p是 passive 而不是 preserve。稳妥做法是在命令序列里显式写passive。5.2 现象文件传上去大小对但内容损坏原因传输模式选错。压缩包、二进制文件用了 ASCII 模式换行符被转换文件结构被破坏。解决传输前发binary命令。如果已经传坏了重新用二进制模式传一遍。检查方法是对比本地和远程的 MD5md5sum在两边各跑一次。5.3 现象mget 批量下载时脚本卡死原因mget和mput默认开启交互确认每个文件都等用户输入y。脚本里没有输入进程就挂在那里。解决在mget之前发prompt命令关闭确认。注意prompt是开关执行一次关再执行一次开。脚本里确保只执行一次。5.4 现象中文文件名传过去变成乱码原因FTP 协议本身不规定文件名编码客户端和服务器用的字符集不一致。服务器可能是 GBK客户端是 UTF-8。解决先确认服务器端的字符集设置。如果服务器用 UTF-8客户端也设 UTF-8。lftp可以用set ftp:charset utf-8指定。纯ftp命令没有这个设置项中文文件名场景建议换lftp或curl。5.5 现象大文件传到一半断开重传要从头开始原因网络抖动或超时导致数据连接中断ftp命令本身不支持断点续传。解决换lftp用mirror --continue或pget -c做断点续传。或者用curl -C -配合 FTP 协议下载。上传断点续传支持较弱常见做法是分卷压缩后传单卷控制在 100MB 以内。6. 进阶技巧用 curl 和 Python 做更可控的传输6.1 curl 的单命令上传下载curl支持 FTP而且不需要交互适合塞进一行命令# 上传文件 curl -T /home/user/app.tar.gz ftp://deploy:password192.168.1.100/data/upload/ # 下载文件 curl -o local_file.zip ftp://deploy:password192.168.1.100/data/remote_file.zip # 断点续传下载 curl -C - -o local_file.zip ftp://deploy:password192.168.1.100/data/remote_file.zip # 列出目录 curl ftp://deploy:password192.168.1.100/data/upload/逻辑说明-T是上传后面跟本地文件路径URL 末尾的斜杠表示传到该目录下并保留原文件名。-o指定下载后的本地文件名。-C -自动从断点处继续下载。参数上用户名密码直接写在 URL 里方便但会暴露在进程列表里。生产环境建议用--netrc或环境变量。curl默认被动模式不需要额外设置。6.2 Python ftplib 的精细控制需要更细粒度控制时Python 的ftplib是首选from ftplib import FTP import os # 连接并登录 ftp FTP() ftp.connect(192.168.1.100, 21, timeout30) ftp.login(deploy, your_password) # 设置被动模式 ftp.set_pasv(True) # 切换远程目录 ftp.cwd(/data/upload) # 上传文件用二进制模式 with open(/home/user/app.tar.gz, rb) as f: ftp.storbinary(STOR app.tar.gz, f) # 下载文件 with open(/home/user/downloads/remote_file.zip, wb) as f: ftp.retrbinary(RETR remote_file.zip, f.write) # 列出目录 files [] ftp.retrlines(LIST, files.append) for line in files: print(line) # 关闭连接 ftp.quit()逻辑说明connect建立控制连接login认证set_pasv(True)切被动模式。storbinary上传retrbinary下载都是二进制模式。retrlines获取目录列表回调函数逐行处理。参数上timeout30是连接超时大文件传输时这个超时只作用于控制连接数据连接有自己的超时。storbinary的第二个参数是文件对象以rb模式打开。如果要显示进度可以传一个带回调的包装函数。6.3 传输后的完整性校验传完文件别急着删源文件先校验# 本地计算 MD5 md5sum /home/user/app.tar.gz # 远程计算 MD5通过 SSH 执行 ssh deploy192.168.1.100 md5sum /data/upload/app.tar.gz对比两个值一致才算传成功。FTP 协议本身不提供校验机制226状态码只表示传输完成不保证内容正确。我一般会在脚本里加这一步尤其是传数据库备份和构建产物的时候。从那以后我每次写 FTP 传输脚本都会强制走一遍“binary → passive → 传完校验 MD5”的流程少一步都可能在后半夜被报警叫醒。希望帮到你。本文还有配套的精品资源点击获取