読者です 読者をやめる 読者になる 読者になる

「Androidは電気羊の夢を見るか」を読みたい管理者のブログ

仕事などでの色々な発見を記事にしてます。不定期更新。

非同期操作でFTP転送するのはどうやるの?

FtpWebRequest クラス (System.Net)
の中の最後のサンプルを徹底解説してみるよ

このコードが何をやってるのかと言えば

非同期操作を使用して、FTP サーバーにファイルをアップロードするコード例を次に示します。

だそうです。
ふみゅ。
非同期操作とは

コマンドの実行など、データベースでの一部の操作は、完了までに長時間かかることがあります。 そのような場合、シングルスレッドのアプリケーションでは、他の操作をブロックして、コマンドが終了するまで待機しなければ操作を続行できません。 これに対して、長時間にわたる操作をバックグラウンド スレッドに割り当てることができれば、フォアグラウンド スレッドがアクティブなまま操作を続行できます。 たとえば、Windows アプリケーションでは、操作を実行中のユーザー インターフェイス スレッドの応答性を維持しながら、時間のかかる操作をバックグラウンド スレッドに委任することができます。
非同期操作

だそうです。
いやわかれ!

using System;
using System.Net;
using System.Threading;

using System.IO;
namespace Examples.System.Net
{
    //他スレッドに渡す引数になるクラスです。
  //その意味ではデータを保持するだけのクラス、と形容してもあながち間違いではないでしょう。
    public class FtpState
    {
        private ManualResetEvent wait;
        private FtpWebRequest request;
        private string fileName;
        private Exception operationException = null;
        string status;

        public FtpState()
        {
            wait = new ManualResetEvent(false);
        }

        public ManualResetEvent OperationComplete
        {
            get {return wait;}
        }

        public FtpWebRequest Request
        {
            get {return request;}
            set {request = value;}
        }

        public string FileName
        {
            get {return fileName;}
            set {fileName = value;}
        }
        public Exception OperationException
        {
            get {return operationException;}
            set {operationException = value;}
        }
        public string StatusDescription
        {
            get {return status;}
            set {status = value;}
        }
    }
  //Main関数が入ってる主要クラスです。
  //今回の解析の本命?となりまする。
    public class AsynchronousFtpUpLoader
    {
        // Command line arguments are two strings:
        // 1. The url that is the name of the file being uploaded to the server.
        // 2. The name of the file on the local machine.
        //
        public static void Main(string[] args)
        {
            // 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はftp://フォルダ名/ファイル名という形式にしましょうね。
            Uri target = new Uri (args[0]);

      //ローカルに保存されてるファイル。
            string fileName = args[1];

      //バックグラウンドスレッドに渡す引数のインスタンス
            FtpState state = new FtpState();

      //FtpWebRequestクラスのインスタンスを作ってるようです。FtpWebRequestはWebRequestクラスを継承してるらしく、WebRequestクラスのCreateメソッドを呼び出してます。その上でキャスティングしてます。
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(target);

      //WebRequestのメソッド名ですね。
            request.Method = WebRequestMethods.Ftp.UploadFile;

            // This example uses anonymous logon.
            // The request is anonymous by default; the credential does not have to be specified. 
            // The example specifies the credential only to
            // control how actions are logged on the server.

      //Credentials:認証 すなわちそういうことだ。
            request.Credentials = new NetworkCredential ("anonymous","janeDoe@contoso.com");

            // 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.
      //あぁなるほど。2つのスレッドで同じインスタンスを持つというわけですか。
            waitObject = state.OperationComplete;

            // Asynchronously get the stream for the file contents.
      //バックグラウンドスレッドが動き出しました。すぐ戻ってきます。
      //ちなみにC#ですと非同期メソッドの命名は[Begin」から始まるようですよ!
            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)
            {
                throw state.OperationException;
            }
            else
            {
                Console.WriteLine("The operation completed - {0}", state.StatusDescription);
            }
        }

    //非同期処理メソッドです。なんでそんなこと分かるかと言えば
    //new AsyncCallback (EndGetStreamCallback)でこの関数を指定してるからなんです。
    //あぁなつかしい。Win32API勉強してる頃、これがわからなかったのだ。
        private static void EndGetStreamCallback(IAsyncResult ar)
        {
      //ちなみにこの関数はFTPファイル転送開始の時点で呼ばれます。

      //非同期処理に投げた引数を受け取ります。
            FtpState state = (FtpState) ar.AsyncState;
      
            Stream requestStream = null;
            // End the asynchronous call to get the request stream.
            try
            {
        //GetRequestを投げ終わったという意味ですね。
                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;
                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();

                // 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();
                return;
            }

        }

        // The EndGetResponseCallback method  
        // completes a call to BeginGetResponse.
        private static void EndGetResponseCallback(IAsyncResult ar)
        {
      //FTP転送が終わりましたです。

      //引数
            FtpState state = (FtpState) ar.AsyncState;
            FtpWebResponse response = null;
            try 
            {
        //FTP終わってるけど終了待ち
                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();
            }
        }
    }
}