Java 轻量级调用 FTP 上传(安卓可用)

简介: Edit:6-19,独立一个标准 Java 项目了,参见在这里的源码。Edit:5-13 更新:为避免与原有 rt.jar 冲突,特意把 sun.* 包名修改,参见在这里的源码。 今时今日 HTTP 断点上传、WebSocket 断点上传兴起,FTP 协议大有被取代之势,不过 FTP 顾名思...

今时今日 HTTP 断点上传、WebSocket 断点上传兴起,FTP 协议大有被取代之势,不过 FTP 顾名思义,始终是为文件传输所服务的,具有 HTTP 所不具备的优势:首先是适合大文件、多批次上传,一般 10MB 甚至上 G 的文件亦可,而且进度条、断点、多线程上传等功能一应俱全;其次开发维护方便,——服务端可以用现成的方案,例如经典的 IIS  FTP、ServU 等,或者写一个服务器端的 FTP 文件接收程序来进行解释,但这样的轮子已经很多了,比如最近的关注 nodejs 也有开源的 FTP 服务方案,所以服务端推荐现成的即可,而且一点就是,服务端一般无需编码而是进行相关设置参数即可;客户端方案,更是有很多选择。姑且不说 Java 领域里面一大堆开源 FTP 项目,其实 Java 类库自带就有 FTP 客户端,只不过并非作为标准库出现,而且蕴含在 rt.jar 的 sun.net.* 下面;最后我想到 FTP 的优点是自带账号系统,甚至可以对权限进行细分。

笔者最开始使用的是 org.apache.commons.net.ftp.* 包,但相关的例子却找不到上传进度的,而且听说“用 FTPClient(apache)想要强行加入上传文件的速度检测比较麻烦”。令我沮丧,于是唯有试试其他方案。发现 Java 开源世界仍旧精彩,各种 FTP Client 比比皆是,


表格出处《用Java实现FTP批量大文件上传下载》

这么多怎么选择呢?如果是一般简单的上传下载,犯不着引入臃肿第三方库,使用 Sun 出品的 sun.net.ftp.FtpClient 即可满足。笔者试过 FtpClient 后,可以加入获取进度的功能,于是就可以抛弃 200多 kb 的  Apache FTP 包啦。——然而遗憾的是,在安卓项目中,Sun FTP 却无法顺利使用。

怎么说?盖因安卓的 JDK 自行体系,是没有加入 rt.jar 这样非标准库的包(而 Web 环境则无压力)。我百度了一下,虽然可以提供部分 FtpClient 源码但不是完整的。好在 Java 就是开源的,——转战谷歌搜索立马就搜到 JDK 的完整代码。开始的时候是 CSDN 下载的,不过够奇葩的是,


想想 Java 布尔类型可以用 0/1 表示那是多的老版本呀,故所以接着各种报错就在所难免。

于是再找新版本的的 JDK,——OpenJDK 1.7,这下可以有了,最新源码,注释也有,可是就是进化得“太先进”了,包分的太细,依赖 N 个类,抽出来太费力了。怎么办?退而求其次,来个 1.6 看怎么样,果然 1.6 的符合刚好够用的原则,主要是以下几个类。

下面是相关 API 说明。

sun.net.ftp.FtpClient 该类库主要提供了用于建立FTP连接的类。利用这些类的方法,编程人员可以远程登录到FTP服务器,列举该服务器上的目录,设置传输协议,以及传送文件。FtpClient类涵盖了几乎所有FTP的功能,FtpClient的实例变量保存了有关建立"代理"的各种信息。

