How to make a multi-client Flash Java server

Introduction - Flash client and Java server

We're seeing an increasing use in online multimedia applications and Flash is an ideal candidate for this type of applications. It has the necessary features to provide a rich user interface, runnable in a browser. In its most recent versions, Flash as gained the ability to work with sockets (and not only XMLSockets). Sockets are a communication end-point, used to establish a communication network between different parties. This has opened a lot of possibilities for Flash applications.

Java makes a good choice for a multi-client capable server. Both Flash and Java are closs-platform solutions, making them very portable as well as easily distributable.

Making a chat application with Flash and Java

In this tutorial we will explore these concepts and we'll make an application using Flash as the client and Java as a multi-client server. An example of a simple application that meets these characteristics is a chat application and we'll do just that.

Adobe has introduced a few security protections and for a Flash application to be able to connect to a server, it needs to know the policy used by that server to guarantee that access is granted to it. To handle this, we'll make a policy server in Java that could also be used separately for other servers that need Flash clients to connect to it.

Basically, this chat application needs 3 different parties: Flash chat client, Java chat server and Java policy server. This flow is described in the following picture.

You'll need ActionScript 3 for this tutorial. I'll use Adobe Flash CS3 Professional for developing the Flash client.

Making the Flash chat client

We'll start by making the Flash client. This will be composed of 2 separate files:

  • ChatClient.as: The ChatClient ActionScript class is responsible for communicating with the server
  • Chat.fla: The main Flash file, with the GUI for the chat and the code to use the client

Making the ChatClient class talk to the server

Let's start by creating our ChatClient class. This class will have the ability to send and receive simple messages from the server. For making this possible, it will need a socket and to know the server's IP and port. We'll also need to pass a TextField to the ChatClient class for it to be able to write the received messages.

The class definition and constructor should be as follow:

package {
    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
    import flash.text.*;
 
    public class ChatClient extends Sprite {
        private var host:String;
        private var port:int;
        private var socket:Socket;
        private var chatArea:TextField;
 
        public function ChatClient(h:String, p:int, ca:TextField) {
            this.host = h;
            this.port = p;
            this.chatArea = ca;
        }
}

Now we can make a connect method and the related socket event handlers. To work with sockets in Flash we add event listeners to the socket that are called when something significant happens. We can do this using the code bellow (put it in the ChatClient class):

        public function connect():Boolean {
            this.socket = new Socket(this.host, this.port);
            this.socket.addEventListener(Event.CONNECT, socketConnect);
            this.socket.addEventListener(Event.CLOSE, socketClose);
            this.socket.addEventListener(IOErrorEvent.IO_ERROR, socketError);
            this.socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityError);
            this.socket.addEventListener(ProgressEvent.SOCKET_DATA, socketData);
 
            try {
                this.socket.connect(this.host, this.port);
            }
            catch (e:Error) {
                trace("Error on connect: " + e);
 
                return false;
            }
 
            return true;
        }
 
        private function socketConnect(event:Event):void {
            trace("Connected: " + event);
        }
 
        private function socketData(event:ProgressEvent):void {
            trace("Receiving data: " + event);
            receiveData(this.socket.readUTFBytes(this.socket.bytesAvailable));
        }
 
        private function socketClose(event:Event):void {
            trace("Connection closed: " + event);
            this.chatArea.appendText("Connection lost." + "n");
        }
 
        private function socketError(event:IOErrorEvent):void {
            trace("Socket error: " + event);
        }
 
        private function securityError(event:SecurityErrorEvent):void {
            trace("Security error: " + event);
        }

The connect method just creates a new socket with the host and port that was provided in the constructor. It adds event listeners for each significant event and tries to connect to the server.

Most of events are handled only for tracing purposes (which provide critical information when debugging a client-server application). The exceptions are the socketData and socketClose handlers. In the socketClose method we just write status information in the chat client. The socketData method reads all the bytes available in the socket buffer and calls the receiveData method which will be described bellow.

The only things that are missing are the methods for handling the received data and for sending messages to the server. We'll create a sendMessage method to write a message to the socket and a receiveData method to handle the data read in the socketData method. The code for these methods can be found bellow (this code belongs to the ChatClient class):

        public function sendMessage(msg:String):void {
            msg += "n";
 
            try {
                this.socket.writeUTFBytes(msg);
                this.socket.flush();
                trace("Message sent: " + msg);
            }
            catch(e:Error) {
                trace("Error sending data: " + e);
            }
        }
 
        private function receiveData(msg:String):void {
            trace("Message received: " + msg);
            this.chatArea.appendText(msg + "n");
        }

The received data is just written to the chat client. The sendMessage method appends a line break to the message before writing it and then flushes the socket. We'll not directly write the client's own message to its chat - we'll wait for the server to broadcast the message to all the clients and we'll only write it then.

