I’ve recently been working on a Java server which will communicate with a Flash frontend. When I started I wanted to use embedded Flash socket code, because I’m more comfortable with Java. Hence I decided to use the XMLSocket protocol. Along the way I’ve found many limitations for this protocol (such as the lack of SSL support), never-the-less I implemented a server side.
The format is and XML file followed by a 0 byte. Unfortunately, there seem to be no pre-written Java classes to read such files. Therefore I wrote one, and decided it’s had enough testing to publish to the world. It’s not amazing, but I physically can’t see any other way to optimise it without losing functionality somewhere. It uses JDOM (it’s all I know), although could probably be easily ported to another Java XML framework.
Anyway here is the code, enjoy it… Improvements welcome and encouraged!
package sockets; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import org.jdom.Document; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.XMLOutputter; /** * * @author chris */ public class XMLSocket { /** Maximum read length so we can't be attacked by memory usage - 10KB */ private final static int MAX_READ_LENGTH = 10240; /** The socket which will be written to/read from */ private final Socket socket; /** The buffer which is read into */ private byte buffer[]; /** The XML read so far */ private String xml; /** Whether to discard until the next NULL byte */ private boolean discard; /** The ammount if bytes left in the buffer */ private int bufferRead; /** The InputSteam of the socket to write to */ private InputStream in; /** The OutputStream of the socket to read from */ private OutputStream out; /** * Initialise a reader and writer for XML with 0 characters between * XML documents. This is a Java implementation of the Flash XMLSocket. * @param s The socket which should be read from/written to * @throws java.io.IOException If there is a problem opening the input or output streams */ public XMLSocket(Socket s) throws IOException { socket = s; xml = null; discard = false; in = s.getInputStream(); out = s.getOutputStream(); buffer = new byte[1024]; bufferRead = 0; } /** * Returns whether the socket is closed * @return true is the socket is closed */ public boolean isClosed() { return socket.isClosed(); } /** * Search a byte array for the first index of a specfic element * @param array The array to search for element * @param element The element to search for * @param length The maximum number of elements to search (1 indexed) * @return The index of the element or -1 if does not exist */ private int indexOf(byte array[], byte element, int length) { if (length > array.length) length = array.length; for (int i = 0; i < length; i++) { if (array[i] == element) return i; } return -1; } /** * Reads from the socket the next XML data. Each packet of data should * be seperated by a 0 character. Will return null if the data between 0 * characters is longer than MAX_READ_LENGTH, but the socket will continue * to search for the next 0. This should not cause a memory issue, but * could result in a DoS attack. * * @param blocking Time to block for waiting for a document (0 is infinity, < 100 will cause the system not to loop) * @param readLength Length (in kb packets) to try to read before closing the connection (0 is inifinity) * @return Null if there was a problem reading or the next read was too large * @throws java.io.IOException If there is a problem reading from the socket * @throws org.jdom.JDOMException If there is a problem with the XML read */ public synchronized Document readXML(int blocking, int readLength) throws IOException, JDOMException { if (in == null) return null; try { socket.setSoTimeout(blocking); } catch (SocketException e) { // Cannot set blocking time... should not risk blocking return null; } // Number of reads from the socket int reads = 0; // Do a pre-read to test socket if (bufferRead == 0) { try { int r = in.read(); if (r == -1) { // Socket closed throw new IOException(); } buffer[0] = (byte)r; bufferRead = 1; } catch (SocketTimeoutException e) { return null; } } int zeroIndex = -1; while (zeroIndex == -1) { zeroIndex = indexOf(buffer, (byte)0, bufferRead); if (zeroIndex == -1) { // EOF not found, read more if (xml != null && xml.length() >= MAX_READ_LENGTH) { // Max read length, ignore input xml = null; discard = true; } if (discard == true) { // We're discarding due to data size, do some throttling // to limit effect of DoS attacks try { // Is this the optimal sleep for 1k of data each loop? Thread.sleep(50); } catch (Exception e) { // Only throttling, not bothered about exceptions } } else { if (xml == null) { xml = new String(buffer, 0, bufferRead); } else { xml += new String(buffer, 0, bufferRead); } bufferRead = 0; } // After the first read subsequent ones should complete faster if (reads == 1) { if (blocking > 0 && blocking < 100) { return null; } else { try { socket.setSoTimeout(100); } catch (SocketException e) { // Cannot set blocking time... should not risk blocking return null; } } } try { bufferRead = in.read(buffer, 0, buffer.length); } catch (SocketTimeoutException e) { bufferRead = -1; } if (bufferRead == -1) { bufferRead = 0; return null; } reads++; if (readLength-- == 0) { // The maximum read length has been reached, close the socket // and return null close(); return null; } if (reads == 32) { // Taken too many reads of junk close(); return null; } } else { if (discard == false && zeroIndex > 0) { xml += new String(buffer, 0, zeroIndex - 1); } bufferRead -= zeroIndex + 1; if (bufferRead <= 0) { bufferRead = 0; } else { System.arraycopy(buffer, zeroIndex + 1, buffer, 0, bufferRead); } if (discard == false) { xml = xml.trim(); } } } // If we got this far the read data is in xml, or discard is true if (discard == true || xml == null) { // The last read was too large. This could still be ok. return null; } else { // Set xml to NULL first incase there is an exception thrown String temp = xml; xml = null; return new SAXBuilder().build(new StringReader(temp)); } } /** * Write XML document to socket followed by a 0 character. * @param d XML Document to write to the socket * @return The document converted to bytes to send, this can be used to * speed multiple sends, null if the connection failed */ public byte[] writeXML(Document d) { if (out == null) return null; byte[] x = checkNullTermination(new XMLOutputter().outputString(d).getBytes()); if (writeXML(x)) { return x; } else { return null; } } /** * Checks that the byte array terminates with a null, if not it adds one * @param xml The byte array to check * @return A definately NULL terminated byte array based on the input */ private byte[] checkNullTermination(byte xml[]) { byte send[]; // If there is a usless character at the end, we can use it // for the 0 byte rather than wasting memory/CPU time. if (xml[xml.length - 1] == '\n' || xml[xml.length - 1] == 0) { xml[xml.length - 1] = 0; send = xml; } else { send = new byte[xml.length + 1]; System.arraycopy(xml, 0, send, 0, xml.length); send[xml.length] = 0; } return send; } /** * Write XML document to socket followed by a 0 character. * @param xml The XML represented in a sendable form * @return true if written successfully, false otherwise */ public boolean writeXML(byte[] xml) { if (out == null) return false; try { xml = checkNullTermination(xml); synchronized(socket) { out.write(xml); out.flush(); } return true; } catch (IOException e) { return false; } } /** * Close input part of the socket */ public void closeInput() { try { in.close(); } catch (Exception e) {} in = null; buffer = null; } /** * Close output part of the socket */ public void closeOutput() { try { out.close(); } catch (Exception e) {} out = null; } /** * Close the socket and all I/O */ public void close() { closeInput(); closeOutput(); try { socket.close(); } catch (Exception e) {} } /** * Returns the InetAddress of the remote connected server * @return The InetAddress of the remote connected server */ public InetAddress getInetAddress() { return socket.getInetAddress(); } }