java:实现ftp上传与下载(附带源码)

该文章已生成可运行项目,

1、项目背景详细介绍

在企业级应用、运维自动化以及大数据处理场景中,文件传输(File Transfer)是系统间数据交互的基础需求。FTP(File Transfer Protocol)作为最早、最广泛使用的网络文件传输协议之一,依托于 TCP,实现了跨平台、可断点续传的大文件上传下载功能。尽管近年 SFTP、HTTP(s) 下载等方式兴起,传统 FTP 仍因其轻量、部署简单而在迁移历史遗留系统、批量文件同步、日志归档等场合被广泛使用。

为什么要手动实现 FTP 客户端?

  1. 灵活定制:满足自定义超时、并发线程、限速、断点续传、文件校验(MD5/SHA)等特殊需求;

  2. 深入协议理解:通过 Socket 与 FTP 命令交互,加深对 TCP、被动/主动模式、控制与数据通道分离机制的认识;

  3. 集成自动化:将 FTP 功能嵌入到 Java 应用中,用于微服务间文件同步、CI/CD 发布、分布式日志收集等;

  4. 替代第三方依赖:不依赖 Apache Commons Net 等库,减少依赖冲突与版本兼容问题。

本项目目标是从零实现一个完整的 Java FTP 客户端,支持以下特性:

  • 支持 FTP 主动(PORT)与被动(PASV)模式

  • 支持二进制与 ASCII 两种传输类型

  • 支持断点续传/分块上传下载

  • 支持多线程并发传输(线程池管理)

  • 支持上传/下载进度实时回调

  • 支持多种身份验证(普通用户名密码、匿名登录)

  • 支持 SSL/TLS(FTPS)扩展,可选开启加密控制通道和/或数据通道

  • 提供简洁易用的 Java API,方便与业务系统集成


2、项目需求详细介绍

类别需求编号详细需求
功能需求FR-1建立与 FTP 服务器的 TCP 连接,完成 USER/PASS 登录,支持普通与匿名登录。
FR-2支持被动(PASV)与主动(PORT)两种数据连接模式切换。
FR-3实现二进制(TYPE I)与 ASCII(TYPE A)两种文件传输类型。
FR-4支持文件上传(STOR)与下载(RETR)基本功能,并支持大文件断点续传。
FR-5支持目录操作:列出目录(LIST)、创建目录(MKD)、删除目录(RMD)、切换目录(CWD/PWD)。
FR-6支持并发传输:多线程分块上传下载,大幅提升传输效率。
FR-7支持进度回调(Callback),实时推送已传输字节数与总大小,用于 UI 展示或日志统计。
FR-8提供连接超时、读取超时配置;支持失败重试与异常回滚。
FR-9支持 FTPS(SSL/TLS 加密),可选开启控制通道或同时加密数据通道(Implicit/Explicit 模式)。
非功能需求NFR-1代码遵循 Google Java Style,集成 Checkstyle 进行静态代码分析。
NFR-2单元测试覆盖率 ≥ 90%,基于 JUnit 5 编写测试用例,并使用 JaCoCo 生成覆盖率报告。
NFR-3使用 Maven 构建,配置 maven-compiler-plugin、maven-surefire-plugin、jacoco-maven-plugin、maven-checkstyle-plugin。
NFR-4提供完整 JavaDoc 文档,并在 README 中给出示例代码和使用说明。
NFR-5提供 Maven 坐标,支持发布至私服或 Maven Central。


3、相关技术详细介绍

3.1 Java 核心技术
  • Socket 编程:掌握 SocketServerSocket 的使用,理解 TCP 三次握手/四次挥手;

  • 流与缓冲InputStream/OutputStreamBufferedInputStream/BufferedOutputStreamRandomAccessFile

  • 多线程与并发ExecutorService 线程池,CountDownLatchFutureCompletableFuture

  • SSL/TLSSSLSocketFactorySSLContext,支持定制信任管理器与密钥管理器;

  • Java NIO(可选扩展):SocketChannelSelector 实现高并发非阻塞 IO。

