データをソケットに送る前やソケットから受け取ったあとで、そのデータを処理することが必要な場合がよくあります。前後にデータを処理して java.net.Socket
ソケットを使う代わりに、別のタイプのソケットを使うこともできます。
このページでは、カスタムソケットタイプの作成手順を説明し、また、データを圧縮して送信する CompressionSocket
という名前のカスタムソケットの実装例を示します。
このチュートリアルでは、CompressionSocket
クラスとそれに関連するクラスは、「カスタム RMI ソケットファクトリの作成」のチュートリアルでも使用されています。そのため、この例のソースファイルはすべて examples.rmisocfac
パッケージにあります。
CompressionSocket
クラスとそれに関連するクラスの作成は、次の 4 つのステップに分かれます。 各ステップは、他のカスタムソケットタイプを作成する場合にも当てはまります。
java.io.FilterOutputStream
を継承してソケットの出力ストリームを作成する。必要に応じて、FilterOutputStream
メソッドをオーバーライドする
java.io.FilterInputStream
を継承してソケットの入力ストリームを作成する。必要に応じて、FilterInputStream
メソッドをオーバーライドする
java.net.Socket
のサブクラスを作成し、適切なコンストラクタを実装してから、getInputStream
、getOutputStream
、close
メソッドをオーバーライドする
java.net.ServerSocket
のサブクラスを作成し、コンストラクタを実装してから、目的のタイプのソケットを作成するために accept
メソッドをオーバーライドする
FilterOutputStream
を継承してソケットの出力ストリームを作成するデータを圧縮するソケットを作成するために、
FilterOutputStream
クラスを継承するCompressionOutputStream
という名前のクラスを作成します。ただし、どんな場合でもFilterOutputStream
の継承が適切だとは限りません。一般的に、実装しようとするソケットのタイプにもっとも適したタイプの出力ストリームを継承すべきです。この例では、FilterOutputStream
が最適です。この例では、最大 62 種の共通文字を 6 ビットに符号化し、その他の文字を 18 ビットに符号化する簡単なアルゴリズムを使います (JavaTM プログラミング言語では、文字は通常 16 ビット)。62 種の共通文字を 6 ビットの数値 (0 から 61) にマップするルックアップテーブルを使います。 符号化した文字には、ルックアップの結果に応じて 6 ビットの符号化か 18 ビットの符号化かを示す定数を付けます。最後に、符号化した文字をストリームに書き込みます。このアルゴリズムでは、出現する文字はすべて ASCII で、各文字の高位のバイトは使用しないと仮定します。
注: 実際のアプリケーションでのデータ圧縮に使用することはお勧めしません。このアルゴリズムは簡単な使用例を示す目的で、実用を目的としていません。
カスタムソケットの作成方法を示すサンプルコードを記述する前に、入力ストリームと出力ストリームのクラス間で共有される情報を含むインタフェースを記述する必要があります。 このため、このアルゴリズムでは、共通文字のルックアップテーブル
codeTable
、および 3 つの定数をメンバとします。この例ではインタフェースを使いますが、インタフェースが必要でない場合もあるので注意してください。
interface CompressionConstants { /** Constants for 6-bit code values. */ /** No operation: used to pad words on flush. */ static final int NOP = 0; /** Introduces raw byte format. */ static final int RAW = 1; /** Format indicator for characters found in lookup table. */ static final int BASE = 2; /** A character's code is it's index in the lookup table. */ static final String codeTable = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?\"'()"; }
CompressionConstants
インタフェースのソースコードを見るには、ここをクリックしてください。これで、入力ストリームと出力ストリームの間の共有情報のインタフェースが定義できました。 次に、ステップ 1 の残りの部分として、ソケットの出力のストリームを作成するために
java.io.FilterOutputStream
を継承するクラスを記述します。 必要に応じてFilterOutputStream
メソッドをオーバーライドします。次は、
CompressionOutputStream.java
クラスのソースコードです。圧縮アルゴリズムについては、ソースコードのコメントの中で説明します。実装については、コードのあとで説明します。
import java.io.*; class CompressionOutputStream extends FilterOutputStream implements CompressionConstants { /* * Constructor calls constructor of superclass. */ public CompressionOutputStream(OutputStream out) { super(out); } /* * Buffer of 6-bit codes to pack into next 32-bit * word. Five 6-bit codes fit into 4 words. */ int buf[] = new int[5]; /* * Index of valid codes waiting in buf. */ int bufPos = 0; /* * This method writes one byte to the socket stream. */ public void write(int b) throws IOException { // force argument to one byte b &= 0xFF; // Look up pos in codeTable to get its encoding. int pos = codeTable.indexOf((char)b); if (pos != -1) // If pos is in the codeTable, write // BASE + pos into buf. By adding BASE // to pos, we know that the characters in // the codeTable will always have a code // between 2 and 63 inclusive. This allows // us to use RAW (RAW is equal to 1) to signify // that the next two groups of 6-bits are necessary // for decompression of the next character. writeCode(BASE + pos); else { // Otherwise, write RAW into buf to signify that // the Character is being sent in 12 bits. writeCode(RAW); // Write the last 4 bits of b into the buf. writeCode(b >> 4); // Truncate b to contain data in only the first 4 // bits and write the first 4 bits of b into buf. writeCode(b & 0xF); } } /* * This method writes up to len bytes to the socket stream. */ public void write(byte b[], int off, int len) throws IOException { // This implementation is quite inefficient because // it has to call the other write method for every // byte in the array. It could be optimized for // performance by doing all the processing in this // method. for (int i = 0; i < len; i++) write(b[off + i]); } /* * Clears buffer of all data (zeroes it out). */ public void flush() throws IOException { while (bufPos > 0) writeCode(NOP); } /* * This method actually puts the data into the output stream * after packing the data from all 5 bytes in buf into one * word. Remember, each byte has, at most, 6 significant bits. */ private void writeCode(int c) throws IOException { buf[bufPos++] = c; // write next word when we have 5 codes if (bufPos == 5) { int pack = (buf[0] << 24) | (buf[1] << 18) | (buf[2] << 12) | (buf[3] << 6) | buf[4]; out.write((pack >>> 24) & 0xFF); out.write((pack >>> 16) & 0xFF); out.write((pack >>> 8) & 0xFF); out.write((pack >>> 0) & 0xFF); bufPos = 0; } } }まず、
CompressionOutputStream
はFilterOutputStream
のサブクラスです。次に、ルックアップテーブルと定数にアクセスできるようCompressionConstants
インタフェースを実装します。データを圧縮するため、
FilterOutputStream
のwrite
メソッドをCompressionOutputStream
でオーバーライドします。また、次のメソッドに注目してください。
public void write(int b)
このメソッドは、1 回呼び出されるごとに 1 文字を書き込みます。 そのとき圧縮定数 (
RAW
またはBASE
) を使って各文字に符号化形式を付け、必要なら文字を 2 つの 4 ビット部分に分割します。また、次のメソッドに注目してください。
public void write(byte b[], int off, int len)
このメソッドは、
len
で指定した数の文字を書き込むために使用します。このメソッドは 1 回に 1 文字を書き込むwrite
メソッドをlen
回呼び出します。
writeCode
メソッドは、6 ビットコードを 1 単語 (最大 5 文字を符号化可能) にパックし、その単語を出力ストリームに書き込みます。
CompressionOutputStream
のソースコードを見るには、ここをクリックしてください。
FilterInputStream
を継承してソケットの入力ストリームを作成するデータを圧縮する出力ストリームが完成しました。 次に、圧縮したデータを解凍する入力ストリームを実装します。ソースコードと説明からわかるように、
CompressionInputStream
の作成方法はCompressionOutputStream
の作成方法とよく似ています。また復号化処理は、符号化処理の逆のアルゴリズムになります。
import java.io.* class CompressionInputStream extends FilterInputStream implements CompressionConstants { /* * Constructor calls constructor of superclass */ public CompressionInputStream(InputStream in) { super(in); } /* * Buffer of unpacked 6-bit codes * from last 32 bits read. */ int buf[] = new int[5]; /* * Position of next code to read in buffer (5 signifies end). */ int bufPos = 5; /* * Reads in format code and decompresses character * accordingly. */ public int read() throws IOException { try { int code; // Read in and ignore empty bytes (NOP's) as // long as they arrive. do { code = readCode(); } while (code == NOP); if (code >= BASE) { // Retrieve index of character in codeTable // if the code is in the correct range. return codeTable.charAt(code - BASE); } else if (code == RAW) { // read in the lower 4 bits and the // higher 4 bits, and return the // reconstructed character int high = readCode(); int low = readCode(); return (high << 4) | low; } else throw new IOException("unknown compression code: " + code); } catch (EOFException e) { // Return the end of file code return -1; } } /* * This method reads up to len bytes from the input stream. * Returns if read blocks before len bytes are read. */ public int read(byte b[], int off, int len) throws IOException { if (len <= 0) { return 0; } // Read in a word and return -1 if no more data. int c = read(); if (c == -1) { return -1; } // Save c in buffer b b[off] = (byte)c; int i = 1; // Try to read up to len bytes or until no // more bytes can be read without blocking. try { for (; (i < len) && (in.available() > 0); i++) { c = read(); if (c == -1) { break; } if (b != null) { b[off + i] = (byte)c; } } } catch (IOException ee) { } return i; } /* * If there is no more data to decode left * in buf, read the next four bytes from the * wire. Then store each group of 6 bits in an * element of buf. Return one element of buf. */ private int readCode() throws IOException { // As soon as all the data in buf has been read // (when bufPos == 5) read in another four bytes. if (bufPos == 5) { int b1 = in.read(); int b2 = in.read(); int b3 = in.read(); int b4 = in.read(); // make sure none of the bytes signify the // end of the data in the stream if ((b1 | b2 | b3 | b4) < 0) throw new EOFException(); // Assign each group of 6 bits to an // element of buf. int pack = (b1 << 24) | (b2 << 16) | (b3 << 8) | b4; buf[0] = (pack >>> 24) & 0x3F; buf[1] = (pack >>> 18) & 0x3F; buf[2] = (pack >>> 12) & 0x3F; buf[3] = (pack >>> 6) & 0x3F; buf[4] = (pack >>> 0) & 0x3F; bufPos = 0; } return buf[bufPos++]; } }入力ストリームを記述するときは、ストリームからデータを取り込むメソッドが必要です。したがって、コンストラクタに加えて、
FilterOutputStream
の 2 つのread
メソッドがオーバーライドされます。また、次のメソッドに注目してください。
public int read()
このメソッドは、1 回呼び出されるごとに 1 文字を読み出し、
writeCode
メソッドによってパックされた 6 ビットコードを復号化します。また、次のメソッドに注目してください。
public int read(byte b[], int off, int len)
このメソッドは、最大
len
バイトを読み取って配列b
に書き込みます。 これは、1 回の呼び出しで 1 文字を読み取るread
メソッドを最大len
回呼び出すことによって行います。このメソッドは、len
バイトの読み取りが終了するか、ファイルの最後まで読み取ったときに復帰します。 また、ブロッキングなしにデータが読み取れなくなった場合にも、すぐに復帰します。
readCode
メソッドはCompressionOutputStream
のwriteCode
メソッドに対応しており、ストリームからデータを読み取って 4 バイトのグループを 5 つの 6 ビットコードに復元します。この 6 ビットコードは、read
によって復号化されます。
CompressionInputStream.java
のソースコードを見るには、ここをクリックしてください。
Socket
のサブクラスを作成し、適切なコンストラクタを実装してから、getInputStream
、getOutputStream
、close
メソッドをオーバーライドする これで、
CompressionInputStream
クラスとCompressionOutputStream
クラスの実装が終わりました。 次に、これらの圧縮ストリームを使って通信するソケットを実装します。このサブクラスはjava.net.Socket
クラスを継承します。次は、
CompressionSocket
クラスのソースコードです。コードに続いて、クラスについて説明します。
import java.io.*; import java.net.*; class CompressionSocket extends Socket { /* InputStream used by socket */ private InputStream in; /* OutputStream used by socket */ private OutputStream out; /* * No-arg constructor for class CompressionSocket */ public CompressionSocket() { super(); } /* * Constructor for class CompressionSocket */ public CompressionSocket(String host, int port) throws IOException { super(host, port); } /* * Returns a stream of type CompressionInputStream */ public InputStream getInputStream() throws IOException { if (in == null) { in = new CompressionInputStream(super.getInputStream()); } return in; } /* * Returns a stream of type CompressionOutputStream */ public OutputStream getOutputStream() throws IOException { if (out == null) { out = new CompressionOutputStream(super.getOutputStream()); } return out; } /* * Flush the CompressionOutputStream before * closing the socket. */ public synchronized void close() throws IOException { OutputStream o = getOutputStream(); o.flush(); super.close(); } }データ圧縮を使って通信するソケットを提供するために
Socket
クラスを継承しているので、次の操作が必要です。
Socket
クラスによって使われる入出力ストリームを直接操作するSocket
クラスのメソッドをオーバーライドする- スーパークラスのコンストラクタを呼び出すコンストラクタを提供する
CompressionSocket
コンストラクタは、単にスーパークラスjava.net.Socket
内の同等のコンストラクタを呼び出します。
getInputStream
メソッドは、まだインスタンスの生成が実行されていない場合に、ソケット用のCompressionInputStream
を作成し、ストリームへの参照を返します。同様に、getOutputStream
メソッドは、必要に応じてCompressionOutputStream
を作成し、CompressionSocket
で使用するためにこれを返します。
close
メソッドは、ソケットが閉じられる前にすべてのデータが確実に送信されるように、背後のCompressionOutputStream
をフラッシュします。
CompressionSocket.java
のソースコードを見るには、ここをクリックしてください。
ServerSocket
のサブクラスを作成し、コンストラクタを実装してから、目的のタイプのソケットを作成するために accept
メソッドをオーバーライドする カスタムソケット作成の最後のステップは、カスタムプロトコルをサポートする
ServerSocket
のサブクラスの作成です。この例では、サブクラスの名前はCompressionServerSocket
です。次は、
CompressionServerSocket
クラスのソースコードです。 ソースコードに続いて、クラスについて説明します。
import java.io.*; import java.net.*; class CompressionServerSocket extends ServerSocket { public CompressionServerSocket(int port) throws IOException { super(port); } public Socket accept() throws IOException { Socket s = new CompressionSocket(); implAccept(s); return s; } }
CompressionSocket
の場合と同様に、圧縮プロトコルを使って通信するサーバソケットを作成するには、コンストラクタを実装し、次に、java.net.Socket
タイプのソケットを使用するすべてのメソッドをCompressionSocket
タイプのソケットを使用するようにオーバーライドする必要があります。コンストラクタの実装は簡単で、スーパークラスのコンストラクタを呼び出すだけです。
ServerSocket
のクラスメソッドのうちオーバーライドする必要のあるものは、accept
メソッドだけです。Socket
タイプではなくCompressionSocket
タイプのソケットのインスタンスを生成するようにオーバーライドします。このように簡単な記述ができる理由は、このチュートリアルで説明している圧縮ソケットタイプは、TCP の上のプロトコル層だからです。 TCP は
java.net.Socket
とjava.net.ServerSocket
がデフォルトで使用するプロトコルです。したがって、圧縮ソケットは他のメソッドに対するものと同一の方法を使い、また類似した接続確立インタフェースを使用します。
CompressionServerSocket.java
のソースコードを見るには、ここ をクリックしてください。
Copyright © 1999 Sun Microsystems, Inc. All Rights Reserved. コメントの送付先: rmi-comments@java.sun.com |
![]() |