FtpWebRequest 30 minutes time out

mon code connaît une exception de temps d'arrêt après exactement 30 minutes lors du téléchargement d'un gros fichier par FTP. Le serveur est FileZilla tournant sous Windows. Nous avons un certificat SSL configuré avec les options Enable FTP over SSL/TLS support (FTPS) et Allow explicit FTP over TLS activées. J'ai accès au serveur et à la configuration FileZilla mais je ne vois rien qui pourrait causer ce comportement. Ci-dessous se trouve le code source qui s'exécute sur .NET 4.6.2 sur un Windows 2012 Serveur. Il peut télécharger des fichiers à partir du serveur FTP, mais il temporisera à l'exception (listée ci-dessous) après exactement 30 minutes si le téléchargement du fichier prend plus de 30 minutes.

comme un test , j'ai utilisé FileZilla Client , en cours d'exécution à partir du même PC client, pour télécharger plusieurs gros fichiers à partir du même point d'extrémité de serveur simultanément de sorte que le téléchargement pour chaque fichier a pris plus de 30 minutes à compléter. Pas d'erreurs s'est produite dans ce scénario.

j'ai cherché sur StackOverflow ainsi que Google mais rien de prometteur n'est apparu. Si quelqu'un a quelques conseils sur l'endroit où regarder (côté serveur ou côté client) je serais très reconnaissant.


code D'Application

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);
        }
    }
}

Journal D'Application

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 - Paramètres Généraux

 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.

l'Avis qu'il y est de 30 minutes entre le débrancher (dernière ligne) et de la ligne avant. S'il avait complété le transfert avec succès, il y aurait également une ligne qui se lit 226 Successfully transferred "/path-to-file" avant la disconnected ligne


mise à jour des Progrès:

  • (2018.02.20) j'ai demandé à notre équipe de réseau de vérifier les règles du mur de feu mais ils ne pouvaient rien trouver d'intéressant.
  • (2018.02.22) j'ai trouvé que la version du serveur de FileZilla utilisée est 0.9.43 beta ( date de sortie 2014-01-02 selon le journal de modification ). Bien que je n'ai rien trouvé dans le journal des modifications qui suggère que ce comportement était un bug corrigé, je vais passez à la dernière version 0.9.60.2 ( date de sortie 2017-02-08 ) et exécutez le test à nouveau. Présentera un rapport dans un délai de 24 heures.
  • (2018.02.23) FileZilla a été mis à jour à la dernière version. Cela n'a pas résolu le problème, j'ai mis à jour le journal du serveur mais il semble presque identique au journal précédent à l'exception que ce dernier transfert a eu lieu sur TLS au lieu de SSL.
  • (2018.02.23) j'ai trouvé le lien suivant Timeouts on large files sur la page de support de FileZilla. Je vais le renvoyer au personnel du réseau de notre hébergeur pour y jeter un autre coup d'oeil.
  • (2018.02.27) il s'est avéré que le pare-feu d'entreprise ( réseau où le client exécute ) et non le pare-feu d'hébergement ( réseau où FileZilla est hébergé ) était le coupable. Il a été configuré pour laisser tomber les connexions inactives après 1800 secondes. Une règle a été ajoutée pour outrepasser ceci entre ces deux points.

Coupable / Réponse

il s'est avéré que le pare-feu d'entreprise ( réseau où le client exécute ) et non le pare-feu d'hébergement ( réseau où FileZilla est hébergé ) était le coupable. Il a été configuré pour supprimer les connexions inactives après 1800 secondes. Un une règle a été ajoutée pour outrepasser ceci entre ces deux points.

20
demandé sur Igor 2018-02-15 22:24:34

2 réponses

probablement vous devriez essayer une autre implémentation du client de protocole FTP qui n'est pas construit sur le dessus du FtpWebRequest .

questions connexes existent depuis longtemps, ils n'ont pas une solution ou une réponse claire. Donc je voudrais essayer quelque chose comme FluentFTP , il utilise directement L'API Winsock. Le commentaire sur la Documentation XML indique que DownloadFile() devrait bien gérer les téléchargements de gros fichiers:

/// <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>

pour plus d'information contrôle:

7
répondu Leonid Vasilev 2018-02-27 17:14:01

Yep, Je ne pense pas qu'il y ait une "erreur" dans votre code; c'est juste que la connexion de contrôle s'éteint après 30 minutes même si la connexion de transfert n'a pas de temps mort. Peut-être qu'il ne sera même pas nécessaire de modifier les valeurs KeepAlive et Timeout, essayez simplement de réutiliser votre requête toutes les 20 minutes ou plus avec un téléchargement fictif: de cette façon, vous réinitialiserez la minuterie de connexion de contrôle.

Btw, quelque part j'ai lu que 30 minutes est un temps d'arrêt standard pour FileZilla Serveur, qui est basé sur 6 keep-alive configuré pour être envoyé toutes les 300 secondes (ce qui donne votre expérience de 30 minutes). Si vous pouviez essayer avec un autre serveur FTP/FTPS, vous découvririez probablement un délai d'inactivité différent et ne frapperiez pas contre cette limite de 30 minutes (mais un autre).

donc, personnellement, je voudrais investir dans la fabrication du code au-dessous de async , de sorte que le flux d'exécution continue après l'enveloppe using et vous pouvez entrer une boucle où toutes les 20 minutes vous réutilisez votre requête (et sa connexion de contrôle) pour faire un téléchargement fictif. Bien sûr, FileZilla Client n'a pas besoin d'un téléchargement fictif car il opère à un niveau inférieur et envoie probablement des commandes TCP pour garder la connexion de contrôle en vie.

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);
            }
        }
0
répondu Francesco B. 2018-02-22 11:04:05