java實(shí)現(xiàn)ftp連接池

前言

由于工作需要使用到ftp服務(wù)睬棚,一開(kāi)始是每次建立ftp連接,上傳文件成功后杜秸,再釋放連接拼余,后來(lái)發(fā)現(xiàn)這個(gè)方法太浪費(fèi)資源和時(shí)間了,就想到了使用ftp連接池的方式實(shí)現(xiàn)亩歹,這樣匙监,預(yù)先創(chuàng)建好ftp連接池,需要上傳的時(shí)候從池子取一個(gè)連接小作,上傳成功后再放回池子即可亭姥,省下了創(chuàng)建和釋放ftp連接的時(shí)間。

實(shí)現(xiàn)

ftp服務(wù)的配置文件

config.properties配置好ftp服務(wù)

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

FtpClientConfig

FtpClientConfig是用于讀取config.properties的一個(gè)實(shí)體類

public class FtpClientConfig {

    private String host;

    private int port;

    private String username;

    private String password;
    ...

FtpClientFactory

FtpClientFactory可以理解為一個(gè)工廠類顾稀,用于生成ftp連接达罗、銷毀ftp連接以及檢測(cè)ftp連接是否有效。

  • 生成ftp連接

在生成ftp連接的時(shí)候静秆,我們可以設(shè)定連接的超時(shí)時(shí)間等粮揉,ftp有主動(dòng)模式被動(dòng)模式兩種模式。

  1. 主動(dòng)模式:FTP客戶端隨機(jī)開(kāi)啟一個(gè)大于1024的端口N向服務(wù)器的21號(hào)端口發(fā)起連接抚笔,然后開(kāi)放N+1號(hào)端口進(jìn)行監(jiān)聽(tīng)扶认,并向服務(wù)器發(fā)出PORT N+1命令。服務(wù)器接收到命令后殊橙,會(huì)用其本地的FTP數(shù)據(jù)端口(通常是20)來(lái)連接客戶端指定的端口N+1辐宾,進(jìn)行數(shù)據(jù)傳輸
  2. 被動(dòng)模式:FTP客戶端隨機(jī)開(kāi)啟一個(gè)大于1024的端口N向服務(wù)器的21號(hào)端口發(fā)起連接,同時(shí)會(huì)開(kāi)啟N+1號(hào)端口膨蛮。然后向服務(wù)器發(fā)送PASV命令叠纹,通知服務(wù)器自己處于被動(dòng)模式。服務(wù)器收到命令后敞葛,會(huì)開(kāi)放一個(gè)大于1024的端口P進(jìn)行監(jiān)聽(tīng)誉察,然后用PORT P命令通知客戶端,自己的數(shù)據(jù)端口是P惹谐〕制客戶端收到命令后,會(huì)通過(guò)N+1號(hào)端口連接服務(wù)器的端口P豺鼻,然后在兩個(gè)端口之間進(jìn)行數(shù)據(jù)傳輸综液。
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);
        //被動(dòng)模式 被動(dòng)模式是客戶端向服務(wù)端發(fā)送PASV命令,服務(wù)端隨機(jī)開(kāi)啟一個(gè)端口并通知客戶端儒飒,客戶端根據(jù)該端口與服務(wù)端建立連接,然后發(fā)送數(shù)據(jù)檩奠。服務(wù)端是兩種模式的桩了,
        //使用哪種模式取決于客戶端附帽,同時(shí)關(guān)鍵點(diǎn)在于網(wǎng)絡(luò)環(huán)境適合用哪種模式,比如客戶端在防火墻內(nèi)井誉,則最好選擇被動(dòng)模式
        //在mac下測(cè)試用被動(dòng)模式?jīng)]問(wèn)題蕉扮,用主動(dòng)模式則報(bào)錯(cuò),在linux服務(wù)器上則相反
        //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);
        }

    }
}
  • 檢測(cè)ftp連接
public boolean validateClient(FTPClient ftpClient) {
    try {
        return ftpClient.sendNoOp();
    } catch (Exception e) {
        log.error("ftpClient validate exception",e);
    }
    return false;
}

FtpClientPool

FtpClientPool就是我們真正使用的類颗圣,我們使用了BlockingQueue阻塞對(duì)列來(lái)實(shí)現(xiàn)連接池的效果喳钟,如果需要進(jìn)行ftp連接,就從連接池獲取一個(gè)連接在岂,完成后就把連接歸還到池子里奔则。使用阻塞對(duì)列是為了防止多線程時(shí)多個(gè)線程同時(shí)獲取了同一個(gè)ftp連接導(dǎo)致失敗。

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);
    }

}
  • 從阻塞對(duì)列獲取一個(gè)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;

}
  • 歸還一個(gè)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("歸還對(duì)象失敗",e);
        throw e;
    }
}

FtpClientKeepAlive

