Golang中FTP进入扩展被动模式的问题探讨
Golang中FTP进入扩展被动模式的问题探讨
我正在编写一个程序,用于从FTP服务器检索目录中的所有文件。第一个文件可以成功下载,但在第二个文件时 c.Retr(entry) 返回错误:
229 Entering Extended Passive Mode (|||44022|).
显然我对FTP的理解有所欠缺。不知道该如何处理。我尝试在 ftp.Dial(...) 中提供 ftp.DialWithDisabledEPSV(true),但在第二个文件时 c.Retr(entry) 返回错误:
226 Transfer complete
但没有文件被成功检索。
以下是代码。请原谅其脚本性质。非常感谢任何帮助。
package main
import (
"fmt"
"io"
"log"
"os"
"time"
"github.com/jlaffaye/ftp"
)
const (
monthOffset = -1
)
func main() {
c, err := ftp.Dial(
os.Getenv("FTP_HOST"),
ftp.DialWithTimeout(5*time.Second),
)
if err != nil {
log.Fatalln("unable to connect: ", err)
}
err = c.Login(
os.Getenv("ACCOUNT"),
os.Getenv("PASSWORD"),
)
if err != nil {
log.Fatal("login failed: ", err)
}
t := time.Now().AddDate(0, monthOffset, 0)
year, month, _ := t.Date()
path := fmt.Sprintf("%d/%02d/", year, month)
entries, err := c.NameList(path)
if err != nil {
log.Fatalf("unable to NLIST %s: %v\n", path, err)
}
for _, entry := range entries {
fmt.Printf("retrieving %s...\n", entry)
resp, err := c.Retr(entry)
if err != nil {
log.Fatalf("unable to retrieve %s: %v\n", entry, err)
}
defer resp.Close()
if _, err := os.Stat(entry); os.IsNotExist(err) {
os.MkdirAll("data/"+path, 0700)
}
destination, err := os.Create("data/" + entry)
if err != nil {
log.Fatalln("failed to create file:", err)
}
b, err := io.Copy(destination, resp)
if err != nil {
log.Fatalln("unable to copy response to dest file: ", err)
}
fmt.Printf("successfully copied: %s, bytes copied: %d\n", destination.Name(), b)
}
}
更多关于Golang中FTP进入扩展被动模式的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
问题在于我在调用 c.Retr(entry) 的 for 循环中使用了 defer resp.Close()。
解决方案是在复制后直接调用 resp.Close() 而不使用 defer。
更多关于Golang中FTP进入扩展被动模式的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我最初没有看到 func DialWithDebugOutput(w io.Writer) DialOption。
使用 ftp.DialWithDebugOutput(os.Stdout) 的结果如下。在调试输出中我没有看到第二次 RETR 尝试。说实话还是有点困惑…
220 (vsFTPd 2.2.2)
FEAT
211-Features:
AUTH SSL
AUTH TLS
EPRT
EPSV
MDTM
PASV
PBSZ
PROT
REST STREAM
SIZE
TVFS
UTF8
211 End
USER <user>
331 Please specify the password.
PASS <password>
230 Login successful.
TYPE I
200 Switching to Binary mode.
OPTS UTF8 ON
200 Always in UTF8 mode.
EPSV
229 Entering Extended Passive Mode (|||44010|).
NLST 2019/07/
150 Here comes the directory listing.
226 Directory send OK.
retrieving 2019/07/<file>...
EPSV
229 Entering Extended Passive Mode (|||44081|).
RETR 2019/07/<file>
150 Opening BINARY mode data connection for 2019/07/<file> (290200 bytes).
successfully copied: data/2019/07/<file>, bytes copied: 290200
retrieving 2019/07/<file>...
EPSV
226 Transfer complete.
PASV
229 Entering Extended Passive Mode (|||44023|).
2019/08/04 07:07:36 unable to retrieve 2019/07/<file>: 229 Entering Extended Passive Mode (|||44023|).
exit status 1
问题出现在处理FTP扩展被动模式(EPSV)和文件传输完成后的连接管理上。当服务器发送"229 Entering Extended Passive Mode"时,它正在建立数据传输连接,但代码没有正确处理多个文件的连续传输。
主要问题:
- 在循环中使用了
defer resp.Close(),这会导致所有响应在函数结束时才关闭 - 没有正确处理EPSV模式下的数据传输
- 文件创建逻辑有问题,应该在传输前创建目录
以下是修复后的代码:
package main
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"time"
"github.com/jlaffaye/ftp"
)
const (
monthOffset = -1
)
func main() {
c, err := ftp.Dial(
os.Getenv("FTP_HOST"),
ftp.DialWithTimeout(5*time.Second),
ftp.DialWithDisabledEPSV(true), // 禁用EPSV,使用传统PASV模式
)
if err != nil {
log.Fatalln("unable to connect: ", err)
}
defer c.Quit()
err = c.Login(
os.Getenv("ACCOUNT"),
os.Getenv("PASSWORD"),
)
if err != nil {
log.Fatal("login failed: ", err)
}
t := time.Now().AddDate(0, monthOffset, 0)
year, month, _ := t.Date()
path := fmt.Sprintf("%d/%02d/", year, month)
entries, err := c.NameList(path)
if err != nil {
log.Fatalf("unable to NLIST %s: %v\n", path, err)
}
// 提前创建目标目录
dataDir := "data/" + path
if err := os.MkdirAll(dataDir, 0700); err != nil {
log.Fatalf("unable to create directory %s: %v\n", dataDir, err)
}
for _, entry := range entries {
fmt.Printf("retrieving %s...\n", entry)
resp, err := c.Retr(entry)
if err != nil {
log.Printf("unable to retrieve %s: %v\n", entry, err)
continue // 继续处理下一个文件而不是退出
}
// 提取文件名并创建完整路径
filename := filepath.Base(entry)
destinationPath := filepath.Join(dataDir, filename)
destination, err := os.Create(destinationPath)
if err != nil {
resp.Close()
log.Printf("failed to create file %s: %v\n", destinationPath, err)
continue
}
b, err := io.Copy(destination, resp)
if err != nil {
resp.Close()
destination.Close()
log.Printf("unable to copy response to dest file %s: %v\n", destinationPath, err)
continue
}
// 立即关闭响应和文件
resp.Close()
destination.Close()
fmt.Printf("successfully copied: %s, bytes copied: %d\n", destinationPath, b)
}
}
关键修改:
- 使用
ftp.DialWithDisabledEPSV(true)禁用EPSV模式 - 移除了循环内的
defer,改为立即关闭响应和文件 - 提前创建目标目录
- 使用
filepath包正确处理文件路径 - 将
log.Fatalf改为log.Printf并继续处理,避免单个文件失败导致整个程序退出
这样修改后应该能够正确处理多个文件的连续下载,避免EPSV模式相关的问题。

