TcpJLib
 
 

The source code of all examples is included in the TcpJLib distribution.

Example 1: Skeleton of a multi-user game application

For most multi-user games a client-server architecture is established where the server acts as a game manager that "knows" the game rules and handles the registration and deregistration of players. With TcpJLib it is possible to run the game server and game clients even behind a firewall because both of them are implemented as TcpNodes that are interlinked through the tunneling relay server. To simplify the development of multi-user game applications even more, TcpJLib provides the classes TcpBridge and TcpAgent, both of them acting as TcpNodes. Let's explain the concept again by an example with the following simple scenario: Several players connect to a game server that runs with a certain session ID. After connection each player throws two dices and reports the outcome to all other players.

Because the players are linked together using the same session ID as the game server, each group uses its own game server. The more general case, where a single game server manages several groups is shown in example 3.

Unlike TcpNode, both TcpBridge and TcpAgent are not fully event-driven. This simplifies the connection process to some extend. To connect to the relay server the method connectToRelay() is called that blocks until the connection is established or the timeout expires. The method returns a list of the attached nodes including the connecting node (eventually with a trailing (n) if a name clash happened). If the connection fails because the timeout expires, the list is empty.

There is another major difference between a TcpNode and a TcpBrigde/TcpAgent : Typically data is not transfered with ASCII strings but with integer arrays. This simplifies the transmission of game state data. All data sent by an agent is caught by the bridge and passed in "pipe", where the TcpBridge developer decides what to do. In our exemple incoming data is just forwarded to all connected agents without any modification. New users are stored in a player list and when a player quits, the name is is removed from the list. (Because all callback methods run in the same thread, there is no need for synchronizing list operations.)

First comes the game server. For convenience we use again a Console window from the package ch.aplu.util. To avoid interference between different users, we start the application by asking for a unique session ID.

import ch.aplu.tcp.*;
import java.util.*;
import ch.aplu.util.*;

public class GameServer extends TcpBridge
{
  private static String sessionID;
  private final static String myname = "Kathrin";
  private ArrayList<String> gamers = new ArrayList<String>();
  private static Console c = new Console();

  static
  {
    c.print("Enter a unique session ID: ");
    sessionID = c.readLine();
  }

  public GameServer()
  {
    super(sessionID, myname);
    addTcpBridgeListener(new TcpBridgeAdapter()
    {
      public void pipeRequest(String source, String destination, int[] data)
      {
        for (String gamer : gamers)
        {
          if (!gamer.equals(source))  // Do not echo
          {
            c.println("pipe: "+ source +"--->"+ gamer);
            send(source, gamer, data);
          }
        }
      }

      public void notifyAgentConnection(String agentName, boolean connected)
      {
        if (connected)
        {
          c.println("Gamer "+ agentName +" connected.");
          gamers.add(agentName);
        }
        else
        {
          c.println("Gamer "+ agentName +" disconnected.");
          gamers.remove(agentName);
        }
      }
    });
    c.print("Trying to connect to relay "+ getRelay() +"...");
    if (connectToRelay(5000).isEmpty())
    {
      c.println("Failed");
      System.exit(1);
    }
    else
      c.println("OK\nReady for service.");
  }

  public static void main(String[] args)
  {
    new GameServer();
  }
}

Execute the program locally using WebStart.

The game client derives from TcpAgent and first asks for the session ID and the player's name. After successful connection the program waits in an endless loop for key strokes. When a key is hit, two random numbers between 1 and 6 are created and sent to the bridge with an empty destination indication. The data received from the bridge is displayed in the console window by overriding the TcpAgentAdapter's dataReceived() callback.

import java.util.*;
import ch.aplu.tcp.*;
import ch.aplu.util.*;

public class GameClient extends TcpAgent
{
  private final static String server = "Kathrin";
  private final static Console c = new Console();
  private static String sessionID;

  static
  {
    c.print("Enter a unique session ID: ");
    sessionID = c.readLine();
  }

  public GameClient()
  {
    super(sessionID, server);
    c.print("What is your name? ");
    String source = c.readLine();
    addTcpAgentListener(new TcpAgentAdapter()
    {
      public void dataReceived(String source, int[] data)
      {
        c.println("Dice data received from " + source + ":");
        for (int z : data)
          c.println(z);
      }
    });
    c.print(source + " connecting to relay " + getRelay() + "...");
    if (connectToRelay(source, 6000).isEmpty())
    {
      c.println("Failed");
      return;
    }
    c.println("OK");
    while (true)
    {
      c.println("Press any key to throw 2 dices...");
      c.getKeyWait();
      Random r = new Random();
      int[] data =
      {
        1 + r.nextInt(6), 1 + r.nextInt(6)
      };
      send("", data);
      c.println("Dice data sent: ");
      for (int z : data)
        c.println(z);
    }
  }

  public static void main(String[] args)
  {
    new GameClient();
  }
}

Execute the program locally using WebStart.

Make sure, that the game server is started before. Run as many client program instances as you want, but select the same session ID as the server. By implementing the TcpAgentListener's notifyAgentConnection() callback you could be informed about log-in and log-out of players.