プロが教える店舗&オフィスのセキュリティ対策術

ソケットを使ってメッセージをリング状に回す通信を考えています。クライアントを一台指定し、サーバを複数立ち上げます。クライアントからメッセージを受け取ったサーバが別のサーバに送ることができません。その時BindExceptionError Address Already in use.とエラー表示されます。ソケット通信で、別のIPとポート番号を新しく生成して送ることができないのでしょうか。プログラム等を記述してもらえたら幸いです。どうかよろしくお願いします。

A 回答 (17件中11~17件)

速度を計るのですか。


かなり重要なことを忘れていましたね。。。

そのように、しっかりした目的があるなら、もう少しまじめに
検討する必要があります。

とりあえず、前回のことは忘れてください。


●まず、速度を計るということは、データを無限ループ的に回すのではなく、
1周させることが目的となるわけです。

何度も計りたいのなら、1周という単位の仕事を複数回繰り返すことになります。
そうすると、考え方が変わってきますね。
実現するには、1周したかどうかの判定が必要になります。

方法はいろいろあると思いますが、送るデータにゴールのマシンのIPアドレスを
入れておくのが簡単でしょう。

ということは、送るデータに約束事(プロトコル)が必要になってくるわけで、
まずはこれをしっかり決めておかなければなりません。

とりあえず、次のようなものでどうでしょうか。

・改行までが1レコード
・空行があれば、データ終了
・レコード内のデータは次のようにエスケープする。
 ・&は&
 ・0x0Dは&CR;
 ・0X0Aは&LF;
・1行目はコマンド

コマンドは、'transport'か'start'

(コマンドがtransportの場合)
・2行目は、ゴールとなるIPアドレス
・3行目は、スタートした時刻
・4行目以降は任意。
・転送する場合は、データの全てを転送する

(コマンドがstartの場合)
・2行目以降は任意。



転送するのは、このまとまりの単位になります。

*コマンドのstartは、転送開始を意味していて、A-B-C-D-B-C-D...
の流れの、A-B間のプロトコルを想定しています。





●次に、受信・送信ですが、送信が終わるたびにセッションを切った方が流れがすっきり
すると思います。(これは主観によりますけど)


●以上をふまえて、セッションの基本的な流れを考えると、


Socket socket = serverSocker.accept();

if(transportコマンド判定){
 //データの受信
 ...
 ...

 //ゴール判定
 if(ゴールの場合){
  //ゴールの処理
  ...
  ...
 }else{
  //転送処理
  ...
  ...
 }
}else
if(startコマンド判定){
 //転送処理
 ...
 ...
}

socket.close();


となります。


●このままでは、1度受信をすると、プログラムが終了してしまうため、
ループで囲みます。

for( ; ;){
 Socket socket = serverSocker.accept();

 ...
 ...

 socket.close();
}


●このままではセッションが終了するまで、他からの接続ができないので、
普通のサーバーのプログラムは、受信後の処理はスレッドにして、すぐにループに
復帰するようにしますが、今回の場合はそこまでしなくてもいい気がします。

for( ; ;){
 Socket socket = serverSocker.accept();
 MySessionThread session = new MySessionThread(socket);
 session.start();
}


●順を追って説明したつもりですが、わからない箇所があれば質問してください。

この回答への補足

3つ質問があります。

>・レコード内のデータは次のようにエスケープする。
 ・&は&
 ・0x0Dは&CR;
 ・0X0Aは&LF;>

この意味がよくわからないのですが、どの分野を勉強すればいいのですか?

>コマンドは、'transport'か'start'>

コマンドとは、プログラムをコンパイルした後に、コマンド入力するするということなんでしょうか。私は、以下の様に解釈したのですが、、最初にメッセージを受け取る場合は'transport',送る場合は、'start'の選択をします。
入力ストリームを使って、ユーザがキーボードで打ち込んで選択しています。

System.out.println("Please transport or start input!!");//送信側か受信側か選択する
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(input);
String s;
while((s = br.readLine()) != null) {
}...........

