• 締切済み

TCP通信のブロッキングについて

初めて投稿させて頂きます。 javaでTCP接続での4人で遊ぶ大富豪を作っております。 サーバーを用意し、各ユーザーがサーバーに接続して遊ぶタイプです。 サーバーの構造は次のようになっております。 ・クライアントから接続を受け付ける待機クラス(Server.java)を常時走らせておく ・待機クラスに接続があれば、1ユーザーごとに送受信のスレッド(client.java)を立ち上げる。各ユーザーからのデータはこのクラスが受け取る ・人数が4人集まれば、テーブルスレッド(table.java)を立ち上げる。各ユーザーと接続されているclient.java(*4)がデータを受け取るとここが計算を行う 各ユーザーがサーバーに接続し、データのやり取りをするところまでは成功したのですが、以下の事について色々調べたのですが分からず、教えて頂けると幸いです。 (質問)ユーザーの接続が切断されたときのTCPの動作について 例えばABCDの4人のプレイヤーが居て、A→B→C→Dの順番だとします。今Aが◇3を切ったとします。するとAから「◇3を切った」というデータをサーバー(サーバーが発行したAとの送受信クラス)に送信し、サーバーが受信、それをABCD全員に送信して手番がBに移ります。この時、仮にCが切断されていてABCDにデータを送信したけどCだけデータが届かない、というケースの場合について、全員のデータの整合性を取るために「サーバーが、"Aが◇3を切った送信"に対するACKパケットを全ユーザーから受信したのを確認してから手番をBに移す」という手順を踏みたいのです。 各クライアントとの送受信のスレッドは余計なところは省略すると以下のようになっております。 class client implements Runnable{ (変数宣言) (コンストラクタ) public void run(){ while(true){ try{ ※(1) in.read(b); // inはSocket.getInputStreamの戻り値 (データを受け取ったことで色々な処理) out.write(b); // outはSocket.getOutputStreamの戻り値 out.flush(); ※(2) }catch(Exception e){} } } } Aと接続されているclientクラスのin.readで「◇3を切った」を受信し、色々な処理を行った後、ABCDと接続している各clientクラスのout.writeで「◇3を切った」を送信します。 ここで質問なのですが、接続が途切れているCの場合、out.write(b)の部分ACKパケットを受信するまで何度もリトライをすると思うのですが、プログラムの動作としてはリトライをしながらもout.write(b)以下の処理に進んでしまうのでしょうか?それともACKを受信するまでここでプログラムはブロックされるorタイムアウト等が発生するまでリトライorサーバーが即切断を認識してExceptionに移行する のでしょうか?私の希望としては例えば上記プログラムの※(1)にf=false;(fはboolean)、※(2)にf=trueなどを置いて「全スレッドのf==trueになれば、次の作業をしてもOK」という風に出来ればプログラム的に楽なのですが、接続が途切れているプレイヤーCのスレッドはout.writeの結果に関わらず(リトライされようが)下の命令を実行してしまうのでしょうか?「ACKパケットを受け取ったか否か」を判断したいのですが、プログラムの動作としては パケットを受け取った/受け取ってない で、どのように差が出るのでしょうか。また、out.writeの内容に関わらずプログラム処理が進んでしまう場合、どのようなプログラムが最適でしょうか。 このサンプルの場合だと、Cとの接続クラスはin.readのところで確実にブロックされますが、もしout.writeでブロックされなければACDのプログラムはBを放っておいてどんどん先に進んでしまい、データの整合性が怪しくなってきます。 詳しい方居られましたらお教え頂ければ幸いです。

  • Java
  • 回答数4
  • ありがとう数3

みんなの回答

  • Lchan0211
  • ベストアンサー率64% (239/371)
回答No.4

> これだと本当は1回のデータの送信なのに5回の送受信を行う必要がでてきます。 > これはかなりのサーバー負荷&遅延になるのかと思うのですがどうなのでしょうか。 どう数えたら1回の送信が5回の送受信という計算になるのかよくわかりませんが、 仮にカードの状態を配信する処理が1msから5msになったとしても、 たいしたサーバ負荷にはならないと思います。 ただ、大富豪のようなカードゲームの場合、テーブル上のカード状態の変化を 適宜クライアント送信していればよいだけで、クライアントがその状態変化を 正しく受け取ったかどうかを毎回サーバが確認する必要もないように思います。 受信待ちをするのは、あるクライアントに「あなたの番ですよ」という通知を出し、 その応答(切り札情報)を待つタイミングだけでよさそうに思います。 TCPは、sendが成功してもそれが相手に届いたことの保証にはなりませんが、 順序制御と再送制御がありますので、送信したデータの途中が抜けて相手に届く ということはありません。 つまり、「あなたの番ですよ」という通知に対するクライアントからの応答を 確認できたなら、それまでに送信したデータは全て順番に正しくクライアントに 届いているということになります。

  • Lchan0211
  • ベストアンサー率64% (239/371)
回答No.3

http://www.ne.jp/asahi/hishidama/home/tech/socket/tcp.html#TCP/IPとソケットの関係 の「send(送信)」の説明が参考になると思いますので、読んでみてください。 send(Javaではout.write)が成功しても、データが相手に届いたという保証にはなりません。 ACKを受信しなくても、ソケットライブラリの送信バッファにデータが入れば、sendは成功し、 処理は次に進みます。 sendしたデータが届いたことを保証したい場合は、データを受けた相手から 応答データをsendしてもらい、その応答を受信するまで 待ち合わせる処理を自分で作る必要があります。

to_dairi3
質問者

補足

参考になるHPの紹介をありがとうございました。 ACKパケットの受領の如何に関わらず次の処理に進んでしまうんですね。 そこで、この場合気になることが、今回のTCPを利用する4人大富豪の場合、完全な同期を目指す場合は「サーバーから送信パケットを送信して、各クライアント側が受信→そして"受信した事を示すパケット"をプログラム的に送信→サーバーが受信→各ユーザーから受信を確認したら次の処理を行う」という風にすると思うのですが、これだと本当は1回のデータの送信なのに5回の送受信を行う必要がでてきます。これはかなりのサーバー負荷&遅延になるのかと思うのですがどうなのでしょうか。また、現在ネットでサービスしているオンラインゲーム(例えばオンライン麻雀とか)などはこのようなプログラムをしているのでしょうか?それともタイムアウトを待っての切断判断なのでしょうか。 ご存知でしたら教えて頂けると幸いです。

  • askaaska
  • ベストアンサー率35% (1455/4149)
回答No.2

私なら4つのスレッドの進行を管理するクラスを作成し、 4人の状態を管理させるわ。 実装方法は人それぞれなので、これが正解って言うものはないけど 現実でも、進行役がいてくれたら便利なことってあると思わない?

to_dairi3
質問者

補足

askaaskaさん助言ありがとうございます。 >私なら4つのスレッドの進行を管理するクラスを作成し、 >4人の状態を管理させる。 これが恐らく最初に説明してあります「テーブルスレッド」になるのではないか?と考えております。(「恐らく」というのは現在はテーブルスレッドはまだ未実装でして、立ち上げた1つのサーバーに全員が接続してデータの送受信を確認した状態なので「恐らく」になります)。 構想では、「4人が揃ったらサーバークラスがテーブル(スレッド)を立ち上げ、殆どのデータはテーブルクラス(スレッド)で管理し、各ユーザーと接続されている送受信クラス(client.java)は主にデータの送受信を行い、送受信クラスがデータの受信→テーブルクラス送受信クラスから連絡とデータを受け取りそれに応じた処理を行う→テーブルクラスが全送受信クラスに命令→各送受信クラスがデータを送信」という考えなのですが、これでは助言にある「4人の状態を管理する」事は出来ないのでしょうか?理想では「送受信クラスの接続が切断された→テーブルクラスにそれを連絡して送受信スレッド自身は終了→テーブルクラスが連絡を受け取る→他の接続されている送受信クラスに連絡→各送受信クラスがユーザーに送信」とできないかと考えているのですがどうでしょうか?逆に言えば「各送受信クラスから正しいデータが来る/正しくデータを送れたことを確認するまではテーブルクラスは待機している」という状態を作りたく、そこで詰まったのが「送受信クラスがOutputStream.writeの命令を実行したときに送信先の接続が切断されていても次の処理に進んでしまうのか?」という状況です。InputStream.readはデータが来るまでブロックされるので次の処理に進まないことはわかるのですが、OutputStream.writeを実行後のACKパケット受信の如何に関わらず次の処理に進んでしまうと送信出来ていないのに送信が完了したとテーブルクラスが理解してしまうかもしれません。 現時点でまだテーブルクラスを生成しておらず理論的にうまくいくのかも分からないのですが、この状況でお分かりの方、もしくは「ここの情報が足りないからアドバイスできない」等ありましたら助言頂ければ幸いです。

  • askaaska
  • ベストアンサー率35% (1455/4149)
回答No.1

同時に4つのスレッドを走らせるんでしょ? その4つがすべて終了するのを待てばいいんじゃないかしら?

to_dairi3
質問者

補足

同時に4つのスレッドを走らせますが、プログラムサンプルをみて頂ければお分かりかと思いますが各スレッドはwhile(true)でループしており、「受信待機→受信→処理→送信→受信待機」を繰り返します。スレッドが終了するのは「ゲームが終了する時」や「ユーザーから明示的な切断を示すパケットが受信されたとき」、つまりユーザーがサーバーから接続を切断するときになります。

関連するQ&A

  • TCP通信の切断の検出について

    現在javaでTCP通信でサーバーに接続するタイプの大富豪を製作しております。 そこでTCP接続の切断の検出についてお聞きしたいです。 例えば既にあるオンラインのテーブルゲームなどをプレイしていると、誰かの通信が不安定になるor切断された場合、20~30秒程画面の間、動きが止まり、切断されたプレイヤー以外とのチャット等は可能で20~30秒後に誰かが「落ち」てプレイが続行されます。 この時、バックグラウンドではどのような処理が行われているのでしょうか? (1)TCP接続でデータの送信(受信)を行っているがとあるユーザーは切断されているのでそこが何度も送信(受信)のリトライ→反応なし(ACKを受信できず)→タイムアウトが20~30秒に指定してあり、時間経過後にタイムアウトが呼び出され切断されて続行される。 (2)通信が不安定なユーザーが居て切断/接続を何度も繰り返している。プログラム的に20~30秒不安定な状況が続けば切断するようなプログラムを組んでいる。 (3)その他 また、接続の切断の検出はサーバープログラムのどこで行われるのでしょうか。 (1)各接続クラスのInputStream.readもしくはOutputStream.write (2)Socket接続が切断されればどこかで例外が発生する? (3)その他 詳しい方居られましたら教えて頂けると幸いです。

  • TCP通信

    OS:Win2000、VisualBasic.netで開発しています。 現在TCP通信のプログラムを製造しています。 TCPサーバとTCPクライアントのテストアプリケーションを作成し、接続テストを行っているのですが、 サーバとクライアントの接続、データ送受信の確認はできました。 しかし、一度クライアント側から接続を切断(ソケットを消去)し、 再びソケットを生成してコネクト要求を出しても接続が確立できません。 このときサーバ側はなにも操作していません。 終始接続待機状態にしてあります。 ソースがないと分かりにくいかもしれませんが、 何か思い当たることがある方、アドバイスよろしくお願いします。

  • BBルータでTCP通信のデータ部が捨てられている?

    インターネット上のServerと自宅PC間でWebを併用したデータ交換をしているのですが、ある限られたデータ部のみ、自宅のBBルータにより毎回捨てられているようで受信できません。 Serverと自宅PC(Client)にEtherealを同時に仕掛けPacketをモニタリングしました。 Server側のLogを見る限り、Serverは該当データを正しく送り、BBルータも正しく受信しているようです。 しかし、Client側のLogを見ると、その部分は Len=0 になっており、ヘッダ部だけのPacketが送信されて来たかのようになっております。 BBルータの設定可能項目では、いたって通常の設定になっており、また、自宅PCはWindowsXP SP2でFirewallはOff、ウィルス対策ソフトもアンインストール状態で実験しております。今回のトラブル以外は全てにおいて不具合は発生しておりません。 何か単純なTCP規則違反に該当しているのではとRFC793も確認したのですが原因がわかりません。 該当部分のEtherealのLogは以下の通りです。 何か大事なことを忘れていそうなのですが、どなたかご教授をお願い致します。 <Server側Log> ※No.292~294は3way handshakeです。 ※()内はTCP詳細部の内容抜粋です。 ※Server側のPortはWeb併用なので80番をそのまま使用。 No. SRC  DIST  Prot  Info 295 Client Server HTTP Continuation or non-HTTP Trafic 296 Server Client HTTP Continuation or non-HTTP Trafic (http > 62327 [PSH,ACK] Seq=1 Ack=24 Win=65512 Len=90) 297 Server Client TCP http > 62327 [FIN,ACK] Seq=91 Ack=24 Win=65512 Len=0 298 Client Server TCP 62327 > http [ACK] Seq=24 Ack=92 Win=65535 Len=0 299 Client Server TCP 62327 > http [FIN,ACK] Seq=24 Ack=92 Win=65535 Len=0 300 Server Client TCP http > 62327 [ACK] Seq=92 Ack=25 Win=65512 Len=0 <Client側Log> ※No.295と297~300はServer側Logと矛盾はありません。 No. SRC  DIST  Prot  Info 296 Server Client TCP http > 1157 [PSH,ACK] Seq=1 Ack=24 Win=65512 Len=0 以上

  • TCPクライアントサーバプログラムで受信したデータを処理するには?

    javaのTCPのクライアントサーバのプログラムでよく見かけると思いますが、データをクライアントもしくはサーバから受け取ったとき、それを表示させるには  cont = true ; while (cont) { try { // 読み込み (サーバorクライアントからのデータ) int n = instr.read(buff); // System.outへの書き出し System.out.write(buff, 0, n) ;                  //↑このデータを使いたい↑ } // 以下は例外処理です catch(Exception e){ // 読み出し終了時にループも終了します cont = false ; } という風にすればできるのですが、読み込んだデータを利用して処理(具体的には文字列(String)として処理したい)するにはどうすればいいのでしょうか? プログラミングに詳しい方教えて頂けないでしょうか?

  • TCP/IP通信について

    現在、以下のようにTCP/IP通信のプログラミングを行っており、 サーバ/クライアント別々に4byteのデータ送信を10msec毎に10秒間行っております。 現在、WimdowsVista-Windows7間で各々をサーバ/クライアントとして順に起動し、 相互に4byte送信しているハズが、倍の8byteや12byteとデータが連なって送信されている 事象が発生してます。 OutputStreamではwrite後にflushを行っているので、flush契機でメモリ上に蓄えられた 送信用バッファが送信されるイメージでおりますが、4byteで送信できていないように見えます。 上記について、解決方法をご存じであればご教授お願い致します。   <Server.java> ===== public class Server {  public static ServerSocket ss = null;  public static Socket soc = null;  private static InputStream is = null;  private static OutputStream os = null;  public static void main(String[] args) {      try {    // サーバソケット生成    ss = new ServerSocket(5000);    soc = ss.accept();    is = soc.getInputStream();    os = soc.getOutputStream();    Thread rcvTh = new ServerRcvThread(is);    rcvTh.start();    Thread sndTh = new ServerSndThread(os);    sndTh.start();    // 10秒スリープ    try{     Thread.sleep(10000);    } catch ( Exception e){     e.printStackTrace();    }    // スレッド停止    rcvTh.stop();    sndTh.stop();   } catch (IOException e) {    e.printStackTrace();   } finally{    try {     is.close();     os.close();     soc.close();     ss.close();    } catch (IOException e) {     e.printStackTrace();    }      }  } } class ServerSndThread extends Thread{  private static OutputStream ous = null;  ServerSndThread( OutputStream os ){   this.ous = os;  }  public void run(){   byte sndData[] = new byte[4];   sndData[0] = 0x01;   sndData[1] = 0x02;   sndData[2] = 0x03;   sndData[3] = 0x04;   try {    while(true){     // データ書込み     ous.write(sndData);     ous.flush();     System.out.println("データ送信");     // 0.01秒スリープ     try{      Thread.sleep(10);     } catch ( Exception e){      e.printStackTrace();     }    }   } catch (IOException e) {    e.printStackTrace();   }  } } class ServerRcvThread extends Thread{  private static InputStream ins = null;  ServerRcvThread( InputStream os ){   this.ins = os;  }    public void run(){   byte rcvData[] = new byte[16];   int size = 0;   try {    while(true){     // データ読込み     size = ins.read(rcvData);     System.out.println("size:"+size+"byte");    }   } catch (IOException e) {    e.printStackTrace();   }  } } =====     <Client.java> ===== public class Client {  private static Socket soc = null;  private static OutputStream os = null;  private static InputStream is = null;  public static void main(String[] args) {   try {    // ソケット生成    soc = new Socket("192.168.3.3", 5000);    is = soc.getInputStream();    os = soc.getOutputStream();    Thread rcvTh = new ClientRcvThread(is);    rcvTh.start();    Thread sndTh = new ClientSndThread(os);    sndTh.start();    // 10秒スリープ    try{     Thread.sleep(10000);    } catch ( Exception e){     e.printStackTrace();    }    // スレッド停止    rcvTh.stop();    sndTh.stop();   } catch (IOException e) {    e.printStackTrace();   } finally{    try {     is.close();     os.close();     soc.close();    } catch (IOException e) {     e.printStackTrace();    }      }     } } class ClientSndThread extends Thread{  private static OutputStream ous = null;  ClientSndThread( OutputStream os ){   this.ous = os;  }    public void run(){   byte sndData[] = new byte[4];   sndData[0] = 0x04;   sndData[1] = 0x03;   sndData[2] = 0x02;   sndData[3] = 0x01;   try {    while(true){     // データ書込み     ous.write(sndData);     ous.flush();     System.out.println("データ送信");     // 0.01秒スリープ     try{      Thread.sleep(10);     } catch ( Exception e){      e.printStackTrace();     }    }   } catch (IOException e) {    e.printStackTrace();   }  } } class ClientRcvThread extends Thread{  private static InputStream ins = null;  ClientRcvThread( InputStream os ){   this.ins = os;  }    public void run(){   byte rcvData[] = new byte[16];   int size = 0;   try {    while(true){     // データ読込み     size = ins.read(rcvData);     System.out.println("size:"+size+"byte");    }   } catch (IOException e) {    e.printStackTrace();   }  } } =====

    • ベストアンサー
    • Java
  • 異なる言語間でのソケット通信について

    簡単なネットワークプログラムを作成して、ソケット通信の確認をしています。 クライアントとサーバが両方ともにC++の場合と、両方ともにJavaの場合で、正常に通信が行われていることは確認しました。 そこで、クライアントでC++のプログラムを動かし、サーバでJavaのプログラムを動かしてみたのですが、うまくいきませんでした。 (ポート番号を合わせたので、相互接続は出来てるみたいですが、データが渡ってきませんでした。OSはクライアント/サーバともに、Windowsです。) 言語が異なる場合の、ソケット通信について良いサイトをご存知でしたら、教えていただけないでしょうか?

    • ベストアンサー
    • Java
  • tcpを使った カウンタプログラム

    サーバーをデーモンで常駐させてクライアントからの接続要求を確立した段階で、data.txt のデータベース(もどき)へ直アクセスしそこに書かれている数値をインクリメントするプログラムを作成しました。 どうか、以下のプログラムを参照してください。 http://userlocalhost.web.fc2.com/ 使用している端末の環境は 2.6.11-1.1369_FC4 です。 xinetd で監視させないで、スタンドアローンでサーバープログラムを動作させ、クライアントから接続要求を送ると、サーバープログラムのrecord() 関数に入って、 データの読み込み ↓ データをインクリメント ↓ データを更新 という流れで期待どおりの動作をしてくれるのですが、デーモンで常駐させると、record()関数に入らないのか、データがインクリメントされて更新されません。 もう一つ問題点として、サーバープログラム又はクライアントプログラムのいずれかをプログラムが置いてあるディレクトリよりも上の階層で実行した場合、プログラムが置いてあるディレクトリよりも遠い場所から実行したばあい、そのプログラムはセグメンテーションエラーになってしまいます。 推論として。これと最初の問題点の相関が強く、別々のファイルから同時に同じファイルへアクセスしているのがマズイ(原因)のかもしれないと思い、クライアントプログラムのmain() 内のcheck() をコメントアウトしたのですが、同様にセグメンテーションエラーが出てしまい、問題点と解決策がわからず、困っております。 どうか、問題解決の答えないしヒントを教えてください。

  • TCPプログラミング

    以下のプログラムをコンパイルして実行したら Exception in thread "main" java.lang.IllegalArgumentException: Paraneter(s):<Server> <Word> [<Port>] at TCPEchoClient.main(TCPEchoClient.java:9) というメッセージが表示されました。 このメッセージの意味が全く分かりません。 分かる方がいらっしゃいましたら教えてください。 お願いします。 本来なら Received:****(←****は自分が指定した文字列) となるはずなんですが… このプログラムはTCPを使ってエコーサーバと通信するクライアントです。 import java.net.*; import java.io.*; public class TCPEchoClient{ public static void main(String[] args)throws IOException{ if((args.length < 2)||(args.length > 3)) throw new IllegalArgumentException("Parameter(s):<Server><Word>[<Port>]"); String server = args[0]; byte[] byteBuffer = args[1].getBytes(); int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7; Socket socket = new Socket(server,servPort); System.out.println("Connected to server...sending echo string"); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); out.write(byteBuffer); int totalBytesRcvd = 0; int bytesRcvd; while(totalBytesRcvd < byteBuffer.length){ if(bytesRcvd = in.read(byteBuffer, totalBytesRcvd, byteBuffer.length - totalBytesRcvd) == -1) throw new SocketException("Connection closed prematurely"); totalBytesRcvd += bytesRcvd; } System.out.println("Received: " + new String (byteBuffer)); socket.close(); } }

  • TCP及びUDP通信について

    TCP及びUDP通信について 現在WinSockAPIを用いた通信を行うプログラムを組んでいます。 しかしながらネットワークに関しては初心者で、わからない点がいくつか出てきたので質問させて頂きます。 -------------------------------------------------- 1.TCPで双方向通信を行う場合、サーバ側は指定したポートにbind→listenし、接続を待ち受けます。 クライアント側はサーバのIPを指定し、接続確立後にポートはOSが自動で割り振ると聞きました。 つまり、クライアント側がポートを開放せずに通信が出来るのは、OSなどが自動でポートを割り当て、割り当てたポートに到着したデータを該当アプリケーションに渡すからでしょうか? ポート関連は全てルータが管理しているものだと思っていたのですが、OSがポートを割り当てるという動作がよく理解できません。 -------------------------------------------------- 2.UDPで双方向通信を行う場合、端末A・端末Bともに指定したポートにbindする必要があると聞きました。 つまり、UDPで双方向通信を行う場合、端末A・端末Bの双方でポートを開放する必要があるということでしょうか? -------------------------------------------------- 是非ともご教授ください。

  • スレッドの作り方

    サーバ/クライアントのプログラムを作っているのですが、javaをやり始めたばかりでスレッドの作り方がいまいち理解できなくて困ってます。 ログインした人にそれぞれスレッド作るにはどのようにしたらよいでしょうか?

専門家に質問してみよう