Page 6: Communications Superstructure
Contributed by Michael Czajkowski

 

[Go back to Page 5: Acquire Engine Architecture | Go on to Page 7: Agent Architecture]

 

1          Overview

 

The Communications System provides the backbone for the Agents, Engines, and the User Controller to communicate with one another. This subsystem is the key component of the entire system, all information between Agents and the Engines is done here.

This system is written in Java, and takes advantage of the networking features in Java to enable communication. Notably, communication is done through a series of clients connecting to a server. 

The Engine of the system is the server, and it accepts connections from the Agents and the User Controller. While it would seem that the User Controller would make more sense to be the server, it was designed this way because:

1.) The majority of information goes through the Engine.
2.) Agents have private Engines, which have to be servers in order
to accept connections between the Agent and the server.

Therefore, an Agent and the User Controller connect to the game server, and register themselves. After this point they can begin playing/monitoring the game as underlined in the design document.

 

2    The Communications Protocol

 

Of course, for communication to happen there has to be a communication protocol in which Agents, Engines, and the user controller can talk to one another. Ours is quite simple indeed. The Protocol focusses on a one-line command sequence as follows:

 

COMMAND:(to):(from):(message):END

 

Every LISP program that was written (the Agent, Engine, and User Controller) prints out these messages to the LISP interpreter. The object below, LispReader, is constantly looking for a line that looks like the above. When it receives this line, the line is parsed and the Superstructure sends the line to the Agent or Engine it is supposed to go to.

 

3    The Class Structure

 

The class structure is somewhat complex, but not too foreboding. Here is a UML diagram of the entire system.

 

Figure 3.1 The Class Structure

 

Each of these classes and their roles are defined below. The classes in this structure allow the Agents, Engines, and the User Controller to talk to one another, with the ServerConnector object acting as the server where client AgentConnector and UserControllerConnector objects connect to the ServerConnector. 

 

3  Classes: The Client/Server Connection objects.

 

We will begin by describing the objects that act as the client and server. These objects all have a built-in LISP process which the Engine, User Controller, and Agent codes can run on. The server object is the ServerConnector, the others are the clients which connect to the server object. These objects are invoked by the (see part 5 of this page) User-defined objects. 

Connector
The Connector class is an abstract class that sets up a LISP process. All parts of the system use LISP, so it made sense from a design standpoint to incorporate the lisp process into the super class. Therefore, all subclasses have a lisp process which they can run their interpreter on.

Abstract Connector

lisp_process_
from_lisp_process_
to_lisp_process_
lisp_writer_
lisp_reader_
Constructor(String)
+ handleLispCommand(String)
+ handleNetCommand(SocketHandler, String)
+ sendLispCommand(String)
+ killLispProcess()
% finalize()
- parseCommand(String)
Fields:
* lisp_process_ :: This is a process object which represents LISP running on the machine.
* from_lisp_process_ :: This is a stream in which we read commands from LISP.
* to_lisp_process_ :: This is a stream in which we can write commands to LISP.
* lisp_reader_ :: This is an object that manages the reading stream. It is a thread.
* lisp_writer_ :: This is an object that allows us to write commands to LISP.


Methods:
* Constructor(String) :: The constructor takes the location of the lisp process that will begin, in most systems it is invoked with "lisp". Because there are many varieties of LISP in the world, and we wanted to allow the user to use any lisp installed on the system, the String is necessary. The constructor creates a LispWriter and LispReader object which will keep track of reading and writing to the Lisp process.
* Public abstract handleLispCommand(String) :: This function will handle commands from Lisp, when implemented by the subclasses. The String is the command which will be handled.
* Public abstract handleNetCommand(SocketHandler, String) :: This function will handle commands from the network, meaning the String is a command sent to this Connector object and has to be parsed and dealt with.
* Public sendLispCommand(String) :: This function takes the String and sends it to the LispWriter object as a command to be written to lisp. This is how a command is sent into LISP.
* Public killLispProcess() :: This function kills off the LISP process running on the system. Since the LispReader is a thread waiting for commands continuously, the function tells the thread to stop running.
* Protected finalize() :: This method was put in so that in the event the Connector is told to destroy itself, it will not leave a hanging LISP process. Inside this method, killLispProcess() is called.
* Private parseCommand(String) :: This method helps in the handling of the commands from the network. See section II. The Protocol for Communication. This method takes in a "COMMAND:TO:FROM:(message):END" command and then returns a Vector containing who the message is to, from, and what that message is. If it is an invalid message then the command is not sent to the LISP process, it is filtered out in the handleNetCommand method of the Connector object.

ServerConnector

This object is a subclass of the Connector object, it essentially is the Server for the entire system. It uses the LISP features of the Connector object to run the Engine code, which is where the game is played. AgentConnector, and UserConnector objects can connect to the ServerConnector object.

