- ベストアンサー
テキストファイルのアップロード
以下の仕様において、FTPプロトコルを用いて ファイルをアップロードする方法を教えてください。 /** * テキストファイルのFTPプロトコルによるアップロード * * @param filename 転送するテキストファイル(フルパス) * @param hostFolder 転送先フォルダ * @param hostAddress ホストアドレス * @param userName ユーザ名 * @param password パスワード */ public static void uploadText( String filename, String hostFolder, String hostAddress, String userName, String password ) throws IOException ネットワークプログラミングに関しては、全くの素人ですが、 Java(スタンドアロン)アプリケーションの開発経験はありますので、 特にコードの内容をご説明頂かなくても構いません。解読します。 すみませんが、よろしくお願い申し上げます。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
2つのFTPサーバA、Bで、 「No.1 補足欄」のコードの実験。 ***実験結果*** (1)ソースコードはそのまま A・・・タイムアウト B・・・タイムアウト (2)メソッドdataConnection()の中の ctrlOutput.println(cmd); の直前(※PORTコマンド発行の直前)に try{Thread.sleep(1000);}catch(Exception e){} と記述。 A・・・ファイル転送成功。 B・・・ファイル転送成功。 (3)「サーバへの接続直後」と「FTPコマンド発行直後」ごとに、 サーバからの応答メッセージを受信ストリーム(ctrlInput)から読み出す。 A・・・ファイル転送成功。 B・・・ファイル転送成功。 --- なんか不可解ですが・・・。
その他の回答 (6)
- kacchann
- ベストアンサー率58% (347/594)
ちょっとくわしいことはわからないのですが、 「クライアントによるaccept()待ち」(※以降、単に「accept()待ち」と書く)で タイムアウトが発生する原因として、 (1)「accept()待ち」より前のSTORコマンド、 またはそれ以前のコマンドが、 すでに失敗している。 (※これはレスポンスコードを確認すればわかる。 失敗時の対処法は、リトライ(コマンド再送信)など) (2)全コマンドが成功しているが、 クライアント側で「accept()待ち」に入るより先に、 サーバからアクセスが来る場合、 そのアクセスが無視される。 (3)その他 --- (1)も(2)も(3)も、対処法は、ややめんどうそうです。 (2)は、本当にそういうことが起こりうるのか、素人の僕には判断できません。 職業柄、ネットプログラミングをやってるプロの人なら わかると思いますが・・・。 (2)の現象が本当に起こると仮定した場合、 その対処法は、 別スレッドで「accept()待ち」をする とかだと思います。 けっこう面倒そう。 (※前に僕が挙げた、Apache Commons NetのFTPClientでは、 これに対処してない) 面倒なので、 「タイムアウト発生時はPORTコマンドあたりからリトライ」 という手もあるかも? 参考資料: ・http://x68000.q-e-d.net/~68user/net/ftp-5.html (このページの下のほうに書いてあるのは、(2)のような状況のこと・・・だと思う) ・http://sourceforge.net/projects/j-ftp (このソースコードは、「別スレッドでaccept()待ち」してるっぽい) --- ・・・なあんて推測で書きましたが、 いずれにせよ、 レスポンスコードを調べて問題発生場所を特定することが 「デバッグ」の第一歩です。
お礼
ご回答ありがとうございます。 使えるJavaサンプルコードは、転がっていないものですね。 Windows上で動作するGUI付きFTPアップロードツールは 腐るほどゴロゴロしているのに... 専門外なので深入りするつもりは無いのですが、 せめて、簡単なネゴシエーションの方法は解っておかないと いけない気もしています。
- kacchann
- ベストアンサー率58% (347/594)
No.5の者です。 とりあえず、"礼儀正しく"、 (1)サーバからの応答メッセージは、送られてくるたびに、残さずきっちりとる (2)クライアント終了時に"QUIT"する の方針は守るようにやってみました。 (※(1)は、ちゃんとできてるかわからない。 FTPプロトコルの仕様を知らず、カンで書いたので) 下にソースコード載せときます。 --- それから、ライブラリを使ったほうが安心かも。 http://commons.apache.org/net/ (の「FTPClient」クラス) 、 FTPプロトコルが隠蔽されてて ひじょうに良さそう。 --- import java.io.*; import java.net.*; public class FTP { Socket ctrlSoc;PrintWriter ctrlOut;BufferedReader ctrlIn;byte[] addr; void o(String s){System.out.print(s);}void on(String s){o(s+"\n");} void com(String s){ on(">"+s);ctrlOut.println(s);ctrlOut.flush();o(getRes()); } String getRes(){ String l=null,sb="";boolean loop=true; try{while(loop&&(l=ctrlIn.readLine())!=null){ char c=l.charAt(3);if(c==' '){loop=false;}sb+=l+"\n";} }catch(Exception e){e.printStackTrace();}return sb; } public FTP(String host,String name,String pass,String dir,String file){ try { ctrlSoc=new Socket(host,21); addr=ctrlSoc.getLocalAddress().getAddress(); ctrlOut = new PrintWriter(ctrlSoc.getOutputStream()); ctrlIn = new BufferedReader(new InputStreamReader(ctrlSoc.getInputStream())); o(getRes()); com("USER " + name); com("PASS " + pass); com("CWD " + dir); com("TYPE I"); send(file);//PORT+STOR com("QUIT "); ctrlOut.close();ctrlIn.close();ctrlSoc.close(); }catch (Exception e) { e.printStackTrace(); } } void send(String file){ try{ FileInputStream fis=new FileInputStream(file); Socket dataSock=dataConnection("STOR " + file); OutputStream out = dataSock.getOutputStream(); int n; byte[] buff = new byte[1024]; while ((n=fis.read(buff))>-1){ out.write(buff,0,n); } out.close();dataSock.close();fis.close(); o(getRes()); }catch(Exception e){e.printStackTrace();} } private Socket dataConnection(String ctrlcmd) throws IOException,UnknownHostException { String p = "PORT ";ServerSocket ss=new ServerSocket(0,1); for (int i=0;i<4;i++) {p+=(addr[i]&0xff)+",";} p+=(((ss.getLocalPort())/256)&0xff)+","+(ss.getLocalPort()&0xff); com(p);com(ctrlcmd);Socket s = ss.accept();ss.close(); return s; } public static void main(String[] args) { new FTP(args[0],args[1],args[2],args[3],args[4]); } }
補足
私の環境では、コマンド送信とレスポンス受信の間に ディレイを挿入しないと数百回に1回程度の確率で タイムアウトとが発生しています。 発生頻度が低いために、現在までのところ、別の対処法が 見つかっていません。 この現象は私だけなのでしょうか?...
- pcbeginner
- ベストアンサー率46% (261/560)
>>発生しているならスタックトレースを見てみては? >とは、どういう意味でしょうか? 例外が発生しているかどうかをtry-catchで括ってみて、 catchの中でException#printStackTrace()をしてみてはどうでしょうか? という意味です。 なんとなく気になったのですが(解決には関係ないかもしれません。)、 private static Socket dataConnectionメソッド の該当個所で タイムアウトが発生する=ServerSocket#accept()で「SocketTimeoutException」が発生 ということですよね? (Socket#accept()ではなくてServerSocket#accept()ですよね?) 該当個所でSocketTimeoutExceptionが発生すると、そのまま例外をスローしてしまうので、直後の serverDataSocket.close() というクローズ処理が行われませんよね? これはまずくないですかね? クローズ処理自体が必要ないためなら良いのですが…気になったので…。 >すみませんが、とりあえず、実験結果を教えていただけませんか。 >>シングルスレッドが終わっただけのよう >ということは、ご自身の環境でテストされでも、 >Socket#accept()メソッドでタイムアウトが発生することを確認した、 >すなわち前出のコードでは、アップロードに成功しなかった >という意味でしょうか? 「実験結果以外は必要ない」というように解釈できてしまいますが、 そういうことでしょうか? で、あれば今回のアドバイスは無視してください。失礼しました。
お礼
ご回答ありがとうございました。 ANo.5に示したサンプルコードにてテストしていただき、 結果だけでもご報告いただくことはできないでしょうか? よろしくお願い申し上げます。
- choconamacream
- ベストアンサー率44% (152/338)
>Java(スタンドアロン)アプリケーションの開発経験はありますので、 この中には、マルチスレッドも入っていますか? 単に、accept()の所でシングルスレッドが終わっただけのように思われますが・・。
お礼
ご回答ありがとうございました。 ANo.5に示したサンプルコードにてテストしていただき、 結果だけでもご報告いただくことはできないでしょうか? よろしくお願い申し上げます。
補足
すみませんが、とりあえず、実験結果を教えていただけませんか。 >シングルスレッドが終わっただけのよう ということは、ご自身の環境でテストされでも、 Socket#accept()メソッドでタイムアウトが発生することを確認した、 すなわち前出のコードでは、アップロードに成功しなかった という意味でしょうか? ちなみに、私自身はJavaで3Dレーダー画像処理アプリケーションの プロトタイプを設計・実装したりしています。 そこで、GUI,バックグラウンド計算とファイル処理等の レベルでのマルチスレッドプログラミングは実用しています。
- pcbeginner
- ベストアンサー率46% (261/560)
何か例外が発生していませんか? 発生しているならスタックトレースを見てみては?
補足
Socket#accept()のタイムアウトなんですが、 >発生しているならスタックトレースを見てみては? とは、どういう意味でしょうか? その後、別のFTPサーバに接続してみましたが、 するとそちらでは1度もアップロードに成功しません。 原因は同じく、Socket#accept()のタイムアウトです。 どなたか、前述のコードで、アップロードをテストして 頂ける方はおられないでしょうか? ”成功した”、”失敗した”という結果だけでもかまいませんので、 複数の方の検証結果を頂ければ非常に幸いです。
こんなページがあります。ほぼご希望の内容では? http://www.hellohiro.com/ftp.htm
お礼
ご回答ありがとうございました。 ANo.5に示したサンプルコードにてテストしていただき、 結果だけでもご報告いただくことはできないでしょうか? よろしくお願い申し上げます。
補足
ご回答ありがとうございます。 ほぼ、仕様通りの実装内容でした。 しかし、2回目以降のアップロードが失敗してしまいます。 下記コードの下から4行目 Socket dataSocket = serverDataSocket.accept(); の部分で、タイムアウトになります。 不思議なことに、Windowsを再起動すると1回目のアップロードは、 正常に行えます。 なお、再起動しても10回に1回程度は失敗します。 ネットワークプログラミングに全く不慣れなもので、このような場合 どのようにデバッグすればよいのでしょうか? また、皆様の環境での、下記コードの実行結果も教えていただければ幸いです。 /** * FTPプロトコルによるファイルアップロード * * @param port ポート番号 * @param localFolder ローカルフォルダ * @param filename ファイル名 * @param hostFolder ホストフォルダ * @param hostAddress ホストアドレス * @param userName ユーザ名 * @param password パスワード */ public static void uploadText( int port, String localFolder, String filename, String hostFolder, String hostAddress, String userName, String password ) throws IOException { // 接続します Socket ctrlSocket = new Socket(hostAddress, port); byte[] localHostAddress = ctrlSocket.getLocalAddress().getAddress(); PrintWriter ctrlOutput = new PrintWriter(ctrlSocket.getOutputStream()); BufferedReader ctrlInput = new BufferedReader(new InputStreamReader(ctrlSocket.getInputStream())); // ユーザー認証します ctrlOutput.println("USER " + userName); ctrlOutput.flush(); ctrlOutput.println("PASS " + password); ctrlOutput.flush(); // 指定したディレクトリに移動します if( hostFolder != null ) { ctrlOutput.println("CWD " + hostFolder); ctrlOutput.flush(); } // バイナリモードに設定します(アスキーモードの場合は'TYPE A') ctrlOutput.println("TYPE I"); ctrlOutput.flush(); // アップロードします FileInputStream fis = new FileInputStream(localFolder + filename); Socket dataSocket = dataConnection("STOR " + filename, ctrlOutput, localHostAddress); OutputStream outstr = dataSocket.getOutputStream(); int n; byte[] buff = new byte[1024]; while ((n = fis.read(buff)) > 0) { outstr.write(buff,0,n); } dataSocket.close(); fis.close(); // 接続を閉じます ctrlOutput.close(); ctrlInput.close(); ctrlSocket.close(); } /** * データ送受信用ソケットを取得します */ private static Socket dataConnection( String ctrlcmd, PrintWriter ctrlOutput, byte[] localHostAddress ) throws IOException,UnknownHostException { String cmd = "PORT "; ServerSocket serverDataSocket = new ServerSocket(0,1); for (int i=0;i<4;i++) { cmd = cmd + (localHostAddress[i] & 0xff) + ","; } cmd = cmd + (((serverDataSocket.getLocalPort())/256) & 0xff) + "," + (serverDataSocket.getLocalPort() & 0xff); ctrlOutput.println(cmd); ctrlOutput.flush(); ctrlOutput.println(ctrlcmd); ctrlOutput.flush(); serverDataSocket.setSoTimeout( 10 * 1000 ); Socket dataSocket = serverDataSocket.accept(); // 2回目以降タイムアウト! serverDataSocket.close(); return dataSocket; }
お礼
テストしていただき、誠にありがとうございます。非常に助かりました。 ご指摘の内容をこちらでも検証しました所、ほぼ同様な結果が得られました。本当に不思議です。 ほぼと書いたのは、確かにタイムアウトは発生しなくなりましたが、 アップロード自身を成功していないという現象が時々発生していたためです。 この修正のままで使用し続けるのも不気味な感じがしたため、その後検討を行ったところ、 コマンドを送出した場合には、少し待ってから、必ず(ダミー)リードする という仕様にしましたところ、正しく動き続けているようです。 これならば、論理的にそんなに不自然さもありませんので、実用性があるかもしれません。 そもそも、ストリームの種別を意識しなくても済むように、PrintWriterクラスで ラップするという仕様のはずなのに、ここまでソケットを意識しなくてはいけないとなると、 逆にPrintWriterクラスの存在が鬱陶しくさえなってしまいます。 ということは、プログラミング手法として正しくなんでしょうね... 以下に、メインメソッドも付けた、テストプログラムを記載します。 (文字数制限の都合で、補足に示します) 下のプログラムを動かした結果を、一人でも多くの方からご報告頂ければ非常に幸いです。 もちろん、ダミーリードというような方法ではなく、様々なレスポンスに対応じて 正しくネゴシエーションするサンプルソースの提供もお待ちしております。
補足
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; public class FtpUploadTest { public static void main(String[] args) { try { uploadFile( 21, "C:\\folder\\subfolder\\", "upfile.txt", "/data/test", "ftp.server.com", "myname", "******" ); } catch (IOException e) { e.printStackTrace(); } } /** * FTPプロトコルによるファイルアップロード * * @param port ポート番号 ex) 21 * @param localFolder ローカルフォルダ ex) "C:\\folder\\subfolder\\" * @param filename ファイル名 ex) "upfile.txt" * @param hostFolder ホストフォルダ ex) "/data/test" * @param hostAddress ホストアドレス ex) "ftp.server.com" * @param userName ユーザ名 ex) "myname" * @param password パスワード ex) "******" */ public static void uploadFile( int port, String localFolder, String filename, String hostFolder, String hostAddress, String userName, String password ) throws IOException { Socket ctrlSocket = new Socket(hostAddress, port); byte[] localHostAddress = ctrlSocket.getLocalAddress().getAddress(); PrintWriter ctrlOutput = new PrintWriter(ctrlSocket.getOutputStream()); BufferedReader ctrlInput = new BufferedReader(new InputStreamReader(ctrlSocket.getInputStream())); // ユーザー認証 command( "USER " + userName, ctrlOutput, ctrlInput ); command( "PASS " + password, ctrlOutput, ctrlInput ); // ディレクトリ移動 if( hostFolder != null ) { command( "CWD " + hostFolder, ctrlOutput, ctrlInput ); } // バイナリモードに設定(アスキーモードの場合は'TYPE A') command( "TYPE I", ctrlOutput, ctrlInput ); // アップロード Socket dataSocket = dataConnection("STOR " + filename, ctrlOutput, ctrlInput, localHostAddress); OutputStream outstr = dataSocket.getOutputStream(); FileInputStream fis = new FileInputStream(localFolder + filename); int n; byte[] buff = new byte[1024]; while ((n = fis.read(buff)) > 0) { outstr.write(buff,0,n); } dataSocket.close(); fis.close(); ctrlOutput.close(); ctrlInput.close(); ctrlSocket.close(); } /** * データ送受信用ソケットを取得します */ private static Socket dataConnection( String ctrlcmd, PrintWriter ctrlOutput, BufferedReader ctrlInput, byte[] localHostAddress ) throws IOException,UnknownHostException { String cmd = "PORT "; ServerSocket serverDataSocket = new ServerSocket(0); for (int i=0;i<4;i++) { cmd = cmd + (localHostAddress[i] & 0xff) + ","; } cmd = cmd + (((serverDataSocket.getLocalPort())/256) & 0xff) + "," + (serverDataSocket.getLocalPort() & 0xff); command( cmd, ctrlOutput, ctrlInput ); command( ctrlcmd, ctrlOutput, ctrlInput ); serverDataSocket.setSoTimeout( 10 * 1000 ); Socket dataSocket = null; dataSocket = serverDataSocket.accept(); serverDataSocket.close(); return dataSocket; } /** * ウェイト,レスポンス受信付きコマンド送信 */ private static String command( String cmd, PrintWriter ctrlOutput, BufferedReader ctrlInput ) throws IOException { final long SLEEP_TIME = 200; // 適宜、調整してください ctrlOutput.println(cmd); try{Thread.sleep(SLEEP_TIME);}catch(Exception e){} String res = ctrlInput.readLine(); ctrlOutput.flush(); //System.out.println( res ); return res; } }