使用 Apache Commons Net (最推荐)
这是最常用、最稳定、功能最全的 Java FTP 客户端库,它封装了复杂的 FTP 协议细节,提供了简单易用的 API。
添加依赖
您需要在您的项目中添加 commons-net 的依赖。
Maven (pom.xml):
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version> <!-- 请使用最新版本 -->
</dependency>
Gradle (build.gradle):
implementation 'commons-net:commons-net:3.9.0' // 请使用最新版本
完整示例代码
这个示例展示了连接服务器、上传文件、下载文件、列出文件和断开连接等核心操作。
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FtpClientExample {
private static final String SERVER = "ftp.example.com";
private static final int PORT = 21;
private static final String USER = "username";
private static final String PASS = "password";
public static void main(String[] args) {
FTPClient ftpClient = new FTPClient();
try {
// 1. 连接到FTP服务器
System.out.println("正在连接到 FTP 服务器: " + SERVER);
ftpClient.connect(SERVER, PORT);
int replyCode = ftpClient.getReplyCode();
// 检查连接是否成功
if (!FTPReply.isPositiveCompletion(replyCode)) {
System.out.println("FTP 服务器拒绝连接。");
return;
}
System.out.println("连接成功。");
// 2. 登录
if (ftpClient.login(USER, PASS)) {
System.out.println("登录成功。");
} else {
System.out.println("登录失败。");
return;
}
// 3. 设置文件传输模式为被动模式 (PASV)
// 对于大多数网络环境(尤其是有防火墙的),被动模式是必需的
ftpClient.enterLocalPassiveMode();
// 4. 设置文件类型为二进制 (重要!)
// 防止在传输文本文件时出现内容损坏
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
// --- 上传文件示例 ---
String localFilePath = "C:/path/to/your/localfile.txt";
String remoteFilePath = "uploadfile.txt";
uploadFile(ftpClient, localFilePath, remoteFilePath);
// --- 下载文件示例 ---
String downloadLocalFilePath = "C:/path/to/your/downloadedfile.txt";
String downloadRemoteFilePath = "uploadfile.txt"; // 下载刚才上传的文件
downloadFile(ftpClient, downloadRemoteFilePath, downloadLocalFilePath);
// --- 列出文件示例 ---
listFiles(ftpClient);
} catch (IOException e) {
System.err.println("FTP 客户端发生错误: " + e.getMessage());
} finally {
// 5. 注销并断开连接
try {
if (ftpClient.isConnected()) {
ftpClient.logout();
ftpClient.disconnect();
System.out.println("已成功断开与服务器的连接。");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 上传文件到FTP服务器
*/
public static void uploadFile(FTPClient ftpClient, String localPath, String remotePath) throws IOException {
try (FileInputStream fis = new FileInputStream(localPath)) {
System.out.println("开始上传文件: " + localPath);
boolean done = ftpClient.storeFile(remotePath, fis);
if (done) {
System.out.println("文件上传成功: " + remotePath);
} else {
System.out.println("文件上传失败。");
}
}
}
/**
* 从FTP服务器下载文件
*/
public static void downloadFile(FTPClient ftpClient, String remotePath, String localPath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(localPath)) {
System.out.println("开始下载文件: " + remotePath);
boolean done = ftpClient.retrieveFile(remotePath, fos);
if (done) {
System.out.println("文件下载成功: " + localPath);
} else {
System.out.println("文件下载失败。");
}
}
}
/**
* 列出FTP服务器上指定目录的文件
*/
public static void listFiles(FTPClient ftpClient) throws IOException {
System.out.println("列出当前目录下的文件:");
String[] files = ftpClient.listNames();
if (files != null && files.length > 0) {
for (String file : files) {
System.out.println(" - " + file);
}
} else {
System.out.println("目录为空或无法列出文件。");
}
}
}
使用 Java 11+ 内置的 FTPClient
从 Java 11 开始,标准库中包含了一个实验性的 sun.net.ftp.FtpClient。注意: 这个类位于 sun.net 包下,是非公开的 API,意味着它可能在未来的 Java 版本中被移除或修改,不推荐用于生产环境。
添加依赖 (无)
此类是 Java 标准库的一部分,无需额外依赖。
示例代码
import sun.net.ftp.FtpClient;
import sun.net.ftp.FtpProtocolException;
import java.io.*;
import java.net.InetAddress;
public class Java11FtpClientExample {
private static final String SERVER = "ftp.example.com";
private static final int PORT = 21;
private static final String USER = "username";
private static final String PASS = "password";
public static void main(String[] args) {
FtpClient ftpClient = null;
try {
// 1. 创建并连接FTP客户端
ftpClient = new FtpClient();
ftpClient.openServer(SERVER, PORT);
System.out.println("连接成功。");
// 2. 登录
ftpClient.login(USER, PASS);
System.out.println("登录成功。");
// 3. 设置传输模式为二进制
ftpClient.setBinaryType();
// 4. 上传文件
String localFilePath = "C:/path/to/your/localfile.txt";
String remoteFilePath = "uploadfile_java11.txt";
uploadFile(ftpClient, localFilePath, remoteFilePath);
// 5. 下载文件
String downloadLocalFilePath = "C:/path/to/your/downloadedfile_java11.txt";
downloadFile(ftpClient, remoteFilePath, downloadLocalFilePath);
} catch (IOException | FtpProtocolException e) {
System.err.println("FTP 客户端发生错误: " + e.getMessage());
} finally {
// 6. 关闭连接
if (ftpClient != null) {
try {
ftpClient.closeServer();
System.out.println("已成功断开与服务器的连接。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void uploadFile(FtpClient ftpClient, String localPath, String remotePath) throws IOException, FtpProtocolException {
try (InputStream is = new FileInputStream(localPath)) {
System.out.println("开始上传文件: " + localPath);
ftpClient.putFile(remotePath, is);
System.out.println("文件上传成功: " + remotePath);
}
}
public static void downloadFile(FtpClient ftpClient, String remotePath, String localPath) throws IOException, FtpProtocolException {
try (OutputStream os = new FileOutputStream(localPath)) {
System.out.println("开始下载文件: " + remotePath);
ftpClient.getFile(remotePath, os);
System.out.println("文件下载成功: " + localPath);
}
}
}
手动实现 FTP 客户端 (仅用于学习)
警告: 这种方法非常复杂,容易出错,且难以处理所有 FTP 协议的边缘情况(如被动模式、不同服务器响应等。强烈不建议在生产环境中使用,此代码仅用于演示 FTP 协议的基本工作原理。
这里只展示一个非常简化的 LIST 命令实现,以帮助你理解底层通信。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class ManualFtpClient {
private static final String SERVER = "ftp.example.com";
private static final int PORT = 21;
private static final String USER = "username";
private static final String PASS = "password";
public static void main(String[] args) {
try (Socket socket = new Socket(SERVER, PORT);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// 读取欢迎信息
System.out.println("服务器响应: " + in.readLine());
// 用户名
out.println("USER " + USER);
System.out.println("服务器响应: " + in.readLine());
// 密码
out.println("PASS " + PASS);
System.out.println("服务器响应: " + in.readLine());
// 切换到被动模式
out.println("PASV");
String pasvResponse = in.readLine();
System.out.println("服务器响应 (PASV): " + pasvResponse);
// --- 解析PASV响应以获取数据连接地址和端口 ---
// PASV响应格式: "227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)"
String[] parts = pasvResponse.split(",");
int p1 = Integer.parseInt(parts[parts.length - 2]);
int p2 = Integer.parseInt(parts[parts.length - 1].replace(")", ""));
int dataPort = p1 * 256 + p2;
// 从响应中提取IP地址 (简化处理,实际需要更复杂的解析)
String ip = parts[1] + "." + parts[2] + "." + parts[3] + "." + parts[4];
System.out.println("数据连接地址: " + ip + ":" + dataPort);
// --- 建立数据连接 ---
try (Socket dataSocket = new Socket(ip, dataPort);
BufferedReader dataIn = new BufferedReader(new InputStreamReader(dataSocket.getInputStream()))) {
// 发送LIST命令
out.println("LIST");
System.out.println("服务器响应 (LIST): " + in.readLine()); // 150 Opening ASCII mode data connection for file list
// 从数据连接中读取文件列表
System.out.println("--- 文件列表 ---");
String dataLine;
while ((dataLine = dataIn.readLine()) != null) {
System.out.println(dataLine);
}
System.out.println("--- 文件列表结束 ---");
System.out.println("服务器响应 (LIST完成): " + in.readLine()); // 226 Transfer complete.
}
// 退出
out.println("QUIT");
System.out.println("服务器响应 (QUIT): " + in.readLine());
} catch (UnknownHostException e) {
System.err.println("未知的主机: " + SERVER);
} catch (IOException e) {
System.err.println("I/O 错误: " + e.getMessage());
}
}
}
总结与建议
| 方法 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| Apache Commons Net | 功能强大、稳定可靠、文档齐全、社区活跃 | 需要添加第三方依赖 | 所有生产环境和严肃项目 |
Java 11+ FtpClient |
无需外部依赖,是标准库的一部分 | 非公开API,有被移除风险,功能相对较少 | 快速原型开发或个人项目,且不介意使用非公开API |
| 手动实现 | 能深入理解FTP协议 | 极其复杂、容易出错、功能有限、不健壮 | 学习和教学目的,绝不应该用于生产 |
对于任何实际应用,请务必选择 Apache Commons Net。 它是业界标准的解决方案,能为你节省大量的开发和调试时间。
