電子書籍の厳選無料作品が豊富!

現在、以下のように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();
  }
 }
}
=====

A 回答 (4件)

> なるほどー。

境界が維持されないんですねー。
> ということは、パケット内にサイズを保持するとかしないといけないんですかね…。
そうなります。

TCPの機能はJava自体ではなく,OS等が実現しています。
パケット未達時に再送してくれたり,パケットの到着順が入れ替わった場合に並び替えるのもJavaではなくOS等の機能です。
着信系の機能はプロセスを横断して行う必要があるので,UNIXやWindowsといったOSではJavaに任せるのは難しいと思いますし。
# どこまでをOSと呼ぶかやデバイスドライバの実装によって実際の処理箇所が異なるのでOS等と書いています。

なので,Java側でflushしても,デバイス (デバイスドライバ含む) のバッファに転送されるだけかと思います。
さらにNagleアルゴリズムが有効な限り,最大で200ms程度 (規格上は500ms) の間の送信はデバイス側でまとめられます。
今回はACKを載せるパケットがどんどん出来るので,遅延ACKによる遅延がほとんどなく,結果としてNagleアルゴリズムも大きく働いてはいないのだと思います。
# サーバー側が送信を止めた場合,800octetくらいのデータが一気に送受信されるかもしれません。


送信側としては,Nagleアルゴリズムを無効にすることでデータを即座に送信することが出来ます。
ただし,受信側でパケット境界が維持されるとは限りませんし,4octetのデータに20octetのTCPヘッダと20octetのIPv4ヘッダ (IPv6だと40octet) が付随するので,ネットワーク効率を下げる可能性があります。
    • good
    • 0

すみません、flushの説明違うかも。

あと、4バイトより短いデータが取れるかもしれません。それはsizeを見て残りをとって・・。
    • good
    • 1

UDPという意見もありますが、今から組み替えて、到達確認、再送処理を入れ込むのは面倒ですね。

この辺、本番環境で問題になるんですよ。
と言いますか、
byte rcvData[] = new byte[16];とやってるのはなぜでしょうか?これは、「最大16バイト取ろう」としていることになります。
size = ins.read(rcvData, 0, 4);とやるのがいいと思いますが。

実際にこのプログラムを動かしてみましたよ。(javacとやったら、なんとjdkインストールしとらんかった!) 8バイト取ることがありましたが、改良後は100秒動かしても4以外はありません・・・。
ちなみに、「OutputStream の flush メソッドは何も行いません。」とAPIドキュメントに書かれてます。APIドキュメントは重要です。
    • good
    • 0
この回答へのお礼

回答ありがとうございます!
サイズ指定しても、8byte となってしまいますね…。
改良って他にも変更されたのでしょうか…?

flushのAPI見ましたが、何もしないとは書かれていないように見えました。
ただ、実際のAPIソースコード見たら、ご指摘通り何にもしない、空APIに見えました。

お礼日時:2012/02/25 00:14

TCPはパケット境界を維持しないプロトコルです。


送信側での複数のパケットが受信側では1つのパケットになることもありますし,
送信側では1つのパケットだったものが受信側では複数のパケットとなることもあります。

そのため,パケット境界が維持されないことを前提にTCP上にプロトコルを作成するか,UDPなどのパケット境界を維持するプロトコルを使う必要があります。
    • good
    • 0
この回答へのお礼

回答ありがとうございます!
なるほどー。境界が維持されないんですねー。
ということは、パケット内にサイズを保持するとかしないといけないんですかね…。

お礼日時:2012/02/25 00:02

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