次のApache Commons Net FTPコードを使用してFTPサーバーに接続し、いくつかのディレクトリでファイルをポーリングし、ファイルが見つかった場合はローカルマシンに取得します。
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());
}
これは、毎分、毎分実行するようにスケジュールされています。 Tomcat(7.0.19)にデプロイすると、このコードは問題なくロードされ、滞りなく動作します。ただし、毎回、ある時点でhangのように見えます。つまり、つまり:
catalina.out
と私のアプリケーション固有のログに、例外がスローされている兆候がありませんしたがって、JVMはまだ実行中です。 Tomcatはまだ稼働しており、デプロイしたWARは稼働していますが、ハングしています。場合によっては2時間実行されてからハングします。それ以外の場合は、数日間実行されてからハングします。しかし、ハングすると、About to check loadables/ for files...
を読み取る行(ログに表示されます)とFile fetch completed.
を読み取る行(表示されません)の間でハングします。
これは、ファイルの実際のポーリング/フェッチ中にハングが発生したことを示しています。これは、 この質問 と同じ方向を示しているため、FTPClientのデッドロックに関係していることがわかりました。これは、これらが同じ問題かどうか疑問に思っています(もしそうであれば、この質問を喜んで削除します!)。しかし、私は信じないそれらが同じだとは思いません(私のログに同じ例外が表示されていません)。
同僚は、「パッシブ」対「アクティブ」なFTPの可能性があると述べました。違いをあまり知らないので、FTPClientのフィールドACTIVE_REMOTE_DATA_CONNECTION_MODE
、PASSIVE_REMOTE_DATA_CONNECTION_MODE
などに少し混乱し、SOが何であるかについて考えていませんでした潜在的な問題。
ここでThrowable
sを最後の手段としてキャッチしているので、何か問題が発生した場合、ログにsomethingが表示されると予想していました。エルゴ、これは明確なハングの問題だと思います。
何か案は?残念ながら、ここではFTPの内部について十分な知識がないため、確実な診断を行うことができません。これはサーバー側の何かでしょうか? FTPサーバーに関連していますか?
これは多くのことになるかもしれませんが、あなたの友人の提案は価値があります。
ftpClient.enterLocalPassiveMode();
を試して、効果があるかどうかを確認してください。
切断をfinally
ブロックに入れるを使用して、接続がそこに残されないようにすることもお勧めします。
昨日は寝ませんでしたが、問題は解決したと思います。
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();
}
}
このコードが誰かのために役立つことを願っています!
ログイン後にs.listFilesを呼び出して「ハング」して最終的に失敗することなく転送するために、以下を含める必要がありました。
s.login(username, password);
s.execPBSZ(0);
s.execPROT("P");
LinuxマシンからIISサーバーにlistfilesを実行しようとするときにも同じ問題がありました。開発者のワークステーションからコードはうまく機能しましたが、特にサーバー上で実行するとハングしますファイアウォールがミックスを妨害しています。
これらを順番に実行する必要があり、FTPSClient 3.5を拡張する必要があります
詳細情報:私の問題は、LinuxマシンとIISサーバー間のファイアウォールに固有です。
私の問題の根本的な原因は、パッシブモードでは、データ接続を行うときにソケットを開くために使用されるIPアドレスが、初期接続を行うために使用されるものと異なるということです。したがって、Apache commons-net 3.5には2つの問題(下記参照)があるため、理解するのが非常に困難でした。私のソリューション:FTPSClientを拡張して、メソッド_parsePassiveModeReply&openDataConnectionをオーバーライドできるようにします。私のparsePassiveModeReplyは実際には、応答からポートを保存しています。これは、応答がどのポートが使用されているかを示しているためです。私のopenDataConnectionメソッドは、保存されたポートと接続中に使用された元のIPを使用しています。
Apache FTPCLient 3.5の問題
注意すべきこと:
以下をクラスに追加してください。これは、実行されているFTPコマンドを知るのに非常に役立ちます。
ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
Curlコマンドを使用して接続が確立されていることを確認します。以下は適切なスタートです。問題がなければ、ルートディレクトリの内容を一覧表示します。
curl -3 ftps://[user id]:[password][ftp server ip]:990/ -1 -v --disable-epsv --ftp-skip-pasv-ip --ftp-ssl --insecure
FTPSClient拡張(サンプルコード)
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);
}
}