杰瑞科技汇

Java FTP Server如何搭建与配置?

Of course! Setting up an FTP server in Java can be done in a few ways, ranging from using a powerful, pre-built library to writing a basic one from scratch for learning purposes.

Java FTP Server如何搭建与配置?-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering:

  1. The Recommended Approach: Using Apache Commons Net

    • Why it's the best choice for most applications.
    • A complete, runnable example of an FTP server.
    • How to use it with a common FTP client like FileZilla.
  2. The Learning Approach: Building a Simple Server from Scratch

    • The key concepts and RFCs involved.
    • A basic implementation to demonstrate the core mechanics.
  3. Alternative Libraries

    Java FTP Server如何搭建与配置?-图2
    (图片来源网络,侵删)

    A quick look at other popular options.


The Recommended Approach: Using Apache Commons Net

For any real-world application, you should use a well-tested, robust library. Apache Commons Net is the de-facto standard for network protocols in Java, including FTP. Its FTPServer class makes creating a server incredibly straightforward.

Why Apache Commons Net?

  • Robust & Stable: It's been around for years and handles many edge cases.
  • Feature-Rich: Supports FTP, FTPS (FTP over SSL/TLS), SFTP (often confused, but different), and other protocols.
  • Easy to Use: The API is relatively simple and well-documented.
  • Actively Maintained: Part of the Apache Software Foundation.

Step 1: Add the Dependency

You need to include the Apache Commons Net library in your project. If you're using Maven, add this to your pom.xml:

<dependency>
    <groupId>commons-net</groupId>
    <artifactId>commons-net</artifactId>
    <version>3.9.0</version> <!-- Check for the latest version -->
</dependency>

If you're using Gradle:

Java FTP Server如何搭建与配置?-图3
(图片来源网络,侵删)
implementation 'commons-net:commons-net:3.9.0' // Check for the latest version

Step 2: Create the FTP Server Logic

You need to implement the org.apache.commons.net.ftp.FTPServerFactory and provide a custom org.apache.commons.net.ftp.FTPSvrSessionHandler to define what happens when a user connects.

This example will create a server that listens on port 2221 and allows anonymous login with read-only access.