また、transportを選択して、ゴールならば、//ゴールの処理は、一周ごとに書き換えられ、カウントとタイムを計る。そうでなければ、転送処理をするためソケットを作って、出力ストリームを用意して、次のサーバに転送すると解釈したのですが以下の※1には、カウントとタイムを計る他に転送する処理が必要ではないのでしょうか。(一周してゴールすると止まってしまうと思ったのですが、)

>if(transportコマンド判定){
 //データの受信
 ...
 ...

 //ゴール判定
 if(ゴールの場合){
  //ゴールの処理 ※1
  ...
  ...
 }else{
  //転送処理
  ...
  ...
 }
}else>

この方法だとそれぞれのマシンに同じプログラムを入れることで、A-B-C-D-A-B-C-D...と一周する方がいいいみたいですね。
 一通り書いてみましたが、IPアドレスとスタートした時間をメッセージと一緒に渡す方法などまだできてません。
 理解できていない点もあり質問内容があいまいですが、ご回答の方よろしくお願いします.

補足日時:2004/12/13 18:34
    • good
    • 0
この回答へのお礼

流れ、概要を少し掴むことができました。ありがとうございます。

お礼日時:2004/12/21 15:01

もうひとつ、先にお断りしておきますが、私の方針でズバリのコードは書きませんので


ご了承下さい。

そのかわり、わかる範囲でなら、しつこく付き合います。


>イ.マシンAから、何かを受け取ったら、すぐに転送する。>
>
>イをやりたいと考えております。


わかりました。

確認しておきますが、そうすると、2バイト以上のデータを流す場合、
データが入り組んでしまいますが、よろしいのでしょうか?

やはり、ある程度のまとまりで送った方がいいと思いますけど。
例えば、改行までの1行ごとにおくるとか。

そうしないと、ごちゃごちゃになりますよ。


それと、プログラムの構造がおかしいようです。

基本的な構造は、

 ・次をループで回す
  ・前のサーバーからデータを読む
  ・次のサーバーへ転送

となります。

もう少し具体的には、

 InputStream is = prevSocket.getInputStream();
 OutputStream os = nextSocket.getOutputStream();
 for( ; ; ){
  int c = is.read();
  os.write(c);
 }

と、こんな感じです。

このままだと、先ほど説明したように、ごちゃごちゃになりますから、
1行分を読んでから、書き込みをする等の工夫がいるでしょう。


この流れからいくと、前のサーバーと次のサーバーの両方に対して、
接続をしておかなければなりません。


とすると、次のサーバーの情報は、プログラム起動直後に設定してしまうのが
いいと思います。

ただし、接続するのは受信をした後です。起動直後では、次のサーバーが起動していない
可能性があるからです。

