FTPClient自定义连接池实现

最新在工作当中使用到了ftp服务器,我是使用org.apache.commons.net.ftp.FTPClient做的连接,但是在测试调用的时候经常报断开的管道错误,就此问题我做了一些分析和总结,是因为在调用的时候我只new了一个对象,而使用了多线程进行调用,也就是同一个对象进行了多次调用,而每次调用都有调disconnect()方法,所有再调用的时候就报了断开的管道错误。

  • 针对上面的问题,我首先想到了使用连接池来避免这种底层的错误,而FTPClient没有提供连接池,就此我就手工写了一个连接池。
  • 下面是实现代码,总共用三个类,在实际使用的时候需要根据业务场景做一些轻微的修改。
    • FTPClientConnectPool:FTPClinet 连接池抽象类
    • FTPClientConnectPoolImpl:FTPClient 连接池具体实现类
    • ConnectTest:连接测试类
package com.jin.demo.lock;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.IOException;
import java.util.LinkedList;

/**
 * FTPClinet 连接池抽象类
 *
 * @auther jinsx
 * @date 2019-01-02 10:24
 */
public abstract class FTPClientConnectPool {
    /**
     * 默认最小连接数
     */
    protected int initCount = 3;
    /**
     * 默认最大连接数
     */
    protected int initMaxCount = 5;
    /**
     * 当前正在使用的连接数
     */
    protected int connectCount = 0;
    /**
     * 获取连接默认等待超时时间
     */
    protected Long waitTime = 10000L;
    /**
     * 获取锁默认等待超时时间,防止死锁
     */
    protected Long waitTimeLock = 30000L;
    /**
     * hostname
     */
    protected String hostname = "192.168.0.100";
    /**
     * port
     */
    protected int port = 21;
    /**
     * username
     */
    protected String username = "jin01";
    /**
     * password
     */
    protected String password = "123456";
    /**
     * ftp默认工作目录
     */
    protected String path = "/";
    /**
     * 定义存放FTPClient的连接池
     */
    protected LinkedList<FTPClient> pool = new LinkedList<>();


    /**
     * 获取FTPClient连接对象
     *
     * @return
     */
    protected abstract FTPClient getFTPClient();

    /**
     * 释放FTPClient连接对象
     *
     * @param ftp
     */
    protected abstract void releaseFTPClient(FTPClient ftp);

