#
Creating a Custom Socket Type

Often it is necessary to process data either before it is sent to, or after it has been received from, a socket. Instead of using `java.net.Socket` sockets with pre- and post-processing, another type of socket can be used.

This page shows you the steps to follow to create a custom socket type. It also contains an example implementation of a custom socket named `CompressionSocket`, which compresses data sent over the connection it uses.

The `CompressionSocket` and its supporting classes are also used in our tutorial on how to create a custom RMI socket factory. As a result, the source files for this example are all in the `examples.rmisocfac` package.

In this tutorial, creating the `CompressionSocket` class and related classes is broken down into the following four steps, which can be adapted to create other custom socket type:

1. Extend `java.io.FilterOutputStream` to create an output stream for the socket. Override `FilterOutputStream` methods as necessary.
2. Extend `java.io.FilterInputStream` to create an input stream for the socket. Override `FilterInputStream` methods as necessary.
3. Create a `java.net.Socket` subclass. Implement the appropriate constructors and override the `getInputStream`, `getOutputStream` and `close` methods.
4. Create a `java.net.ServerSocket` subclass. Implement the constructor, and overload the `accept` method to create a socket of the desired type.

## Step 1:
Extend `FilterOutputStream` to Create an Output Stream for the Socket.

> Because we are writing a socket that does data compression, we will write a class named `CompressionOutputStream` that extends `FilterOutputStream`. However, extending `FilterOutputStream` will not be appropriate in every case. In general, you should extend the type of output stream best-suited for the type of socket you are implementing. In this example, `FilterOutputStream` is most appropriate.

> For our example, we will use a very simple algorithm that provides 6-bit character encoding for up to 62 common characters and 18-bit encoding for the other characters (remember that in the JavaTM programming language characters are usually 16 bits). There is a lookup table to map the 62 common characters to 6-bit numbers in the range 0 to 61. Depending on the result of the lookup, each encoded character is marked with a constant to indicate whether it has 6-bit or 18-bit encoding. Finally, the encoded characters are written to the stream. This algorithm assumes that all characters encountered are ASCII, and that the high-order byte of each character is unused.

> Please note: This algorithm is not recommended for use in any application requiring data compression. It is included only for this example and is not intended for practical use.

> Before writing any sample code that demonstrates how to create custom sockets, it is necessary to write an interface containing the information to be shared between the input and output stream classes so the common character lookup table, `codeTable` and 3 constants are included as members. It is useful to note that while an interface is included in this example, an interface is not always required.

>     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 ,.!?\"'()";
>     }

> The source code for the `CompressionConstants` interface can be found here.

> Now that the interface for sharing information between the input and output streams has been defined, we will complete Step 1, which was to write a class that extends `java.io.FilterOutputStream` in order to create an output stream for the socket, overriding `FilterOutputStream` methods as necessary.

> Below is the source code for the class `CompressionOutputStream.java`. An explanation of the compression algorithm can be found in the comments of the source code. An explanation of the implementation follows the code.

>     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;
>             }
>         }
>     }

> First, `CompressionOutputStream` subclasses `FilterOutputStream`. Then it implements the interface `CompressionConstants` so that it can have access to the lookup table and constants.

> In order to compress the data, the `FilterOutputStream write` methods are overridden by `CompressionOutputStream`. The method

> `public void write(int b)`

> writes one character per invocation, and is responsible for marking each character with its encoding format using a compression constant (either `RAW` or `BASE`) and dividing the character into two 4-bit parts when necessary.

> The method

> `public void write(byte b[], int off, int len)`

> allows one to write `len` characters. It calls the `write` method that writes one character per invocation `len` times.

> The method `writeCode` is responsible for packing five 6-bit codes into one word (which could encode up to five characters) and writing that word to the output stream.

> The source code for `CompressionOutputStream` can be found here.

## Step 2:
Extend `FilterInputStream` to Create an Input Stream for the Socket.

> Now that we have an output stream that compresses the data, we need to implement an input stream that decompresses it. Creating the `CompressionInputStream` is very similar to creating the `CompressionOutputStream` as you will notice from the source code and the following discussion.

> As you might expect, algorithmically, the decoding process is the just reverse of the encoding process.

>     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++];
>         }
>     }

> When writing your own input stream, it is necessary to provide methods for getting data from the stream. Therefore, in addition to a constructor, two `FilterOutputStream read` methods were overridden. The method

> `public int read()`

> reads one character per invocation and is responsible for decoding the 6-bit codes unpacked by the method `writeCode`.

> The method

> `public int read(byte b[], int off, int len)`

> causes up to `len` bytes to be read and placed into the array `b`. It accomplishes this through up to `len` calls to the `read` method that reads one character per invocation. In addition to returning once `len` bytes have been read or once the end of the file has been reached, this method also returns as soon as no more bytes can be read without blocking.

> The method `readCode`, which corresponds to the `writeCode` method in `CompressionOutputStream`, is responsible for reading data from the stream and unpacking each group of four bytes into five 6-bit codes (which are then decoded by `read`).

> The source code for `CompressionInputStream.java`, can be found here.

## Step 3:
Create a `Socket` subclass. Implement the appropriate constructors and override the `getInputStream`, `getOutputStream`, and `close` methods.

> Now that we have implemented the classes `CompressionInputStream` and `CompressionOutputStream`, we can implement the socket that communicates using these compression streams. Our subclass extends class `java.net.Socket`.

> Below is the source code for the class `CompressionSocket`. Following the source is a discussion of the class.

>     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();
>         }

>     }

> Because we are extending the `Socket` class to provide sockets that communicate using data compression, we need to:

> - override the class `Socket` methods that directly manipulate the input and output streams used by class `Socket` and
> - provide constructors that call the constructor of the superclass.

> The `CompressionSocket` constructors just call the equivalent constructors in the superclass, `java.net.Socket`.

> The method `getInputStream` creates a `CompressionInputStream` for the socket if one has not already been instantiated and returns a reference to that stream. Likewise, `getOutputStream` creates a `CompressionOutputStream` if necessary, and returns the one in use by the `CompressionSocket`.

> The `close` method flushes the underlying `CompressionOutputStream`, ensuring that all the data is sent before the socket is closed.

> The source code for `CompressionSocket.java`, can be found here.

## Step 4:
Create a `ServerSocket` subclass. Implement the constructor, and override the `accept` method to create a socket of the desired type.

> The last step in writing a custom socket is creating a subclass of `ServerSocket` that supports your protocol. In this case, our subclass will be `CompressionServerSocket`.

> Below is the source code for the `CompressionServerSocket` class, followed by a discussion of the class.

>     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;
>         }
>     }

> As was the case with `CompressionSocket`, in order to provide server sockets that communicate using our compression protocol, we need to implement the constructor, and then make sure that all methods that use sockets of type `java.net.Socket` are overridden to use sockets of type `CompressionSocket`.

> Implementing the constructor is as simple as calling the constructor of the super class.

> The only `ServerSocket` class method that needs to be overridden is the method `accept`. It is overridden to instantiate a socket of type `CompressionSocket` instead of type `Socket`.

> The above simplifications are possible because the compression socket type described in this tutorial is a protocol layer on top of TCP, the default protocol used by `java.net.Socket` and `java.net.ServerSocket`. Accordingly, it shares the same meaning for the rest of the methods, as well as a similar connection-establishment interface.

> The source code for `CompressionServerSocket.java`, can be found here.

Copyright © 1999 Sun Microsystems, Inc. All Rights Reserved. Please send comments to: rmi-comments@java.sun.com |