Асинхронен FTP при грешки при свързване

Направих услуга за Windows, която качва всички файлове (снимки), които са поставени в папка.

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

Всичко работи добре, докато не извадя мрежовия кабел.

Предполага се, че ftp методът хваща грешката и връща false bool. След това премествам файла в неуспешна папка и той продължава със следващия файл. Когато се достигне третият файл, действието 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;
                }
            }

AsynchronousFtpUpLoader:

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
Не съм сигурен дали това има значение или не, но в моя do цикъл, където пиша в буфера на потока на заявката, също имам ред като 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