public static boolean useFtpProxy
  这个变量用于表明FTP传输过程中是否使用了一个代理,因此,它实际上是一个标记,此标记若为TRUE,表明使用了一个代理主机。

  public static String ftpProxyHost   此变量只有在变量useFtpProxy为TRUE时才有效,用于保存代理主机名。

 

  public static int ftpProxyPort   此变量只有在变量useFtpProxy为TRUE时才有效,用于保存代理主机的端口地址。

 

  FtpClient有三种不同形式的构造函数,如下所示:

  1、public FtpClient(String hostname,int port)    此构造函数利用给出的主机名和端口号建立一条FTP连接。

 

  2、public FtpClient(String hostname)
  此构造函数利用给出的主机名建立一条FTP连接,使用默认端口号。

 

  3、FtpClient()
  此构造函数将创建一FtpClient类,但不建立FTP连接。这时,FTP连接可以用openServer方法建立。

  一旦建立了类FtpClient,就可以用这个类的方法来打开与FTP服务器的连接。类ftpClient提供了如下两个可用于打开与FTP服务器之间的连接的方法。

  public void openServer(String hostname)
  这个方法用于建立一条与指定主机上的FTP服务器的连接,使用默认端口号。

  public void openServer(String host,int port)
  这个方法用于建立一条与指定主机、指定端口上的FTP服务器的连接。

 

  打开连接之后,接下来的工作是注册到FTP服务器。这时需要利用下面的方法。

  public void login(String username,String password)
  此方法利用参数username和password登录到FTP服务器。使用过Intemet的用户应该知道,匿名FTP服务器的登录用户名为anonymous,密码一般用自己的电子邮件地址。

 

  下面是FtpClient类所提供的一些控制命令。

  public void cd(String remoteDirectory):该命令用于把远程系统上的目录切换到参数remoteDirectory所指定的目录。
  public void cdUp():该命令用于把远程系统上的目录切换到上一级目录。
  public String pwd():该命令可显示远程系统上的目录状态。
  public void binary():该命令可把传输格式设置为二进制格式。
  public void ascii():该命令可把传输协议设置为ASCII码格式。
  public void rename(String string,String string1):该命令可对远程系统上的目录或者文件进行重命名操作。

 

  除了上述方法外,类FtpClient还提供了可用于传递并检索目录清单和文件的若干方法。这些方法返回的是可供读或写的输入、输出流。下面是其中一些主要的方法。

  public TelnetInputStream list()
  返回与远程机器上当前目录相对应的输入流。

 

  public TelnetInputStream get(String filename)
  获取远程机器上的文件filename,借助TelnetInputStream把该文件传送到本地。

 

  public TelnetOutputStream put(String filename)
  以写方式打开一输出流,通过这一输出流把文件filename传送到远程计算机。

至此,我们已经把 sun.net.ftp 的包单独抽取出来,安卓也可用使用了。

下面是调用的例子,主要是 UploadFtp 工具类,继承 FtpClient。UploadProgressListener 是上传进度条控制器,你可以修改其中的 update() 通知 UI 更新进度条。

package ftp;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import sun.net.TelnetInputStream;
import sun.net.TelnetOutputStream;
import sun.net.ftp.FtpClient2;

public class UploadFtp extends FtpClient2 {
    public UploadFtp(String server, int port) throws IOException  {
        super(server, port);
    }