Our ChatClient class is done.

Making the chat's GUI and Flash client

Now we need to setup the main Flash file with the GUI. It will be composed of an input text box, a button for sending the message and a text area to display the chat's messages.

Start by adding a new text field. Name it inputLine_txt and change its type to Input Text. Makes this long enough to hold a line of chat.

Now make a button and call it send_btn. This will be used to send the message (alternatively we will also use the Enter for this).

The only thing that's left to add is the text area where we'll display the chat. Add a new text field, call it chatArea_txt and change its type to Dynamic Text. Make sure Multiline is selected.

This setup is described in the following picture.

Now we need to add the necessary actions. Create a new layer, select the first frame and open the ActionScript panel.

Three things need to be done:

  1. Make a policy request to the server
  2. Create a ChatClient and connect to the server
  3. Handle the process of sending messages

Remember, and this is important, the connection to the server cannot be made if the Flash client hasn't received a policy string from the server confirming that it has access to the server. The Flash client will implicitly send a policy request to the server (<policy-file-request/>) and wait for the policy to be sent. We can also explicitly request a policy file from a specific location (this will be our approach).

After loading the policy file we'll create a new instance of ChatClient and connect to the server. We'll need to define the server's IP and port. In this example I will use a private address, because I have several computers in a LAN and I can test the application this way. You should change the IP to whatever is your server's IP and the same for the port. We'll use the same IP for the policy request, but on another port.

We'll then add event listeners for the mouse click on the Send button and also for key presses on the input text field to catch the Enter key. To send messages we define a function that checks to see if inputLine_txt is not empty, then it uses the ChatClient object to send a message to the server and also clears the text on inputLine_txt.

The code for accomplishing this is the following:

import ChatClient;
import flash.display.*;
 
var host:String = "192.168.1.12";
var chatPort:int = 5555;
var policyPort:int = chatPort + 1;
 
Security.loadPolicyFile("xmlsocket://" + host + ":" + policyPort);
var client:ChatClient = new ChatClient(host, chatPort, chatArea_txt);
client.connect();
 
send_btn.addEventListener(MouseEvent.CLICK, onSendClick);
inputLine_txt.addEventListener(KeyboardEvent.KEY_UP, onInputLineKey);
 
function sendMessage():void {
    if (inputLine_txt.text != "") {
        client.sendMessage(inputLine_txt.text);
        inputLine_txt.text = "";
    }
}
 
function onSendClick(e:MouseEvent):void {
    sendMessage();
}
 
function onInputLineKey(e:KeyboardEvent):void {
    if (e.keyCode == 13) { // ENTER was pressed
        sendMessage();
    }
}

And we're finished with the client. Now we need to make the server side of the chat application.

How to make a policy server with Java

As mentioned previously, Flash expects the server to handle policy requests. A policy is a XML containing the addresses which have access to the server and also to which ports they have access to. It's a security requirement that Adobe introduced in Flash and every server to which a Flash client wants to connect to with sockets must be prepared for this.

A policy server can be easily and quickly created using Java and you could use the solution we're going to build in other servers to which you need Flash applications to connect to.

The policy server will consist on two different classes:

  • PolicyServer: listens for clients' connections and creates PolicyServerConnections to handle them
  • PolicyServerConnection: reads policy requests from clients and writes the policy to them

I'll only explain and put here the code for the most important methods. This tutorial's files include all the code and all of it is well documented.

Let's start by defining the PolicyServer. This class will also contain the policy XML. In this case I setup the server to accept connections from all domains and granted access to all ports. You should be more restrictive and only give limited access.

public class PolicyServer extends Thread {
    public static final String POLICY_REQUEST = "<policy-file-request/>";
    public static final String POLICY_XML =
            "<?xml version="1.0"?>"
            + "<cross-domain-policy>"
            + "<allow-access-from domain="*" to-ports="*" />"
            + "</cross-domain-policy>";
 
    protected int port;
    protected ServerSocket serverSocket;
    protected boolean listening;
 
    /**
     * Creates a new instance of PolicyServer.
     *
     * @param serverPort the port to be used by the server
     */
    public PolicyServer(int serverPort) {
        this.port = serverPort;
        this.listening = false;
    }
 
    /**
     * Waits for clients' connections and handles them to a new PolicyServerConnection.
     */
    public void run() {
        try {
            this.serverSocket = new ServerSocket(this.port);
            this.listening = true;
            debug("listening");
 
            while (this.listening) {
                Socket socket = this.serverSocket.accept();
                debug("client connection from " + socket.getRemoteSocketAddress());
                PolicyServerConnection socketConnection = new PolicyServerConnection(socket);
                socketConnection.start();
            };
        }
        catch (Exception e) {
            debug("Exception (run): " + e.getMessage());
        }
    }
}

