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 }