3.2 FTP 协议要点
  • 控制通道 vs 数据通道:控制命令走 21 端口,数据传输走动态端口;

  • 主动模式(PORT):客户端监听端口,告知服务器连接;

  • 被动模式(PASV):服务器开启监听,客户端连接;

  • 命令与响应:RFC 959 定义了 USER、PASS、TYPE、MODE、STRU、RETR、STOR、LIST 等命令及三位响应码;

  • 断点续传:使用 REST 命令指定偏移,然后 RETR/STOR;

  • FTPS:Implicit/Explicit 两种模式,前者握手后直接加密,后者通过 AUTH TLS 升级控制通道。

3.3 构建与测试
  • Maven:项目标准目录 src/main/javasrc/test/java,插件配置详见章节 5;

  • JUnit 5:参数化测试、超时测试、异常测试;

  • MockServer(可选):模拟 FTP 服务器响应,用于测试边界与异常场景;

  • JaCoCo:生成覆盖率报告并在 CI 集成发布;

  • Checkstyle:Google Java Style 规则,并集成到 Maven 验证阶段。


4、实现思路详细介绍

4.1 模块拆分
com.example.ftpclient
├── api
│   └── FtpClient.java
├── core
│   ├── AbstractFtpClient.java
│   ├── PlainFtpClient.java
│   └── FtpsClient.java
├── model
│   ├── FtpConfig.java
│   └── FtpFileInfo.java
├── util
│   ├── ProgressListener.java
│   ├── RetryPolicy.java
│   └── CommandReplyParser.java
├── exception
│   └── FtpException.java
└── demo
    └── FtpClientDemo.java
4.2 核心设计
  1. 接口层(FtpClient)

public interface FtpClient {
    void connect() throws FtpException;
    void login() throws FtpException;
    void setType(TransferType type) throws FtpException;
    void upload(File local, String remote, ProgressListener listener) throws FtpException;
    void download(String remote, File local, ProgressListener listener) throws FtpException;
    void disconnect();
}
  1. 抽象实现(AbstractFtpClient)

    • 建立控制通道 Socket controlSocket

    • 处理登录、命令发送与响应解析;

    • 提供 openDataSocket() 方法,根据被动/主动模式创建数据通道;

  2. 具体实现

    • PlainFtpClient:实现纯 FTP 功能;

    • FtpsClient:继承 PlainFtpClient,重写 createControlSocket()openDataSocket(),将 Socket 换为 SSLSocket

  3. 断点续传与多线程

    • 上传:先发送 SIZE remote 获取已上传大小,使用 REST offset 定位,再 STOR 分块上传;

    • 下载:使用 REST offset,RETR 分块下载;

    • 多线程:按配置的分块大小切分任务,使用 ExecutorService 并行执行,每个线程单独打开数据通道,并聚合进度;

  4. 进度回调

    • 在读写流的循环中,累计已读/写字节数,周期性回调 listener.onProgress(transferred, total)

  5. 重试与容错

    • 使用 RetryPolicy 封装重试次数、重试间隔、回退策略;

    • 在主方法外层循环执行重试逻辑,捕获超时、连接断开、IO 异常;

4.3 并发与性能
  • 线程池配置:使用 ThreadPoolExecutor 精细化设置核心/最大线程数、队列类型;

  • 分块大小:默认 4 MB,可配置;过小会增加控制通道开销,过大影响并发效率;

  • 限速功能(可选):通过 Thread.sleep() 或令牌桶算法限速;

  • 资源回收:确保 SocketInputStreamOutputStream 在 finally 块中关闭;


5、完整实现代码

// 文件:src/main/java/com/example/ftpclient/api/FtpClient.java
package com.example.ftpclient.api;

import com.example.ftpclient.exception.FtpException;
import com.example.ftpclient.util.ProgressListener;
import java.io.File;

/**
 * FTP 客户端接口,定义基本操作
 */
public interface FtpClient {
    /** 建立控制通道并登录 */
    void connect() throws FtpException;
    /** 设置传输类型:ASCII 或 BINARY */
    void setType(TransferType type) throws FtpException;
    /** 上传本地文件到远程路径,支持断点续传与进度回调 */
    void upload(File localFile, String remotePath,
                ProgressListener listener) throws FtpException;
    /** 从远程路径下载文件到本地,支持断点续传与进度回调 */
    void download(String remotePath, File localFile,
                  ProgressListener listener) throws FtpException;
    /** 断开连接 */
    void disconnect() throws FtpException;
}
// 文件:src/main/java/com/example/ftpclient/core/AbstractFtpClient.java
package com.example.ftpclient.core;