    /**
     * 初始化FTPClient连接对象
     *
     * @return
     */
    protected FTPClient initFTPClient() {
        FTPClient ftp = new FTPClient();
        try {
            ftp.connect(hostname, port);
            if(!FTPReply.isPositiveCompletion(ftp.getReplyCode())){
                ftp.disconnect();
                System.out.println("FTPServer refused connection");
            }
            boolean login = ftp.login(username, password);
            if(!login){
                ftp.disconnect();
                System.out.println("FTPServer login failed");
            }
            ftp.setControlEncoding("UTF-8");
            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftp.changeWorkingDirectory(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("initFTPClient result : " + ftp.getReplyString());
        return ftp;
    }

    /**
     * 销毁FTPClient连接对象
     *
     * @param ftp
     */
    protected void destroyFTPClient(FTPClient ftp) {
        try {
            if (ftp != null && ftp.isConnected()) {
                ftp.logout();
            }
        } catch (IOException io) {
            io.printStackTrace();
        } finally {
            // 注意,一定要在finally代码中断开连接,否则会导致占用ftp连接情况
            try {
                if (ftp != null) {
                    ftp.disconnect();
                }
            } catch (IOException io) {
                io.printStackTrace();
            }
        }
    }

    /**
     * 验证FTPClient是否可用
     * @param ftp
     * @return
     */
    protected boolean validateFTPClient(FTPClient ftp) {
        try {
            return ftp.sendNoOp();
        } catch (IOException e) {
            throw new RuntimeException("Failed to validate client: " + e, e);
        }
    }
    public int getInitCount() {
        return initCount;
    }
    public void setInitCount(int initCount) {
        this.initCount = initCount;
    }
    public int getInitMaxCount() {
        return initMaxCount;
    }
    public void setInitMaxCount(int initMaxCount) {
        this.initMaxCount = initMaxCount;
    }
    public int getConnectCount() {
        return connectCount;
    }
    public void setConnectCount(int connectCount) {
        this.connectCount = connectCount;
    }
    public Long getWaitTime() {
        return waitTime;
    }
    public void setWaitTime(Long waitTime) {
        this.waitTime = waitTime;
    }
    public Long getWaitTimeLock() {
        return waitTimeLock;
    }
    public void setWaitTimeLock(Long waitTimeLock) {
        this.waitTimeLock = waitTimeLock;
    }
    public String getHostname() {
        return hostname;
    }
    public void setHostname(String hostname) {
        this.hostname = hostname;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public LinkedList<FTPClient> getPool() {
        return pool;
    }
    public void setPool(LinkedList<FTPClient> pool) {
        this.pool = pool;
    }
}
package com.jin.demo.lock;

import org.apache.commons.net.ftp.FTPClient;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * FTPClient 连接池具体实现类
 *
 * @auther jinsx
 * @date 2019-01-02 10:46
 */
public class FTPClientConnectPoolImpl extends FTPClientConnectPool {

    private static final FTPClientConnectPoolImpl FTP_CLIENT_CONNECT_POOL = new FTPClientConnectPoolImpl();
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    /***
     * 私有构造器,并初始化FTPClient连接池
     */
    private FTPClientConnectPoolImpl() {
        for (int i = 0; i < super.initCount; i++) {
            FTPClient ftp = super.initFTPClient();
            super.pool.addLast(ftp);
        }
    }

    /**
     * 提供对外获取实例的静态方法
     *
     * @return
     */
    public static FTPClientConnectPoolImpl getInstance() {
        return FTP_CLIENT_CONNECT_POOL;
    }

    @Override
    protected FTPClient getFTPClient() {
        lock.lock();
        try {
            // 1.计算超时结束时间
            Long endTime = System.currentTimeMillis() + waitTime;
            while (endTime >= System.currentTimeMillis()) {
                if (super.pool.isEmpty()) {
                    // 2.连接池中没有连接对象,进入等待
                    System.out.println(Thread.currentThread().getName() + "进入等待");
                    condition.await(endTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
                }
                if (!super.pool.isEmpty()) {
                    // 3.从连接池中取出一个连接对象
                    FTPClient ftp = pool.removeFirst();
                    // 4.验证是否可用
                    if (super.validateFTPClient(ftp)) {
                        return ftp;
                    } else {
                        // 5.销毁不可用连接对象
                        super.destroyFTPClient(ftp);
                        // 6.重新创建一个连接对象
                        return super.initFTPClient();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        // 4.返回null或抛出异常
        System.out.println(Thread.currentThread().getName() + "获取超时返回null");
        return null;
    }

    @Override
    protected void releaseFTPClient(FTPClient ftp) {
        lock.lock();
        try {
            // 1.如果连接池没有满,并且ftp可用,则放到连接池中
            if (super.pool.size() < super.initCount && ftp != null && super.validateFTPClient(ftp)) {
                ftp.changeWorkingDirectory(super.path);
                super.pool.addLast(ftp);
            } else {
                super.destroyFTPClient(ftp);
            }
            // 2.如果连接池有连接,则唤醒一个等待
            if (!super.pool.isEmpty()) {
                condition.signal();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
package com.jin.demo.lock;

import org.apache.commons.net.ftp.FTPClient;

/**
 * 连接测试类
 * @auther jinsx
 * @date 2019-01-02 09:10
 */
public class ConnectTest {

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "前" + System.currentTimeMillis());
                    FTPClientConnectPoolImpl pool = FTPClientConnectPoolImpl.getInstance();
                    FTPClient ftp = pool.getFTPClient();
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "后" + System.currentTimeMillis() + ftp);
                    pool.releaseFTPClient(ftp);
                }
            }).start();
        }
    }

}
经过几天的琢磨,去看了csdn上一位大牛的数据库的连接池实现方案,从中感悟很多,感谢这位大神。 让我才能有信心去坚持下去。也不知道写的好不好··不好的话,大家指出。但是我是努力去做了,这一个过程,很享受,大家互相学习吧~ 其实ftp连接池跟数据库连接池的原理是差不多的,不同的是ftp连接池有个连接时间的限制,如果你没设置的话,它的默认连接服务器的时间是0,所以我们要合理的设置它的服务器的时间,ftp.setConnectTimeout(5000);在这里设置了它的时间是5s。 写ftp连接池的目的就是合理的利用资源,本文的目的是在初始的时候,创建10个Ftp连接,放到一个队列中去,当多个用户同时去下载ftp上的文件的时候,就会从队列中取,若当前的队列中存在着空闲的连接,就获取该ftp的连接,并设置此连接为忙的状态,否则就在创建新的连接到连接池中去(有最大的连接池数的限制,不能超过这个连接数,超过的话,就会进入等待状态,直到其它连接释放连接),在执行下载操作的前对登录ftp时间进行判断。看是否超时,超时的话,就重新连接到ftp服务器,在这里我所做的操作就是,在开始创建ftp连接池的时候,记录下系统的当前时间,例如为:long beginTime=System.currentTimeMillis(),在取文件之前获得 当前系统的时间 long endTime=System.currentTimeMillis(),此时我们就可以获得系统登录ftp的时间time=endTime-beginTime,在此我们可以用time与ftp最大登录服务器时间(ftpPool.getConnection();)进行比较。 当然了,在操作完之后我们需要将所操作的连接池中的ftp设置为空闲状态。代码在文件中,为了测试,我本地自己创建了一个ftp服务器,创建ftp的方法,大家可以到网上查资料,我用的是Serv-U工具。傻瓜式的。所用到的jar包是commons-net2.0.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值