    /**
     * 用书上传本地文件到ftp服务器上
     * 
     * @param source
     *            上传文件的本地路径
     * @param target
     *            上传到ftp的文件路径
     * @return
     */
    public void upload(String source, String target) {
        TelnetOutputStream ftp = null;
        InputStream file = null;
        
        try {
            binary();
            
            ftp = put(target);
            file = new FileInputStream(new File(source));
            BufferedInputStream in = new BufferedInputStream(file);
            
            new ProgressListener().copy(in, new BufferedOutputStream(ftp), in.available());
            
            System.out.print("put file suc from " + source + "   to  " + target + "\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(ftp != null)ftp.close();
                if(file != null)file.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 从ftp上下载所需要的文件
     * 
     * @param source
     *            在ftp上路径及文件名
     * @param target
     *            要保存的本地的路径
     * @return
     */
    public void getFile(String source, String target) {
        TelnetInputStream ftp = null;
        OutputStream file = null;
        
        try {
            binary();
            ftp = get(source);
            file = new FileOutputStream(new File(target));
            
            ProgressListener listener = new ProgressListener();
            listener.setFileName(target);
            listener.copy(
                new BufferedInputStream(ftp), 
                new BufferedOutputStream(file), 
                getFileSize(source, ftp)
            );
    
            System.out.print("get file suc from " + source + "   to  " + target + "\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(ftp != null)ftp.close();
                if(file != null)file.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 为了计算下载速度和百分比,读取ftp该文件的大小
     * 
     * @param source
     * @param ftp
     * @return
     * @throws IOException
     */
    private int getFileSize(String source, TelnetInputStream ftp) throws IOException {
        // 这里的组合使用是必须得 sendServer 后到 readServerResponse
        sendServer("SIZE " + source + "\r\n");
        
        if (readServerResponse() == 213) {
            String msg = getResponseString();
            
            try {
                return Integer.parseInt(msg.substring(3).trim());
            } catch (Exception e) {}
        }
        
        return 0;
    }

    public static void main(String[] args)  {
        UploadFtp client;
        try {
            client = new UploadFtp("192.168.61.83", 21);
            client.login("upup", "upup@123");
//            client.PutFile("D:\\code.jar", "/test/1344439.jar");
            client.upload("E:\\aa.mp4", "/temp/zzz.mp4");
            client.closeServer();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

package ftp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 上传进度
 *
 */
public class ProgressListener {
    private String fileName;
    private volatile long bytesRead;
    private volatile long contentLength;

    public void update(long aBytesRead, long aContentLength) {
        bytesRead = aBytesRead / 1024L;
        contentLength = aContentLength / 1024L;

        // long megaBytes = aBytesRead / 1048576L;

        System.out.println("上传或者下载文件:" + fileName + ",文件的大小:" + aBytesRead + "/" + aContentLength);
    }

    /**
     * 
     * @param pIn
     * @param pOut
     * @return
     * @throws IOException
     */
    public long copy(InputStream in, OutputStream out, long size) {
        byte[] buffer = new byte[8192];

        long total = 0L;
        int res;

        try {
            while (true) {
                res = in.read(buffer);
                if (res == -1) {
                    break;
                }
                if (res > 0) {
                    total += res;
                    if (out != null) {
                        out.write(buffer, 0, res);
                        System.out.println("文件的大小" + size + "读取的大小" + total);
                        update(total, size);
                    }
                }
            }
                
            return total;
        }catch (IOException e) {
            return 0L;
        }  finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
            }
        }
    }

    public long getBytesRead() {
        return this.bytesRead;
    }

    public long getContentLength() {
        return this.contentLength;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }
}

使用过程中遇到一个问题,就是不知道为什么不能实例化 FtpClient。但是把 FtpClient 改名字就好了。 *"错误: 在类 sun.net.ftp.FtpClient 中找不到主方法, 请将主方法定义为:public static void main(String[] args)。

目录
相关文章
|
29天前
|
JSON 自然语言处理 Java
这款轻量级 Java 表达式引擎,真不错!
AviatorScript 是一个高性能、轻量级的脚本语言,基于 JVM(包括 Android 平台)。它支持数字、字符串、正则表达式、布尔值等基本类型,以及所有 Java 运算符。主要特性包括函数式编程、大整数和高精度运算、完整的脚本语法、丰富的内置函数和自定义函数支持。适用于规则判断、公式计算、动态脚本控制等场景。
|
2月前
|
安全 Java API
Java 泛型在安卓开发中的应用
在Android开发中,Java泛型广泛应用于集合类、自定义泛型类/方法、数据绑定、适配器及网络请求等场景,有助于实现类型安全、代码复用和提高可读性。例如,结合`ArrayList`使用泛型可避免类型转换错误;自定义泛型类如`ApiResponse<T>`可处理不同类型API响应;RecyclerView适配器利用泛型支持多种视图数据;Retrofit结合泛型定义响应模型,明确数据类型。然而,需注意类型擦除导致的信息丢失问题。合理使用泛型能显著提升代码质量和应用健壮性。
46 7
|
3月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
159 1
|
4月前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!
|
4月前
|
Java 调度 Android开发
Android经典实战之Kotlin的delay函数和Java中的Thread.sleep有什么不同?
本文介绍了 Kotlin 中的 `delay` 函数与 Java 中 `Thread.sleep` 方法的区别。两者均可暂停代码执行,但 `delay` 适用于协程,非阻塞且高效;`Thread.sleep` 则阻塞当前线程。理解这些差异有助于提高程序效率与可读性。
84 1
|
4月前
|
Android开发
Cannot create android app from an archive...containing both DEX and Java-bytecode content
Cannot create android app from an archive...containing both DEX and Java-bytecode content
41 2
|
4月前
|
Java Android开发
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
567 1
|
4月前
|
IDE Java Linux
探索安卓开发:从基础到进阶的旅程Java中的异常处理:从基础到高级
【8月更文挑战第30天】在这个数字时代,移动应用已经成为我们日常生活中不可或缺的一部分。安卓系统由于其开放性和灵活性,成为了开发者的首选平台之一。本文将带领读者踏上一段从零开始的安卓开发之旅,通过深入浅出的方式介绍安卓开发的基础知识、核心概念以及进阶技巧。我们将一起构建一个简单的安卓应用,并探讨如何优化代码以提高性能和应用的用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供宝贵的知识和启发。
|
5月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
148 4
|
5月前
|
SQL Java Unix
Android经典面试题之Java中获取时间戳的方式有哪些?有什么区别?
在Java中获取时间戳有多种方式,包括`System.currentTimeMillis()`(毫秒级,适用于日志和计时)、`System.nanoTime()`(纳秒级,高精度计时)、`Instant.now().toEpochMilli()`(毫秒级,ISO-8601标准)和`Instant.now().getEpochSecond()`(秒级)。`Timestamp.valueOf(LocalDateTime.now()).getTime()`适用于数据库操作。选择方法取决于精度、用途和时间起点的需求。
68 3