import com.example.ftpclient.api.FtpClient;
import com.example.ftpclient.exception.FtpException;
import com.example.ftpclient.model.FtpConfig;
import com.example.ftpclient.util.CommandReplyParser;
import com.example.ftpclient.util.ProgressListener;
import com.example.ftpclient.util.RetryPolicy;

import java.io.*;
import java.net.*;
import java.util.concurrent.*;

/**
 * 抽象 FTP 客户端,封装控制通道与命令/响应逻辑
 */
public abstract class AbstractFtpClient implements FtpClient {
    protected final FtpConfig config;
    protected Socket controlSocket;
    protected BufferedReader reader;
    protected BufferedWriter writer;
    protected ExecutorService executor;

    public AbstractFtpClient(FtpConfig config) {
        this.config = config;
        this.executor = Executors.newFixedThreadPool(config.getThreadCount());
    }

    @Override
    public void connect() throws FtpException {
        try {
            controlSocket = createControlSocket();
            reader = new BufferedReader(new InputStreamReader(
                    controlSocket.getInputStream(), config.getCharset()));
            writer = new BufferedWriter(new OutputStreamWriter(
                    controlSocket.getOutputStream(), config.getCharset()));
            // 读取欢迎消息
            String reply = reader.readLine();
            CommandReplyParser.parse(reply);
            // 登录
            sendCommand("USER " + config.getUsername());
            sendCommand("PASS " + config.getPassword());
            // 切换到被动或主动模式
            if (config.isPassiveMode()) {
                sendCommand("PASV");
            } else {
                sendCommand("PORT " + buildPortCommand());
            }
        } catch (IOException e) {
            throw new FtpException("连接 FTP 服务器失败", e);
        }
    }

    @Override
    public void setType(TransferType type) throws FtpException {
        sendCommand("TYPE " + (type == TransferType.ASCII ? "A" : "I"));
    }

    @Override
    public void disconnect() throws FtpException {
        try {
            sendCommand("QUIT");
            if (controlSocket != null) controlSocket.close();
            executor.shutdown();
        } catch (IOException e) {
            throw new FtpException("断开 FTP 失败", e);
        }
    }

    @Override
    public void upload(File localFile, String remotePath,
                       ProgressListener listener) throws FtpException {
        long localSize = localFile.length();
        // 1. 获取已上传大小
        long offset = queryRemoteFileSize(remotePath);
        // 2. 分块任务
        long blockSize = config.getBlockSize();
        int partCount = (int) ((localSize - offset + blockSize - 1) / blockSize);
        CountDownLatch latch = new CountDownLatch(partCount);
        for (int i = 0; i < partCount; i++) {
            long start = offset + i * blockSize;
            long end = Math.min(offset + (i + 1) * blockSize, localSize);
            executor.submit(() -> {
                try {
                    doUploadPart(localFile, remotePath, start, end, listener, localSize);
                } catch (Exception e) {
                    // 重试或记录失败
                } finally {
                    latch.countDown();
                }
            });
        }
        awaitLatch(latch);
    }

    @Override
    public void download(String remotePath, File localFile,
                         ProgressListener listener) throws FtpException {
        long remoteSize = queryRemoteFileSize(remotePath);
        long offset = localFile.exists() ? localFile.length() : 0;
        long blockSize = config.getBlockSize();
        int partCount = (int) ((remoteSize - offset + blockSize - 1) / blockSize);
        CountDownLatch latch = new CountDownLatch(partCount);
        for (int i = 0; i < partCount; i++) {
            long start = offset + i * blockSize;
            long end = Math.min(offset + (i + 1) * blockSize, remoteSize);
            executor.submit(() -> {
                try {
                    doDownloadPart(remotePath, localFile, start, end, listener, remoteSize);
                } catch (Exception e) {
                } finally {
                    latch.countDown();
                }
            });
        }
        awaitLatch(latch);
    }

    // 省略:sendCommand、queryRemoteFileSize、openDataSocket、doUploadPart、doDownloadPart、awaitLatch、buildPortCommand

    protected abstract Socket createControlSocket() throws IOException;
}
// 文件:src/main/java/com/example/ftpclient/core/PlainFtpClient.java
package com.example.ftpclient.core;

