go 中应使用 github.com/jlaffaye/ftp 而非实验性 net/ftp,因其支持自动 pasv 模式、二进制传输及稳定维护;上传前须显式登录、切换目录并设置数据连接超时,避免权限错误与超时中断。

Go 里没有内置 FTP 客户端,得用第三方库
标准库 net/ftp 在 Go 1.22 之前根本不存在,现在虽有实验性支持(net/ftp 包),但仅限客户端且标记为 Experimental,不建议用于生产。实际项目中几乎都依赖 github.com/jlaffaye/ftp —— 它稳定、维护活跃、API 清晰。
安装命令:
go get github.com/jlaffaye/ftp
- 别用
net/ftp写上传逻辑,它不支持被动模式(PASV)的自动协商,多数 FTP 服务器(尤其是带 NAT 或防火墙的)必须走 PASV -
github.com/jlaffaye/ftp默认启用 PASV,且会自动处理数据连接超时和重试 - 注意该库不支持 FTPS(显式 TLS),如需加密传输,得换
github.com/secsy/goftp或自己封装 TLS 连接
上传文件前必须先登录并确认工作目录
FTP 不是“连上就传”,得显式登录、切换路径、检查权限。常见错误是上传失败却报 550 Permission denied 或静默中断,其实只是当前目录不可写或根本没切对。
示例关键步骤:
conn, err := ftp.Dial("ftp.example.com:21")
if err != nil { /* 处理连接错误 */ }
defer conn.Quit()
<p>err = conn.Login("user", "pass")
if err != nil { /<em> 处理认证失败 </em>/ }</p><p>// 必须显式进入目标目录,不能依赖服务器默认 home
err = conn.ChangeDir("/uploads")
if err != nil { /<em> 检查目录是否存在、是否有写权限 </em>/ }</p><p>file, err := os.Open("local.txt")
if err != nil { /<em> 处理本地文件打开失败 </em>/ }
defer file.Close()</p><p>err = conn.Stor("remote.txt", file)
if err != nil { /<em> 注意:Stor 返回 error 不一定代表文件损坏,可能是网络中断或磁盘满 </em>/ }</p>
-
ChangeDir失败时,Stor很可能直接返回550错误,但错误信息不明确,务必提前校验 - 有些 FTP 服务器对路径大小写敏感,
/UPLOADS和/uploads可能是两个目录 -
Stor是阻塞调用,大文件上传期间无法做进度反馈;如需进度条,得包装io.Reader并在Read方法里更新计数器
二进制模式上传才能保证文件不被破坏
FTP 默认用 ASCII 模式传输文本,会自动转换换行符(CRLF ↔ LF),上传图片、ZIP、PDF 等二进制文件时会导致损坏。Go 的 ftp 库默认使用二进制模式(TYPE I),但某些老旧服务器或自定义配置可能覆盖该行为。
- 无需手动发
TYPE I命令 ——github.com/jlaffaye/ftp的Stor内部已强制设置二进制模式 - 如果上传后文件校验失败(比如 SHA256 不匹配),优先检查 FTP 服务器是否禁用了
TYPE命令或强制 ASCII 模式 - 避免用
Retr下载后再比对:上传过程出错时,部分数据可能已写入服务器,造成残留垃圾文件
超时和连接复用容易被忽略
FTP 协议本身有控制连接和数据连接之分,github.com/jlaffaye/ftp 默认对控制连接设了 30 秒超时,但数据连接(真正传文件的那个)超时是独立的,默认 5 秒 —— 上传大文件或网络抖动时极易触发 read tcp: i/o timeout。
正确做法是显式设置:
conn, err := ftp.Dial("ftp.example.com:21", ftp.DialWithTimeout(30*time.Second))
// ... 登录等
<p>// 设置数据连接超时(关键!)
conn.SetDeadline(time.Now().Add(5 * time.Minute))</p>
-
SetDeadline影响后续所有操作(包括Stor),必须在Login后、Stor前调用 - 不要重复 Dial 多次上传多个文件 —— 控制连接可复用,频繁新建连接会触发服务器限频或拒绝服务
- 上传完别立刻
Quit,留着连接可用于后续操作;但也要防长连接空闲断开,可加简单心跳(如定期Noop)
真正麻烦的是被动模式下数据连接的 NAT 穿透问题 —— 如果你的程序跑在 Docker 容器或内网,FTP 服务器看到的数据连接 IP 可能是宿主机或网关地址,导致上传卡在 PASV 响应后无响应。这没法靠 Go 代码解决,得配服务器或改用 SFTP。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











