Replacing Sockets ClientServer Communications

- 51 - option provides a full dump of most network-related structures cumulative readings since the machine was started. By filtering this, taking differences, and plotting various data, you get a good idea of the network traffic background and the extra load imposed by your application. Using netstat with this application shows that connection, resolution of server object, and the three remote method invocations require four TCP sockets and 39 packets of data frames to be transferred. These include a socket pair opened from the client to the registry to determine the server location, and then a second socket pair between the client and the server. The frames include several handshake packets required as part of the RMI protocol, and other overhead that RMI imposes. The socket pair between the registry and server are not recorded, because the pair lives longer than the interval that measures differences recorded by netstat. However, some of the frames are probably communication packets between the registry and the server. Another useful piece of equipment is a network sniffer. This is a hardware device you plug into the network line that views and can save all network traffic that is passed along that wire. If you absolutely must know every detail of what is happening on the wire, you may need one of these. More detailed information on network utilities and tools can be found in system-specific performance tuning books see Chapter 14 , for more about system-specific tools and tuning tips.

2.6.1 Replacing Sockets

Occasionally, you need to be able to see what is happening to your sockets and to know what information is passing through them and the sizes of the packets being transferred. It is usually best to install your own trace points into the application for all communication external to the application; the extra overheads are generally small compared to network or any IO overheads and can usually be ignored. The application can be deployed with these tracers in place but configured so as not to trace until required. However, the sockets are often used by third-party classes, and you cannot directly wrap the reads and writes. You could use a packet sniffer that is plugged into the network, but this can prove troublesome when used for application-specific purposes and can be expensive. A more useful possibility I have employed is to wrap the socket IO with my own classes. You can almost do this generically using the SocketImplFactory , but if you install your own SocketImplFactory , there is no protocol to allow you to access the default socket implementation, so another way must be used. You could add a SocketImplFactory class into java.net , which then gives you access to the default PlainSocketImpl class, but this is no more generic than the previous possibility, as it too cannot normally be delivered with an application. My preferred solution, which is also not deliverable, is to wrap the sockets by replacing the java.net.Socket class with my own implementation. This is simpler than the previous alternatives and can be quite powerful. Only two methods from the core classes need changing, namely those that provide access to the input stream and output stream. You need to create your own input stream and output stream wrapper classes to provide logging. The two methods in Socket are getInputStream and getOutputStream , and the new versions of these look as follows: public InputStream getInputStream throws IOException { return new tuning.socket.SockInStreamLoggerthis, impl.getInputStream ; } public OutputStream getOutputStream throws IOException { return new tuning.socket.SockOutStreamLoggerthis, impl.getOutputStream ; } - 52 - The required stream classes are listed shortly. Rather than using generic classes, I tend to customize the logging on a per-application basis. I even tend to vary the logging implementation for different tests, slowly cutting out more superfluous communications data and headers, so that I can focus on a small amount of detail. Usually I focus on the number of transfers, the amount of data transferred, and the application-specific type of data being transferred. For a distributed RMI type communication, I want to know the method calls and argument types, and occasionally some of the arguments: the data is serialized and so can be accessed using the Serializable framework. As with the customized Object class in the Section 2.4 section, you need to ensure that your customized Socket class comes first in your boot classpath, before the JDK Socket version. The RMI example from the previous section results in the following trace when run with customized socket tracing. The trace is from the client only. I have replaced lines of data with my own interpretation in bold of the data sent or read: Message of size 7 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry handshake Message of size 16 read by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry handshake Message of size 15 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry handshake: client identification Message of size 53 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry query: asking for the location of the Server Object Message of size 210 read by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry query: reply giving details of the Server Object Message of size 7 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake Message of size 16 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake Message of size 15 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake: client identification Message of size 342 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake: security handshake Message of size 283 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake: security handshake Message of size 1 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] Message of size 1 read by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] Message of size 15 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry handoff Message of size 1 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] Message of size 1 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] Message of size 42 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set boolean request Message of size 22 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set boolean reply - 53 - Message of size 1 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] Message of size 1 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] Message of size 120 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set Object request Message of size 22 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set Object reply Message of size 45 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set int request Message of size 22 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set int reply Here is one possible implementation for the stream classes required by the altered Socket class: package tuning.socket; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.net.Socket; public class SockStreamLogger { public static boolean LOG_SIZE = false; public static boolean LOG_MESSAGE = false; public static void readSocket so, int sz, byte[] buf, int off { logfalse, so, sz, buf, off; } public static void writtenSocket so, int sz, byte[] buf, int off { logtrue, so, sz, buf, off; } public static void logboolean isWritten, Socket so, int sz, byte[] buf, int off { if LOG_SIZE { System.err.printMessage of size ; System.err.printsz; System.err.printisWritten ? written : read; System.err.print by Socket ; System.err.printlnso; } if LOG_MESSAGE System.err.printlnnew Stringbuf, off, sz; } } public class SockInStreamLogger extends InputStream { Socket s; InputStream in; byte[] one_byte = new byte[1]; public SockInStreamLoggerSocket so, InputStream i{in = i; s = so;} public int available throws IOException {return in.available ;} public void close throws IOException {in.close ;} public void markint readlimit {in.markreadlimit;} public boolean markSupported {return in.markSupported ;} public int read throws IOException { int ret = in.read ; one_byte[0] = byte ret; - 54 - SockStreamLogger.reads, 1, one_byte, 0; return ret; } public int readbyte b[] throws IOException { int sz = in.readb; SockStreamLogger.reads, sz, b, 0; return sz; } public int readbyte b[], int off, int len throws IOException { int sz = in.readb, off, len; SockStreamLogger.reads, sz, b, off; return sz; } public void reset throws IOException {in.reset ;} public long skiplong n throws IOException {return in.skipn;} } public class SockOutStreamLogger extends OutputStream { Socket s; OutputStream out; byte[] one_byte = new byte[1]; public SockOutStreamLoggerSocket so, OutputStream o{out = o; s = so;} public void writeint b throws IOException { out.writeb; one_byte[0] = byte b; SockStreamLogger.writtens, 1, one_byte, 0; } public void writebyte b[] throws IOException { out.writeb; SockStreamLogger.writtens, b.length, b, 0; } public void writebyte b[], int off, int len throws IOException { out.writeb, off, len; SockStreamLogger.writtens, len, b, off; } public void flush throws IOException {out.flush ;} public void close throws IOException {out.close ;} }

2.7 Performance Checklist