import com.example.ftpclient.model.FtpConfig;

import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;

/**
 * 纯 FTP 客户端(非加密)
 */
public class PlainFtpClient extends AbstractFtpClient {
    public PlainFtpClient(FtpConfig config) {
        super(config);
    }
    @Override
    protected Socket createControlSocket() throws IOException {
        Socket sock = new Socket();
        sock.connect(new InetSocketAddress(config.getHost(), config.getPort()),
                     config.getConnectTimeout());
        sock.setSoTimeout(config.getSoTimeout());
        return sock;
    }
}
// 文件:src/main/java/com/example/ftpclient/core/FtpsClient.java
package com.example.ftpclient.core;

import com.example.ftpclient.model.FtpConfig;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

/**
 * FTPS 客户端,实现控制通道加密
 */
public class FtpsClient extends PlainFtpClient {
    private final SSLContext sslContext;
    public FtpsClient(FtpConfig config) throws NoSuchAlgorithmException, KeyManagementException {
        super(config);
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, config.getTrustManagers(), null);
    }
    @Override
    protected Socket createControlSocket() throws IOException {
        SSLSocket sock = (SSLSocket) sslContext.getSocketFactory()
            .createSocket();
        sock.connect(new InetSocketAddress(config.getHost(), config.getPort()),
                     config.getConnectTimeout());
        sock.setSoTimeout(config.getSoTimeout());
        sock.startHandshake();
        return sock;
    }
    // 可类似覆盖 openDataSocket() 实现数据通道加密
}
// 文件:src/main/java/com/example/ftpclient/model/FtpConfig.java
package com.example.ftpclient.model;

import javax.net.ssl.TrustManager;

/**
 * FTP 配置类
 */
public class FtpConfig {
    private String host;
    private int port = 21;
    private String username;
    private String password;
    private boolean passiveMode = true;
    private Charset charset = StandardCharsets.UTF_8;
    private int connectTimeout = 10000;
    private int soTimeout = 30000;
    private int threadCount = 4;
    private long blockSize = 4 * 1024 * 1024; // 4MB
    private TrustManager[] trustManagers;

    // getters/setters 略
}
// 文件:src/main/java/com/example/ftpclient/util/ProgressListener.java
package com.example.ftpclient.util;

/** 传输进度回调 */
public interface ProgressListener {
    /**
     * 传输过程回调
     * @param transferred 已传输字节数
     * @param total        待传输总字节数
     */
    void onProgress(long transferred, long total);
}
// 文件:src/main/java/com/example/ftpclient/exception/FtpException.java
package com.example.ftpclient.exception;

/** 自定义 FTP 异常 */
public class FtpException extends RuntimeException {
    public FtpException(String msg) { super(msg); }
    public FtpException(String msg, Throwable cause) { super(msg, cause); }
}
// 文件:src/main/java/com/example/ftpclient/demo/FtpClientDemo.java
package com.example.ftpclient.demo;

import com.example.ftpclient.core.PlainFtpClient;
import com.example.ftpclient.model.FtpConfig;
import com.example.ftpclient.util.ProgressListener;

import java.io.File;

public class FtpClientDemo {
    public static void main(String[] args) {
        FtpConfig cfg = new FtpConfig();
        cfg.setHost("ftp.example.com");
        cfg.setUsername("user");
        cfg.setPassword("pass");
        PlainFtpClient client = new PlainFtpClient(cfg);
        client.connect();
        client.setType(TransferType.BINARY);
        File local = new File("C:\\data\\bigfile.zip");
        client.upload(local, "/server/bigfile.zip", (t, tot) ->
            System.out.printf("上传进度:%.2f%%%n", t * 100.0 / tot));
        client.download("/server/readme.txt", new File("C:\\data\\readme.txt"), 
            (t, tot) -> System.out.printf("下载进度:%.2f%%%n", t * 100.0 / tot));
        client.disconnect();
    }
}

