go标准库无net/ftp包,必须用github.com/jlaffaye/ftp;默认pasv模式易因防火墙超时,应禁用改用port模式;retr/stor返回的io.readcloser/io.writecloser须手动close;中文名需规避编码问题或改用英文路径;并发需独立连接,不可复用conn。

net/ftp 包根本不存在,Go 标准库不提供 FTP 客户端或服务端实现。所有试图 import "net/ftp" 的代码都会编译失败——这不是配置问题,是 Go 语言本身就没这个包。
github.com/jlaffaye/ftp 连不上或 List() 卡住/报 EOF
这是最常踩的坑:默认启用 PASV(被动模式),但你的网络环境(如公司防火墙、NAT、本地 Docker)大概率会拦截数据连接,导致控制通道看似连上,List() 或 Retr() 却卡死或返回 EOF。
- 用命令行验证服务器是否真支持 PASV:
ftp -v your-server.com,然后输入ls看是否成功 - 如果不行,初始化连接时必须显式禁用:
conn, err := ftp.Dial("192.168.1.100:2121", ftp.DialWithDisabledPASV(), ftp.DialWithDisabledEPSV()) - 禁用 PASV 后走 PORT 模式,意味着服务器要能主动连回你本机的随机端口——开发机在内网基本不可行,仅适合服务端环境可控的场景
- 连接后立刻调
NoOp()检查通道活性,别等到上传时才发现连接已断
Conn.Stor() 传文件失败,报 553 Could not create file 或静默无反应
错误几乎都出在路径和权限上,跟 Go 代码逻辑关系不大:
-
Stor()的远程路径是相对于登录后根目录的相对路径,不能写成"/upload/file.txt",得写"upload/file.txt" - 目标目录必须提前存在,
Stor()不创建父级目录 - 若服务端挂载的是
os.DirFS("/tmp"),它是只读的,Stor()会静默失败(无 error 返回),需自定义FileSystem实现写入逻辑 - 中文文件名大概率乱码:FTP 协议不协商编码,Linux 服务端常按 ISO-8859-1 解,结果变成
????;稳妥做法是上传前用filepath.Base()提取纯文件名,并确保不含:、|、?等 Windows 非法字符
并发上传多个文件时报 use of closed network connection
*ftp.ServerConn(即 conn)不是 goroutine 安全的,也不能跨协程复用:
- 每个 goroutine 必须建立独立连接,不要把同一个
conn传给多个go func() -
Retr()和Stor()返回的io.ReadCloser/io.WriteCloser必须手动Close(),否则底层 TCP 连接泄漏,后续操作易复用坏状态 - 不要用
io.ReadAll(rc)直接读取大文件,容易 OOM 或超时;改用io.Copy(dst, rc)+ 显式rc.Close()
github.com/jlaffaye/ftp 是目前最稳定的 Go FTP 客户端,但它不处理字符编码、不自动建目录、不管理连接池——这些都得你自己兜底。真正麻烦的从来不是“怎么连”,而是“连上之后怎么让每一步都不掉链子”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











