- 締切済み
ソケットを使ってメッセージを回す
ソケットを使ってメッセージをリング状に回す通信を考えています。クライアントを一台指定し、サーバを複数立ち上げます。クライアントからメッセージを受け取ったサーバが別のサーバに送ることができません。その時BindExceptionError Address Already in use.とエラー表示されます。ソケット通信で、別のIPとポート番号を新しく生成して送ることができないのでしょうか。プログラム等を記述してもらえたら幸いです。どうかよろしくお願いします。
- みんなの回答 (17)
- 専門家の回答
みんなの回答
- ngsvx
- ベストアンサー率49% (157/315)
●String#split(String regex)で指定する、\\.について ドキュメントからの引用で、 「この文字列を、指定された正規表現に一致する位置で分割します。」 とあります。 このため、単純に文字列で区切ることもできますが、もっと高度なことも可能です。 例えば、 「カンマかスペースで区切りたい」 というときは、 split("[, ]") で可能です。 この「正規表現」というのは、文字列中から、あるパターンにマッチした文字列を 探すためのルールです。 正規表現のルールの中に、特殊な扱いをする文字が幾つか定義されています。 「.」もその一つで、特別な意味を持っています。 「.」の文字そのものと一致させるには、前に\を付け、「\.」とする必要があります。 注意しなければならないのは、今の話は正規表現を解析するプログラムに渡す文字の話だということです。 JAVAのコードで書くときには、さらに「\」を増やして、「\\.」とする必要があります。 というのは、JAVAのコンパイラも「\」を特殊な文字と認識しているからです。 ソースコード \\. コンパイル後 \. 正規表現の入力 \. となるわけです。 正規表現という言葉は、JAVAだけで使われるものではありません。 (JAVAで使えるようになったのは、つい最近のことです) 正規表現の詳しいことは別途ネットで検索して下さい。 これだけで本が1冊書けるほどなので、ここでは説明しきれません。 ●再度待ち状態にする件 BCDのプログラムで、「終了処理」のところで、実行に関係することを何もしなければ、 次のマシンに転送せずにメインループに戻る つまり、待ち状態になる。 という動作になるはずです。 再度マシンAからメッセージを送れば動くと思います。 ちなみに、マシンAと、マシンBは、物理的に同じマシンでも問題はありません。 その場合、 A-A-B-C-A-B-C... というループになります。 ●最後に ようやく動作したようで、よかったですね。 今回、いろいろと書いたつもりなので、後で今回のQ&Aを読み直してください。 特に、改造をするなら仕組みは覚えておく必要があります。 今回のプログラムは、通信の基本が入っているので、他のことでも役にたつかもしれません。 では、頑張っていろいろ改造して下さい。
- ngsvx
- ベストアンサー率49% (157/315)
●マシンAのコード プリミティブ型というのをご存じでしょうか? byte,char,short,int,long,float,doubleの変数です。 これらは、Objectクラスとは関係のないものです。 ArrayList#add()は引数にObject型(サブクラスを含む)を要求します。 ですから、add()メソッドにlongの変数などを直接渡すことはできません。 そのような場合、プリミティブ型をObject型に変換する必要があります。 変換には、考え方が2通りあります。 ・プリミティブ型のラッパークラスを使う ・Stringクラスにする 今回の場合は、プリミティブ型の変数は送信するだけであり、送信の際には 文字列として送ることになっていますから、Stringクラスにしてしまえば問題ありません。 プリミティブ型を文字列にするには、そのプリミティブ型のラッパークラスの スタティックメソッドを使います。 例えば、 long a = 100; String s = Long.toString(a); とします。 ですから、コメントになっている部分は、 datas.add(1, Long.toString(startTime) ); のようにして下さい。 ●サブルーチン化に関して サブルーチン化することは、非常に重要なことです。 「そのうちやろう」ではなく、最重要課題だと思って、積極的に使ってください。 サブルーチン化されていないコードは、テストするときにも大変です。 テストは、下位のサブルーチンから行います。そのルーチンが問題なければ、 次に、それを使っているルーチンをテストしていき、さらにそれを使っているもの をテストする・・・というように行います。 下位のものからテストすることで、保証済みの部分を増やしていくことができます。 これはバグが出た場合、発見が用意になってきます。 例えば、Javaの標準APIはほとんど信用して使いますよね? 自分で標準APIを増やしていくようなイメージです。 長いコードのメソッドはテストも行いにくくなるはずです。 メソッド{ //処理A ....(20行) //処理B ....(15行) //処理C ....(10行) } これをテストするには、メソッドに修正を加えなければいけなくなります。 このようなのは、 メソッド{ int a = sub1(); int b = sub2(a); int c = sub3(b); } のような形にしてしまいましょう。 繰り返しになりますが、積極的に使ってください。
お礼
おおー?!やっと、動きました。。丁寧なアドバイスありがとうございます。サブルーチンの重要性を理解することができました。ありがとうございます。
補足
プログラムの方が、完成いたしました。長い間お付き合 いいただきありがとうございました。 お手数ですが、最後に質問を2つさせていただきたいと思います。 ●質問1 1: public static InetAddress getAddressByString(String sIp) { 2: 3: try { 4: String[] parts = sIp.split("\\."); 5: byte[] bParts = new byte[4]; 6: for(int i = 0 ; i < 4 ; i++) { 7: bParts[i] = (byte)Integer.parseInt(parts[i]); 8: } 9: InetAddress adr = InetAddress.getByAddress(bParts); 10: return adr; 11: } 12: catch (IOException e) { 13: System.out.println(e); 14: } 15: catch (Exception e) { 16: System.out.println(e); 17: } 18: 19: } String[] parts = sIp.split("\\."); の("\\.")ですがバックスラッシュコード\\を使う必要はあるのでしょうか?(".")だとエラーが出てしまいます。つまらない質問ですみません。 ●質問2 作ったプログラムでは、AがメッセージをなげるとBCD、BCD........とmax回数周り、終了時間と周回数が表示して終わるようになっています。 終われば、またB、C、Dは、再びメッセージ待ち受け状態にすることは容易でしょうか。 スレッドなどを使うのですか?それとも、終了処理にメソッドを呼び出せばいいのでしょうか? 回答をいただけた時点で回答を締め切り、後は自分で改良を進めようと思います。改めてお世話になりました。
- ngsvx
- ベストアンサー率49% (157/315)
●「return文が・・・」のメッセージに関して 確認の意味も含めて、順を追って説明します。 ・メソッドで戻り値を宣言したら、そのタイプを戻すreturn文が必須となる。 メソッドで¥ public String getValue(){ のように宣言したら、必ず、Stringを返すreturn文が必要です。 ですから、次のコードはコンパイルエラーとなります。 public String getValue(){ } //String型をreturnしていないためエラー ・その場合、プログラムがどのように流れても、returnで返らなければならない。 public String getValue(int type){ if( type == 1 ){ return "abc"; } } この場合、typeが1以外のとき、return文が実行されないため、コンパイルエラー。 ここまでは、多分大丈夫ですよね?ご質問のケースは、今のと似ています。 1: public static InetAddress getAddressByString(String sIp) { 2: 3: try { 4: String[] parts = sIp.split("\\."); 5: byte[] bParts = new byte[4]; 6: for(int i = 0 ; i < 4 ; i++) { 7: bParts[i] = (byte)Integer.parseInt(parts[i]); 8: } 9: InetAddress adr = InetAddress.getByAddress(bParts); 10: return adr; 11: } 12: catch (IOException e) { 13: System.out.println(e); 14: } 15: catch (Exception e) { 16: System.out.println(e); 17: } 18: 19: } これが問題のコードですが、もし4~10行目のどこかで例外が発生すると、 return文が実行されません。 例えばメソッド引数のsIpに"192.a.1.3"と入っていたとします。 すると、7行目(iが1のとき)のInteger.parseInt("a")が実行されることになります。 "a"は数字ではないので、NumberFormatExceptionという例外が発生します。 すると、実行の流れは、 7行目→16行目→17行目→18行目→19行目 となり、return文が実行されずに終了することになります。 従って、コンパイルエラーになります。 解決するには、 ・14、17行目にreturn nullを入れる ・18行目にreturn nullを入れる ・14、17行目で例外をthrowする ・18行目で例外をthrowする ・tryの囲みとcatchを外し、(必要なら)メソッドにthrows節を加える。 などがあります。 ●コードに関して 細かくは見ていませんが、いくつかアドバイスをします。 ・socketのクローズはストリームのクローズの次でいいはずです。 使わない変数をずっと持っているのは、可読性の意味でもよくありません。 ・コードは読みやすくなるように書くことを心がけてください。 読みやすいコードにするには、メソッドをあまり長くかかないことです。 だいたい20行くらいが目安です。 そうすることで、全体の流れがわかりやすくなります。 もし20行以上長くなりそうな場合は、処理のブロックをサブルーチン化します。 ちなみに、20行というのは、画面に入るだいたいの行数です。 今回のコードでは、 for(;;){ //コネクション受付 //コネクション処理 } とできるはずです。 コネクション受付 → Socket socket = server.accept(); コネクション処理 → サブルーチン それに伴い、例外のtryの囲みも別の場所に移動することになります。 また、サブルーチン化しておくと、変更時やテスト時に便利なことが多いです。 私が書いた、transportToNext()メソッドで、getAddressByString()の内容を直接記述 しなかったのも読みやすくするためです。 ●バグの発見 transportToNext()メソッドで、データを全て書いた後で、空行を書き込んでください。 空行がデータの終わりとしているので、このままではバグになります。
お礼
丁寧なご回答ありがとうございます。return文についてや っと理解できました. サブルーチンについては使い慣れていない点もあります が、頑張って心がけて行きたいと思います。 バグの発見もAのマシンで最後のlistに空行を入れるとい うことですね。
- ngsvx
- ベストアンサー率49% (157/315)
サーバーに関しては、コンパイルは完了してますね? もう、わかったとは思いますが、一応、マシンAの送信用プログラムの要件を書いておきます。 ・ユーザーから指定されたポート番号、IPアドレスに対し、メッセージを送る。 (ループ回数も指定してもいいかもしれません) ・送る内容は次の通り(#9で仕様は決めました) ・1行目:コマンド 'transport' ・2行目:ゴールのIPアドレス(ユーザーから指定されたIPアドレス) ・3行目:スタートした時刻 ・4行目:最大のループ数(任意で設定しましょう) ・5行目:現在のループ数(0(ゼロ)) ・6行目:空行 ・必要な内容を送信したら、ストリームやソケットを閉じて、終了します。 こちらの方は、それほど難しくはないでしょう。
補足
●質問内容 ArrayListの以下の設定が分らず、このようなプログラムに なってしまいました。 ・1行目:コマンド 'transport' ・2行目:ゴールのIPアドレス(ユーザーから指定されたIPアドレス) ・3行目:スタートした時刻 ・4行目:最大のループ数(任意で設定しましょう) ・5行目:現在のループ数(0(ゼロ)) ・6行目:空行 コンパイルはできたのですが、どうすればリストごとバインドさせて送ることができるのでしょうか? import java.net.*; import java.io.*; import java.util.*; import java.lang.*; class RingClient { public static void main(String[] args) { try { //ip port入力 String sIp = args[0]; int sPort = Integer.parseInt(args[1]); List datas = new ArrayList(); //初期設定 String cmd ="transport";//args[2]; //1行目 String goalip = "150.84.26.72"; //args[3];//goalipの設定//2行目 Date data = new Date(); //3行目 long startTime = data.getTime(); // 現在の時刻を取得(総経過ミリ秒) int maxLoop = 3; //Integer.parseInt(args[4]);//4行目 int curLoop = 0; //Integer.parseInt(args[5]);//5行目 String last = ""; //空白 /*datas.add(0,cmd); datas.add(1,goalip); datas.add(2,startTime); datas.add(3,maxLoop); datas.add(4,curLoop); datas.add(5,"");*/ //次へ転送 transportToNext(sIp,sPort,datas); } catch (Exception e) { System.out.println(e); } } public static void transportToNext(String sIp, int sPort, List datas) { try { //次のマシンへ接続 Socket nextsocket = new Socket(sIp,sPort); //出力ストリーム取得 OutputStream os = nextsocket.getOutputStream(); PrintWriter pw = new PrintWriter(os); //ストリームへ書き込み for(int i = 0 ; i < datas.size() ; i++) { pw.println((String)datas.get(i)); } pw.flush(); pw.close(); nextsocket.close(); } catch (IOException e) { System.out.println(e); } catch (Exception e) { System.out.println(e); } } }
- ngsvx
- ベストアンサー率49% (157/315)
>コンパイルするとreturn文が指定されていませんというエラーが出ます 例外が発生したときのreturn文がないからです。 >おかしい箇所がありましたら、指摘してくださると幸いです。 おかしい箇所と言われても、概略だけですからねえ(^^; 実際のコードでは「新周回処理」はサブルーチンにした方がいいですね。 それと、質問をして回答をもらったら、解決したのか等の返事をして下さいね。(#9,#11で質問をしてますよね?) それがエチケットですよ。
お礼
ありがとうございました。
補足
すみませんm(_ _)m #9,#11でも質問させて頂いてますが、どうしても以下の質問の点だけが理解できないです。ですので、全体のプログラムを載せて見ました。他の回答に関しては、ほぼ理解できました。ありがとうございました。 ●質問内容 >コンパイルするとreturn文が指定されていませんというエラーが出ます ●回答内容 例外が発生したときのreturn文がないからです。 実際のコードでは「新周回処理」はサブルーチンにした方がいいですね。> class Ring2 { public static void main(String[] args) { try { String sIp = args[0]; int sPort = Integer.parseInt(args[1]); //サーバーソケット立ち上げ ServerSocket server = new ServerSocket(sPort); //自分のアドレス取得 InetAddress myadr = InetAddress.getLocalHost(); String myip = myadr.getHostAddress(); System.out.println("自分のIPアドレスは" + myip + "です"); for( ;; ) { Socket socket = server.accept(); //入力ストリームを取得 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); //全行を読み込む List datas = new ArrayList(); String line = br.readLine(); while( !line.equals("") ) { datas.add(line); line = br.readLine(); } br.close(); //コマンドによる分岐処理 String cmd = (String)datas.get(0); if(cmd.equals("transport")) { String goalip = (String)datas.get(1); long startTime = Long.parseLong( (String)datas.get(2) ); int maxLoop = Integer.parseInt( (String)datas.get(3) ); int curLoop = Integer.parseInt( (String)datas.get(4) ); //equalsメソッドによって文字列を比較 if(myip.equals("goalip")) { if(maxLoop <= curLoop) { //終了処理 System.out.println("終了時間は"+ datas + "回った回数は" + maxLoop); //追加 } else { //新周回処理 datas.set(4, Integer.toString(curLoop + 1)); //次へ転送 transportToNext(sIp,sPort,datas); } } else { //IPが一致しない場合単純に次へ転送 transportToNext(sIp,sPort,datas); } } socket.close(); } } catch (IOException e) { System.out.println("IOException main" + e); } catch (Exception e) { System.out.println("Exception main" + e); } } public static void transportToNext(String sIp, int sPort, List datas) { try { //次のマシンへ接続 InetAddress adr = getAddressByString(sIp); //呼び出し Socket nextsocket = new Socket(adr,sPort); //出力ストリーム取得 OutputStream os = nextsocket.getOutputStream(); PrintWriter pw = new PrintWriter(os); //ストリームへ書き込み for(int i = 0 ; i < datas.size() ; i++) { pw.println((String)datas.get(i)); } pw.flush(); pw.close(); nextsocket.close(); } catch (IOException e) { System.out.println(e); } catch (Exception e) { System.out.println(e); } } public static InetAddress getAddressByString(String sIp) { try { String[] parts = sIp.split("\\."); byte[] bParts = new byte[4]; for(int i = 0 ; i < 4 ; i++) { bParts[i] = (byte)Integer.parseInt(parts[i]); } InetAddress adr = InetAddress.getByAddress(bParts); return adr; } catch (IOException e) { System.out.println(e); } catch (Exception e) { System.out.println(e); } } }
- ngsvx
- ベストアンサー率49% (157/315)
●コマンド引数について コマンド引数は、プログラムの先頭で、意味のある名前の変数に代入してから使いましょう。 その方が、わかりやすくなります。 public static void main(String[] args) { //コマンド引数 String sIpaddress = args[0]; String sPortNo = args[1]; ●文字列比較について compareToメソッドは、文字列の大小判定のためのものです。 同じか違うかだけでよければ、equalsメソッドを使うのが常識です。 常識から外れたことをすると、読んだ人に誤解を与えるので気を付けてください。 ●回答1 >transportToNext(ipadr,port,datas); >のところで、static void transportToNext(String ipadr, int port, List datas) {............}メソッドをどうやって >引数(ipadr,port,datas)と共に渡して呼び出すことができるのでしょうか? 意味がわかりません。transportToNext()メソッドからはtransportToNext()メソッドは呼び出してませんよ。 ●回答2 >static InetAddress getAddressByString(String ipadr) {...............} >メソッドの部分は、どこから呼び出してくるのでしょうか? transportToNext()メソッドの先頭で使っているはずです。 あなたが書いたソースでは、 static void transportToNext(String ipadr, int port, List datas) { //次のマシンへ接続 //InetAddress adr = getAddressByString(args[0]); Socket nextsocket = new Socket(ipadr,port); とコメントになってますね。 ドキュメントでは、SocketクラスのコンストラクタでString、intを渡すと、 「ホスト名」、「ポート番号」ということになるとのことです。 この「ホスト名」がIPアドレス文字列を受け付けるのかは試してませんが、 受け付けるのであれば、getAddressByString()メソッドは必要ありません。 私は、「ホスト名」だから、IPアドレスはダメだと思い、このようなコードにしました。
お礼
解決しました。ありがとうございます
補足
InetAddress adr = getAddressByString(sIp); *1 Socket nextsocket = new Socket(adr,sPort); *1によって以下の文が呼び出されますが、コンパイルするとreturn文が指定されていませんというエラーが出ます。 public static InetAddress getAddressByString(String sIp) { try { String[] parts = sIp.split("\\."); byte[] bParts = new byte[4]; for(int i = 0 ; i < 4 ; i++) { bParts[i] = (byte)Integer.parseInt(parts[i]); } InetAddress adr = InetAddress.getByAddress(bParts); return adr; } catch (IOException e) { System.out.println(e); } catch (Exception e) { System.out.println(e); } } 全体的な流れは、以下の様になっています。 main(){ 引数指定; サーバーソケット立ち上げ; 自分のアドレス取得; for{ 入力ストリーム取得 全行読み込み //コマンドによる分岐 if(myipとgoalipが等しい){ if(ループした回数がmax回数と同じ){ System.out.println(終了時間と回数表示); }else{ 新周回処理 transportTonext();メソッド呼び出し } }else{ IPが一致しない場合 transportTonext(); } } socket.close; } } public static void transportToNext() { try { 次のマシンへ接続; 出力ストリーム取得; for { ストリームへの書き込み } ソケット、ストリームを閉じる; } catch(){ } }//transport....文を閉じる public static InetAddress getAddressByString(){ try{ 文字列アドレスをbyteで変換(省略) } catch(){ } }//Inet...文を閉じる }//main文を閉じる おかしい箇所がありましたら、指摘してくださると幸いです。 そこで、public static netAddress getAddressByString(String sIp) {................}; の部分を省略して、java ホスト名 ポート番号 を入力して実行して待ち受ける事ができました。動かしてみないと分りませんが、次のステップの送信者Aのプログラムをつくって見ようと思います。 どうもありがとうございます。
- ngsvx
- ベストアンサー率49% (157/315)
2つ書き忘れましたので追記しておきます。 1.文字列の比較 文字列の比較は==ではありません。 a = "1234"; if(a.equals("1234")){ } とします。 2.自マシンのIPアドレスの取得方法 IPアドレスの文字列を取得するには、 import java.net.InetAddress; InetAddress adr = InetAddress.getLocalHost(); String ipadr = adr.getHostAddress(); とすればOKです。
お礼
解決しました.ありがとうございます。
補足
分りました。次回からは、自分がどれだけできるかを説明するように気をつけます。今回初めてこの質問コーナーを利用させて貰いましたがいろいろと勉強になりました。以降も引き続き頑張りたいと思います。 しかし、いろいろ調べましたが難しいですね。ngsvxさんの言われた箇所書き直したつもりですが、ここでは、次のIPアドレスとport番号をそれぞれargs[0]、args[1]のパラメータをとるようにしました。あと、文字列の比較はint型のcompareToによる比較で記述しました. 質問1. transportToNext(ipadr,port,datas); のところで、static void transportToNext(String ipadr, int port, List datas) {............}メソッドをどうやって引数(ipadr,port,datas)と共に渡して呼び出すことができるのでしょうか? 質問2. static InetAddress getAddressByString(String ipadr) {...............} メソッドの部分は、どこから呼び出してくるのでしょうか?質問1では、transportToNext(ipadr,port,datas);によってメソッドが呼び出されていると思うのですが、、 質問の内容おかしいかもしれませんが、お願いします!! class Ring2 { public static void main(String[] args) { try { //ポートを取得する int port = Integer.parseInt(args[1]); //サーバーソケット立ち上げ ServerSocket server = new ServerSocket(port); //自分のアドレス取得 InetAddress myadr = InetAddress.getLocalHost(); String myip = myadr.getHostAddress(); System.out.println("自分のIPアドレスは" + myip + "です"); //次のマシンへ接続 InetAddress ipadr = getAddressByString(args[0]); for( ;; ) { Socket socket = server.accept(); //入力ストリームを取得 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); //全行を読み込む List datas = new ArrayList(); String line = br.readLine(); while( !line.equals("") ) {//読み込んだ行が空白でなければループ datas.add(line);//連結リストに読み込んだ行を加える line = br.readLine(); } br.close(); //コマンドによる分岐処理 String cmd = (String)datas.get(0); if(cmd.equals("transport")) { String goalip = (String)datas.get(1); long startTime = Long.parseLong( (String)datas.get(2) ); int maxLoop = Integer.parseInt( (String)datas.get(3) ); int curLoop = Integer.parseInt( (String)datas.get(4) ); //compareToメソッドによって文字列を比較 int flag; flag = myip.compareTo(goalip); if(flag == 0) {//自分の持っているIPがgoalIPと等しい場合 if(maxLoop <= curLoop) { //終了処理 System.out.println("終了時間は"+ datas + "回った回数は" + maxLoop); //追加 } else { //新周回処理 datas.set(4, Integer.toString(curLoop + 1)); //次へ転送 transportToNext(ipadr,port,datas); } } else { //IPが一致しない場合単純に次へ転送 transportToNext(ipadr,port,datas); } } socket.close(); } } catch (IOException e) { System.out.println("IOException main" + e); } catch (Exception e) { System.out.println("Exception main" + e); } } static void transportToNext(String ipadr, int port, List datas) { //次のマシンへ接続 //InetAddress adr = getAddressByString(args[0]); Socket nextsocket = new Socket(ipadr,port); //出力ストリーム取得 OutputStream os = nextsocket.getOutputStream(); PrintWriter pw = new PrintWriter(os); //ストリームへ書き込み for(int i = 0 ; i < datas.size() ; i++) { pw.println((String)datas.get(i)); } pw.flush(); pw.close(); nextsocket.close(); } static InetAddress getAddressByString(String ipadr) { String[] parts = ipadr.split("\\."); byte[] bParts = new byte[4]; for(int i = 0 ; i < 4 ; i++) { bParts[i] = (byte)Integer.parseInt(parts[i]); } InetAddress adr = InetAddress.getByAddress(bParts); return adr; } }
- ngsvx
- ベストアンサー率49% (157/315)
>プログラムを組んだことがないのでけっこう難しいと思うのですが どっひゃー!!初心者だとは思ってましたけど、そこまでとは思いませんでした。 こりゃ、たいへんかも。。。 まあ、しかし、ここまできたら最後まで一緒にがんばりましょう。 次回から、質問をするときには、自分のレベルを説明しておくようにして下さい。 そうすると、答える方も楽になります。 では、回答をします。 ●回答1 Connectクラスというのを定義したようですが、定義した理由は何でしょうか? クラスにする理由は特にないと思うのですが。 また、使うにしても、このクラスはおかしい箇所がいくつかあります。 class Connect{ String ipaddress = "150.84.26.75"; //←ここ int port; //←ここ List datas; //←ここ 矢印で示した部分は、必要ありません。どこからも触っていませんから。 さらに、tranportToNextメソッドをstaticにしたのなら、インスタンスを生成する必要はありません。 もうひとつ、クラスにするのなら、getAddressByString()も定義しておかなければなりません。 ●回答2 その通りです。 Socketクラスには、文字列のIPアドレスを直接渡せず、InetAddress クラスで渡す必要があります。 また、InetAddress にも文字列で渡せず、byte配列で渡す必要があります。 そのために、このような処理になっています。 ●回答3 意味がよくわかりません。 接続先IPアドレスをどうやって設定するかということでしょうか? いくつか方法はありますが、簡単なのはプログラムの起動時にパラメータとして渡すことです。 プログラムの起動は、 C:>java クラス名 のようにすると思いますが、ここで次のようにします。 C:>java クラス名 192.0.0.1 プログラム側では、 public static main(String[] args){ のargs[0]に192.0.0.1が入ります。
お礼
解決しました。ありがとうございます。
- ngsvx
- ベストアンサー率49% (157/315)
前々回では説明しませんでしたが、概要を記述しておきます。 構成は、 A-B-C-D-B-C-D... です。 前々回はA-B間のやりとりのためにstartというコマンドを定義しました。 それは、Bが受信したのが、AからなのかCからなのか判別するのが難しいのでは ないかと予想したからです。(あまり深く考えていませんでした) 今回、よく考えたら、このstartコマンドは必要なさそうです。 そのかわり、transportコマンドにパラメータを定義し、開始時刻はAのマシンでセットします。 ・最大ループ数(何周するか) ・現在のループ数(何周目かを表す。初めてマシンBに来たときには1が入っている) を加えましょう。 そうすると必要なのは、 ・Aの役割をするプログラム ・B~Dのプログラム です。 この2つは、役割が違います。 ●Aのプログラム ・開始IPアドレス、計測回数などを入力し、マシンBに接続する。 ●B~Dのプログラム ・受信したデータを次のマシンに転送する。 ・もし、ゴールIPアドレスが自分なら、現在のループ数に1を加算する。 ・最大ループ数を越えるようなら終了する。 実際には、AとBのマシンは同一でも問題ありません。 その場合、自分のIPアドレスに対して接続することになります。 仕様を確認しておきましょう。 ●プロトコル ・改行までが1レコード ・空行があれば、データ終了 ・レコード内のデータは次のようにエスケープする。 ・&は& ・0x0Dは&CR; ・0X0Aは&LF; ・1行目はコマンド コマンドは、現時点では'transport'のみ (コマンドがtransportの場合) ・2行目は、ゴールとなるIPアドレス ・3行目は、スタートした時刻(java.util.Date#getTime()の値) ・4行目は、最大ループ数。 ・5行目は、現在のループ数。 ・6行目以降は任意。 ・転送する場合は、データの全てを転送する ●プログラムの流れ(B~Dマシン用) ServerSocket serevr = new ServerSocket(); for( ;; ){ Socket socket = serverSocker.accept(); //ストリームを取得 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); //全行を読み込む List datas = new ArrayList(); String line = br.readLine(); while( !line.equals("") ){ datas.add(line); line = br.readLine(); } br.close(); //コマンドによる分岐処理 String cmd = (String)datas.get(0); if(cmd.equals("transport")){ String goalIpAaddress = (String)datas.get(1); long startTime = Long.parseLong( (String)datas.get(2) ); int maxLoop = Integer.parseInt( (String)datas.get(3) ); int curLoop = Integer.parseInt( (String)datas.get(4) ); if(自分がゴールの場合){ if(maxLoop <= curLoop){ //終了処理 }else{ //新周回処理 datas.set(4, Integer.toString(curLoop + 1)); //次へ転送 tranportToNext(nextIp, portNo, datas); } }else{ //単純に次へ転送 tranportToNext(nextIp, portNo, datas); } } socket.close(); } static void transportToNext(String ipaddress, int port, List datas){ //次のマシンへ接続 InetAddress adr = getAddressByString(ipaddress); Socket socket = new Socket(adr); //出力ストリーム取得 OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); //ストリームへ書き込み for(int i = 0 ; i < datas.size() ; i++){ pw.println((String)datas.get(i)); } pw.flush(); pw.close(); socket.close(); } static InetAddress getAddressByString(String ipaddress){ String[] parts = ipaddress.split("\\."); byte[] bParts = new byte[4]; for(int i = 0 ; i < 4 ; i++){ bParts[i] = (byte)Integer.parseInt(parts[i]); } InetAddress adr = InetAddress.getByAddress(bParts); return adr; } ●補足 前の時とは少し変更になりました。 それと、保留になっていた回答の分もあったので、 かなり具体的なコードになってしまいましたが、 雰囲気だけだと思ってください。 (ドキュメントを見ていない部分もありますから、保証できません)
お礼
コマンドという言葉になれませんでしたが、理解できました.ありがとうございます
補足
全体の流れを大まかに理解できました。 class Connect{ String ipaddress = "150.84.26.75"; int port; List datas; static void tranportToNext(String ipaddress, int port, List datas) { //次のマシンへ接続 InetAddress adr = getAddressByString(ipaddress); Socket nextsocket = new Socket(adr); //出力ストリーム取得 OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); //ストリームへ書き込み for(int i = 0 ; i < datas.size() ; i++) { pw.println((String)datas.get(i)); } pw.flush(); pw.close(); socket.close(); } } 質問1 上記の部分は、クラスとして、以下の*1、*2でConnectクラスを生成して呼び出すという形になるのでしょうか? if(goalipaddress == "150.84.26.75") { if(maxLoop <= curLoop) { //終了処理 System.out.println("終了時間は"+ datas + "回った回数は" + maxLoop); } else { //新周回処理 datas.set(4, Integer.toString(curLoop + 1)); //次へ転送 Connect con = new Connect();*1 //tranportToNext(nextIp, portNo, datas); } } else { //単純に次へ転送 //tranportToNext(nextIp, portNo, datas); Connect con new Connect(); *2 } } 質問2 また、↓のプログラムについてはsplitで文字列を分割している部分("\\.")は"150.84.8.122"とあればドットによって分割されていることになるのでしょうか? 質問3 次へ転送するローカルで通信しているのであればnextIPは int nextIp = "150.84.8.122"又はホスト名で"sougou"と何処かで指定してやることで良いのでしょうか? static InetAddress getAddressByString(String ipaddress) { String[] parts = ipaddress.split("\\."); byte[] bParts = new byte[4]; for(int i = 0 ; i < 4 ; i++) { bParts[i] = (byte)Integer.parseInt(parts[i]); } InetAddress adr = InetAddress.getByAddress(bParts); return adr; } } プログラムを組んだことがないのでけっこう難しいと思うのですが、少しでも理解して前に進みたい為、今日考えて分らない事は質問させてもらいたいと思います。何度もすみませんがよろしくお願いします。
- ngsvx
- ベストアンサー率49% (157/315)
1.エスケープ文字について 今回決めたプロトコルでは、 「改行までが1レコード」 としました。そのため、データの中に改行がある場合は困ってしまいます。 そのため、改行コードは別の文字に置き換えてしまうのです。 改行は、0x0A、0x0Dを使いますから、この2つを別の文字にしてしまえばいいわけです。 今回は、私が個人的によく使う方法(XMLを手本にしています)にしただけです。 ・0x0Dは&CR; ・0X0Aは&LF; だけだと、&CR;というデータなのか、0x0Dを置き換えたものかわかりません。 そこで、&も置き換えてしまいます。 ・&は& すると、&CR;というデータの場合は&CR;となり区別できます。 2.コマンドについて これは、単に転送するデータの項目の名前だと思ってください。 誤解があるようですが、 ・改行までが1レコード ・空行があれば、データ終了 ・レコード内のデータは次のようにエスケープする。 ・&は& ・0x0Dは&CR; ・0X0Aは&LF; ・1行目はコマンド というのは、すべて転送するデータです。 つまり、ソケットの出力ストリームに書き込みます。 3.ゴールについて 前回では、ゴール時に何をするのかまでは決められていません。 従って、転送しても、終了しても構いません。 とりあえず、ここまで理解できたら次の質問を回答します。
お礼
ご丁寧に回答ありがとうございます。 1と2について 改行コードなどのコマンドを文字に置き換えていることになるのですね。具体的には、扱ったことがない為良くわかりませんが、記述されていることは理解できました。 3について >前回では、ゴール時に何をするのかまでは決められていません> ゴールした時に転送か終了かを選択するのではなく、ゴールすれば1周ごとにカウントされ、例えば10周すれば終了するようにしたいと思います。何周するかをあらかじめ定義しておくのが良いのではないかと考えております。 1.2.3.の件理解できました。
- 1
- 2
お礼
>「この文字列を、指定された正規表現に一致する位置で分割します。」 とあります。> なるほど、よくわかりました。参考書を探してもなかなかないわけですね(笑) >今回のプログラムは、通信の基本が入っているので、他のことでも役にたつかもしれません> もちろん、活用させてもらいます。頑張ります!!