main(){

 //次のサーバーの情報(IPアドレス、ポート番号)を入力
 (入力処理)

 //受信待ち待機
 Socket prevSocket = server.accept();

 //次のサーバーへ接続
 Socket nextSocket = .....

 InputStream is = prevSocket.getInputStream();
 OutputStream os = nextSocket.getOutputStream();
 for( ; ; ){
  int c = is.read();
  os.write(c);
 }


もう一度見直しをして下さい。

この回答への補足

>そのかわり、わかる範囲でなら、しつこく付き合います。>
ありがとうございます。感謝しております。

>1行分を読んでから、書き込みをする等の工夫がいるでしょう。>
について、while((s = dis.readLine()) != null) のように改行ごとに読み込むように書き換えました。

ひとついい忘れましたことがありました。最終的には、Aが「Hello」と言うメッセージを投げると、受け取った複数のサーバ同士でリング状にグルグル回し、速度を測るアプリケーションを作ることが目標です。よって

>//次のサーバーの情報(IPアドレス、ポート番号)を入力
 (入力処理)>

を、ホスト名、ポート番号を指定してnextSocket = new Socket("sougou2-2",5555);//次のサーバーへ接続
と書き換えました。
私からの質問2つあるのですが

(1)readLine()で読み込んだのをwriteUTF()、writeChars()で書き込もうとすると、コンパイルで推奨されないAPIと警告が表示されます。どう書き換えればいいのかが分りません。

(2)速度を測るアプリケーションを作るには、入出力をDataInputStreamとBufferedInputStreamなどをどちらを記述すれば、最小限の効率の良い実装ができるのでしょうか?

以下、見直して書き換えました。よろしくお願いします。

import java.io.*;
import java.net.*;
import java.lang.*;
//メインクラス
class revp2p3 {

public static void main (String args[]) {
try {
System.out.println("サーバの用意始めます");
while(true){
ServerSocket ss = new ServerSocket(5555);

Socket prevSocket = ss.accept();//受信待ち待機

Socket nextSocket = new Socket("sougou2-2",5555);//次のサーバーへ接続
InputStream is = prevSocket.getInputStream();
DataInputStream dis = new DataInputStream(is);
OutputStream os = nextSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
String s;
while((s = dis.readLine()) != null) {
System.out.println("メッセージ" + s);
dos.writeUTF(s);
}
ss.close();
dis.close();
dos.close();
}
}
catch(IOException e){
System.out.println("IOException main "+e);
}
catch(Exception e){
System.out.println("Exception main "+e);
}
}
注: revp2p3.java は推奨されない API を使用またはオーバーライドしています。
注: 詳細については、-deprecation オプションを指定して再コンパイルしてください。

補足日時:2004/12/11 15:36
    • good
    • 0

はじめから、そのように書いてもらえれば、無駄がなかったのですが。

。。

質問する側は、教えてもらうという立場なのですから、
文章を面倒がってはいけませんね。

まあ、説教はこのくらいにして。。。


●補足要求

2度手間になりそうな予感があるので、まず仕様に関して確認しておきたいことがあります。

データの送るタイミングの問題です。

マシンBがマシンCに転送するのは、次のどちらのタイミングでしょうか?、


  ア.マシンAから全てを受け取ってから、マシンCに転送する。

  イ.マシンAから、何かを受け取ったら、すぐに転送する。


コードを見ると、「イ」のパターンでやりたいようにも見えますが、
実際は「ア」のパターンで動作しています。



*先にお断りしておきますが、私は土日曜日は返事ができません

この回答への補足

はい。次回からは気をつけますm(_ _)m

*先にお断りしておきますが、私は土日曜日は返事ができません>
分りました.

●補足要求返答

イ.マシンAから、何かを受け取ったら、すぐに転送する。>

イをやりたいと考えております。

補足日時:2004/12/10 14:16
    • good
    • 0
この回答へのお礼

はい。すみません

お礼日時:2004/12/21 15:03

>複数のパソコンにサーバプログラムを入れているので、



はあ?

また変なことを言ってますね。

私からの質問は、ちゃんと理解してますか?


動作させている環境を正確に説明してください。

初心者であることと、人の話を聞かないことは関係ありません。

この回答への補足

>複数のパソコンにサーバプログラムを入れているので、

 すみません。何度も、、質問の意味を理解できていませんでした。

 windows2000環境で3台のパソコンを起動させて、そこでjavaプログラムを動かしています。例えば、パソコンAはメッセージを投げる役割をするのみのプログラムがあり、パソコンBとCは、先ほど書いたrevp2p2javaプログラムを実装させています。
BとCを先にコンパイル実行してメッセージ待ち状態にします。A→B→C→B→C→B→C....とBとCの間で無限ループでグルグル回したいのです。
 そこでBがAからメッセージを受け取ったんですけど、Cへ渡すことができない状態なのです。

 最終的には、同様にAのプログラムを一台、B~H...のプログラムを複数増やしてB~H...にメッセージを渡して、リング状に回したいのです。

 こんな感じでいいでしょうか?

補足日時:2004/12/10 10:32
    • good
    • 0

ServerクラスとClientクラスが何をしているのかわからないため断言は出来ませんが、


1台のマシンで動かしているということなので、複数のサーバープログラムが
同一のソケットで動こうとしてませんか?

1つのマシンでは、1つのポートに対し、1つのサーバープログラムしか動かせませんよ。

確認してみてください。


それとは別に、mainメソッドでsocketをクローズしてますが、おかしくありませんか?
Serverクラスのコンストラクタに渡しているのだから、Serverクラスで使っているような
気がするのですが。

もし、そうなら、mainメソッドでクローズするのは問題があると思いますけど。。。

この回答への補足

サーバクラスでsocketを閉じるのですね。分かりました。ありがとうございます。複数のパソコンにサーバプログラムを入れているので、サーバークラスではメッセージを受け取る為、入力ストリームを取得しています。クライアントクラスでは、出力ストリームを取得し、相手の宛先を新しく指定して、受け取ったメッセージを渡したいのですけど、クライアントメソッドがスタートした後は、サーバクラスの待ち状態になるようにしたいのです。すみませんけど、アドバイスをお願いします。
//サーバクラス
class Server extends Thread{
Socket socket;
Client client;
public Server(Socket socket) {
this.socket = socket;
}

public void start() {

//debug
System.out.println("サーバ呼び出しました");

try{
//debug
System.out.println("サーバ稼動中");
BufferedReader in = new BufferedReader(//入力ストリーム
new InputStreamReader(
socket.getInputStream()));
String message = in.readLine();
System.out.println("読み込んだメッセージは" + message);
socket.close();
in.close();
}
catch(Exception e){
System.out.println("Exception "+e);
}
}
}
//クライアントクラス
class Client extends Thread {
String ip;
int port;
String message;
public Client(String ip, int port) {
this.message =message;
this.ip =ip;
this.port = port;
}
public void start(String message) {
try{
Socket socket = new Socket(ip,port);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeChars(message);//出力ストリームと共に隣りへ送信する。
socket.close();
out.close();
}
catch(Exception e){
System.out.println("Exception "+ e);
}
}
}

補足日時:2004/12/09 22:32
    • good
    • 0

答えがおかしいですよ。



 マシンのことですか?
 →はい、そうです。
  (マシンのこと?)

  プログラムのことでローカルで通信しています。
  (やっぱり、プログラムのこと?)

どっちなのですか?

この回答への補足

プログラムのことです。すみません。
サーバ側のプログラムのmainは以下のとおりです。
よろしくお願いします。
class revp2p2 {
public static void main (String args[]) {
try {
System.out.println("サーバの用意始めます");
while(true){
//Debug
ServerSocket ss = new ServerSocket(5555);
System.out.println("受信を待ちうけます");

Socket socket = ss.accept();//クライアントから要求を受け取る
Server server = new Server (socket);
server.start();
System.out.println("サーバスレッド呼び出し完了");
System.out.println("IPとポートを指定する");
BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
String ip = d.readLine();
int port = Integer.parseInt(d.readLine());
socket.close();
Client client = new Client(ip,port);
client.start();
System.out.println("クライアントスレッド呼び出し完了");
}
}
catch(IOException e){
System.out.println("IOException main "+e);
}
catch(Exception e){
System.out.println("Exception main "+e);
}
}
}

補足日時:2004/12/09 11:50
    • good
    • 0

補足要求をします。




1.サーバーと言っているのは、マシンのことですか?

つまり、複数台のマシンにそれぞれサーバーのソフトが動いているのでしょうか?


2.エラーが出るタイミングをもう少し詳しく説明してください。

この回答への補足

1.はいそうです。サーバーとは、プログラムのことでローカルで通信しています。以下にサーバとして起動しているプログラムの内容を記述さしてもらいます。
main文の内容(whileで無限ループしてます)
ServerSocket ss = new ServerSocket(5555)
Socket socket = ss.accept();
Server server = new Server (socket);
server.start();
System.out.println("IPとポートを指定する");
BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
String ip = d.readLine();
int port = Integer.parseInt(d.readLine());
socket.close();
Client client = new Client(ip,port);
client.start();//ここでエラーが出ます
main文では、socketを2度生成できないということでしょうか?

補足日時:2004/12/08 18:24
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!