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.
![](com.jpg)
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)
- Action: a class which
represents the actions/operations an Agent may make on
the world; see the Acquire Engine Architecture section.
- State: a class which
encapsulates the state of the world as it currently is,
the most useful information, to use as a container to
pass the state between objects; see the Acquire Engine Architecture section.
[Go back to Page 5:
Acquire Engine Architecture | Go on to Page 7: Agent
Architecture]