ServerConnector

server_socket_
server_socket_handler_
user_controller_
controlled_
players_
connections_
id_counter_
max_player_count_
server_type_
+ Constructor (String, int, String)
+ broadcastToAll(String)
+ handleLispCommand(String)
+ handleNewConnection(Socket)
+ handleNetCommand (SocketHandler, String)
+ killServerConnection()
- handlePlayerRegistration (SocketHandler)
Fields:
* server_socket_ :: This is a Socket object which handles the incoming commands from the Agents, or User Controller.
* server_socket_handler_ :: This is the object which waits for new connections to come in.
* user_controller_ :: This is a SocketHandler object which handles communication to the User Controller.
* controlled_ :: This is a boolean variable saying whether or not there is a User Controller for this Engine.
* players_ :: This is a vector which contains a list of all the playing Agents.
* connections_ :: This is a vector that contains a list of all connections, in or out of the game.
* id_counter_ :: This is an integer that gives us new IDs for the next connecting Agent.
* max_player_count_ :: Default at 6, this tells us how many max Agents can connect.
* server_type_ :: This is a string, default "REFEREE" saying this is a referee Engine.


Methods:
* Constructor(String, int, String) :: This constructor takes in the name of the machine we're running the server on, the port to open up to, and the location of the LISP to be run.
* Public broadcastToAll(String) :: This method takes the passed String and sends it out to all of the connections to the ServerConnector.
* Public handleLispCommand(String) :: This defines the abstract method in the superclass, all commands being sent from the Engine's LISP come through this method and then out into the network where they belong.
* Public handleNewConnection(Socket) :: This takes a socket object that has just connected to the server and handles it by either making it a player or a User Controller, or neither.
* Public handleNetCommand(SocketHandler, String) :: This defines the abstract method in the superclass, all commands coming in from the network to the Engine LISP process go through here. First they are parsed and then passed on to the LISP process if found to be valid.
* Public handleServerConnection() :: This kills this object in an effective manner so no sockets are dangling or LISP processes unfinished. It sends a termination call to all the Agents saying that the server is going to shut down.
* Private handlePlayerRegistration(SocketHandler) :: If a new connection to the server is a player we have to handle it by seeing if the game hasn't started, or that there is room. This method deals with all of that.

AgentConnector

The AgentConnector class serves as a client to the Referee Engine. It therefore needs 2 SocketHandlers. The AgentConnector houses the LISP process which the Agent code is running on, and allows this Agent to send moves and receive messages from either Engine.

AgentConnector

game_socket_
private_socket_
game_socket_handler_
private_socket_handler_
+ Constructor(String, String, int, int, String)
+ sendCommandToGameServer(String)
+ sendCommandToPrivateServer(String)
+ handleLispCommand(String)
+ handleNetCommand(SocketHandler, String)
+ killAgentConnection()
Fields:
* game_socket_ :: This is a Socket object that goes to the Referee Engine.
* private_socket_ :: This is a Socket object that goes to the Players Engine.
* game_socket_handler_ :: This SocketHandler object deals with sending and receiving messages from the Referee Engine.

 
Methods:
* Constructor(String, String, int, int, String) :: This constructor takes the location of the Referee Engine, the ports on which they run, and the location of the LISP process that will be run for the Agent.
* Public sendCommandToGameServer(String) :: This method sends the String command to the Referee Engine. It is in the format described above in part 2.
* Public handleLispCommand(String) :: This defines the abstract method in the superclass, all commands being sent from the Agent's LISP come through this method and then out into the network where they belong.
* Public handleNetCommand (SocketHandler, String) :: This defines the abstract method in the superclass, all commands coming in from the network to the Agent LISP process go through here. First they are parsed and then passed on to the LISP process if found to be valid.
* Public killAgentConnection() :: This method kills the Agent's connection, process, and other assorted running threads cleanly. This is done on system destruction.

UserControllerConnector

The UserControllerConnector object acts very much the same to the AgentConnector class. However, the UserControllerConnector class only needs to have one socket, to the game server which it is being the user controller for. The UserControllerConnector allows a LISP process which is running the User Controller code to maintain the system.

UserControllerConnector

game_socket_
game_socket_handler_
+ Constructor(String, int, int, int, String)
+ sendCommandToGameServer(String)
+ handleLispCommand(String)
+ handleNetCommand(SocketHandler, String)
+ killUserControllerConnector()
Fields:
* game_socket_ :: This is a Socket object that connects this object to the Engine server.
* game_socket_handler_ :: This is a SocketHandler object that handles the above socket.


