2012-03-14 14 views
16

Stiamo utilizzando il seguente codice di Apache Commons Net FTP per connettersi a un server FTP, il polling alcune directory per i file, e se vengono trovati i file, per recuperarli al computer locale:Apache Commons FTPClient Hanging

try { 
logger.trace("Attempting to connect to server..."); 

// Connect to server 
FTPClient ftpClient = new FTPClient(); 
ftpClient.setConnectTimeout(20000); 
ftpClient.connect("my-server-host-name"); 
ftpClient.login("myUser", "myPswd"); 
ftpClient.changeWorkingDirectory("/loadables/"); 

// Check for failed connection 
if(!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) 
{ 
    ftpClient.disconnect(); 
    throw new FTPConnectionClosedException("Unable to connect to FTP server."); 
} 

// Log success msg 
logger.trace("...connection was successful."); 

// Change to the loadables/ directory where we poll for files 
ftpClient.changeWorkingDirectory("/loadables/");  

// Indicate we're about to poll 
logger.trace("About to check loadables/ for files..."); 

// Poll for files. 
FTPFile[] filesList = oFTP.listFiles(); 
for(FTPFile tmpFile : filesList) 
{ 
    if(tmpFile.isDirectory()) 
     continue; 

    FileOutputStream fileOut = new FileOutputStream(new File("tmp")); 
    ftpClient.retrieveFile(tmpFile.getName(), fileOut); 
    // ... Doing a bunch of things with output stream 
    // to copy the contents of the file down to the local 
    // machine. Ommitted for brevity but I assure you this 
    // works (except when the WAR decides to hang). 
    // 
    // This was used because FTPClient doesn't appear to GET 
    // whole copies of the files, only FTPFiles which seem like 
    // file metadata... 
} 

// Indicate file fetch completed. 
logger.trace("File fetch completed."); 

// Disconnect and finish. 
if(ftpClient.isConnected()) 
    ftpClient.disconnect(); 

logger.trace("Poll completed."); 
} catch(Throwable t) { 
    logger.trace("Error: " + t.getMessage()); 
} 

Abbiamo programmato l'esecuzione ogni minuto, al minuto. Quando viene distribuito su Tomcat (7.0.19) questo codice viene caricato perfettamente e inizia a funzionare senza intoppi. Ogni volta però, ad un certo punto o in un altro, sembra solo hang. Con questo voglio dire:

  • Nessun dump heap esistono
  • Tomcat è ancora in esecuzione (che posso vedere il suo PID e può accedere al Web Manager app)
  • All'interno il manager app, posso vedere il mio GUERRA è ancora in esecuzione/cominciato
  • catalina.out e il mio spettacolo registro specifico per l'applicazione senza segni di eventuali eccezioni gettati

Quindi la JVM è ancora in esecuzione. Tomcat è ancora in esecuzione e il mio WAR distribuito è ancora in esecuzione, ma è appena sospeso. A volte funziona per 2 ore e poi si blocca; altre volte funziona per giorni e poi si blocca. Ma quando si blocca, lo fa tra la riga che legge About to check loadables/ for files... (che vedo nei registri) e la riga che legge File fetch completed. (che non vedo).

Questo mi dice che il blocco si verifica durante il polling/recupero effettivo dei file, che mi indica nella stessa direzione di this question che sono riuscito a trovare che riguarda se stesso con deadlock di FTPClient. Questo mi chiede se questi sono gli stessi problemi (se lo sono, eliminerò felicemente questa domanda!). Tuttavia non credo che creda che siano uguali (non vedo le stesse eccezioni nei miei registri).

Un collega ha affermato che potrebbe trattarsi di una cosa FTP "passiva" o "attiva". Non sapendo veramente la differenza, sono un po 'confuso dai campi FTPClient ACTIVE_REMOTE_DATA_CONNECTION_MODE, PASSIVE_REMOTE_DATA_CONNECTION_MODE, ecc. E non sapevo cosa ne pensasse SO come potenziale problema.

Dal momento che sto rilevando Throwable s come ultima risorsa qui, mi sarei aspettato di vedere qualcosa di nei registri se qualcosa non funziona. Ergo, mi sembra che questo sia un vero problema.

Qualche idea? Sfortunatamente non conosco abbastanza gli interni FTP per fare una diagnosi precisa. Potrebbe essere qualcosa lato server? Relativo al server FTP?

risposta

26

Questo potrebbe essere un numero di cose, ma il suggerimento del tuo amico sarebbe valso la pena.

Prova ftpClient.enterLocalPassiveMode(); per vedere se aiuta.

Vorrei anche suggerire di inserire nel blocco finally in modo che non lasci mai una connessione.

+0

