Author: Steven Webb -
Date: 1-12-06
Modified:
This HOWTO gives a brief introduction on how to create a new architecture for use with NGS. For simplicity, I will demonstrate how to create a Client/Server (C/S) architecture, as everyone will be familiar with it.
Firstly, for NGS to collect the correct metrics we must extend the correct architecture type. The different possibilities are shown in Figure 1.
Figure 1: Different classifications of architectures.
Architectures can be classified as either Centralised (Client/Server, Federated Client/Server, etc), Distributed (P2P) (SimMud, VAST, etc), or hybrid (PP-CA). Distributed architectures can be further classified as neighbour based, region based, or hybrid; however, there are no specific metrics to differentiate these at the time of writing.
As C/S is a centralised architecture we will create a new sub directory in ngs/src/nsg/architecture/centralised called clientserver. All of the files we need to create a listed in Table 1.
ClientServerArchitecture.java | This class constructs all of the nodes that will interact in the simulation. It is responsible for linking them all together, and setting up any architecture specific parameters - in this case, the only architecture specific parameter is the Area of Interest (AoI) for the players. |
Server.java | This class simulates the actions of the Server. |
ServerALR.java | This class specifies and implements the server communication model - it can send messages to any node. |
Client.java | This class simulates the actions of the client nodes. |
ClientALR.java | This class specifies and implements the client communication model - it can only send messages to the server. |
Table 1: Files and descriptions required for the Client/Server architecture.
ClientServerArchitecture is responsible for initialising all of the objects that will be used in the simulation. E.g. construct the server, all of the clients, and link them together. The bulk ClientServerArchitecture.java is in the constructor. As described below:
public ClientServerArchitecture(final double simulationTime, final int numNodes, final boolean aggregate, final double alpha, final int worldHeight, final int worldWidth, final String args[], final MobilityModelFactory factory, final boolean animate) throws Exception { //Just call the super class constructor with the simulation time and should the simulation animate. these //are passed in by a factory object (discussed later). super(simulationTime, animate); //parse the command line (this will just extract the AoI for the players). parseArgs(args); //one array for all nodes. The architecture class that collects all the metrics //will be expecting a single array that stores all nodes - the server and all clients. CentralisedNode nodes[] = new CentralisedNode[numNodes]; //centralised architectures are not limited to one server, so create an array of length 1 for this server. Server servers[] = new Server[1]; //create an array to store all of the clients. Client clients[] = new Client[numNodes-1]; //The two previous arrays (servers and clients) are used by the CentralisedArchitecture class to record specific metrics. //create the server ALR. //this will allow the server to communicate with any node (shown later). ServerALR serverALR = new ServerALR(); //initialise the server, and assign it as the first node in the nodes array. servers[0] = new Server(simulationTime, aoi, (alpha >= 0.0), worldHeight, worldWidth, clients, serverALR); nodes[0] = servers[0]; //create the client ALR - they can all share the same alr in this example. In other architectures it may be required to construct one ALR per node. //note that the client ALR takes in the server, so that all clients can communicate with the server. ClientALR clientALR = new ClientALR(servers[0]); //construct and initialise all of the clients, and add them to the nodes array for (int i=0; i<clients.length; i++) { clients[i] = new Client(servers[0], simulationTime, alpha, aoi, aggregate, factory.create(), clientALR); nodes[i+1] = clients[i]; } //This is the final thing that needs to be done in the constructor. Now that all of the nodes have been initialised they need //to be passed up to the super classes to allow for metrics to be collected. Note that this method must only be called once. //If it is called more than once a logic error has occurred, and an exception will be thrown. setNodes(nodes, servers, clients); }
The server ALR is very basic. As many architecture allow any node to signal any other node, this functionality is implemented as a protect method in the abstract ALR class. To make this public, the server ALR simply has a public messageClient() method that allows the server to send a message to any client.
This class only provides one method, which allows the client to send messages to the server - messageServer().
The client simulates the avatar a player is controlling in the game. This implementation allows dead-reckoning. As resolving dead-reckoning is architecture specific, this is not implemented in the abstract node class.
Every client has four properties:
The constructor is simple, just creating an avatar object and joining the server. Notice that as every method call corresponds to a message, this must be transmitted by the ALR:
super(simulationTime, aggregate); this.server = server; this.alpha = alpha; this.alr = alr; avatar = new Avatar(model, alpha, aoi); //construct the new message Message m = new Message(Message.COMMAND, 0.0, this); //send it to the server. alr.messageServer(m); //actually join server.join(this, 0.0);
The update() method simulates a single frame of the client. Firstly the super class update() method is called to update the statistics. Then the avatar is updated - the mobility model is called to move the avatar - in the case of Client/Server, this only generates a message if the avatar cannot be dead reckoned. When a new message needs to be generated, the message object is constructed and sent to the server using the alr, this simulates sending the new coordinates to the server.
Table 2 gives a description of the trivial methods in the Client class.
Method | Description |
---|---|
getLocation() | returns the location of the avatar controlled by this client. |
withingAoI() | Used to cull nodes that we are no-longer interested in. This is an abstract method in node that is helpful when enabling dead reckoning. Returns true if the opponent is within the client's AoI. |
paint() | Paints the node onto the world. At the moment this just does a dot representing their location, but you could also draw the AoI or any other relevant features (such as the Voronoi diagram in VAST). |
Table 2: Trivial methods in the Client class.
For every message the server receives, the server must broadcast that message out to every interested client - every client who's avatar's AoI overlaps the message originator's avatar. Unfortunately, calculating the distance between the sender and every other node is O(n); hence, if every node generates a message, distributing them is O(n^2), making the server's processing power a bottleneck. This manifests itself by dramatically slowing down the simulation. To remove this problem, the server divides the world up into a logical grid. When an update is received, only nodes within the same cell and surrounding cells need to be compared to determine if they have overlapping AoI, greatly increasing the speed of the simulation.
Each of the significant methods in the Server class will be described in detail.
This just initialises the server with the following steps:
This method is called for the server once per update. It is responsible for ensuring all clients receive the correct updates. The following actions are performed:
This method is responsible for delivering messages to all interested players. It loops through a 3x3 grid of cells surrounding the player that generated the event. For every player within that region it determines if the avatar that generated the message is within their AoI. If it is, the message is forwarded to that node through the alr.
This method is called by clients when they want to join the game. Client's send a message and call join - see client.java. The sends a response message to the client, calculates the cell the client should occupy, and calculates the opponents it should be aware of.
This method finds all of the opponents that a client should be aware of. It cycles through the 3x3 grid of cells surrounding the client's avatar, and adds any that are within its AoI. This is used to update dead reckoning for opponents that the client is aware of, but did not generate updates..
Table 3 summarises the minor methods in the Server class.
Method | Description | |
---|---|---|
receive() | Receives a message from a client and stores it for later processing. Also passes it to the super class. | |
withingAoI() | Returns false as the server does not have an AoI (illogical). Required to implement the abstract method. | |
paint() | Do nothing, as the server does not have a virtual presence. This is required to implement the abstract method. |
Table 3: Trivial methods in the Server class.
To enable ngs to construct the Client/Server architecture we need to modify ngs/architecture/ArchitectureFactory.java:
import ngs.architecture.centralised.clientserver.*;
else if (args[i+1].equals("ClientServer")) { architecture = new ClientServerArchitecture(simulationTime, numNodes, aggregate, alpha, worldHeight, worldWidth, args, factory, animate); }
ngs should now compile into ngs.jar.
To display the command line arguments use:
java -jar ngs.jar -usage
Some typical examples are:
#Client/Server architecture, random walk mobility model, 1000 nodes, simulation time 1000, world width 1000, world height 1000 java -jar ngs.jar -architecture ClientServer -model RandomWalk
#DHT architecture 40,000 nodes, random way point mobility model , world width 6300, world height 6300, simulation time 100 #(use approximately 1.5Gb of memory for the JVM) java -Xmx1500m -jar ngs.jar -numNodes 40000 -architecture DHT -model RandomWayPoint -worldWidth 6300 -worldHeight 6300 -simulationTime 100
Typically I run several simulations on different machines simultaneously. This is achieved using public/private key pairs with ssh to execute commands remotely. I find that for best results redirecting the output into individual files and then using cat to concatenate them together is the most efficient. An example script I source to run this is (note that my home area is network mounted; hence, it is very easy for me to cat all of these files together):
ssh host-1 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 100 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-1" & ssh host-2 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 250 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-2" & ssh host-3 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 500 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-3" & ssh host-4 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 750 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-4" & ssh host-5 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 1000 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-5" & ssh host-6 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 2500 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-6" & ssh host-7 "source $(HOME)/.cshrc; cd pathtongs.jar; java ngs.jar -numNodes 5000 -simulationTime 1000 -architecture ClientServer -model randomWalk > host-7" &
The gnuplot command line allows us to get the gnuplot syntax for all centralised architectures:
>java -jar ngs.jar -architecture ClientServer -gnuplot set style data linespoints set xlabel "Number of players" set ylabel "Bytes per time unit" set key below plot \ "data/results.txt" using 2:3 title "Average Capacity Inbound", \ "data/results.txt" using 2:4 title "Average Capacity Outbound", \ "data/results.txt" using 2:5 title "Maximum Capacity Inbound", \ "data/results.txt" using 2:6 title "Maximum Capacity Outbound", \ "data/results.txt" using 2:7 title "Average Game State Delay", \ "data/results.txt" using 2:8 title "Maximum Game State Delay", \ "data/results.txt" using 2:9 title "Average Game State Delay Range", \ "data/results.txt" using 2:10 title "Maximum Game State Delay Range", \ "data/results.txt" using 2:11 title "Average Server Capacity Inbound", \ "data/results.txt" using 2:12 title "Average Server Capacity Outbound", \ "data/results.txt" using 2:13 title "Maximum Server Capacity Inbound", \ "data/results.txt" using 2:14 title "Maximum Server Capacity Outbound", \ "data/results.txt" using 2:15 title "Average Client Capacity Inbound", \ "data/results.txt" using 2:16 title "Average Client Capacity Outbound", \ "data/results.txt" using 2:17 title "Maximum Client Capacity Inbound", \ "data/results.txt" using 2:18 title "Maximum Client Capacity Outbound", \ "data/results.txt" using 2:0 title "Simulation Time", \ "data/results.txt" using 2:0 title "Memory Used", \
Delete the metrics that are not of interest to produce clear graphs. To output extended postscript files ready for inclusion into a latex document use:
set terminal postscript eps set output "output/results.eps"
If you have any further questions please email me: