Network

The Network class makes it possible for you program to have simple String-based communication with copies of itself running on other computers. In general, a network-enabled ObjectDraw program will have a WindowController subclass that implements the ClientListener interface and draws things on the screen in response to server messages while sending messages to the server in response to local mouse and keyboard events. The program may optionally also have a class implementing the ServerListener interface to customize how the server behaves.

Because networking is done using Strings, a good understanding of how to parse a string and extract numerical data from it is necessary if you want to be able to communicate anything more than just text information.

Table of Contents

1. Configuring the network

It is only possible to use the networking feature of ObjectDraw in a Java application, not an applet. (Actually, applets can make client connections, but only to the web server they were loaded from) If you are used to running WindowController programs as applets, it's not hard to make them into applications instead: simply write a main() method that will create an instance of your program and tell it to startController() with the desired size and title:

public static void main(String[] args) {
    new MyProgramClass().startController(400, 400, "My Program");
}

In order to allow your program to connect over the network, you first need to inform the Network class which object you would like to act as the client on this computer, and which object you would like to act as the server in case the user decides to host a game. You do this by calling the method Network.configure():

public void configure(String gameIdentifier, ServerListener server, ClientListener client)

The "game identifier" here should be a name that uniquely identifies your particular program. In choosing a server to connect to, the user will only be shown those servers running a game of the same name. This prevents you from accidentally connecting to a server running someone else's program.

As soon as you have called configure(), a new "Network" menu should appear in the menu bar for all ObjectDraw windows. It provides you with the option to either host a new network game, or join an existing one. When either option is selected, the listeners you specified in configure() will be notified that a connection has been made.

2. Writing a client listener

Usually your main WindowController class will be the one that implements ClientListener. This allows you to easily draw on the screen in response to network messages, and send messages to the server in response to mouse and keyboard events.

Here is a simple client program that will simply count how many messages it receives, representing each with a dot on the screen. Since it sends only one message, when it has first connected, the dots give a count of how many clients have connected since this one:

public class ClientCounter extends WindowController implements ClientListener {
    Location point;
    
    public void begin() {
        point = new Location(10, 10);
        Network.configure("ClientCounter", ServerListener.ECHO, this);
    }
    
    public void messageReceived(Client client, String message) {
        new FilledOval(point, 20, 20, canvas);
        point.translate(30, 0);
    }
    
    public void clientConnected(Client client) {
        client.sendMessage("I'm here!";
    }
    
public void clientDisconnected(Client client) { }
    
    public static void main(String[] args) {
        new ClientCounter().startController(600, 40, "ClientCounter");
    }
}

Usually, when the client first connects you will want to store the Client object in an instance variable so that you can later send messages through it when necessary. If you use ServerListener.ECHO as your server listener, sending a message through the Client will send it to all other clients on the network.

3. Writing a server listener

If merely broadcasting each client's messages to all clients is not the behavior you need for the server, you will have to write your own server listener. Usually, this will be a separate, new class whose only job is to run the server. Here is an example of a server listener that receives messages from clients and sends them out with the client's name attached:

public class ChatServer implements ServerListener {
    public void messageReceived(Server server, int channelID, String channelName, String message) {
        server.sendMessage(channelName + ": " + message;
    }
public void serverStarted(Server server) { }
public void serverStopped(Server server) { }
public void channelClosed(Server server, int channelID, String channelName) { }
public void channelOpened(Server server, int channelID, String channelName) { }
}

If starting the server takes a lot of memory or processor time, it is a good idea not to set up the server until serverStarted() is called. So, for example, if you want to have an array big enough to hold ten thousand clients, just don't create it until you know you're really a server. Remember, every copy of your program will have an instance of your ServerListener class, but it will only be asked to handle messages if the user on that computer chooses to host a game.

In addition to the Server's sendMessage() method that you see above, which sends a message to all connected clients, there is a sendMessage() method that takes a client ID number and sends only to that particular client. This can be very useful when that client has just logged on and you need to negotiate with it a bit to get it ready to join the game. The client ID numbers count up from 0 and are never recycled, even if a client logs out; so, you can be sure that that client number always refers to a particular client.

4. Encoding and decoding Strings

All messages sent over the server need to be in String format. If you are used to reading and writing files in Java, this will be easy for you to handle, but otherwise, it is probably a challenge.

What I advise you to do is to decide on a format where the individual numbers you want to send are separated by a particular character. So, for example, if I want to send a Location object, I could encode it as two numbers with a comma between them:

public String encodeLocation(Location loc) {
    return loc.getX() + "," + loc.getY();
}

When decoding this, the comma provides a key to where to break up the string. I start out by locating what index in the string the comma appears at; then, I break off the parts of the string before and after that comma and parse each into an integer:

public Location decodeLocation(String message) {
    int commaIndex = message.indexOf(",");
    int x = Integer.parseInt(message.substring(0, commaIndex));
    int y = Integer.parseInt(message.substring(commaIndex + 1));
    return new Location(x, y);
}

This same technique can be used when you have a list of numbers or objects to encode and you do not know the length. Suppose that I want to encode an array of Location objects, separating them with semicolons:

public String encodeLocations(Location[] locations) {
    String message = "";
    for(int i = 0; i < locations.length; i++) {
        message += encodeLocation(locations<[>i<]>) + ";";
    }
    return message;
}

The trailing ";" this will produce is not a problem if we break up the message on the receiving end by using split():

public Location[] decodeLocations(String message) {
    String[] strings = message.split(";");
    Location[] locations = new Location<[>strings.length<]>;
    for(int i = 0; i < locations.length; i++) {
        locations[i] = decodeLocation(strings[i]);
    }
    return locations;
}

The split() method takes as its argument a regular expression, not just a String. You can read about regular expressions elsewhere if you really want to, but what this means for this application is that your string separator should not be one of these characters: *+?.^$()[]{}|

There are some lessons in my AP CS class about parsing strings that might be useful to you; there are also lots of resources about this online.

If you want to see some more detailed examples of networking, take a look at my Creating Network Games with ObjectDraw seminar.

5. Listing of useful networking methods

In the Network class:

public static void configure(String gameIdentifier, ServerListener server, ClientListener client)

ClientListener methods (methods you need to implement to be a client listener):

public void messageReceived(Client client, String message)
public void clientConnected(Client client)
public void clientDisconnected(Client client)

ServerListener methods (methods you need to implement to be a server listener):

public void messageReceived(Server server, int channelID, String channelName, String message)
public void serverStarted(Server server)
public void serverStopped(Server server)
public void channelClosed(Server server, int channelID, String channelName)
public void channelOpened(Server server, int channelID, String channelName)

In the Client class:

public void sendMessage(String message)

In the Server class:

public void sendMessage(String message)
public void sendMessage(String message, int channelID)

Complete Javadoc documentation for these classes: