//-----------
Multiserver 2.0
//-----------
Multiserver is an XML routing server meant for use with Flash 5 clients. This document
is meant to serve as a jumpstart guide to the aspiring backend developer. It is taken
for granted that you are familiar with the Java programming language, compiling
and running java programs, editing the classpath environment variable, etc. i.e. You
can at least just barely install java and get it operational.
Multiserver is meant for experimental / low-load use only. If you place this program in a
production environment for something system-critical, you do so at your own risk.
Security omitted for clarity and novelty :)
//----------
Download:
multiserver.zip
//----------
Multiserver Files:
MultiServer.java -- the class that gets everything running. It's responsible
for putting the server into a "listen" state
XmlHandler.java -- an XmlHandler gets created for each client connection.
pinger.java -- an object which sets up a heartbeat between the server and
client. If the heartbeat cannot be detected, we kill the connection
Logger.java -- an object which logs messages to a file.
xml classes -- xml parsing jar libraries
xmlTest -- sample & server test application
//-----------
The Classpath:
The following files contain java xml parsing/building libraries. Add them to your
classpath.
xerces.jar
jdom.jar
Custom Class Support: If you wish to use a custom application class, you'll
need to add the directory which contains those class files to your classpath.
example classpath: .;.\dynClasses;d:\jdk1.3.1\lib\xerces.jar;d:\jdk1.3.1\lib\jdom.jar
//-----------
Installation
To run the server, copy all the .class files to your server. Add the jar files
refered to above to your classpath. You need to have the java runtime environment
installed and be familiar with all that whoey. Download it free from java.sun.com
Refer to macromedia's documentation regarding security and whatnot. It's important.
It won't work unless you follow the rules. The machine you're doing this on is
the one that your Flash file will connect to using the "server" variable we talk
about below. type:
java MultiServer 9999 logfile.log
That assumes you want to run the server application on port 9999. Unix users will
want to add an "&" to the end of that command so that it spawns a new process.
You should now be able to connect to your MultiServer ... give it a whirl.
//------------
Security Restrictions
Ok, so the fact that you can only connect back to servers within your domain is
a mite limiting. What to do?
1) Get access to your own DNS server so you can tweak everything. If you don't
have control over your own DNS, you *can*, and for *free* Visit granitecanyon.com
2) Add an entry for whatever box has your xml server so that it appears that it's
part of the same domain as www.yourwebserver.com I personally like to use xml.mywebserver.com
//------------
A Look At The Source Code
XmlHandler.java implements several very useful functions that you'll be using
in your own custom classes (more on custom classes in a moment)
broadcast: send this message to all users
sendToOthers: send this message to all users except the one being handled
by the current Thread
reply: send this message to the user being handled by the current Thread
sendToId: send this message to a specific user id
First, take a look at the process() function. It reads the nodename of the first
element, and passes the xml off to another function for specific processing. You'll
see that the first if statement catches messages with nodeName equal to "broadcast".
So, if we send:
<broadcast a="b" b="c" c="123" />
the xml will be sent to the broadcast function where it will, in turn, be sent
to all users. Once you've received a message and determined what it contains,
you will more than likely pass it to one of the functions above so that it can
be sent to the recipients who need it.
//------------
Sample Flash Code:
//Server config
port = 9999
server = "xml.yourserver.com"
function init() {
// queue initialization
xmlQueue = new Array()
//xml initialization
xsock = new XMLSocket()
//once we connect to the server we call sockConnect
//but nothing actually happens yet
xsock.onConnect = sockConnect
//when we receive new data we call handleXML
//again...this doesn't do anything yet
xsock.onXML = handleXML
//try to connect
//this kicks off the sockConnect function and NOW our
//handleXML function will get called when new data arrives.
xsock.connect(server, port)
trace ("connecting...")
}
function sockConnect (success) {
if(success){
trace("Connected")
}
else {
trace ("UNSUCCESSFUL CONNECTION")
}
}
function handleXML (xmlDoc) {
//if the queue is empty go ahead and process it.
//if there's a line, wait your turn
if (xmlQueue.length==0) {
processXML (xmlDoc)
}
else {
xmlQueue[xmlQueue.length]=xmlDoc
}
}
function handleXMLQueue () {
//get the next thing in the queue and deal with it.
var xmlDoc = xmlQueue.shift()
if (xmlDoc!=null) {
processXML (xmlDoc)
//when you're done with that one, process the queue again
//yeah, I know, this could cause a recursive mess...
handleXMLQueue()
}
}
function processXML(xmlDoc) {
trace ("RECIEVED: " + xmlDoc)
var xml = xmlDoc.childNodes[0]
//grab the root node.
trace ("node: " + xml.nodeName)
if (xml.nodeName == "broadcast") {
var username = xml.attributes["username"]
var msg = xml.attributes["msg"]
//call a function with this data
doSomethingWithThisData(username,msg)
}
if (xml.nodeName == "addUser") {
var id = xml.attribuites ["id"]
doSomethingElse(id)
}
}
function sendXmlExample (text) {
trace ("text: " + text)
var obj = new Object ()
obj.username = myUsername
obj.msg = text
var xml = Object.io.objToXML(obj, "broadcast")
xsock.send(xml)
}
//Useful library functions--now contained in ioLib
//object to xml ---- xml to object function
Object.io.objToXML (obj, name) {
var xml = new XML();
var xdata = xml.createElement(name);
for (i in obj) {
xdata.attributes[i] = obj[i]
}
xml.appendChild(xdata)
return xml
}
function Object.io.xmlToObj (doc) {
var xml = doc.childNodes[1]
var objName = xml.nodeName
var attribCount = xml.attributes.length
var returnObj = new Object ()
for (i in xml.attributes) {
set (returnObj[i], xml.attributes[i])
}
return returnObj
}
The code above is a stripped-down, slightly modified version of the chat code
being used at theremediproject.com This should give you one helluva head start
in creating your multi-user masterpiece. Let's break it down.
First thing we do is set two variables, port and server. These should be familiar
from the Flash 5 manual. What?! Didn't read the manual? Shame shame.
init()
This function sets up our xml socket. You can have more than one if you want,
but there's really no point unless you're connecting to more than one server.
You can probably cut/paste this function as is.
sockConnect(success)
Flash automatically calls this function (we told it to in init() ) after the call
to connect either succeeds or fails. This is called a "callback function". The
"success" parameter is a boolean that tells us if the connect worked or not. Note
that this function isn't doing anything in this example. In real life, you would
tell your app to allow sending/recieving data if everything is cool, and if the
connect failed, you'd offer a retry or something.
handleXML (xmlDoc) & handleXMLQueue ()
Flash calls the handleXML function when new xml is received. I've made it a bit
more complex than it really needs to be and added a queue. Since this function
gets called everytime new xml arrives, we want to be sure one request doesn't
stomp on the processing of another. If you want to make sense of it, just follow
the processing path with your finger...*be* the xml...Just keep in mind that we're
not processing the data at all in this function; all we're doing is making a list
of stuff that needs to get processed. So these functions aren't really complicated.
processXML(xmlDoc)
Now we're getting somewhere. This function breaks the xml down and figures out
what's in it. If it's just a simple one-node message (and most of the stuff you
build probably will be at first), this function will figure out the node name,
and process the data based on the node name. So, If my XML looks like:
<broadcast username="paul" msg="whatever man, I just xml here." />
This function will see that the node name is "broadcast", and pull the
username and message attribute values out of the rest of the xml string. Neato,
huh?
sendXmlExample (text)
I've included this for completeness sake, even though I didn't include the functions
that deal with how you display a new piece of chat once you've got it. If you
want to send XML, here's how. I've included two pretty usefull (I think) library
functions that convert xml into objects and vice versa. The sendXmlExample function
uses the Object.io.objToXml function. First, we build an object, that contains
a username and a message, then we call it "broadcast", convert it to xml, and
send it down the pipe. More on how this works in a sec.
Object.io.objToXML (obj, name)
The first library function converts any object into an xml string. You just specify
a node-name. This is particularly usefull for pulling all the data out of a movie
clip and sending it to the server, without knowing exactly what data it contains.
You simply pass the movieclip as the "obj" parameter, and give it a node-name.
The xml string is returned. If this isn't making any sense, refer back to the
code, and look up the "for in" loop in the ActionScript guide. Keep in mind too,
that you should be able to use this function without completely understanding
it...at least that's my hope.
Object.io.xmlToObj (doc)
This function does the opposite, give it an xml string (again, single node only)
and it'll return an object containing all the name/value pairs from that object.
We grab the node-name, but we don't do anything with it in this example, and as
you might have noticed, I'm not even using this function in the example at all...it's
just here as an analog to the function above.
//------------
Custom Application Classes
Take a look at defaultXMLHandler.java for a simple example of a custom application
class. The only requirement is the implementation of a process method. For examples
and tips on creating your own, check out the pong game example. The code in pongQ.java
and pongGame.java are great starting points. Any message not handled by XmlHandler.java
is sent to whatever custom class has been set. (there is no default) Within the
custom class, messages are simply routed to the correct XmlHandler function via
the "owner" object.
//------------
Setting the Current Application Class
Your Flash movie should connect to the MultiServer application, and send it the
following xml. The classpath must contain the file "myCustomApplicationClass.class"
for the example to work.
<setAppClass="myCustomApplicationClass" />
//------------
Creating Custom Application Classes
1) Create a new application class file & implement your own "process" method.
It should accept two arguments: an XmlHandler and an XmlDocument.
//example
import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.*;
public class customRouter {
public XmlHandler owner;
public Document xmlDoc;
public void process (XmlHandler owner, Document xmlDoc) {
...
}
}
2) Compile the java file into a .class file & copy it into your classpath.
3) Test the loading this class with your client by sending:
<setAppClass="myCustomApplicationClass" />
the XmlHandler should load a copy of your custom class (in this case: "myCustomApplicationClass.class")
4) Send a message to be handled by the custom class
NOTE: if you get a NullPointerException, your custom class loader was not properly
loaded. When a message is recieved by XmlHandler, it first tries to process the
message itself (with its own process method) if the nodeName doesn't match any
of its handlers, the message will be passed to *your* process method. Although
you have the whole of the Java language at your disposal when creating these custom
classes, In many cases, no additional work is necessary. Simply determine the
type of message and route it. Don't forget that you have access to the XmlHandler
functions via the owner object.
example: owner.broadcast (myXml); //pie
//------------
XmlHandler Events
When a new user connects, they are sent a simple reply message:
They (and everyone else) are sent an updated user count:
When a user disconnects, the "removeUser" method of that user's custom application
class is called (if present): The custom application class is responsible for
notifying other users (via the available message routing functions)
//------------
XmlHandler Function Definitions
Your custom application classes have access to the following methods via the owner
object.
//example: to access the broadcast function, simply call
//owner.broadcast (myXML);
protected static void broadcast (Document xmlDoc)
//sends the xmlDocument to everyone including the current user
//example: broadcast (myXML);
public void sendToOthers (Document xmlDoc)
//sends the xmlDocument to everyone except the current user
//example: sentToOthers (myXML);
public void reply (Document xmlDoc)
//sends the xmlDocument to the current user
//example: reply (myXML);
public void sendToId (Document xmlDoc, String toId, String fromId)
//sends the xmlDocument to the specified user
//example: sendToId (myXML, toId, myId);
public void setId (Document xmlDoc)
//example: setId (myXML);
//expected xml format:
myXML = <setid id="bob" />
//returns (or)
//NOTE: setId must be used before attempting sendToId
public void setAppClass (Document xmlDoc)
//example: setAppClass (myXML);
//expected xml format: myXML = <setappclass classname="defaultXMLHandler" />
//this would attempt to load defaultXMLHandler.class as the custom application
class
//NOTE: setAppClass must be called before custom xml processing is attempted.
//custom xml processing will be attempted on any message not handled by XmlHandler.class
protected void upDateUserCount()
//sends xml to all users (EXCEPT the current one) containing the current number
of user connections
//example xml: <usercount count="25" />
protected void pong (Document xmlDoc)
//clients connecting to MultiServer are expected to reply to messages
with messages
//this is neccessary to support detection of hung connections
//expected xml format: myXML = <pong />