This is quite simple and doesn't require many explanations. We put some debugging messages which are very much needed to know what's going on under the hood. You should see the source code for the rest of the methods, but there isn't much more to it.

Handling Flash policy requests with Java

Now, let's define the PolicyServerConnection class. This class will read policy requests from clients and if they are valid, it sends the policy XML to them. It will receive a socket from the server and will open up a reader and a writer to be able to use the socket to talk to the client.

We'll make a readPolicyRequest method which will read a message from the socket, make sure it's a policy request and then calls the writePolicy method. This method writes the server's XML policy file and then closes the socket.

So, our PolicyServerConnection class should be something like this:

public class PolicyServerConnection extends Thread {
    protected Socket socket;
    protected BufferedReader socketIn;
    protected PrintWriter socketOut;
 
    /**
     * Creates a new instance of PolicyServerConnection.
     *
     * @param socket client's socket connection
     */
    public PolicyServerConnection(Socket socket) {
        this.socket = socket;
    }
 
    /**
     * Create a reader and writer for the socket and call readPolicyRequest.
     */
    public void run() {
        try {
            this.socketIn = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            this.socketOut = new PrintWriter(this.socket.getOutputStream(), true);
            readPolicyRequest();
        }
        catch (Exception e) {
            debug("Exception (run): " + e.getMessage());
        }
    }
 
    /**
     * Reads a string from the client and if it is a policy request we write the policy, then we close the connection.
     */
    protected void readPolicyRequest() {
        try {
            String request = read();
            debug("client says '" + request + "'");
 
            if (request.equals(PolicyServer.POLICY_REQUEST)) {
               writePolicy();
            }
        }
        catch (Exception e) {
            debug("Exception (readPolicyRequest): " + e.getMessage());
        }
        finalize();
    }
 
    /**
     * Writes the policy of the server.
     */
    protected void writePolicy() {
        try {
            this.socketOut.write(PolicyServer.POLICY_XML + "u0000");
            this.socketOut.close();
            debug("policy sent to client");
        }
        catch (Exception e) {
            debug("Exception (writePolicy): " + e.getMessage());
        }
    }
}

Don't forget that's not the whole class. You should see the source code for the rest.

Our policy server is done. Quite simple, uh?

Making the Java chat server

The main server structure is similar to the policy server. We'll also use two classes for this:

  • ChatServer: listens for clients' connections and creates ChatServerConnection to handle them, adds and removes clients and broadcasts messages
  • ChatServerConnection: reads and writes chat messages to the clients

We'll start with the ChatServer class. You'll notice that it's very similar to the PolicyServer class. We'll add the remove method for when clients want to exit from the server and the writeToAll method for broadcasting messages.

Here is part of this class definition:

public class ChatServer extends Thread {
    protected ServerSocket socketServer;
    protected int port;
    protected boolean listening;
    protected Vector<ChatServerConnection> clientConnections;
 
    /**
     * Creates a new instance of ChatServer.
     *
     * @param serverPort the port to be used by the server
     */
    public ChatServer(int serverPort) {
        this.port = serverPort;
        this.clientConnections = new Vector<ChatServerConnection>();
        this.listening = false;
    }
 
    /**
     * Listens for client conections and handles them to ChatServerConnections.
     */
    public void run() {
        try {
            this.socketServer = new ServerSocket(this.port);
            this.listening = true;
            debug("listening");
 
            while (listening) {
                Socket socket = this.socketServer.accept();
                debug("client connection from " + socket.getRemoteSocketAddress());
                ChatServerConnection socketConnection = new ChatServerConnection(socket, this);
                socketConnection.start();
                this.clientConnections.add(socketConnection);
            };
        }
        catch (Exception e) {
            debug(e.getMessage());
        }
    }
 
    /**
     * Broadcasts a message to all the clients.
     *
     * @param msg the message to be sent
     */
    public void writeToAll(String msg) {
        try {
            for (int i = 0; i < this.clientConnections.size(); i++) {
                ChatServerConnection client = this.clientConnections.get(i);
                client.write(msg);
            }
            debug("broadcast message '" + msg + "' was sent");
        }
        catch (Exception e) {
            debug("Exception (writeToAll): " + e.getMessage());
        }
    }
 
    /**
     * Removes a client from the server (it's expected that the client closes its own connection).
     *
     * @param remoteAddress the remote address of the client's socket connection
     * @return true if the client was successfully removed
     */
    public boolean remove(SocketAddress remoteAddress) {
        try {
            for (int i = 0; i < this.clientConnections.size(); i++) {
                ChatServerConnection client = this.clientConnections.get(i);
 
                if (client.getRemoteAddress().equals(remoteAddress)) {
                    this.clientConnections.remove(i);
                    debug("client " + remoteAddress + " was removed");
                    writeToAll(remoteAddress + " has disconnected.");
 
                    return true;
                }
            }
        }
        catch (Exception e) {
            debug("Exception (remove): " + e.getMessage());
        }
 
        return false;
    }
}

