A Special-Purpose Socket Special-Purpose Sockets

programmers to specify what type of sockets to use when making a connection between a client and a server via the RMIClientSocketFactory and RMIServerSocketFactory interfaces; see Chapt er 18 for more details.

2.5.3 A Special-Purpose Socket

Creating custom socket classes is only little bit more complicated than you might expect from the previous discussion. Exam ple 2- 2 shows the complete implementation of CompressingSocket , a socket that uses the compressing streams to save bandwidth: Example 2-2. CompressingSocket.java public class CompressingSocket extends Socket { private InputStream _compressingInputStream; private OutputStream _compressingOutputStream; public CompressingSocket throws IOException { } public CompressingSocketString host, int port throws IOException { superhost, port; } public InputStream getInputStream throws IOException { if null==_compressingInputStream { InputStream originalInputStream = super.getInputStream ; _compressingInputStream = new CompressingInputStreamoriginalInputStream; } return _compressingInputStream; } public OutputStream getOutputStream thr ows IOException{ if null==_compressingOutputStream { OutputStream originalOutputStream = super.getOutputStream ; _compressingOutputStream= new CompressingOutputStreamoriginalOutputStream; } return _compressingOutputStream; } public synchronized void close throws IOException { ifnull=_compressingOutputStream { _compressingOutputStream.flush ; _compressingOutputStream.close ; } ifnull=_compressingInputStream { _compressingInputStream.close ; } } } All that we did to write CompressingSocket was move the streams customization code inside the Socket class definition. Note that in order to do this, however, we also had to override the close method to close the special-purpose stream we created. Theres one other subtlety here: we didnt use GZIPInputStream and GZIPOutputStream directly. Instead, we defined custom stream classes that wrapped around GZIPInputStream and GZIPOutputStream. Here is our implementation of CompressingOutputStream : public class CompressingOutputStream extends OutputStream { private OutputStream _actualOutputStream; private GZIPOutputStream _delegate; public CompressingOutputStreamOutputStream actualOutputStream { _actualOutputStream = actualOutputStream; } public void writeint arg throws IOException { if null==_delegate { _delegate = new GZIPOutputStream_actualOutputStream; } _delegate.writearg; return; } public void close throws IOException { if null=_delegate { _delegate.close ; } else { _actualOutputStream.close ; } } public void flush throws IOException { if null=_delegate { _delegate.finish ; } } } We needed to use this extra layer of indirection because of the way that GZIPOutputStream handles flush . Recall that subclasses of DeflaterOutputStream dont actually commit all data to the underlying stream when flush is called. This means were faced with the following problems: • Because were subclassing Socket , clients will call getInputStream and getOutputStream . • When theyre done sending data, clients will call flush to make sure all the data has been sent. • Some of the data wont be sent when the client calls flush . To handle these problems, we implement flush so it calls finish . Remember, though, that clients and servers must use the same type of socket if the client compresses, the server must uncompress. In practice, this simply means that we also need to create a subclass of ServerSocket and override the accept method to return a CompressingSocket . Exam ple 2- 3 shows the complete code for CompressingServerSocket . Example 2-3. CompressingServerSocket.java public class CompressingServerSocket extends ServerSocket { public CompressingServerSocketint port throws IOException { superport; } public Socket accept throws IOException { Socket returnValue = new CompressingSocket ; implAcceptreturnValue; return returnValue; } } This works by creating an instance of CompressingSocket and passing it as an argument to implAccept . implAccept is a protected method that actually listens for connections and blocks. When a connection is made, implAccept configures the CompressingSocket it has been passed and then returns. Logging and Tracing Frequently, the portions of code that perform data translation are also the ideal points to insert logging, tracing, and debugging code. For example, in the com.ora.rmibook.chapter2.sockets package, there are three classes that together illustrate the general idea: LoggingInputStream , LoggingOutputStream , and Recorder . LoggingInputStream and LoggingOutputStream dont perform any data manipulation at all, but they do have a re ference to an instance of Recorder . And they tell the recorder whenever data flows through them, as in the following code snippet from LoggingInputStream : public int readbyte[] b throws IOException { int numberOfBytes = super.readb; _recorder.incrementCounternumberOfBytes; return numberOfBytes; } While this implementation is very primitive the recorder is told the number of bytes received, but does not, for example, know where they came from, the idea is clear. Subclassing Socket , and using the custom subclass in your application, can provide a powerful hook for analyzing network performance.

2.5.4 Factories