java实现ftp连接池

前言

由于工作需要使用到ftp服务,一开始是每次建立ftp连接,上传文件成功后,再释放连接,后来发现这个方法太浪费资源和时间了,就想到了使用ftp连接池的方式实现,这样,预先创建好ftp连接池,需要上传的时候从池子取一个连接,上传成功后再放回池子即可,省下了创建和释放ftp连接的时间。

实现

ftp服务的配置文件

config.properties配置好ftp服务

ftp.ip=127.0.0.1
ftp.username=root
ftp.password=root
ftp.port=21

FtpClientConfig

FtpClientConfig是用于读取config.properties的一个实体类

public class FtpClientConfig {

    private String host;

    private int port;

    private String username;

    private String password;
    ...

FtpClientFactory

FtpClientFactory可以理解为一个工厂类,用于生成ftp连接、销毁ftp连接以及检测ftp连接是否有效。

  • 生成ftp连接

在生成ftp连接的时候,我们可以设定连接的超时时间等,ftp有主动模式被动模式两种模式。

  1. 主动模式:FTP客户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,然后开放N+1号端口进行监听,并向服务器发出PORT N+1命令。服务器接收到命令后,会用其本地的FTP数据端口(通常是20)来连接客户端指定的端口N+1,进行数据传输
  2. 被动模式:FTP客户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,同时会开启N+1号端口。然后向服务器发送PASV命令,通知服务器自己处于被动模式。服务器收到命令后,会开放一个大于1024的端口P进行监听,然后用PORT P命令通知客户端,自己的数据端口是P。客户端收到命令后,会通过N+1号端口连接服务器的端口P,然后在两个端口之间进行数据传输。
public FTPClient makeClient() throws Exception{
    FTPClient ftpClient = new FTPClient();
    ftpClient.setConnectTimeout(1000 * 10);
    try {
        ftpClient.connect(config.getHost(), config.getPort());
        boolean result = ftpClient.login(config.getUsername(), config.getPassword());
        if(!result) {
            log.info("ftp登录失败,username: {}",config.getUsername());
            return null;
        }

        ftpClient.setControlEncoding(encode);
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        //被动模式 被动模式是客户端向服务端发送PASV命令,服务端随机开启一个端口并通知客户端,客户端根据该端口与服务端建立连接,然后发送数据。服务端是两种模式的,
        //使用哪种模式取决于客户端,同时关键点在于网络环境适合用哪种模式,比如客户端在防火墙内,则最好选择被动模式
        //在mac下测试用被动模式没问题,用主动模式则报错,在linux服务器上则相反
        //ftpClient.enterLocalPassiveMode();
        ftpClient.enterLocalActiveMode();

    } catch (Exception e) {
        log.error("makeClient exception",e);
        destroyClient(ftpClient);
        throw e;
    }
    return ftpClient;
}
  • 销毁ftp连接
public void destroyClient(FTPClient ftpClient) {
    try {
        if(ftpClient != null && ftpClient.isConnected()) {
            ftpClient.logout();
        }
    } catch (Exception e) {
        log.error("ftpClient logout exception",e);
    } finally {
        try {
            if(ftpClient != null) {
                ftpClient.disconnect();
            }
        } catch (Exception e2) {
            log.error("ftpClient disconnect exception",e2);
        }

    }
}
  • 检测ftp连接
public boolean validateClient(FTPClient ftpClient) {
    try {
        return ftpClient.sendNoOp();
    } catch (Exception e) {
        log.error("ftpClient validate exception",e);
    }
    return false;
}

FtpClientPool

FtpClientPool就是我们真正使用的类,我们使用了BlockingQueue阻塞对列来实现连接池的效果,如果需要进行ftp连接,就从连接池获取一个连接,完成后就把连接归还到池子里。使用阻塞对列是为了防止多线程时多个线程同时获取了同一个ftp连接导致失败。

private static final int DEFAULT_POOL_SIZE = 16;

private BlockingQueue<FTPClient> pool;

private FtpClientFactory factory;

public FtpClientPool(FtpClientFactory factory) {
    this(factory, DEFAULT_POOL_SIZE);
}

public FtpClientPool(FtpClientFactory factory,int size) {
    this.factory = factory;
    this.pool = new ArrayBlockingQueue<>(size);
    initPool(size);
}
  • 初始化
private void initPool(int maxPoolSize) {
    try {
        int count = 0;
        while (count < maxPoolSize) {
            pool.offer(factory.makeClient(),10,TimeUnit.SECONDS);
            count ++;
        }
    } catch (Exception e) {
        log.error("ftp连接池初始化失败",e);
    }

}
  • 从阻塞对列获取一个ftp连接
public FTPClient borrowClient() throws Exception{
    FTPClient client = pool.take();
    if(client == null) {
        client = factory.makeClient();
        //addClient(client);
        returnClient(client);
    }else if(!factory.validateClient(client)) {
        invalidateClient(client);
        client = factory.makeClient();
        //addClient(client);
        returnClient(client);
    }
    return client;

}
  • 归还一个ftp连接
public void returnClient(FTPClient ftpClient) throws Exception{
    try {
        if(ftpClient != null && !pool.offer(ftpClient, 10, TimeUnit.SECONDS)) {
            factory.destroyClient(ftpClient);
        }
    } catch (Exception e) {
        log.error("归还对象失败",e);
        throw e;
    }
}

FtpClientKeepAlive

如果服务器设置了ftp连接在一段时间内不使用会自动断开连接,就会导致我们的连接超过时间就会失败,为了避免一直重复创建连接,这里使用了长连接,FtpClientKeepAlive负责保持长连接,如果连接失效,就重新创建连接。

根据服务器超时时间设置长连接保持的时间,每隔一段时间,从阻塞对列获取连接来进行验证。

public class FtpClientKeepAlive {

