質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
FTP

FTP(File Transfer Protocol)は、ネットワークでのファイル転送を行うための通信プロトコルの1つである。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

10749閲覧

C# WebClient FTP送信で動作不良

ShukugawaSakura

総合スコア11

FTP

FTP(File Transfer Protocol)は、ネットワークでのファイル転送を行うための通信プロトコルの1つである。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2016/11/08 02:21

編集2016/11/08 02:41

C#でWebClientを使ってFTP送信した際、
レスポンスが遅かったり例外が発生したりすることがあります。

FTPはいろんな設定等があって難しいのですが、あちこち調べて作ったモジュールが下記です。
WebClientなので、Passiveモード+KeepAlive有効だと思いますが、
そこらに問題があるのかと推測しつつよく分かりません。
どのような改善点がありますでしょうか。

因みに、接続先FTPサーバーは、OCNの「Bizメール&ウェブ ビジネス」というものです。

ご教授よろしくお願いします。

現象

下記使用モジュールにあるようなクラスを作り、その中のメソッドを使ってFTP送信をしています。
メソッドは4つのスレッドA,B,C,Dから呼ばれます。
スレッドA,B,Cは10秒毎にそれぞれ150KB程度のファイルを1つ送信します。
スレッドDは60秒毎に40KB程度のファイルを1つx30回(合計30)送信します。
スレッドから呼びますが、下記メソッド部分は排他され、同時並行に実行はされません。
(従いまして、下記メソッドは常に1ファイルのみ送信します。)

大抵は上手くいくのですが、少なければ数時間毎、多ければ10数分毎に、
次の2つの現象のいずれかが発生します。
(現象1)
下記メソッドの webClient.UploadFile からの復帰が、
上手くいくときはあっという間に終わるものが、10秒前後かかる。
(現象2)
下記例外が発生する。

使用モジュール

static class TESTFTP
{
. static NetworkCredential _cred = null; // FTP認証情報
. // 使用メソッド
. public static void WebClientSend(string url,
. string id, string password,
. string serverDir,
. string[] fullFileNames, string[] fileNames)
. {
. string uri = "";
. try {
. if (_cred == null) {
. _cred = new NetworkCredential(id, password);
. }
. using (WebClient webClient = new WebClient()) {
. webClient.Credentials = _cred;
. webClient.Proxy = null;
. for (int i = 0; i < fullFileNames.Length; i++) {
. uri = "ftp://" + url + "/";
. uri += serverDir + "/";
. uri += fileNames[i];
. webClient.UploadFile(uri, fullFileNames[i]);
. }
. }
. }
. catch(WebException webex) {
. throw;
. }
. catch(Exception ex) {
. throw;
. }
. }
}

###例外メッセージ
要因=System.Net.WebException: FTP送信エラー(ftp://.../www/htdocs/data/data.txt) ---> System.Net.WebException: リモート サーバーがエラーを返しました: 227 Entering Passive Mode (,,,,195,100.
---> System.Net.Sockets.SocketException: 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続できませんでした。または接続済みのホストが応答しなかったため、確立された接続は失敗しました。 ...:50020
場所 System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
場所 System.Net.Sockets.Socket.Connect(EndPoint remoteEP)
場所 System.Net.FtpControlStream.QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, Boolean timeout, Stream& stream, Boolean& isSocketReady)
場所 System.Net.FtpControlStream.PipelineCallback(PipelineEntry entry, ResponseDescription response, Boolean timeout, Stream& stream)
場所 System.Net.CommandStream.PostReadCommandProcessing(Stream& stream)
場所 System.Net.CommandStream.PostSendCommandProcessing(Stream& stream)
場所 System.Net.CommandStream.ContinueCommandPipeline()
場所 System.Net.CommandStream.SubmitRequest(WebRequest request, Boolean async, Boolean readInitalResponseOnConnect)
場所 System.Net.FtpWebRequest.TimedSubmitRequestHelper(Boolean async)
場所 System.Net.FtpWebRequest.SubmitRequest(Boolean async)
--- 内部例外スタック トレースの終わり ---
場所 System.Net.WebClient.UploadFile(Uri address, String method, String fileName)
場所 System.Net.WebClient.UploadFile(String address, String fileName)
--- 内部例外スタック トレースの終わり ---
メッセージ=リモート サーバーがエラーを返しました: 227 Entering Passive Mode (,,,,195,100).

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

