//-----------
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 />