Communication
The communication package holds objects that facilitate network communication
between clients and servers.
Client/Server Model
Current
Figure 1. The PITA Client/Server Model
In short, a client is a user who runs a client program and contacts the
main PITA server--the
UniversityAcceptDaemon (UAD). The UAD
immediately passes the communication link (or "passes the client," so to speak)
to the AuthenticateDaemon, which registers
the client. After authentication, the AuthenticateDaemon begins the client's
session by passing the client to the appropriate
dedicated session daemon (or DSD); different
kinds of DSD's exist for different client needs. When a client
registers to start a session, he or she registers to play a certain
"role," which determines what services the client
needs and therefore which DSD will serve the client.
Figure 2. Multi-User, Multi-Role Model
History
The current client/server model was developed through a number of stages,
each stage representing a different factor pushing the project in a different
direction. The client portion of the project underwent few changes; however,
the server portion changed significantly during development.
Object Oriented Java Development:
- Base Case
- In the simplest base case for a client/server model
a single daemon process/program serves a single client process/program.
- Multi-User Model
- Using sockets for a multi-user communication model means
implementing a daemon to continually accept socket connections.
Because this daemon runs a continual accept-loop, it cannot also serve
a client session; therefore, each time it accepts a socket connection
it starts a copy of a second kind of daemon that is dedicated to serve
that single connection.
- Secure Multi-User Model
- The main accepting daemon for the project listens to a network port
waiting for connections, which means that it is open to the world and to
anyone who wants to access it. Different clients, however, should be
allowed access to different kinds of information (e.g. a professor to
test answers, an administrator to raw database information, a student
only to test questions). For this reason, the daemons are responsible
for enforcing security. The main accepting daemon--or a delegated
helper--must determine what kind of client is connecting, then verify
that registration in a local authentication database, and lastly pass the
client to a dedicated daemon capable of handling that client's session
needs. A single dedicated session daemon could be built to service all
possible needs of all clients, but at the time of each request for
service the dedicated session daemon would have to verify that that
client was allowed access to that service. A better way to implement
dedicated session daemons is to connect each client to a daemon that is
capable only of the tasks allowed to that type of client. Any
similarities between different types of clients are shared by inheriting
different dedicated session daemons from common base classes.
Interesting Note: As will be described later, clients send an
object called a Request that asks for a particular
service from a dedicated session daemon. An object oriented programmer might
be tempted to build a method into the Request class called something like
"Request.serve()" which allows the Request to be received by the daemon and
then serve itself; after all, Requests are already specific to services.
According to this model, a dedicated session daemon would simply receive
Requests and then invoke Request.serve(). Because of security issues, however,
this model is impossible because security would then be the responsiblity of
the client sending the Request instead of the daemon receiving the Request.
The only conceptual change in the client side of the project was a change
from three client programs (to represent the three current client roles)
to a single client program (that can function for any of the roles). The first
model is not necessary because the client programs are not responsible for
security.
SocketGateways
SocketGateways put a "programmer-friendly" interface on
socket communication, providing send and receive methods that appear to
communicate not Strings--the typical unit of communication over a Socket--
but rather any object that implements the
SocketSendable interface. The typical process
starts when a SocketGateway converts a SocketSendable object to a String
(encrypted for secure communication) using the SocketSendable object's own
conversion methods. After the conversion, the SocketGateway sends the string
across the network to a receiving SocketGateway object, which re-creates the
object in a skeleton form, and uses the new object's conversion methods to
flesh it out with data from the string.
A SocketGateway is initialized with a Socket, from which it gets an input
stream for inbound messages and an output stream for outbound messages.
Some of the important methods of SocketGateways are listed below.
public void send (SocketSendable)
- Given a SocketSendable object, this method sends it over the Socket.
It prints to the output stream the full class name of the SocketSendable
object (see documentation on Class.getName()),
a character separator, and the String representing the object (generated
by invoking a conversion method of SocketSendable).
public SocketSendable receive (SocketSendable)
- A user can safely receive a SocketSendable object over the network by
calling this method with an empty object of the type expected. The empty
SocketSendable object passed to the function allows for run-time error
checking--the class name of the expected object is compared with the
class name of the received object. The fields of the empty object are
filled by invoking the fromSocketString() conversion method of
SocketSendable, and the object is returned to the user. A
typical invocation of this method might look like this, if class MyClass
implements the SocketSendable interface:
MyClass r = (MyClass) sock_gate.receive (new MyClass());
public SocketSendable blindReceive ()
- This method performs the same task as the receive() method but does
not require an expected object; rather, it "blindly" receives whatever
SocketSendable object comes over the Socket, and returns it to the user.
public SocketSendable hazyReceive (Class)
- This method is a compromise between a receive() and a blindReceive().
It receives a SocketSendable object over the Socket, and then compares
its class to the parameter Class. If the received object's class is one
that can be cast to the expected Class, then the SocketSendable object
is returned; otherwise, an exception is thrown.
If a SocketGateway ever receives a String from which it can't generate a
SocketSendable object, it throws a BadMessageException. If the
SocketGateway finds itself unable to use the Socket streams at all, it throws
a BrokenConnectionException.
SocketSendable Objects
SocketSendable objects are objects that may be sent and received by
SocketGateways to allow easy and secure
communication over a network using Sockets. The SocketSendable interface
requires two methods: one to write the data fields of the object to a String,
and one to read the data fields of the object from a String. (Note: Because of
the way that SocketGateways send SocketSendable objects over Sockets, the
conversion String should never contain a newline character.) Some important
SocketSendable objects are listed below.
- SocketSendableObject
- The generic implementer of the SocketSendable interface is--what else?--
SocketSendableObject, which provides a number of helpful methods and
constants. The functionality is static and public in case an object
needs it but must extend a different object.
- Name
- Name is a base class for SocketSendable units of information called
"names." A few examples of names are DepartmentName, CourseName, TestName,
and PersonName; as a group, names hold information concerning the
university.
- ID
- An ID uniquely identifies a single Name within a context of a number
of other Names. For instance, a CourseID consists of the name of a certain
course--like "101"--within the context of a DepartmentName (like "MATH")
and a CollegeName (like "Calvin College"). ID is an abstract base class.
- Request
- Requests are used by a client to ask for a certain service
from a dedicated session daemon. Different
clients (distinguished by Roles) are allowed to make
different Requests, regulated by which daemon each client is attached
to--certain daemons can serve certain Requests. An example of a Request
would be a request to get the list of students registered in a certain class;
a second example would be a request to get the next question of a test.
- Role
- A Role distinguishes different kinds of clients; each client has a role
which determines what dedicated session daemon services the client may
access. Currently, there are three roles: Administrator, Creator, and Taker.
An Administrator is responsible for modifying stored information about the
university: for instance, what classes should be listed under a certain
department. A Creator is responsible for creating, editing, and giving
tests. A Taker is reponsible only for taking tests. The three Roles of
Administrator, Creator, and Taker roughly correspond to the duties of a
university registrar, professor, and student.
- Token
- A Token is a SocketSendable object provided for communication of specific
non-data messages, used when no other substitute seems to make sense. For
instance, a daemon may wish to inform a client that its login registration
has been refused, but it hardly seems appropriate to send a Name or a Role
or some other totally new unique SocketSendable object; rather, the daemon
just sends a Token that contains a code meaning "Login Registration Refused."
- SocketSendableList
- SocketSendableLists provide a means to send a whole list of
SocketSendable objects at once, appropriate to
the fact that much of the background information that a client must learn
from a daemon consists of lists--whether the list of departments in the
university, or the list of currently offered tests, or something else. A
SocketSendableList extends the java.util Vector class to hold the
SocketSendable objects, and but is itself SocketSendable. There are
currently two kinds of SocketSendableLists: HomogeneousSSL, for lists of
one kind of SocketSendable object, and HeterogeneousSSL, for lists of
different kinds of SocketSendable objects. A HeterogeneousSSL would work
in both cases, but a HomogeneousSSL is more efficient.
Table of Contents
Joel C. Adams (adams@calvin.edu)
Karl Voskuil (kvosku94@calvin.edu)
November 1997
Calvin College