L'unica cosa che potrebbe non avere senso con la mia risposta è il motivo per cui questo funziona a volte. – tjg184

+0

Esattamente! E non solo * a volte * ... funziona circa il 99,9% delle volte! Ecco perché mi sento come questo è un problema lato server ... grazie per i suggerimenti, però proverò entrambi! – IAmYourFaja

+0

Per curiosità, qual è la vera differenza tra passivo/attivo? La mia comprensione è che, sotto attivo, è il server che avvia la connessione. È questo il senso? – IAmYourFaja

20

Ieri, non ho dormito ma penso di aver risolto il problema.

È possibile aumentare la dimensione del buffer con FTPClient.setBufferSize();

/** 
* Download encrypted and configuration files. 
* 
* @throws SocketException 
* @throws IOException 
*/ 
public void downloadDataFiles(String destDir) throws SocketException, 
     IOException { 

    String filename; 
    this.ftpClient.connect(ftpServer); 
    this.ftpClient.login(ftpUser, ftpPass); 

    /* CHECK NEXT 4 Methods (included the commented) 
    * they were very useful for me! 
    * and icreases the buffer apparently solve the problem!! 
    */ 
    // ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); 
    log.debug("Buffer Size:" + ftpClient.getBufferSize()); 
    this.ftpClient.setBufferSize(1024 * 1024); 
    log.debug("Buffer Size:" + ftpClient.getBufferSize()); 


    /* 
    * get Files to download 
    */ 
    this.ftpClient.enterLocalPassiveMode(); 
    this.ftpClient.setAutodetectUTF8(true); 
      //this.ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 
    this.ftpClient.enterLocalPassiveMode(); 
    FTPFile[] ftpFiles = ftpClient 
      .listFiles(DefaultValuesGenerator.LINPAC_ENC_DIRPATH); 

    /* 
    * Download files 
    */ 
    for (FTPFile ftpFile : ftpFiles) { 

     // Check if FTPFile is a regular file   
     if (ftpFile.getType() == FTPFile.FILE_TYPE) { 
      try{ 

      filename = ftpFile.getName(); 

      // Download file from FTP server and save 
      fos = new FileOutputStream(destDir + filename); 

      //I don't know what useful are these methods in this step 
      // I just put it for try 
      this.ftpClient.enterLocalPassiveMode(); 
      this.ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 
      this.ftpClient.setAutodetectUTF8(true); 
      this.ftpClient.enterLocalPassiveMode(); 

      ftpClient.retrieveFile(
        DefaultValuesGenerator.LINPAC_ENC_DIRPATH + filename, 
        fos 
        ); 

      }finally{ 
       fos.flush(); 
       fos.close();    } 
     } 
    } 
    if (fos != null) { 
     fos.close(); 
    } 
} 

Mi auguro che questo codice potrebbe essere utile per qualcuno!

+3

Mi è stato molto utile, quindi grazie! La linea importante è this.ftpClient.setBufferSize (1024 * 1024); – Bambax

+0

La funzione di dimensione del buffer ha avuto un impatto incredibile sulle prestazioni, grazie molavec, top lad! – Sipty

+3

Perché non hai dormito ieri? – TriCore

2

ho dovuto includere la seguente dopo il login per chiamare s.listFiles e trasferimento senza di essa 'appeso' e, infine, non riuscendo:

s.login(username, password); 
s.execPBSZ(0); 
s.execPROT("P"); 
+0

molto buono. Sì!! Questo ha funzionato per me. –

1

Ho avuto questo stesso problema quando si cerca di eseguire un listfiles da un Macchina Linux su un server IIS. Il codice funzionava alla perfezione dalla mia workstation per sviluppatori, ma si bloccava durante l'esecuzione sul server, in particolare a causa di un firewall che incollava il mix.

Deve fare queste cose in ordine e richiedono di estendere la FTPSClient 3,5

  1. collegare (implicito = true, SSLContext = TLS)
  2. controllo isPositiveCompletion
  3. autenticazione (ovviamente)
  4. execPBSZ (0)
  5. execPROT ("P")
  6. set booleano per indicare Skip Passive IP (classe FTPSClient personalizzata)
  7. impostare l'indirizzo IP di connessione salvare (classe FTPSClient personalizzato)
  8. setUseEPSVwithIPv4 (false)
  9. enterLocalPassiveMode() o enterRemotePassiveMode()
  10. initiateListParsing() o qualsiasi comando list a.) A questo punto il openDataConnection verrà eseguito, assicurarsi di salvare la porta in uso qui b.) Il comando PASV viene eseguito c.) Viene eseguito _parsePassiveModeReply, qui si aprirà il socket con l'indirizzo IP utilizzato per la connessione e il salvataggio porta.
  11. disconnessione (sempre)