認証情報も通信時にnewしなければいけないのでは??
認証情報のオブジェクトが同一だから、送信先で同一のコネクションになってたりしませんか??

単一スレッドで発生しない場合は、接続待ち等になっている可能性が高いので、
WebClientのオブジェクトを呼び出し側で一回だけ(スレッド数分)生成して、
使いまわすように修正してみてください。(未検証)

投稿2016/11/08 02:40

編集2016/11/08 04:24
himakuma

総合スコア952

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ShukugawaSakura

2016/11/08 03:01

早速返答いただいてありがとうございます。 認証情報も通信時にnewするというのは、 上記ソースで _cred をローカル変数にして毎回newし、 . cred = new NetworkCredential(id, password); . webClient.Credentials = cred; のようにするということでしょうか。 idとpasswordは変わらないので、毎回同じことをしなくてもと思い、 フィールドに定義していました。 毎回newすることに意味があるのですね??? 一度変更してやってみます!
ShukugawaSakura

2016/11/08 04:00

上記修正を試してみましたが、変わらないようです。 また何か指摘がありましたら、ぜひお願い致します。
himakuma

2016/11/08 04:17

サーバー側で接続を絞っているということは無いでしょうか?単一スレッドの場合は発生しますか?
himakuma

2016/11/08 04:21

おそらく単一スレッドでは発生しない気がします。ファイル送信が完了しているコネクションが終了しきっていなくて、接続待ちになってしまっている気がします。
haru666

2016/12/07 06:57

WebClientを全体で1つだけ生成して、各スレッドで使いまわす場合はどうですか? 排他制御しているならこれでも問題無いと思うのですが。
himakuma

2016/12/07 07:29 編集

マルチスレッド処理をシングル見たいにするって事ですか?その場合、メソッドの処理が完了したらって感じの排他制御ですか?WebClientを使いまわすと相手先のサーバーのコネクション状態によってエラーが発生したりしませんかね?
himakuma

2016/12/07 07:33

すいません、FTPはステートフルなので大丈夫ですね。
haru666

2016/12/07 07:35

いえ、WebClientを使った処理を1個のサービスとしてとらえ、WebClientを1メソッド内で生成して即Disposeするのを止める、ということです。複雑なことを排したければ、一度WebClientを接続した後接続しっぱなしでもいいです。(切断の再接続とかをそのクラス内で処理し、WebClientはずっと生かしたままにする。) かなり頻繁に送信しているので再接続のコストが見合っているのかという疑問もあります。 排他制御は現在の方法が分からないので細かいところまでは言えませんが…。 セマフォで排他制御するのか、Queueに処理を足しこむのか。送信するだけならQueueに積み上げるだけでも良いんですよね。レスポンス必要な場合はセマフォか、継続タスクをアクションとして登録する。文脈がわからないので細かく提案できかねますが。 ファイル送信が元々排他的で互いにブロックしてしまう設計なら、WebClientを複数作ること自体に違和感を覚えます。1プロセス1WebClientは、1人の人が順次実行すればいいタスクを、10人整列させてるような書き方っていうんですかね。
haru666

2016/12/07 07:51

WebClient自体はスレッドセーフではないので、排他制御が怪しかったら各スレッド毎に1個WebClient作ってキープしていてもいいのでは…とも思います。
himakuma

2016/12/07 07:54

