Golang FTP服务器实现

我正在尝试用Golang实现一个简单的FTP服务器,但遇到了一些问题。目前已经实现了基本的连接和命令处理,但在处理文件传输时总是报错。具体表现为:当客户端尝试上传或下载文件时,服务器端会抛出"connection reset by peer"错误。我使用的是标准库net和os包,没有用第三方库。请问可能是什么原因导致的?有没有推荐的实现方案或者需要注意的关键点?另外,想请教下大家Golang实现FTP服务器时,如何处理并发连接和资源释放的问题?

2 回复

在Go语言中实现一个基础的FTP服务器,可以使用标准库net和自定义协议处理。以下是一个简化版本,支持基本的FTP命令(如USER、PASS、LIST、CWD、PWD、RETR、STOR等)。使用Go的并发特性处理多客户端连接。

核心代码示例

package main

import (
    "fmt"
    "net"
    "strings"
    "io"
    "os"
    "path/filepath"
)

type FTPConn struct {
    conn     net.Conn
    dataConn net.Conn
    rootDir  string
    workDir  string
    authenticated bool
}

func main() {
    listener, err := net.Listen("tcp", ":21")
    if err != nil {
        fmt.Println("Error starting server:", err)
        return
    }
    defer listener.Close()
    fmt.Println("FTP Server started on port 21")

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Connection error:", err)
            continue
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    ftp := &FTPConn{
        conn:    conn,
        rootDir: "./ftp_root", // 设置服务器根目录
        workDir: "/",
    }
    ftp.sendResponse(220, "FTP Server Ready")

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            break
        }
        command := strings.TrimSpace(string(buf[:n]))
        ftp.handleCommand(command)
    }
}

func (ftp *FTPConn) handleCommand(command string) {
    parts := strings.Fields(command)
    if len(parts) == 0 {
        return
    }
    cmd := strings.ToUpper(parts[0])
    args := ""
    if len(parts) > 1 {
        args = strings.Join(parts[1:], " ")
    }

    switch cmd {
    case "USER":
        ftp.authenticated = (args == "anonymous") // 简单匿名认证示例
        if ftp.authenticated {
            ftp.sendResponse(230, "User logged in, proceed.")
        } else {
            ftp.sendResponse(530, "Not logged in.")
        }
    case "PASS":
        // 可扩展密码验证
    case "SYST":
        ftp.sendResponse(215, "UNIX Type: L8")
    case "PWD":
        ftp.sendResponse(257, "\""+ftp.workDir+"\" is current directory.")
    case "CWD":
        newDir := filepath.Join(ftp.rootDir, args)
        if _, err := os.Stat(newDir); err == nil {
            ftp.workDir = args
            ftp.sendResponse(250, "Directory changed to "+args)
        } else {
            ftp.sendResponse(550, "Directory not found.")
        }
    case "LIST":
        ftp.handleList()
    case "RETR":
        ftp.handleRetr(args)
    case "STOR":
        ftp.handleStor(args)
    case "QUIT":
        ftp.sendResponse(221, "Goodbye.")
        ftp.conn.Close()
    default:
        ftp.sendResponse(502, "Command not implemented.")
    }
}

func (ftp *FTPConn) sendResponse(code int, message string) {
    fmt.Fprintf(ftp.conn, "%d %s\r\n", code, message)
}

// 处理LIST命令(简化版,仅列出当前目录文件)
func (ftp *FTPConn) handleList() {
    dir := filepath.Join(ftp.rootDir, ftp.workDir)
    files, err := os.ReadDir(dir)
    if err != nil {
        ftp.sendResponse(550, "Failed to list directory.")
        return
    }

    // 建立数据连接(示例使用同一连接,实际需按PASV/PORT模式实现)
    ftp.sendResponse(150, "Here comes the directory listing.")
    var list string
    for _, file := range files {
        list += file.Name() + "\r\n"
    }
    ftp.conn.Write([]byte(list))
    ftp.sendResponse(226, "Directory send OK.")
}

// 处理文件下载
func (ftp *FTPConn) handleRetr(filename string) {
    path := filepath.Join(ftp.rootDir, ftp.workDir, filename)
    file, err := os.Open(path)
    if err != nil {
        ftp.sendResponse(550, "File not found.")
        return
    }
    defer file.Close()

    ftp.sendResponse(150, "Opening data connection.")
    // 实际传输需通过数据连接,此处简化
    io.Copy(ftp.conn, file)
    ftp.sendResponse(226, "Transfer complete.")
}

// 处理文件上传
func (ftp *FTPConn) handleStor(filename string) {
    path := filepath.Join(ftp.rootDir, ftp.workDir, filename)
    file, err := os.Create(path)
    if err != nil {
        ftp.sendResponse(550, "Could not create file.")
        return
    }
    defer file.Close()

    ftp.sendResponse(150, "Ready to receive file.")
    // 实际接收数据需通过数据连接
    io.Copy(file, ftp.conn)
    ftp.sendResponse(226, "Transfer complete.")
}

关键说明:

  1. 基础结构:使用FTPConn管理连接状态和目录。
  2. 命令处理:解析FTP命令并响应标准代码(如220、230等)。
  3. 文件操作:通过LISTRETRSTOR实现目录列表和文件传输。
  4. 认证:示例支持匿名登录,实际应用需扩展用户验证。
  5. 数据连接:简化版直接使用控制连接传输数据,标准FTP需实现PASV/PORT模式建立独立数据连接。

运行步骤:

  1. 创建根目录(如./ftp_root)。
  2. 运行程序,监听端口21。
  3. 使用FTP客户端(如ftp localhost 21)测试。

可根据需求扩展错误处理、日志、TLS支持及完整FTP命令。

回到顶部