Maggiori informazioni: Il mio problema è specifico di un firewall tra la macchina Linux e il server IIS.
La radice del mio problema è che in modalità passiva l'indirizzo IP utilizzato per aprire il socket quando si effettua una connessione dati è diverso da quello utilizzato per la connessione iniziale. Quindi a causa di due problemi (vedi sotto) con APACHE commons-net 3.5 era incredibilmente difficile da capire. La mia soluzione: Estendi FTPSClient in modo da poter eseguire l'override dei metodi _parsePassiveModeReply & openDataConnection. My parsePassiveModeReply sta in realtà salvando la porta dalla risposta poiché la risposta indica quale porta viene utilizzata. Il mio metodo openDataConnection utilizza la porta salvata e l'IP originale utilizzato durante la connessione.

Problemi con Apache FtpClient 3,5

  1. connessione dati non timeout (si blocca) quindi la sua non risulta quale sia il problema.
  2. La classe FTPSClient non salta gli indirizzi IP passivi. L'impostazione di passiveNatWorkaround su true non funziona come mi aspettavo o forse non salta affatto l'IP.

cose da prestare attenzione a:

  • Quando si passa attraverso un firewall è necessario avere accesso alla gamma porto definito da IIS (vedi configurazione firewall di Microsoft IIS).
  • È inoltre necessario assicurarsi di disporre di certificati appropriati nel proprio archivio chiavi o nel cert specificato in fase di esecuzione.
  • Aggiungi quanto segue alla tua classe, è molto utile sapere quali sono i comandi FTP in esecuzione.

    ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); 
    
  • controllare i log del server FTP, come vi dirà ciò che viene eseguita e possibile perché si hanno problemi. Dovresti sempre vedere un canale dati aperto prima di eseguire un elenco. Confronta i risultati di la tua applicazione con quella di ciò che esegue un comando di arricciamento riuscito.
  • I codici di risposta indicheranno dove si sta verificando un problema.
  • Utilizzare il comando di arricciatura per verificare la presenza di connettività. Il seguente è un buon avvio e se tutto va bene verrà elencato il contenuto nella directory radice .

    curl -3 ftps://[user id]:[password][ftp server ip]:990/ -1 -v --disable-epsv --ftp-skip-pasv-ip --ftp-ssl --insecure 
    

FTPSClient prolungare (CODICE DI ESEMPIO)

import java.io.IOException; 
import java.net.Inet6Address; 
import java.net.InetSocketAddress; 
import java.net.Socket; 

import javax.net.ssl.SSLContext; 

import org.apache.commons.net.MalformedServerReplyException; 
import org.apache.commons.net.ftp.FTPReply; 
import org.apache.commons.net.ftp.FTPSClient; 

/** 
* TODO Document Me! 
*/ 
public class PassiveFTPSClient extends FTPSClient { 
    private String passiveSkipToHost; 
    private int passiveSkipToPort; 
    private boolean skipPassiveIP; 


    /** Pattern for PASV mode responses. Groups: (n,n,n,n),(n),(n) */ 
    private static final java.util.regex.Pattern PARMS_PAT;  
    static { 
    PARMS_PAT = java.util.regex.Pattern.compile(
      "(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})"); 
     } 
    /** 
    * @param b 
    * @param sslContext 
    */ 
    public PassiveFTPSClient(boolean b, SSLContext sslContext) { 
    super(b, sslContext); 
    } 

    protected void _parsePassiveModeReply(String reply) throws MalformedServerReplyException { 
    if (isSkipPassiveIP()) { 
     System.out.println("================> _parsePassiveModeReply" + getPassiveSkipToHost()); 
     java.util.regex.Matcher m = PARMS_PAT.matcher(reply); 
     if (!m.find()) { 
     throw new MalformedServerReplyException(
      "Could not parse passive host information.\nServer Reply: " + reply); 
     } 
     try { 
     int oct1 = Integer.parseInt(m.group(2)); 
     int oct2 = Integer.parseInt(m.group(3)); 
     passiveSkipToPort = (oct1 << 8) | oct2; 
     } 
     catch (NumberFormatException e) { 
     throw new MalformedServerReplyException(
      "Could not parse passive port information.\nServer Reply: " + reply); 
     }    
     //do nothing 
    } else { 
     super._parsePassiveModeReply(reply); 
    } 
    } 

