1. 程式人生 > >JAVA]Apache FTPClient操作“卡死”問題的分析和解決

JAVA]Apache FTPClient操作“卡死”問題的分析和解決

最近在和一個第三方的合作中不得已需要使用FTP檔案介面。由於FTP Server由對方提供,而且雙方背後各自的網路環境環境都很不單純等等原因,造成測試環境無法模擬實際情況。測試環境中程式一切正常,但是在部署到生產環境之後發現FTP操作不規律性出現“卡死”現象:程式捕獲不到任何異常一直卡著,導致輪巡無法正常工作(由於擔心在輪巡時間間隔內處理不能完成,我沒有采用類似quartz或者crontab的定時任務,而是採用while-true然後sleep的方式)。

    為了解決這個問題,我首先考慮的是對於FTPClient的使用上沒有設定超時時間,於是設定了ConnectTimeout、DataTimeout、DefaultTimeout後在生產環境上繼續觀察,但是問題依舊沒有解決。後來我有些懷疑FTPClient api本身是不是有什麼問題,想實在不行自己實現一個超時機制吧,不過還是不甘心,還是想從FTPClient api本身去解決問題。又經過一翻研究之後發現:需要使用被動模式,以下摘抄別人的一段簡單描述:
在專案中使用commons-net-3.0.1.jar實現FTP檔案的下載,在windows xp上執行正常,但是放到linux上,卻出現問題,程式執行到 FTPClient.listFiles()或者FTPClient.retrieveFile()方法時,就停止在那裡,什麼反應都沒有,出現假死狀態。google一把,發現很多人也出現了此類問題,最終在一個帖子裡找到了解決辦法。在呼叫這兩個方法之前,呼叫FTPClient.enterLocalPassiveMode();這個方法的意思就是每次資料連線之前,ftp client告訴ftp server開通一個埠來傳輸資料。為什麼要這樣做呢,因為ftp server可能每次開啟不同的埠來傳輸資料,但是在linux上,由於安全限制,可能某些埠沒有開啟,所以就出現阻塞。OK,問題解決。

    於是我回滾了之前的修改,改為被動模式(關於FTP主動/被動模式的解釋,這裡我不多說了,關注的朋友可以自己查閱)。但是問題依舊。於是能想到的就是最有的絕招:實在不行自己實現一個超時機制吧。經過一翻研究最簡單的方式就是使用:Future解決:

 1 public static void main(String[] args) throws InterruptedException, ExecutionException { 2     final ExecutorService exec = Executors.newFixedThreadPool(1); 3  4     Callable<String> call = new Callable<String>() { 5       public String call() throws Exception { 6         Thread.sleep(1000 * 5); 7         return "執行緒執行完成."; 8       } 9     };10 11     try {12       Future<String> future = exec.submit(call);13       String obj = future.get(4 * 1000, TimeUnit.MILLISECONDS); // 任務處理超時時間設定14       System.out.println("任務成功返回:" + obj);15     } catch (TimeoutException ex) {16       System.out.println("處理超時啦....");17       ex.printStackTrace();18     } catch (Exception e) {19       System.out.println("處理失敗.");20       e.printStackTrace();21     }22     // 關閉執行緒池23     exec.shutdown();24     25     System.out.println("完畢");26   }

當然了還有很多其他方式:
http://tech.sina.com.cn/s/2008-07-04/1051720260.shtml
http://itindex.net/blog/2010/08/11/1281486125717.html
http://darkmasky.iteye.com/blog/1115047
http://www.cnblogs.com/wasp520/archive/2012/07/06/2580101.html
http://coolxing.iteye.com/blog/1476289
http://www.cnblogs.com/chenying99/archive/2012/10/24/2737924.html

    雖然找到了終極的“必殺技”,但是此時我還是不甘心,還是想從FTPClient api本身去解決問題,但此時看來也別無它他法。只能試試:即設定被動模式又設定超時時間。經過實際測試,發現問題得以解決。下面把我的FTP工具類貼給大家分享,希望能幫到遇到同樣問題的人。

 1 import org.apache.commons.net.ftp.FTP; 2 import org.apache.commons.net.ftp.FTPClient; 3 import org.apache.commons.net.ftp.FTPFile; 4 import org.apache.commons.net.ftp.FTPReply; 5  6 import java.io.BufferedInputStream; 7 import java.io.BufferedOutputStream; 8 import java.io.File; 9 import java.io.FileInputStream; 10 import java.io.FileNotFoundException; 11 import java.io.FileOutputStream; 12 import java.io.IOException; 13 import java.io.InputStream; 14 import java.io.OutputStream; 15 import java.net.UnknownHostException; 16 import java.util.ArrayList; 17 import java.util.List; 18  19 public class FtpUtil { 20   public static final String ANONYMOUS_LOGIN = "anonymous"; 21   private FTPClient ftp; 22   private boolean is_connected; 23  24   public FtpUtil() { 25     ftp = new FTPClient(); 26     is_connected = false; 27   } 28    29   public FtpUtil(int defaultTimeoutSecond, int connectTimeoutSecond, int dataTimeoutSecond){ 30     ftp = new FTPClient(); 31     is_connected = false; 32      33     ftp.setDefaultTimeout(defaultTimeoutSecond * 1000); 34     ftp.setConnectTimeout(connectTimeoutSecond * 1000); 35     ftp.setDataTimeout(dataTimeoutSecond * 1000); 36   } 37  38   /** 39    * Connects to FTP server. 40    *  41    * @param host 42    *      FTP server address or name 43    * @param port 44    *      FTP server port 45    * @param user 46    *      user name 47    * @param password 48    *      user password 49    * @param isTextMode 50    *      text / binary mode switch 51    * @throws IOException 52    *       on I/O errors 53   */ 54   public void connect(String host, int port, String user, String password, boolean isTextMode) throws IOException { 55     // Connect to server. 56     try { 57       ftp.connect(host, port); 58     } catch (UnknownHostException ex) { 59       throw new IOException("Can't find FTP server '" + host + "'"); 60     } 61  62     // Check rsponse after connection attempt. 63     int reply = ftp.getReplyCode(); 64     if (!FTPReply.isPositiveCompletion(reply)) { 65       disconnect(); 66       throw new IOException("Can't connect to server '" + host + "'"); 67     } 68  69     if (user == "") { 70       user = ANONYMOUS_LOGIN; 71     } 72  73     // Login. 74     if (!ftp.login(user, password)) { 75       is_connected = false; 76       disconnect(); 77       throw new IOException("Can't login to server '" + host + "'"); 78     } else { 79       is_connected = true; 80     } 81  82     // Set data transfer mode. 83     if (isTextMode) { 84       ftp.setFileType(FTP.ASCII_FILE_TYPE); 85     } else { 86       ftp.setFileType(FTP.BINARY_FILE_TYPE); 87     } 88   } 89  90   /** 91    * Uploads the file to the FTP server. 92    *  93    * @param ftpFileName 94    *      server file name (with absolute path) 95    * @param localFile 96    *      local file to upload 97    * @throws IOException 98    *       on I/O errors 99   */100   public void upload(String ftpFileName, File localFile) throws IOException {101     // File check.102     if (!localFile.exists()) {103       throw new IOException("Can't upload '" + localFile.getAbsolutePath() + "'. This file doesn't exist.");104     }105 106     // Upload.107     InputStream in = null;108     try {109 110       // Use passive mode to pass firewalls.111       ftp.enterLocalPassiveMode();112 113       in = new BufferedInputStream(new FileInputStream(localFile));114       if (!ftp.storeFile(ftpFileName, in)) {115         throw new IOException("Can't upload file '" + ftpFileName + "' to FTP server. Check FTP permissions and path.");116       }117 118     } finally {119       try {120         in.close();121       } catch (IOException ex) {122       }123     }124   }125 126   /**127    * Downloads the file from the FTP server.128    * 129    * @param ftpFileName130    *      server file name (with absolute path)131    * @param localFile132    *      local file to download into133    * @throws IOException134    *       on I/O errors135   */136   public void download(String ftpFileName, File localFile) throws IOException {137     // Download.138     OutputStream out = null;139     try {140       // Use passive mode to pass firewalls.141       ftp.enterLocalPassiveMode();142 143       // Get file info.144       FTPFile[] fileInfoArray = ftp.listFiles(ftpFileName);145       if (fileInfoArray == null) {146         throw new FileNotFoundException("File " + ftpFileName + " was not found on FTP server.");147       }148 149       // Check file size.150       FTPFile fileInfo = fileInfoArray[0];151       long size = fileInfo.getSize();152       if (size > Integer.MAX_VALUE) {153         throw new IOException("File " + ftpFileName + " is too large.");154       }155 156       // Download file.157       out = new BufferedOutputStream(new FileOutputStream(localFile));158       if (!ftp.retrieveFile(ftpFileName, out)) {159         throw new IOException("Error loading file " + ftpFileName + " from FTP server. Check FTP permissions and path.");160       }161 162       out.flush();163     } finally {164       if (out != null) {165         try {166           out.close();167         } catch (IOException ex) {168         }169       }170     }171   }172 173   /**174    * Removes the file from the FTP server.175    * 176    * @param ftpFileName177    *      server file name (with absolute path)178    * @throws IOException179    *       on I/O errors180   */181   public void remove(String ftpFileName) throws IOException {182     if (!ftp.deleteFile(ftpFileName)) {183       throw new IOException("Can't remove file '" + ftpFileName + "' from FTP server.");184     }185   }186 187   /**188    * Lists the files in the given FTP directory.189    * 190    * @param filePath191    *      absolute path on the server192    * @return files relative names list193    * @throws IOException194    *       on I/O errors195   */196   public List<String> list(String filePath) throws IOException {197     List<String> fileList = new ArrayList<String>();198     199     // Use passive mode to pass firewalls.200     ftp.enterLocalPassiveMode();201     202     FTPFile[] ftpFiles = ftp.listFiles(filePath);203     int size = (ftpFiles == null) ? 0 : ftpFiles.length;204     for (int i = 0; i < size; i++) {205       FTPFile ftpFile = ftpFiles[i];206       if (ftpFile.isFile()) {207         fileList.add(ftpFile.getName());208       }209     }210     211     return fileList;212   }213 214   /**215    * Sends an FTP Server site specific command216    * 217    * @param args218    *      site command arguments219    * @throws IOException220    *       on I/O errors221   */222   public void sendSiteCommand(String args) throws IOException {223     if (ftp.isConnected()) {224       try {225         ftp.sendSiteCommand(args);226       } catch (IOException ex) {227       }228     }229   }230 231   /**232    * Disconnects from the FTP server233    * 234    * @throws IOException235    *       on I/O errors236   */237   public void disconnect() throws IOException {238 239     if (ftp.isConnected()) {240       try {241         ftp.logout();242         ftp.disconnect();243         is_connected = false;244       } catch (IOException ex) {245       }246     }247   }248 249   /**250    * Makes the full name of the file on the FTP server by joining its path and251    * the local file name.252    * 253    * @param ftpPath254    *      file path on the server255    * @param localFile256    *      local file257    * @return full name of the file on the FTP server258   */259   public String makeFTPFileName(String ftpPath, File localFile) {260     if (ftpPath == "") {261       return localFile.getName();262     } else {263       String path = ftpPath.trim();264       if (path.charAt(path.length() - 1) != '/') {265         path = path + "/";266       }267 268       return path + localFile.getName();269     }270   }271 272   /**273    * Test coonection to ftp server274    * 275    * @return true, if connected276   */277   public boolean isConnected() {278     return is_connected;279   }280 281   /**282    * Get current directory on ftp server283    * 284    * @return current directory285   */286   public String getWorkingDirectory() {287     if (!is_connected) {288       return "";289     }290 291     try {292       return ftp.printWorkingDirectory();293     } catch (IOException e) {294     }295 296     return "";297   }298 299   /**300    * Set working directory on ftp server301    * 302    * @param dir303    *      new working directory304    * @return true, if working directory changed305   */306   public boolean setWorkingDirectory(String dir) {307     if (!is_connected) {308       return false;309     }310 311     try {312       return ftp.changeWorkingDirectory(dir);313     } catch (IOException e) {314     }315 316     return false;317   }318 319   /**320    * Change working directory on ftp server to parent directory321    * 322    * @return true, if working directory changed323   */324   public boolean setParentDirectory() {325     if (!is_connected) {326       return false;327     }328 329     try {330       return ftp.changeToParentDirectory();331     } catch (IOException e) {332     }333 334     return false;335   }336 337   /**338    * Get parent directory name on ftp server339    * 340    * @return parent directory341   */342   public String getParentDirectory() {343     if (!is_connected) {344       return "";345     }346 347     String w = getWorkingDirectory();348     setParentDirectory();349     String p = getWorkingDirectory();350     setWorkingDirectory(w);351 352     return p;353   }354 355   /**356    * Get directory contents on ftp server357    * 358    * @param filePath359    *      directory360    * @return list of FTPFileInfo structures361    * @throws IOException362   */363   public List<FfpFileInfo> listFiles(String filePath) throws IOException {364     List<FfpFileInfo> fileList = new ArrayList<FfpFileInfo>();365     366     // Use passive mode to pass firewalls.367     ftp.enterLocalPassiveMode();368     FTPFile[] ftpFiles = ftp.listFiles(filePath);369     int size = (ftpFiles == null) ? 0 : ftpFiles.length;370     for (int i = 0; i < size; i++) {371       FTPFile ftpFile = ftpFiles[i];372       FfpFileInfo fi = new FfpFileInfo();373       fi.setName(ftpFile.getName());374       fi.setSize(ftpFile.getSize());375       fi.setTimestamp(ftpFile.getTimestamp());376       fi.setType(ftpFile.isDirectory());377       fileList.add(fi);378     }379 380     return fileList;381   }382 383   /**384    * Get file from ftp server into given output stream385    * 386    * @param ftpFileName387    *      file name on ftp server388    * @param out389    *      OutputStream390    * @throws IOException391   */392   public void getFile(String ftpFileName, OutputStream out) throws IOException {393     try {394       // Use passive mode to pass firewalls.395       ftp.enterLocalPassiveMode();396       397       // Get file info.398       FTPFile[] fileInfoArray = ftp.listFiles(ftpFileName);399       if (fileInfoArray == null) {400         throw new FileNotFoundException("File '" + ftpFileName + "' was not found on FTP server.");401       }402 403       // Check file size.404       FTPFile fileInfo = fileInfoArray[0];405       long size = fileInfo.getSize();406       if (size > Integer.MAX_VALUE) {407         throw new IOException("File '" + ftpFileName + "' is too large.");408       }409 410       // Download file.411       if (!ftp.retrieveFile(ftpFileName, out)) {412         throw new IOException("Error loading file '" + ftpFileName + "' from FTP server. Check FTP permissions and path.");413       }414 415       out.flush();416 417     } finally {418       if (out != null) {419         try {420           out.close();421         } catch (IOException ex) {422         }423       }424     }425   }426 427   /**428    * Put file on ftp server from given input stream429    * 430    * @param ftpFileName431    *      file name on ftp server432    * @param in433    *      InputStream434    * @throws IOException435   */436   public void putFile(String ftpFileName, InputStream in) throws IOException {437     try {438       // Use passive mode to pass firewalls.439       ftp.enterLocalPassiveMode();440       441       if (!ftp.storeFile(ftpFileName, in)) {442         throw new IOException("Can't upload file '" + ftpFileName + "' to FTP server. Check FTP permissions and path.");443       }444     } finally {445       try {446         in.close();447       } catch (IOException ex) {448       }449     }450   }451 }