read - ftp c#
FtpWebRequest 30 minutos de tiempo fuera (2)
Mi código está experimentando una excepción de tiempo de espera después de exactamente 30 minutos al descargar un archivo grande a través de FTP. El servidor es FileZilla ejecutándose en Windows. Tenemos un certificado SSL configurado con las opciones Enable FTP over SSL/TLS support (FTPS)
y Allow explicit FTP over TLS
habilitado. Tengo acceso al servidor y a la configuración de FileZilla , pero no puedo ver nada que pueda causar este comportamiento. A continuación se muestra el código fuente que se ejecuta en .NET 4.6.2 en una máquina con Windows 2012 Server. Puede descargar archivos desde el servidor FTP, pero se agotará el tiempo de espera con la excepción (que se muestra a continuación) después de exactamente 30 minutos si el archivo tarda más de 30 minutos en descargarse.
Como prueba, he usado FileZilla , ejecutándose desde la misma PC cliente, para descargar varios archivos grandes desde el mismo punto final del servidor simultáneamente, de modo que la descarga de cada archivo tomó más de 30 minutos. No se han producido errores en este escenario.
He buscado en Home , así como en Google, pero no apareció nada prometedor. Si alguien tiene algunos consejos sobre dónde buscar (del lado del servidor o del cliente), le agradecería mucho.
Codigo de aplicacion
public class FtpFileDownloader
{
// log4net
private static readonly ILog Logger = LogManager.GetLogger(typeof(FtpFileDownloader));
public void DownloadFile()
{
// setting the SecurityProtocol did not change the outcome, both were tried. Originally it was not set at all.
// ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
const int timeout = 7200000;
const string file = "some-existing-file";
try
{
var request = (FtpWebRequest) WebRequest.Create("uri-path-to-file");
request.KeepAlive = false;
request.Timeout = -1;
request.ReadWriteTimeout = timeout;
request.Credentials = new NetworkCredential("userName", "password");
request.UsePassive = true;
request.EnableSsl = true;
request.Method = WebRequestMethods.Ftp.DownloadFile;
Logger.Debug($"Downloading ''{file}''");
using (var response = (FtpWebResponse) request.GetResponse())
using (var sourceStream = response.GetResponseStream())
using (var targetStream = new FileStream("some-target-on-disk", FileMode.Create, FileAccess.Write))
{
try
{
sourceStream.CopyTo(targetStream);
targetStream.Flush();
Logger.Debug($"Finished download ''{file}''");
}
catch (Exception exInner)
{
Logger.Error($"Error occurred trying to download file ''{file}''.", exInner);
}
}
}
catch (Exception ex)
{
Logger.Error($"Error occurred trying to dispose streams when downloading file ''{file}''.", ex);
}
}
}
Registro de aplicación
ERROR FtpFileDownloader - Error occurred trying to download file ''some-existing-file''.
System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream.
at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
at System.Net.Security._SslStream.StartFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security._SslStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security._SslStream.StartReading(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security._SslStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.TlsStream.Read(Byte[] buffer, Int32 offset, Int32 size)
at System.Net.FtpDataStream.Read(Byte[] buffer, Int32 offset, Int32 size)
at System.IO.Stream.InternalCopyTo(Stream destination, Int32 bufferSize)
at System.IO.Stream.CopyTo(Stream destination)
at FtpFileDownloader.DownloadFile
ERROR FtpFileDownloader - Error occurred trying to dispose streams when downloading file ''some-existing-file''.
System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive.
at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
at System.Net.FtpWebRequest.RequestCallback(Object obj)
at System.Net.CommandStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.Stream.Dispose()
at System.Net.ConnectionPool.Destroy(PooledStream pooledStream)
at System.Net.ConnectionPool.PutConnection(PooledStream pooledStream, Object owningObject, Int32 creationTimeout, Boolean canReuse)
at System.Net.FtpWebRequest.FinishRequestStage(RequestStage stage)
at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
at System.Net.FtpWebRequest.RequestCallback(Object obj)
at System.Net.CommandStream.Abort(Exception e)
at System.Net.CommandStream.CheckContinuePipeline()
at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
at System.Net.FtpDataStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.Stream.Dispose()
at FtpFileDownloader.DownloadFile
FileZilla - Configuración general
Listen on these ports: 21
Max. number of users: 0 (infinite)
Number of threads: 2
Connection timeout: 120 (seconds)
No Transfer timeout: 9000 (seconds)
Log timeout: 60 (seconds)
FileZilla Server Log
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> Connected on port 21, sending welcome message...
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> 220 Welcome
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> AUTH TLS
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> 234 Using authentication type TLS
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> TLS connection established
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> USER my-user-account
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> 331 Password required for my-user-account
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> PASS **************
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 230 Logged on
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PBSZ 0
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 200 PBSZ=0
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PROT P
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 200 Protection level set to P
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> OPTS utf8 on
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 202 UTF8 mode is always enabled. No need to send this command.
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PWD
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 257 "/" is current directory.
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> TYPE I
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 200 Type set to I
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PASV
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 227 Entering Passive Mode (IP-ADDRESS,245,222)
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> RETR path-to-file
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 150 Opening data channel for file download from server of "/path-to-file"
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> TLS connection for data connection established
23-2-2018 12:10:41 - my-user-account (194.123.75.2)> disconnected.
Observe que hay 30 minutos entre la desconexión (última línea) y la línea anterior. Si hubiera completado la transferencia con éxito, también habría una línea que decía 226 Successfully transferred "/path-to-file"
antes de la línea disconnected
Progreso de la actualización:
- (2018.02.20) Le pedí a nuestro equipo de la red que verificara las reglas de Fire Wall, pero no pudieron encontrar nada de interés.
- (2018.02.22) Encontré que la versión de FileZilla Server que se está usando es
0.9.43 beta
( fecha de lanzamiento 2014-01-02 de acuerdo con el registro de cambios ). Aunque no encontré nada en el registro de cambios que sugiera que este comportamiento fue alguna vez un error corregido, actualizaré a la última versión0.9.60.2
( fecha de lanzamiento 2017-02-08 ) y volveré a ejecutar la prueba. Informará dentro de 24 horas. - (2018.02.23) FileZilla se ha actualizado a la última versión. Esto no solucionó el problema, actualicé el registro del servidor pero parece casi idéntico al registro anterior, con la excepción de que esta última transferencia se realizó a través de TLS en lugar de SSL.
- (2018.02.23) Encontré los siguientes tiempos de espera de enlaces en archivos grandes en la página de soporte de FileZilla. Voy a enviarlo nuevamente al personal de la red de nuestro proveedor de alojamiento para que eche un vistazo.
- (2018.02.27) Resultó que el firewall de la empresa (la red donde se ejecuta el cliente ) y no el firewall de alojamiento (la red donde se hospeda FileZilla ) fue el culpable. Se configuró para dejar las conexiones inactivas después de
1800
segundos. Se ha agregado una regla para anular esto entre estos 2 puntos finales.
Culpable / Respuesta
Resultó que el firewall de la compañía (la red donde se ejecuta el cliente ) y no el firewall de alojamiento (la red donde se hospeda FileZilla ) fue el culpable. Se configuró para dejar las conexiones inactivas después de 1800
segundos. Se ha agregado una regla para anular esto entre estos 2 puntos finales.
Probablemente deberías intentar otra implementación de cliente de protocolo FTP que no esté construida sobre FtpWebRequest
.
Los problemas relacionados existen desde hace mucho tiempo, no tienen una solución o respuesta clara. Entonces intentaría algo como FluentFTP , usa la API de Winsock directamente. XML Documentation Comment indica que DownloadFile()
debe manejar bien las descargas de archivos grandes:
/// <summary>
/// Downloads the specified file onto the local file system.
/// High-level API that takes care of various edge cases internally.
/// Supports very large files since it downloads data in chunks.
/// </summary>
Para más información verifique:
- La descarga de archivos grandes (~ 150MB) desde el servidor FTP bloquea la discusión en
- FtpWebRequest es un artículo roto por Matt Mitchell
- Página de la API de Winsock en Microsoft TechNet
- Sección de código fuente en el artículo de Descarga parcial de FTP por Elmue
Sí, no creo que haya un "error" en tu código; es solo que la conexión de control se apaga después de 30 minutos, aunque la conexión de transferencia no se agote. Tal vez ni siquiera sea necesario alterar los valores de KeepAlive y Timeout, solo intente reutilizar su solicitud cada 20 minutos aproximadamente con una descarga ficticia: de esa manera, restablecerá el temporizador de conexión de control.
Por cierto, en algún lugar donde he leído que 30 minutos es un tiempo de espera estándar para FileZilla Server, que se basa en 6 keep-alive configurados para ser enviados cada 300 segundos (lo que le da una experiencia de 30 minutos). Si pudieras probar con otro servidor FTP / FTPS, probablemente descubrirías un tiempo de espera inactivo diferente y no alcanzarías ese límite de 30 minutos (sino otro diferente).
Por lo tanto, personalmente invertiría en hacer que el código que se muestra a continuación sea async
, de modo que el flujo de ejecución continúe después de using
y puede ingresar un ciclo donde cada 20 minutos reutiliza su solicitud (y su conexión de control) para realizar una descarga ficticia. Por supuesto, FileZilla Client no necesita una descarga ficticia porque opera a un nivel inferior y probablemente envía comandos TCP para mantener viva la conexión de control.
using (var response = (FtpWebResponse) request.GetResponse())
using (var sourceStream = response.GetResponseStream())
using (var targetStream = new FileStream("some-target-on-disk", FileMode.Create, FileAccess.Write))
{
try
{
sourceStream.CopyTo(targetStream);
targetStream.Flush();
Logger.Debug($"Finished download ''{file}''");
}
catch (Exception exInner)
{
Logger.Error($"Error occurred trying to download file ''{file}''.", exInner);
}
}