Асинхронный FTP при ошибках подключения

Я сделал службу Windows, которая загружает все файлы (изображения), помещенные в папку.

FTP работает асинхронно и основан на коде msdn.

Все работает нормально, пока я не отключил сетевой кабель.

Предполагается, что метод ftp перехватывает ошибку и возвращает ложное логическое значение. Затем я перемещаю файл в папку с ошибкой, и он продолжает работу со следующим файлом. Когда достигается третий файл, действие WaitOne никогда не останавливается, а EndGetStreamCallback никогда не достигается. Я действительно не знаю, что происходит. После этого служба также перестает отвечать, пока я ее не перезапущу.

Обработчик файла:

string[] files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
            AsynchronousFtpUpLoader ftp = new AsynchronousFtpUpLoader();
            foreach (string file in files) {
                var result = ftp.Upload(ConfigurationManager.AppSettings["ftp_host"] + ConfigurationManager.AppSettings["ftp_path"] + file.Replace(path, "").Substring(1), file);
                if (!result) { 
                    return false;
                }
            }

АсинхронныйFtpUpLoader:

public class AsynchronousFtpUpLoader
    {
        public bool Upload(string sTarget,string file)
        {
            bool succeed = false;
            // Create a Uri instance with the specified URI string. 
            // If the URI is not correctly formed, the Uri constructor 
            // will throw an exception.
            ManualResetEvent waitObject;

            Uri target = new Uri(sTarget);
            string fileName = file;
            FtpState state = new FtpState();

            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(target);
            request.Method = WebRequestMethods.Ftp.UploadFile;

            request.Credentials = new NetworkCredential(ConfigurationManager.AppSettings["ftp_user"], ConfigurationManager.AppSettings["ftp_password"]);

            // Store the request in the object that we pass into the 
            // asynchronous operations.
            state.Request = request;
            state.FileName = fileName;

            // Get the event to wait on.
            waitObject = state.OperationComplete;

            // Asynchronously get the stream for the file contents.
            request.BeginGetRequestStream(
                new AsyncCallback(EndGetStreamCallback),
                state
            );

            // Block the current thread until all operations are complete.
            waitObject.WaitOne();

            // The operations either completed or threw an exception. 
            if (state.OperationException != null)
            {
                Log.writeLog("ERROR","FTP error", sTarget.Substring(0, sTarget.IndexOf("_")).Substring(sTarget.LastIndexOf("\\") + 1), sTarget);
                Log.writeError(state.OperationException.ToString() + (state.OperationException.InnerException != null ? state.OperationException.InnerException.ToString() : ""), sTarget);
                //throw state.OperationException;
            }
            else
            {
                succeed = true;
                Console.WriteLine("The operation completed - {0}", state.StatusDescription);
            }

            return succeed;
        }
        private static void EndGetStreamCallback(IAsyncResult ar)
        {
            FtpState state = (FtpState)ar.AsyncState;

            Stream requestStream = null;
            // End the asynchronous call to get the request stream. 
            try
            {
                Console.WriteLine("Opened the stream");
                using(requestStream = state.Request.EndGetRequestStream(ar)){
                    // Copy the file contents to the request stream. 
                    const int bufferLength = 2048;
                    byte[] buffer = new byte[bufferLength];
                    int count = 0;
                    int readBytes = 0;
                    using (FileStream stream = File.OpenRead(state.FileName))
                    {
                        do
                        {
                            readBytes = stream.Read(buffer, 0, bufferLength);
                            requestStream.Write(buffer, 0, readBytes);
                            count += readBytes;
                        }
                        while (readBytes != 0);
                        Console.WriteLine("Writing {0} bytes to the stream.", count);
                        // IMPORTANT: Close the request stream before sending the request.
                        requestStream.Close();
                        stream.Close();
                    }
                }
                Console.WriteLine("Closed the stream");
                // Asynchronously get the response to the upload request.
                state.Request.BeginGetResponse(
                    new AsyncCallback(EndGetResponseCallback),
                    state
                );
            }
            // Return exceptions to the main application thread. 
            catch (Exception e)
            {
                Console.WriteLine("Could not get the request stream.");
                state.OperationException = e;
                state.OperationComplete.Set();

                if (requestStream != null) {
                    requestStream.Close();
                }
                return;
            }

        }

        // The EndGetResponseCallback method   
        // completes a call to BeginGetResponse. 
        private static void EndGetResponseCallback(IAsyncResult ar)
        {
            FtpState state = (FtpState)ar.AsyncState;
            FtpWebResponse response = null;
            try
            {
                response = (FtpWebResponse)state.Request.EndGetResponse(ar);
                response.Close();
                state.StatusDescription = response.StatusDescription;
                // Signal the main application thread that  
                // the operation is complete.
                state.OperationComplete.Set();
            }
            // Return exceptions to the main application thread. 
            catch (Exception e)
            {
                Console.WriteLine("Error getting response.");
                state.OperationException = e;
                state.OperationComplete.Set();

                if (response != null) {
                    response.Close();
                }
            }
        }
    }

person JustNathan    schedule 10.04.2015    source источник
comment
Не уверен, будет ли это иметь значение или нет, но в моем цикле выполнения, где я пишу в буфер потока запросов, у меня также есть такая строка, как if (readBytes == 0) continue; после вашей строки readBytes = stream.Read(buffer, 0, bufferLength);.   -  person Arvo Bowen    schedule 27.10.2015


Ответы (1)


Была такая же проблема. Код основан на том же примере MSDN, но улучшен для работы с SSL/TLS. Файлы могут успешно загружаться часами или днями, но иногда зависают в среде с плохим подключением к Интернету - основной поток блокируется навсегда. Воспроизвел проблему с помощью http://jagt.github.io/clumsy/ и отладил ее. Кажется, что когда загрузка байтов завершена, мы застряли здесь на закрытии потока запросов.

                        // IMPORTANT: Close the request stream before sending the request.
                    requestStream.Close();

Когда мы вызываем это, FtpWebRequest закрывает соединение для загрузки и ожидает завершения операции. С точки зрения FtpWebRequest этап должен быть изменен на ReleaseConnection. Это изменение этапа выполняется, когда FTP-сервер отправляет сообщение по каналу управления о том, что загрузка завершена (когда мы закрыли бинарное соединение). Почему-то никогда не бывает. Возможно, из-за сетевых ошибок ftp-клиент не получает сообщения от сервера и ждет вечно.

Возможные решения:

  1. Реализовать сторожевой таймер, который прерывает соединение после истечения времени ожидания в requestStream.Close().
  2. Похоже, с KeepAlive = false может работать стабильнее (полностью не проверено)
person Pavel Samokha    schedule 21.02.2016