Very simple. When we remove a client we tell the others which client has disconnected. Also, it is the ChatServerConnection responsability to break the connection after being removed. We don't do this in the remove method.

In the ChatServerConnection class we simply wait for a client's message. We check to see if it equals "quit" which will be the command to exit from the chat. We also provide a write method to send a message to the client.

Here is the code for the ChatServerConnection class:

public class ChatServerConnection extends Thread {
    protected Socket socket;
    protected BufferedReader socketIn;
    protected PrintWriter socketOut;
    protected ChatServer server;
 
    /**
     * Creates a new instance of ChatServerConnection.
     *
     * @param socket the client's socket connection
     * @param server the server to each the client is connected
     **/
    public ChatServerConnection(Socket socket, ChatServer server) {
        this.socket = socket;
        this.server = server;
    }
 
    /**
     * Waits from messages from the client and then instructs the server to send the messages to all clients.
     */
    public void run() {
        try {
            this.socketIn = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            this.socketOut = new PrintWriter(this.socket.getOutputStream(), true);
            this.server.writeToAll(this.getRemoteAddress() + " has connected.");
            String line = this.socketIn.readLine();
 
            while (line != null) {
                debug("client says '" + line + "'");
 
                // If it's a quit command, we remove the client from the server and exit
                if (line.compareToIgnoreCase("quit") == 0) {
                    if (this.server.remove(this.getRemoteAddress())) {
                        this.finalize();
                        return;
                    }
                }
                this.server.writeToAll(this.getRemoteAddress() + ": " + line);
                line = this.socketIn.readLine();
            }
        }
        catch (Exception e) {
            debug("Exception (run): " + e.getMessage());
        }
    }
 
    /**
     * Sends a message to the connected party.
     *
     * @param msg the message to send
     */
    public void write(String msg) {
        try {
            this.socketOut.write(msg + "u0000");
            this.socketOut.flush();
        }
        catch (Exception e) {
            debug("Exception (write): " + e.getMessage());
        }
    }
}

It's never too much to say that these are only the main methods. For the complete code see the source code.

The server is now done, but we'll spice up our server in the following chapter.

Setting up a GUI and test application

The final step in our server will be to tie everything together and provide a GUI so we can see what's happening. We'll need 2 more classes for this:

  • ChatServerGUI: providing a graphical interface for a more friendly testing experience
  • Main: the main class of the application, responsible for launching both the policy server and also the chat server

The ChatServerGUI is a very simple JFrame with a JLabel providing the number of connected clients and a JTextArea for displaying the debug messages.

I created a TimeTask to update the number of connected clients. This is easy to do but beyond the scope of this tutorial. You can see the source code for how to do this.

The ChatServerGUI only has a method: write. This method is used to write the debugging messages. It's defined as following:

    /**
     * Writes a message to the text area of the form.
     *
     * @param msg the message to be written.
     */
    public void write(String msg) {
        try {
            this.debugTextArea.getDocument().insertString(0, msg + "n", null);
        }
        catch (Exception e) {          
        }
    }

There is not much more to this class. Now to the Main class. This class will launch both the policy and the chat server as well as the GUI.

This is the code for its main method:

    public static void main(String[] args) {
        try {
            int chatPort = 5555;
            int policyPort = chatPort + 1;
 
            for (int i = 0; i < args.length; i++) {
                if (i == 0) {
                    chatPort = Integer.parseInt(args[i]);
                }
 
                if (i == 1) {
                    policyPort = Integer.parseInt(args[i]);
                }
            }
            PolicyServer policyServer = new PolicyServer(policyPort);
            policyServer.start();
 
            ChatServer chatServer = new ChatServer(chatPort);
            chatServer.start();
 
            ChatServerGUI gui = new ChatServerGUI(chatServer);
            gui.setTitle("ChatServer");
            gui.setLocationRelativeTo(null);
            gui.setVisible(true);
        }
        catch (Exception e) {
            debug("Main", "Exception (main)" + e.getMessage());
        }
    }

That's pretty much it.

Testing the chat application

Finally we come to the fun part!

Start by executing the server application. This needs to be the first application running. Only then can the clients connect to it.

Now, our last fix. Because in my case I needed to test the application locally, I had to change Global Security Settings for my Flash player. This shouldn't be needed when running the Flash remotely. We need to give permissions to our Flash file.

The Flash Settings Manager is located on the web. Open your Flash Settings Manager. It should already be in the Global Security Settings tab. Now add a new trusted location for the location of your Flash file.

I know, this is a bummer! Also the message they give on the Global Security Settings don't make sense - we're using the latest features in ActionScript and Flash, not old methods! Finally, start your clients and happy chatting!

Here is a picture of the server running.

Now, a picture of the chat client.

Resources