Quantcast
Channel: Blog of a wandering mind
Viewing all articles
Browse latest Browse all 14

Java Flash communications over XMLSocket

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

}

Viewing all articles
Browse latest Browse all 14

Latest Images

Trending Articles





Latest Images