相手のサーバーの設定しだいですが、自分の場合、いままでFTPなどのアップロードの接続で接続時間が一定時間を超えると切断するという設定をしたりしていたので、接続しっぱなしの考えがありませんでした。確かに10秒おきに再接続はコストが高いですね。
himakuma

2016/12/07 07:55

各スレッド毎に1個WebClientを生成して、各スレッドで使いまわすが現実的ですかね?
haru666

2016/12/07 08:14

おっしゃる通りサーバーの設定次第なので明確なことはこれ以上は質問主であるShukugawaSakuraさんがいないと言えないのですが。しかも私の手元にも現在検証可能な環境がありませんし…。(ちょっと無責任すぎましたかね。) 10秒に1回どころか、10秒に3回(しかもほぼ連続実行されるのかな?)と思うので。FTPではありませんが、自分も随分前に連続でWebClientを作って破棄してを繰り返すとポートがオープンできなくなる問題に直面したことがあります。最終的に1個使いまわすように書き換えたんですが、マルチスレッドの要件ではなかったので、そこがちょっと分からないところなのです。 > 各スレッド毎に1個WebClientを生成して、各スレッドで使いまわすが現実的ですかね? ですかね。動いてくれればですが。同じ内容の接続を4つのクライアントで作った時内部的にどうなるんだったっけ??というのが今一思い出せなくてですね…自前で排他してるなら1個を使い回せば問題は起きないと思うのですが。
ShukugawaSakura

2016/12/09 03:56