MyFTPServer.java

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPCmd;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPServer;
import org.apache.commons.net.ftp.FTPServerFactory;
import org.apache.commons.net.ftp.FTPSession;
import org.apache.commons.net.ftp.FTPSessionAdapter;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
public class MyFTPServer {
    public static void main(String[] args) {
        // 1. Create the server factory
        FTPServerFactory serverFactory = new FTPServerFactory();
        // 2. Configure the server
        Properties props = new Properties();
        props.setProperty("server.address", "127.0.0.1"); // Listen only on localhost
        props.setProperty("server.port", "2221");        // Use a non-standard port
        serverFactory.setServerProperties(props);
        // 3. Define the root directory for the FTP server
        // This directory will be created if it doesn't exist.
        File ftpRootDir = new File("ftp_root");
        if (!ftpRootDir.exists()) {
            ftpRootDir.mkdirs();
        }
        // 4. Create a listener and bind it to the factory
        // We are using the default listener for simplicity.
        // For more control, you can create a custom listener.
        serverFactory.addListener("default", serverFactory.createListener());
        // 5. Create the FTP server instance
        FTPServer ftpServer = serverFactory.createServer();
        // 6. Add a session listener to handle user interactions
        ftpServer.addSessionListener(new FTPSessionAdapter() {
            @Override
            public void onLogin(FTPSession session, String username, String password) {
                System.out.println("User logged in: " + username);
                // For this example, we'll make all users anonymous and read-only.
                // In a real app, you would authenticate against a database or file.
                if ("anonymous".equals(username)) {
                    // You can set user-specific properties here
                    session.setAttribute("user", "anonymous");
                }
            }
            @Override
            public void onCommand(FTPSession session, FTPCmd command, String argument) {
                // This is where you can implement custom logic for commands.
                // For example, to prevent file uploads:
                if (command == FTPCmd.STOR || command == FTPCmd.STOU) {
                    // Check if user is anonymous and deny the command
                    if ("anonymous".equals(session.getAttribute("user"))) {
                        try {
                            session.sendResponse(FTPReply.ACTION_NOT_TAKEN, "Permission denied.");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        // 7. Start the server
        try {
            ftpServer.start();
            System.out.println("FTP Server started on 127.0.0.1:2221");
            System.out.println("Root directory: " + ftpRootDir.getAbsolutePath());
            System.out.println("Press Enter to stop the server...");
            System.in.read(); // Wait for user input to stop
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("Stopping FTP server...");
            ftpServer.stop();
            System.out.println("FTP server stopped.");
        }
    }
}

Step 3: Run the Server and Test with a Client

  1. Run the Java code: Execute the MyFTPServer class.
  2. Open an FTP Client: Use a client like FileZilla, Cyberduck, or even the command line.
  3. Connect to the Server:
    • Host: 0.0.1 (or localhost)
    • Port: 2221
    • Username: anonymous
    • Password: (leave blank or use your email)

You should now be able to browse the ftp_root directory that was created in your project's root. You can download files, but the code above prevents uploads for the anonymous user.


The Learning Approach: Building a Simple Server from Scratch

This is a great exercise to understand the underlying protocol. The FTP protocol works over two connections:

  1. Control Connection (Port 21): For sending commands (e.g., USER, PASS, LIST, RETR).
  2. Data Connection (Port 20 or a random high port): For transferring files and directory listings.

This simplified example will only handle the LIST command to show directory contents.

SimpleFTPServer.java

import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleFTPServer {
    private static final int SERVER_PORT = 2121;
    private static final String SERVER_DIR = "simple_ftp_root";
    private static final String USERNAME = "admin";
    private static final String PASSWORD = "password";
    public static void main(String[] args) {
        // Create the server directory if it doesn't exist
        Path serverPath = Paths.get(SERVER_DIR);
        if (!Files.exists(serverPath)) {
            try {
                Files.createDirectories(serverPath);
            } catch (IOException e) {
                System.err.println("Could not create server directory: " + e.getMessage());
                return;
            }
        }
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
            System.out.println("Simple FTP Server started on port " + SERVER_PORT);
            System.out.println("Serving files from: " + serverPath.toAbsolutePath());
            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("New client connected: " + clientSocket.getInetAddress());
                threadPool.execute(new ClientHandler(clientSocket, serverPath));
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
        } finally {
            threadPool.shutdown();
        }
    }
    static class ClientHandler implements Runnable {
        private final Socket clientSocket;
        private final Path serverPath;
        private BufferedReader in;
        private PrintWriter out;
        private boolean isAuthenticated = false;
        public ClientHandler(Socket socket, Path serverPath) {
            this.clientSocket = socket;
            this.serverPath = serverPath;
        }
        @Override
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                out = new PrintWriter(clientSocket.getOutputStream(), true);
                // Send welcome message
                sendResponse("220 Simple FTP Server Ready");
                String command;
                while ((command = in.readLine()) != null) {
                    System.out.println("Received command: " + command);
                    String[] parts = command.split(" ");
                    String cmd = parts[0].toUpperCase();
                    switch (cmd) {
                        case "USER":
                            handleUser(parts.length > 1 ? parts[1] : "");
                            break;
                        case "PASS":
                            handlePass(parts.length > 1 ? parts[1] : "");
                            break;
                        case "LIST":
                            handleList();
                            break;
                        case "QUIT":
                            sendResponse("221 Goodbye!");
                            return;
                        default:
                            sendResponse("502 Command not implemented.");
                            break;
                    }
                }
            } catch (IOException e) {
                System.out.println("Error handling client: " + e.getMessage());
            } finally {
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    System.err.println("Error closing client socket: " + e.getMessage());
                }
                System.out.println("Client disconnected.");
            }
        }
        private void handleUser(String username) {
            if (USERNAME.equals(username)) {
                sendResponse("331 Username OK, need password.");
            } else {
                sendResponse("530 Not logged in.");
            }
        }
        private void handlePass(String password) {
            if (USERNAME.equals(USERNAME) && PASSWORD.equals(password)) {
                isAuthenticated = true;
                sendResponse("230 User logged in, proceed.");
            } else {
                sendResponse("530 Not logged in.");
            }
        }
        private void handleList() throws IOException {
            if (!isAuthenticated) {
                sendResponse("530 Please login with USER and PASS.");
                return;
            }
            sendResponse("150 Opening data connection.");
            // Create a new socket for data transfer (passive mode is more complex)
            try (Socket dataSocket = new Socket(clientSocket.getInetAddress(), 2122);
                 PrintWriter dataOut = new PrintWriter(dataSocket.getOutputStream(), true)) {
                File[] files = serverPath.toFile().listFiles();
                if (files != null) {
                    for (File file : files) {
                        dataOut.println(file.getName() + (file.isDirectory() ? "/" : ""));
                    }
                }
            }
            sendResponse("226 Transfer complete.");
        }
        private void sendResponse(String message) {
            out.println(message);
        }
    }
}

How to Test the Simple Server:

  1. Run the SimpleFTPServer.java.
  2. Use a command-line FTP client like ftp (on Linux/macOS) or WinSCP/FileZilla.
  3. Connect to localhost:2121.
  4. Enter USER admin, then PASS password.
  5. Enter LIST. You should see the files in your simple_ftp_root directory.

Limitations of this Simple Server:

  • No file transfers (RETR, STOR).
  • No passive mode support (required for most clients to work behind firewalls).
  • No security.
  • Very limited command set.

Alternative Libraries

While Apache Commons Net is excellent, other options exist:

  • Mina FTP Server: A more modern, event-driven server built on Apache MINA. It's highly performant and scalable but has a steeper learning curve.
  • NanoFTPd: A very lightweight, single-file FTP server. Good for embedded systems or extremely simple needs where you don't want a large dependency.
  • SFTP vs. FTPS:
    • FTP (File Transfer Protocol): The original, unencrypted protocol. Not recommended for any modern use due to security risks.
    • FTPS (FTP Secure): FTP over SSL/TLS. This is what you get with FTPServer in Commons Net. It's the standard, secure way to do FTP.
    • SFTP (SSH File Transfer Protocol): This is NOT FTP. It runs over an SSH connection (port 22). It's a completely different protocol, more secure and feature-rich. For SFTP in Java, you'd use a library like JSch.

Summary & Recommendation

Feature Apache Commons Net Simple DIY Server
Ease of Use Excellent Hard
Features Full FTP, FTPS, more Very Basic (LIST only)
Reliability High Low (for production)
Security Good (FTPS support) None
Use Case Production applications, quick setups Learning, academic exercises

For any practical purpose, use Apache Commons Net. It saves you from reinventing the wheel and provides a secure, stable, and feature-rich solution. The DIY server is only useful for understanding the raw protocol mechanics.

分享:
扫描分享到社交APP
上一篇
下一篇