Methods:
* Constructor(String, int, int, int, String) :: This constructor takes in the location of the game server, the port, the number of games, the number of Agents, and the name of the LISP process which the user controller runs on.
* sendCommandToGameServer(String) :: This method sends a command to the game server which the User Controller is connected to.
* handleLispCommand(String) :: This defines the abstract method in the superclass, all commands being sent from the User Controller's LISP come through this method and then out into the network where they belong.
* handleNetCommand (SocketHandler, String) :: This defines the abstract method in the superclass, all commands coming in from the network to the User Controller LISP process go through here. First they are parsed and then passed on to the LISP process if found to be valid.
* killUserControllerConnector() :: This method kills the UserController's game server and all of its Agents cleanly. This includes the LISP process which the User Controller code is running on.

4  Classes: The Threaded Objects

These objects are associated with the Thread class in java. Almost all of them are threads. They basically maintain a watchful eye on a socket or a process (LISP or other) and wait until commands are sent from that process/socket. When a command has been sent from the process/socket, they essentially tell the objects in part 3 above that a command was sent in and now it has to be dealt with (to find out who to send it to).

LispReader

The LispReader class essentially allows a Connector object to read in commands from LISP. The Reader essentially calls the handleLispCommand on the objects above whenever the LISP process has said something to an Agent, server, or User Controller. This object is a thread, and is run as a thread.

LispReader

connection_
lisp_stream_
listening_
+ Constructor(Connector, BufferedReader)
+ run()
+ killLispReader()
Fields:
* connection_ :: This is the Connector object which this reader is associated to.
* lisp_stream_ :: This is the input stream from the LISP process.
* listening_ :: This is a boolean variable, indicates whether or not we are listening to the stream at the moment. Useful on startup and closure of the Reader.

Methods:
* Public Constructor(Connector, BufferedReader) :: The constructor takes in the Connector object which makes the reader, and the stream which we will be reading in from.
* Public run() :: This is the run method for the thread. It will do the waiting until the LISP process has something to say.
* Public killLispReader() :: This method kills the LISP reader stream making sure there is no more reading coming from a dead stream.

LispWriter

This class is not a thread, but it does deal with the LISP Process. It allows the objects in part 3 to write to the LISP process commands that will be used as method calls in LISP.

LispWriter

connection_
lisp_stream_
+ Constructor(Connector, BufferedWriter)
+ sendCommand(String)
Fields:
* connection_ :: This is the Connector object which this reader is associated to.
* lisp_stream_ :: This is the output stream to the LISP process.


Methods:
*
Public Constructor(Connector,BufferedReader) :: The constructor takes in the Connector object which makes the writer, and the stream which we will be writing out to the LISP process.
* Public sendCommand(String) :: This method sends the String parameter as a method call to LISP.

SocketHandler

This is the biggest thread object. The SocketHandler class essentially allows one of the objects in part 3 to write to a Socket. Each SocketHandler essentially controls one java Socket object. Commands coming in from the socket have to be dealt with as well, so it is a threaded object which waits for input from the network.

SocketHandler

socket_
connector_
net_input_stream_
net_output_stream_
listening_
connector_type_
+ Constructor(Socket, Connector, BufferedReader, BufferedWriter, String)
+ sendCommand(String)
+ getConnectionType()
+ run()
+ killSocketHandler()
Fields:

* socket_ :: This is a Socket object which this class is handling.
* connector_ :: This is the Connector object which made this socket.
* net_input_stream_ :: The stream we are reading commands in from the network.
* net_output_stream :: The stream we are writing commands to the network.
* listening_ :: This is a boolean variable, indicates whether or not we are listening to the stream at the moment. Useful on startup and closure of the handler.
* connection_type_ :: A string telling what kind of socket we are handling, the options are: User Controller, Game Server, Private Server, or Agent ID number.


Methods:
* Constructor(Socket, Connector, BufferedReader, BufferedWriter, String) :: This constructor takes the socket we are managing, the Connector object which made the socket, the Buffered Reader and Writer we will read and write to the network, and the Connector Type, which is either a User Controller, Game Server, Private Server, or Agent ID number.
* Public sendCommand(String) :: This method sends the String parameter as a command through the socket.
* Public getConnectionType() :: This method gives back the connection type.
* Public run() :: This method runs the listening part of the socket, it will listen for commands to come through the socket and then call the handleNetCommand in the Connection object.
* Public killSocketHandler() :: This method cleanly kills the socket so that no stream is left reading or writing to a dead Socket.

ServerSocketHandler

Like the SocketHandler, this class essentially deals with the network. Unlike the SocketHandler, it has only one purpose: To handle incoming connections to the server. Since the server is responsible for taking in new connections, this thread runs continuously waiting for the new connections to come in from Agents, and the User Controller.

ServerSocketHandler

socket_
connector_
listening_
+ Constructor(ServerSocket,Connector)
+ run()
+ killServerSocketHandler()
Fields:
* socket_ :: This is a ServerSocket object which will be dealing with the incoming connections.
* connector_ :: This is the Connector object which made the ServerSocketHandler.
* listaning_ :: This is a boolean variable which indicates whether or not we are currently listening for connections. Useful on startup and closure of object.


