【Java TCP/IP Socket】アプリケーションプロトコルにおけるメッセージのフレーム化と解析(コードを含む)


転載は出典を明記してください.http://blog.csdn.net/ns_code/article/details/14225541
     プログラム間で達成される情報交換の形式と意味を含む共通認識をプロトコルと呼び,特定のアプリケーションを実現するためのプロトコルをアプリケーションプロトコルと呼ぶ.ほとんどのアプリケーションプロトコルは、フィールドシーケンスからなる離散情報に基づいて定義され、各フィールドには、ビットシーケンス符号化(すなわち、バイナリバイト符号化)された特定の情報(すなわち、テキスト符号化に基づいてもよいが、TCP、UDP、HTTPなど、データを伝送する際にビットシーケンスで符号化される)が含まれる.アプリケーション・プロトコルでは、情報の送信者がこれらのビット・シーケンスをどのように並べて解釈すべきかを明確に定義し、受信者がどのように解析すべきかを定義し、情報の受信者が各フィールドの意味を抽出できるようにします.TCP/IPプロトコルの唯一の制約:情報はブロック内で送信され、受信されなければならないが、ブロックの長さは8ビットの倍数でなければならないので、TCP/IPプロトコルで伝送される情報はバイトシーケンスであると考えることができる.
     プロトコルは、通常、フィールドのセットからなる個別の情報を処理するため、アプリケーション・プロトコルは、メッセージの受信者がメッセージが完全に受信されたタイミングをどのように決定するかを指定する必要があります.フレーム形成技術は、受信側がメッセージの先頭と末尾の位置をどのように位置決めするかという問題を解決するものである.  プロトコルは、通常、フィールドのセットからなる個別の情報を処理するため、アプリケーション・プロトコルは、メッセージの受信者がメッセージが完全になったタイミングをどのように決定するかを指定する必要があります.主に、受信者がメッセージの終了位置を正確に見つけることができる2つの技術があります.
     1.デリミタベース:メッセージの終了は、送信者がデータを送信した後に明示的に追加した特定のバイトシーケンスであり、この特殊なタグは、送信されたデータには現れない(これも絶対的なものではない.アプリケーション・フィル・テクノロジーは、受信者がデリミタとして認識しないように、消息中に現れたデリミタを修正することができる)唯一のタグによって指摘される.この方法は、通常、テキストで符号化されたメッセージに用いられる.
     2、明示的な長さ:フィールドまたはメッセージが長くなる前に固定サイズのフィールドを追加し、そのフィールドまたはメッセージに何バイトが含まれているかを示します.この方法は主にバイナリバイト方式で符号化されたメッセージに用いられる.
     UDPソケットはメッセージの境界情報を保持しているため、フレーム化処理を行う必要はない(実際には、主にDatagramPacket負荷のデータには一定の長さがあり、受信者はメッセージの終了位置を正確に知ることができる)が、TCPプロトコルにはメッセージ境界の概念がないため、TCPソケットを使用する場合、フレーム化は非常に重要な考慮要因である(TCP接続では、受信者が最後のメッセージの最後のバイトを読み出した後、ストリーム終了フラグ、すなわちread()が−1を返し、このフラグはメッセージの最後に読み込まれたことを示し、厳密には、これもデリミタ法に基づく特殊な場合である).
     次に、上記の2つのフレーム化技術をカスタマイズして実装するDemo(本の例)を示し、まずフレーム化情報を追加して指定されたストリームに指定されたメッセージを出力するframeMag()メソッドと、指定されたストリームをスキャンして次のメッセージを抽出するFramerインタフェースを定義する.
import java.io.IOException;
import java.io.OutputStream;

public interface Framer {
  void frameMsg(byte[] message, OutputStream out) throws IOException;
  byte[] nextMsg() throws IOException;
}

     次のコードは、デリミタに基づくフレーム化方法を実現し、デリミタが改行文字「」であり、frameMsg()方法は充填を実現していない.フレームのバイトシーケンスにデリミタが含まれている場合、単純に異常を放出するだけである.nextMsg()メソッドは劉をスキャンし、デリミタが読み込まれるまでデリミタの前のすべての文字を返し、ストリームが空の場合nullを返し、ストリームが終わるまでデリミタが見つからない場合、プログラムはフレームエラーを示す異常を投げ出す.
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class DelimFramer implements Framer {

  private InputStream in;        //     
  private static final byte DELIMITER = '
'; // public DelimFramer(InputStream in) { this.in = in; } public void frameMsg(byte[] message, OutputStream out) throws IOException { for (byte b : message) { if (b == DELIMITER) { // , throw new IOException("Message contains delimiter"); } } out.write(message); out.write(DELIMITER); out.flush(); } public byte[] nextMsg() throws IOException { ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream(); int nextByte; while ((nextByte = in.read()) != DELIMITER) { // if (nextByte == -1) { // , null if (messageBuffer.size() == 0) { return null; } else { // , throw new EOFException("Non-empty message without delimiter"); } } messageBuffer.write(nextByte); } return messageBuffer.toByteArray(); } }

     
     次のコードは、長さが65535バイト未満のメッセージに適用される長さベースのフレーム化方法を実現している.送信者は、まず指定されたメッセージの長さを与え、長さ情報をbig-endian順(左から上位から下位へ送信)にする2バイトの整数に格納し、2バイトを完全なメッセージコンテンツの前に格納し、メッセージとともに出力ストリームに書き込む.受信側では、DataInputStreamを使用して整数の長さ情報を読み出し、readFully()メソッドは、所与の配列が完全に満たされるまでブロック待機する.このフレーム化メソッドを使用すると、送信者は、フレーム化するメッセージの内容を検査する必要はなく、メッセージの長さが制限を超えているかどうかを検査するだけである.
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class LengthFramer implements Framer {
  public static final int MAXMESSAGELENGTH = 65535;
  public static final int BYTEMASK = 0xff;
  public static final int SHORTMASK = 0xffff;
  public static final int BYTESHIFT = 8;

  private DataInputStream in;

  public LengthFramer(InputStream in) throws IOException {
    this.in = new DataInputStream(in);    //    
  }
 
  //    message      ,        
  public void frameMsg(byte[] message, OutputStream out) throws IOException {
    //         65535
    if (message.length > MAXMESSAGELENGTH) {
      throw new IOException("message too long");
    }
    out.write((message.length >> BYTESHIFT) & BYTEMASK);
    out.write(message.length & BYTEMASK);
    out.write(message);
    out.flush();
  }

  public byte[] nextMsg() throws IOException {
    int length;
    try { 
      //     2   ,     big-endian      ,  int         
      length = in.readUnsignedShort(); 
    } catch (EOFException e) { // no (or 1 byte) message
      return null;
    }
    // 0 <= length <= 65535
    byte[] msg = new byte[length];
    //        ,                  
    in.readFully(msg); 
    return msg;
  }
}