長い間すみませんでした。 お二方のやりとり、とても勉強になりました。 どこまで詳細に載せたら良いかわからかったのですが、 以下のような処理でいまのところ問題なく動作しているようです。 最初の頃より並行して起動されるFTPの数を減らすようにしたこと、 NetworkCredential をFTP送信スレッド分用意して最初に初期化しておいたこと、 この2つが主な修正点です。 ただ、これらの修正が有効だったのかどうか、あまり自信はありません。 ウェブサーバーや社内のLAN環境にも原因があった可能性があり...。 現在、WebClient は送信の度に作成しています。 お二人のやりとりを参考に、 各スレッド毎に1個WebClientを生成する方法も試してみたいと思います。 ありがとうございました。 ' class Thread0 ' { ' NetworkCredential[] _creds; ' ' public Thread0() ' { ' // FTP送信スレッド分の NetworkCredential を作成 ' _creds = new NetworkCredential[2]; ' for (int i = 0; ' i < _creds.Length; ' _creds[i++] = new NetworkCredential(userName, password)); ' } ' ' void MainLoop() ' { ' // それぞれのスレッドにそれぞれの NetworkCredential を渡して... ' Thread1 thread1 = new Thread1(_creds[0]); ' thread1.Init(); ' Thread2 thread2 = new Thread2(_creds[1]); ' thread2.Init(); ' ' for (終了まで) { ' if (タイミングが来れば) { ' // thread1 内のセマフォを開放 ' thrad1.Exec(); ' } ' ' if (タイミングが来れば) { ' // thread2 内のセマフォを開放 ' thrad2.Exec(); ' } ' } ' } ' } ' ' class Thread1 ' { ' NetworkCredential _cred; ' SemaphoreSlim _sem; ' ' public Thread1(NetworkCredential cred) ' { ' _cred = cred; ' _sem = new SemaphoreSlim(0); ' } ' ' public void Init() ' { ' // ファイルを作ってFTPアップロードするスレッドを起動しておく ' var makeDataThread = Task.Run(()=>SubThread()); ' } ' ' // 上位から指示があるとセマフォを開放 ' // これにより SubThread が1度だけ実行される ' public void Exec() ' { ' // セマフォ開放 ' _sem.Release(); ' } ' ' // ファイルを作ってFTPアップロードするスレッド ' // 上位からの指示によってセマフォが開放されると1度だけ実行され、 ' // 再びセマフォ開放待ちに入る ' async Task SubThread() ' { ' for (終了まで) { ' // セマフォ開放待ち ' await _sem.WaitAsync().ConfigureAwait(false); ' ' // いろんなファイルを作って... ' : ' ' // それらをFTPアップロード ' Upload(url, file); ' } ' } ' ' void Upload(string url, string file) ' { ' FTP ftp = new FTP(); ' ftp.Send(url, file, _cred); ' } ' } ' ' class Thread2 ' { ' // Thread1とだいたい同じ ' } ' ' class FTP ' { ' public void Send(string url, string file, NetworkCredential cred) ' { ' using (WebClient webClient = new WebClient()) { ' webClient.Credentials = cred; ' webClient.Proxy = null; ' : ' webClient.UploadFile(uri, file); ' } ' } ' }
ShukugawaSakura

2016/12/09 03:58

ソースを再掲します。 '  class Thread0 '  { '    NetworkCredential[] _creds; ' '    public Thread0() '    { '      // FTP送信スレッド分の NetworkCredential を作成 '      _creds = new NetworkCredential[2]; '      for (int i = 0; '         i < _creds.Length; '         _creds[i++] = new NetworkCredential(userName, password)); '    } ' '    void MainLoop() '    { '      // それぞれのスレッドにそれぞれの NetworkCredential を渡して... '      Thread1 thread1 = new Thread1(_creds[0]); '      thread1.Init(); '      Thread2 thread2 = new Thread2(_creds[1]); '      thread2.Init(); ' '      for (終了まで) { '        if (タイミングが来れば) { '          // thread1 内のセマフォを開放 '          thrad1.Exec(); '        } ' '        if (タイミングが来れば) { '          // thread2 内のセマフォを開放 '          thrad2.Exec(); '        } '      } '    } '  } ' '  class Thread1 '  { '    NetworkCredential _cred; '    SemaphoreSlim _sem; ' '    public Thread1(NetworkCredential cred) '    { '      _cred = cred; '      _sem = new SemaphoreSlim(0); '    } ' '    public void Init()     '    { '      // ファイルを作ってFTPアップロードするスレッドを起動しておく '      var makeDataThread = Task.Run(()=>SubThread()); '    } ' '    // 上位から指示があるとセマフォを開放 '    // これにより SubThread が1度だけ実行される '    public void Exec() '    { '      // セマフォ開放 '      _sem.Release(); '    } ' '    // ファイルを作ってFTPアップロードするスレッド '    // 上位からの指示によってセマフォが開放されると1度だけ実行され、 '    // 再びセマフォ開放待ちに入る '    async Task SubThread() '    { '      for (終了まで) { '        // セマフォ開放待ち '        await _sem.WaitAsync().ConfigureAwait(false); ' '        // いろんなファイルを作って... '          : ' '        // それらをFTPアップロード '        Upload(url, file); '      } '    } ' '    void Upload(string url, string file) '    { '      FTP ftp = new FTP(); '      ftp.Send(url, file, _cred); '    } '  } ' '  class Thread2 '  { '    // Thread1とだいたい同じ '  } ' '  class FTP '  { '    public void Send(string url, string file, NetworkCredential cred) '    { '      using (WebClient webClient = new WebClient()) { '        webClient.Credentials = cred; '        webClient.Proxy = null; '           : '        webClient.UploadFile(uri, file); '      } '    } '  }
haru666

2016/12/09 04:10

んん…Thread1自体は複数回同時実行されませんが、Thread1とThead2は同時実行される場合があるんですね とすると、結構WebClientを1本化する案は大変そうでしたね…根本的にいろいろ変えないといけないので 解決したようならよかったです
ShukugawaSakura

2016/12/09 05:55

ありがとうございます。 そうなんです。 Thread1と2は場合によって並行で走ります。 これらそれぞれに WebClient を作っておいて使いまわす方法も試してみたいと思います。 またご報告できると良いのですが。 本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問