    private static final Logger log = LoggerFactory.getLogger(FtpClientKeepAlive.class);

    private KeepAliveThread keepAliveThread;

    @Autowired
    private FtpClientPool ftpClientPool;

    public void init() {
        // 启动心跳检测线程
        if (keepAliveThread == null) {
            keepAliveThread = new KeepAliveThread();
            Thread thread = new Thread(keepAliveThread);
            thread.start();
        }
    }

    class KeepAliveThread implements Runnable {
        @Override
        public void run() {
            FTPClient ftpClient = null;
            while (true) {
                try {
                    BlockingQueue<FTPClient> pool = ftpClientPool.getPool();
                    if (pool != null && pool.size() > 0) {
                        Iterator<FTPClient> it = pool.iterator();
                        while (it.hasNext()) {
                            ftpClient = it.next();
                            boolean result = ftpClient.sendNoOp();
                            log.info("心跳结果: {}",result);
                            if (!result) {
                                ftpClientPool.invalidateClient(ftpClient);
                            }
                        }

                    }
                } catch (Exception e) {
                    log.error("ftp心跳检测异常", e);
                    ftpClientPool.invalidateClient(ftpClient);
                }

                // 每30s发送一次心跳,服务器超时时间为60s
                try {
                    Thread.sleep(1000 * 30);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    log.error("ftp休眠异常", e);
                }
            }

        }
    }
}

spring-ftp.xml

由于项目是使用spring的,所以在xml配置文件里进行bean的配置。

<!-- 省略了spring beans头部配置-->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:config.properties" />
    </bean>

    <bean id="ftpClientConfig" class="com.zeromk.study.util.FtpClientConfig">
        <property name="host" value="${ftp.ip}"/>
        <property name="port" value="${ftp.port}"/>
        <property name="username" value="${ftp.username}"/>
        <property name="password" value="${ftp.password}"/>
    </bean>

    <bean id="ftpClientFactory" class="com.zeromk.study.util.FtpClientFactory">
        <constructor-arg index="0" ref="ftpClientConfig"/>
    </bean>

    <bean id="ftpClientPool" class="com.zeromk.study.util.FtpClientPool">
        <constructor-arg index="0" ref="ftpClientFactory"/>
        <constructor-arg index="1" value="8"/>
    </bean>

    <bean id="ftpClientKeepAlive" class="com.zeromk.study.util.FtpClientKeepAlive" init-method="init">
    </bean>

源码

详细代码请参考github。

https://github.com/wumingzhizhu/springTest

参考

https://github.com/jimiyi/ftpService
http://blog.51cto.com/11010174/1983978

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • ftp服务器: 参考资料:(详细的配置参数介绍见此链接)http://blog.csdn.net/wave_110...
    点点渔火阅读 1,504评论 0 0
  • 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编...
    程序员欧阳阅读 2,004评论 1 37
  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,210评论 0 10
  • 老师上周发的,就关键词生物艺术去网上搜了一下,稍微了解了一下。 这是最近中央美院里面的一个生物艺术工作坊招...
    5467lq阅读 862评论 0 0
  • 你在设想未来的时候真的会分成有孩子和没孩子两种。 你是真的允许我选择。 Eleven 对不起要你等我这么久。 每一...
    Ken_E阅读 256评论 1 0