
本文介绍如何使用 go 语言通过 ftp 协议上传文件的同时安全、可靠地获取文件状态(如是否存在、大小等),重点解决因共享 ftp 连接导致的并发阻塞与竞态问题,并提供双连接方案与同步优化实践。
本文介绍如何使用 go 语言通过 ftp 协议上传文件的同时安全、可靠地获取文件状态(如是否存在、大小等),重点解决因共享 ftp 连接导致的并发阻塞与竞态问题,并提供双连接方案与同步优化实践。
在 Go 中使用 github.com/dutchcoders/goftp 库进行 FTP 文件上传时,若尝试在上传过程中(如调用 ftp.Stor())并发执行 ftp.List() 查询文件状态,极易因复用同一 FTP 连接实例而引发阻塞或竞态——因为该库的底层连接并非线程安全,且 FTP 协议本身要求命令串行执行(控制通道独占)。正如示例代码所示:两个 goroutine 共享 *goftp.FTP 实例,Stor() 与 List() 会相互抢占控制连接,导致上传挂起、查询返回空列表,甚至出现不可预测的行为(如日志中 7 <nil> 表明 Stor 返回 nil 错误但未显式报错,实为连接被另一 goroutine 中断)。
✅ 正确做法是 为并发操作分配独立的 FTP 连接实例:
// 创建两个独立连接
ftpUpload, err := goftp.Connect("serverip:port")
if err != nil {
log.Fatal("upload connect failed:", err)
}
defer ftpUpload.Close()
ftpMonitor, err := goftp.Connect("serverip:port") // 第二个独立连接
if err != nil {
log.Fatal("monitor connect failed:", err)
}
defer ftpMonitor.Close()
// 分别认证与登录
config := tls.Config{InsecureSkipVerify: true}
ftpUpload.AuthTLS(config)
ftpUpload.Login("userName", "pass")
ftpMonitor.AuthTLS(config)
ftpMonitor.Login("userName", "pass")
// 切换工作目录(各自独立)
ftpUpload.Cwd("/home/myDir/")
ftpMonitor.Cwd("/home/myDir/")
file, err := os.Open("sth")
if err != nil {
log.Fatal("open file failed:", err)
}
defer file.Close()
fmt.Println("start upload...")
// goroutine 1:上传文件
go func() {
if err := ftpUpload.Stor("sth", file); err != nil {
log.Printf("upload failed: %v", err)
} else {
fmt.Println("upload completed")
}
}()
// goroutine 2:轮询监控(使用独立连接)
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
files, err := ftpMonitor.List("sth")
if err != nil {
log.Printf("list failed: %v", err)
continue
}
for _, f := range files {
fmt.Printf("File: %s, Size: %d, Modified: %s\n", f.Name, f.Size, f.Time)
}
if len(files) > 0 {
fmt.Println("→ File detected. Upload likely in progress or complete.")
break // 可选:检测到即退出
}
}
}()
// 防止主 goroutine 退出(实际项目中建议用 sync.WaitGroup 或 context 控制)
select {}
⚠️ 注意事项:
- 绝不复用 FTP 实例:每个并发 FTP 操作(尤其是 Stor/Retr/List 等阻塞型命令)必须使用独立 *goftp.FTP 实例,否则必然触发底层 TCP 连接竞争;
- 资源清理:务必对每个 ftp.Close() 显式调用(推荐 defer),避免连接泄漏;
- 超时控制:生产环境应在 goftp.Connect() 前设置 net.Dialer.Timeout,并在 Stor() 调用中加入上下文超时(可通过封装或升级至支持 context 的 fork 版本);
- 进度替代方案:goftp 原生不暴露上传字节流进度。如需精确进度(如百分比),建议改用 io.Pipe + 自定义 io.Reader 包装源文件,在读取时统计已传输字节数,再结合 ftp.Stor 使用(注意仍需单连接顺序执行);
- 协议限制认知:FTP 本身无标准“上传进度推送”机制,List 轮询仅能反映服务端文件存在性/大小快照,无法替代真实流式进度反馈。
综上,解决 FTP 并发监控的核心在于连接隔离——用多个轻量级 FTP 连接分别承担上传与查询职责,既符合协议规范,又规避了竞态,是当前生态下最稳健的实践路径。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xitongjiaocheng/123922.html