    protected Socket _openDataConnection_(String command, String arg) throws IOException { 
    System.out.println("================> _openDataConnection_" + getPassiveSkipToHost()); 
    System.out.println("================> _openDataConnection_ isSkipPassiveIP: " + isSkipPassiveIP());   
    if (!isSkipPassiveIP()) { 
     return super._openDataConnection_(command, arg); 
    } 
    System.out.println("================> getDataConnectionMode: " + getDataConnectionMode()); 
    if (getDataConnectionMode() != ACTIVE_LOCAL_DATA_CONNECTION_MODE && 
     getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { 
     return null; 
    } 

    final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; 

    Socket socket; 
    if (getDataConnectionMode() == ACTIVE_LOCAL_DATA_CONNECTION_MODE) { 
     return super._openDataConnection_(command, arg); 

    } 
    else 
    { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE 

     // Try EPSV command first on IPv6 - and IPv4 if enabled. 
     // When using IPv4 with NAT it has the advantage 
     // to work with more rare configurations. 
     // E.g. if FTP server has a static PASV address (external network) 
     // and the client is coming from another internal network. 
     // In that case the data connection after PASV command would fail, 
     // while EPSV would make the client succeed by taking just the port. 
     boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; 
     if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) 
     { 

     System.out.println("================> _parseExtendedPassiveModeReply a: ");     
     _parseExtendedPassiveModeReply(_replyLines.get(0)); 
     } 
     else 
     { 
     if (isInet6Address) { 
      return null; // Must use EPSV for IPV6 
     } 
     // If EPSV failed on IPV4, revert to PASV 
     if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { 
      return null; 
     } 
     System.out.println("================> _parseExtendedPassiveModeReply b: "); 
     _parsePassiveModeReply(_replyLines.get(0)); 
     } 
     // hardcode fore testing 
     //__passiveHost = "10.180.255.181"; 
     socket = _socketFactory_.createSocket(); 
     if (getReceiveDataSocketBufferSize() > 0) { 
     socket.setReceiveBufferSize(getReceiveDataSocketBufferSize()); 
     } 
     if (getSendDataSocketBufferSize() > 0) { 
     socket.setSendBufferSize(getSendDataSocketBufferSize()); 
     } 
     if (getPassiveLocalIPAddress() != null) { 
     System.out.println("================> socket.bind: " + getPassiveSkipToHost()); 
     socket.bind(new InetSocketAddress(getPassiveSkipToHost(), 0)); 
     } 

     // For now, let's just use the data timeout value for waiting for 
     // the data connection. It may be desirable to let this be a 
     // separately configurable value. In any case, we really want 
     // to allow preventing the accept from blocking indefinitely. 
     //  if (__dataTimeout >= 0) { 
     //   socket.setSoTimeout(__dataTimeout); 
     //  } 

     System.out.println("================> socket connect: " + getPassiveSkipToHost() + ":" + passiveSkipToPort); 
     socket.connect(new InetSocketAddress(getPassiveSkipToHost(), passiveSkipToPort), connectTimeout); 
     if ((getRestartOffset() > 0) && !restart(getRestartOffset())) 
     { 
     socket.close(); 
     return null; 
     } 

     if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) 
     { 
     socket.close(); 
     return null; 
     } 
    } 

    if (isRemoteVerificationEnabled() && !verifyRemote(socket)) 
    { 
     socket.close(); 

     throw new IOException(
      "Host attempting data connection " + socket.getInetAddress().getHostAddress() + 
      " is not same as server " + getRemoteAddress().getHostAddress()); 
    } 

    return socket; 
     } 

    /** 
    * Enable or disable passive mode NAT workaround. 
    * If enabled, a site-local PASV mode reply address will be replaced with the 
    * remote host address to which the PASV mode request was sent 
    * (unless that is also a site local address). 
    * This gets around the problem that some NAT boxes may change the 
    * reply. 
    * 
    * The default is true, i.e. site-local replies are replaced. 
    * @param enabled true to enable replacing internal IP's in passive 
    * mode. 
    */ 
    public void setSkipPassiveIP(boolean enabled) { 
    super.setPassiveNatWorkaround(enabled); 
    this.skipPassiveIP = enabled; 
    System.out.println("================> skipPassiveIP: " + skipPassiveIP); 
    } 
    /** 
    * Return the skipPassiveIP. 
    * @return the skipPassiveIP 
    */ 
    public boolean isSkipPassiveIP() { 
    return skipPassiveIP; 
    } 
    /** 
    * Return the passiveSkipToHost. 
    * @return the passiveSkipToHost 
    */ 
    public String getPassiveSkipToHost() { 
    return passiveSkipToHost; 
    } 

    /** 
    * Set the passiveSkipToHost. 
    * @param passiveSkipToHost the passiveSkipToHost to set 
    */ 
    public void setPassiveSkipToHost(String passiveSkipToHost) { 
    this.passiveSkipToHost = passiveSkipToHost; 
    System.out.println("================> setPassiveSkipToHost: " + passiveSkipToHost); 
    } 

}