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:

  1. Base Case
    In the simplest base case for a client/server model a single daemon process/program serves a single client process/program.
  2. 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.
  3. 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