Methods:
* Constructor(Socket, Connector) :: This constructor takes the ServerSocket object and the Connector which made this object.
* Public run() :: This is the thread's run command. It runs continuously while waiting for new connections to come into the server. These connections can come from Agents, or the user controller.
* Public killServerSocketHandler() :: This method allows us to cleanly kill the handler so we are no longer waiting for connections and reading in from a dead Socket. 

LocalProcessHandler

This object handles local processes on the machine. It is used primarily when we invoke the entire system using the LocalSystem class which is mentioned below. This handler makes sure all the java processes run OK and that they can close when the system dies. It is run as a thread so we can handle incoming messages from that process.

LocalProcessHandler

input_stream_
output_stream_
this_process_
Constructor(Process)
run()
Fields:
* input_stream_ :: This is the stream which we will gather information in from the process.
* output_stream_ :: This is the stream which we will write information out to the process.
* this_process_ :: A java Process object which represents this process.


Methods:
* Constructor(Process) :: This takes in a process which we will be monitoring with the thread this object represents.
* run() :: This is the thread's run command. It is primarily used to handle the closure of the process. The problem is not starting up the Agents, server, and User Controller, but rather the killing of them such that the process dies properly and does not clog up the system. The thread waits for a destruction message and then kills itself.

5 Classes: The User-Defined Objects. 

The User-Defined objects are essentially the ways you as the user can begin an Agent, server, or User Controller. There are four classes. The Agent, Server, and UserController classes start one of these processes respectively. The other, LocalSystem, is a way to begin a bunch of Agents, Engines, and a User Controller all on ONE system without having to worry about all the networking problems of setting up all the Agent windows, server windows.. etc.

LocalSystem

The LocalSystem allows you to start a whole acquire system up on one machine without having to worry about connecting Agents from different machines, servers on different machines, or the User Controller on a different machine. Since it is all done on one machine, the speed of the machine matters on how quickly you will get results, whereas in starting up the processes individually you can pick and choose which machines get Agents, servers, or the User Controller.

LocalSystem

 
+ static void main(String[])
Fields:
No fields.


Methods:
* static void main(String[]) :: This is the way to invoke the entire local system. The arguments are:
1 -- The local machine name.
2 -- Starting port range number.
3 -- Server's lisp
4 -- Agent's lisp
5 -- User Controllers's lisp
6 -- Number of Agents.
7 -- Number of games.
The main function essentially sets up all the Agents, servers, and the User Controller. It calls the sequence of connecting in the right order so that everything works fine.

Server

The Server object allows you to start a Server process, the LISP running the Engine, and the waiting for new connections to occur. Servers must be started first, before any of the other components of the system (Agents, the User Controller) can start.

Server

 
+ static void main(String[])
Fields:
* No fields.


Methods:
* static void main(String[]) :: This is the way to invoke a server object. The arguments are:
1 -- This machine's DNS name.
2 -- The port this server will go up on.
3 -- The location of the lisp command on the system.
The main function starts up the server and allows it to wait for connections.

UserController

The UserController object allows you to start up a UserController, the LISP running the code. The UserController connects to the server you specify and becomes the User Controller for that server. This must happen before the Agents connect since the User Controller knows how many Agents belong in a game and how many games will be played.

UserController

 
+ static void main(String[])
Fields:
* No fields.


Methods:
* static void main(String[]) :: This is the way to invoke the UserController object. The arguments are: 
1 -- The DNS name of the game server.
2 -- The port of the game server.
3 -- Number of Agents.
4 -- Number of Games.
5 -- The location of LISP on the machine.
The UserController starts by sending a message to the Server that it is taking over as the UserController for the system. Then it sets the number of Agents and games to the Engine's LISP. Once that many Agents have connected, it starts playing.

Agent

The Agent object allows you to start up an Agent which will play the game on the Referee's Engine. The Agent connects to the ServerConnector object first. In turn, the ServerConnector sends the Agent an ID number. Then the Agent registers with the Engine's LISP as being that ID number and requests entry to the game. Once it has entered the game, then it can begin playing as that ID.

Agent

 
+ static void main(String[])
Fields:
* No fields


Methods:
* static void main(String[]) :: This is the way to invoke the Agent that is going to be playing the game. The arguments are:
1 -- This machine's DNS name.
2 -- The port this Machine's private server is on.
3 -- The DNS name of the game server.
4 -- The port which the game server is on.
5 -- The location of LISP on the system.
The Agent starts by connecting to the ServerConnector through the ServerSocketHandler. It gets an ID number from the ServerConnector, and then registers on the Referee Engine as being that ID.

 

6  Supporting Classes: (defined elsewhere)


[Go back to Page 5: Acquire Engine Architecture | Go on to Page 7: Agent Architecture]