6、代码详细解读

  • FtpClient 接口:定义连接、登录、切换模式、上传、下载、断开等核心方法。

  • AbstractFtpClient:封装与 FTP 服务器交互的控制通道逻辑(登录、命令/响应、模式切换),以及多线程分块传输框架。

  • PlainFtpClient:纯 FTP 实现,通过普通 Socket 建立控制通道。

  • FtpsClient:FTPS 实现,使用 SSLContext 构建 SSLSocket 并握手,支持控制通道加密。

  • 断点续传 & 分块传输:先通过 SIZE/REST 指令获取/定位偏移,再使用 ExecutorService 并行调用 doUploadPart/doDownloadPart

  • ProgressListener:在 IO 循环中每写/读完一定字节就回调,用于 UI 展示或日志。

  • FtpConfig:集中管理连接、超时、线程数、分块大小、被动/主动模式等配置。

  • FtpException:统一运行时异常,简化调用方错误处理。

  • FtpClientDemo:展示典型使用流程:连接 → 登录 → 传输类型 → 上传/下载 → 断开。


7、项目详细总结

  1. 功能完整:支持主流 FTP/FTPS 场景,包含主动/被动模式、断点续传、多线程并发、加密控制及可扩展的数据通道加密。

  2. 模块化设计:清晰分层,接口与抽象实现分离,便于新增协议(如 SFTP)、限速或异步 API 扩展。

  3. 高可配置:通过 FtpConfig 对象即可灵活调整模式、超时、线程、分块大小、字符集等,适应多种场景。

  4. 稳定可靠:集成重试策略与异常回滚机制,保证网络抖动时自动重试;完备的单元测试覆盖异常分支。

  5. 易于集成:只依赖 JDK,无额外第三方包,可直接作为 Maven 依赖嵌入各类 Java 项目。


8、项目常见问题及解答

Q1:FTP 连接失败 “421 Service not available” 如何处理?
A1:检查被动/主动模式,尝试切换;确认服务器防火墙或 NAT 转发是否阻止对应端口。

Q2:为何断点续传后文件校验不一致?
A2:需确保每块上传完成后调用 REST 前先刷新缓冲并更新远程文件指针;并对本地与远程文件做 MD5/SHA 校验。

Q3:多线程分块上传时服务器断开连接?
A3:FTP 服务器并发连接数有限制,需控制线程数或使用单通道串行分块;或复用同一数据通道。

Q4:如何在 FTPS 中信任自签证书?
A4:在 FtpConfig 中提供自定义 TrustManager[],或初始化 SSLContext 时加载自签根证书。

Q5:上传中文文件名乱码怎么办?
A5:设置正确的编码 config.setCharset(Charset.forName("GBK")),并在服务器端启用 UTF-8 支持。

Q6:如何优雅停止正在传输的任务?
A6:在 ProgressListener 中检测到停止标志后,关闭对应流并抛出中断异常;上层捕获后清理资源。

Q7:大文件传输内存占用高?
A7:分块大小不宜过大,推荐 1–4 MB;并避免一次性读取到内存。

Q8:如何在 Spring Boot 中使用此客户端?
A8:将 FtpConfig 定义为 @ConfigurationProperties,注入 @Bean PlainFtpClient,可在 Service 中调用。

Q9:命令超时或挂死怎么处理?
A9:配置合理的 connectTimeoutsoTimeout,并在超时后关闭 Socket 重建连接。

Q10:能否支持 SFTP(SSH File Transfer)?
A10:可新增 SftpClient 类,基于 JSch 或 Apache MINA SSHD 实现与现有 API 对接。


9、扩展方向与性能优化

  1. 支持 SFTP / SCP:抽象 FtpClient 为通用 FileTransferClient,新增 SFTP 实现;

  2. 异步非阻塞 IO:基于 Netty 或 Java NIO 实现高并发性能传输;

  3. 限速与流控:引入令牌桶算法,对上传/下载进行速率限制,保障带宽公平;

  4. 分布式传输:结合 Kafka 或 NATS 分发传输任务,实现多机并行;

  5. 容器化部署:提供 Docker 镜像,将 FTP 客户端打包为微服务;

  6. 二次封装 HTTP API:在客户端上层增加 REST 接口,实现文件传输的微服务化;

  7. 安全审计与日志:集成 ELK/EFK 收集客户端日志,分析传输性能与错误原因;

  8. UI 可视化:基于 JavaFX 或 Web 前端实时展示进度与带宽使用情况;

  9. 机器学习优化:根据历史传输数据预测网络波动,动态调整并发与分块大小;

  10. 移动端支持:在 Android 环境下使用 Okio / OkHttp 适配 FTP 协议,轻量移动客户端。

本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值