如果服務(wù)器設(shè)置了ftp連接在一段時(shí)間內(nèi)不使用會(huì)自動(dòng)斷開(kāi)連接蔽午,就會(huì)導(dǎo)致我們的連接超過(guò)時(shí)間就會(huì)失敗易茬,為了避免一直重復(fù)創(chuàng)建連接,這里使用了長(zhǎng)連接及老,FtpClientKeepAlive負(fù)責(zé)保持長(zhǎng)連接抽莱,如果連接失效,就重新創(chuàng)建連接骄恶。

根據(jù)服務(wù)器超時(shí)時(shí)間設(shè)置長(zhǎng)連接保持的時(shí)間食铐,每隔一段時(shí)間,從阻塞對(duì)列獲取連接來(lái)進(jìn)行驗(yàn)證僧鲁。

public class FtpClientKeepAlive {

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

    private KeepAliveThread keepAliveThread;

    @Autowired
    private FtpClientPool ftpClientPool;

    public void init() {
        // 啟動(dòng)心跳檢測(cè)線程
        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("心跳結(jié)果: {}",result);
                            if (!result) {
                                ftpClientPool.invalidateClient(ftpClient);
                            }
                        }

                    }
                } catch (Exception e) {
                    log.error("ftp心跳檢測(cè)異常", e);
                    ftpClientPool.invalidateClient(ftpClient);
                }

                // 每30s發(fā)送一次心跳璃岳,服務(wù)器超時(shí)時(shí)間為60s
                try {
                    Thread.sleep(1000 * 30);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    log.error("ftp休眠異常", e);
                }
            }

        }
    }
}

spring-ftp.xml

由于項(xiàng)目是使用spring的,所以在xml配置文件里進(jìn)行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>

源碼

詳細(xì)代碼請(qǐng)參考github铃慷。

https://github.com/wumingzhizhu/springTest

參考

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蜕该,隨后出現(xiàn)的幾起案子犁柜,更是在濱河造成了極大的恐慌,老刑警劉巖堂淡,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馋缅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绢淀,警方通過(guò)查閱死者的電腦和手機(jī)萤悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)皆的,“玉大人覆履,你說(shuō)我怎么就攤上這事。” “怎么了硝全?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵栖雾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我伟众,道長(zhǎng)析藕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任凳厢,我火速辦了婚禮账胧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘先紫。我一直安慰自己治泥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布泡孩。 她就那樣靜靜地躺著车摄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仑鸥。 梳的紋絲不亂的頭發(fā)上吮播,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音眼俊,去河邊找鬼意狠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疮胖,可吹牛的內(nèi)容都是我干的环戈。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼澎灸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼院塞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起性昭,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拦止,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后糜颠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汹族,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年其兴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顶瞒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡元旬,死狀恐怖榴徐,靈堂內(nèi)的尸體忽然破棺而出守问,到底是詐尸還是另有隱情,我是刑警寧澤箕速,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布酪碘,位于F島的核電站朋譬,受9級(jí)特大地震影響盐茎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜徙赢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一字柠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狡赐,春花似錦窑业、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至搀擂,卻和暖如春西潘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哨颂。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工喷市, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人威恼。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓品姓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親箫措。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腹备,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • ftp服務(wù)器: 參考資料:(詳細(xì)的配置參數(shù)介紹見(jiàn)此鏈接)http://blog.csdn.net/wave_110...
    點(diǎn)點(diǎn)漁火閱讀 1,504評(píng)論 0 0
  • 網(wǎng)絡(luò)編程 網(wǎng)絡(luò)編程對(duì)于很多的初學(xué)者來(lái)說(shuō),都是很向往的一種編程技能斤蔓,但是很多的初學(xué)者卻因?yàn)楹荛L(zhǎng)一段時(shí)間無(wú)法進(jìn)入網(wǎng)絡(luò)編...
    程序員歐陽(yáng)閱讀 2,006評(píng)論 1 37
  • 計(jì)算機(jī)網(wǎng)絡(luò)概述 網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(gè)(或多個(gè))設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸植酥。 按照計(jì)算機(jī)網(wǎng)絡(luò)的定義,通過(guò)一定...
    蛋炒飯_By閱讀 1,210評(píng)論 0 10
  • 老師上周發(fā)的附迷,就關(guān)鍵詞生物藝術(shù)去網(wǎng)上搜了一下惧互,稍微了解了一下。 這是最近中央美院里面的一個(gè)生物藝術(shù)工作坊招...
    5467lq閱讀 862評(píng)論 0 0
  • 你在設(shè)想未來(lái)的時(shí)候真的會(huì)分成有孩子和沒(méi)孩子兩種喇伯。 你是真的允許我選擇喊儡。 Eleven 對(duì)不起要你等我這么久。 每一...
    Ken_E閱讀 259評(píng)論 1 0