Initial revision of cocktail multiplayer networked gaming platform. (Far

from complete.)


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-05-22 06:08:00 +00:00
parent 53d02843bb
commit 40ecd80e09
25 changed files with 1544 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
<!-- build configuration -->
<project name="cocktail" default="compile" basedir=".">
<!-- configuration parameters -->
<property name="app.name" value="cocktail"/>
<property name="deploy.home" value="dist"/>
<property name="dist.home" value="${deploy.home}"/>
<property name="dist.jar" value="${app.name}.jar"/>
<property name="javadoc.home" value="${deploy.home}/docs"/>
<property name="build.compiler" value="jikes"/>
<property name="java.libraries" value="/usr/share/java"/>
<!-- prepares the application directories -->
<target name="prepare">
<mkdir dir="${deploy.home}"/>
<mkdir dir="${deploy.home}/classes"/>
<mkdir dir="${javadoc.home}"/>
</target>
<!-- cleans out the installed application -->
<target name="clean">
<delete dir="${deploy.home}"/>
</target>
<!-- build the java class files -->
<target name="compile" depends="prepare">
<javac srcdir="src/java" destdir="${deploy.home}/classes"
debug="on" optimize="off" deprecation="off">
<classpath>
<fileset dir="${java.libraries}" includes="**/*.jar"/>
<fileset dir="lib" includes="**/*.jar"/>
<pathelement location="${deploy.home}/classes"/>
</classpath>
</javac>
</target>
<!-- build the javadoc documentation -->
<target name="javadoc" depends="prepare">
<javadoc sourcepath="src/java"
packagenames="robodj.*"
classpath="${deploy.home}/classes"
destdir="${javadoc.home}"/>
</target>
<!-- the default target is to rebuild everything -->
<target name="all" depends="clean,prepare,compile,javadoc"/>
<!-- builds our distribution files (war and jar) -->
<target name="dist" depends="prepare,compile">
<jar jarfile="${dist.home}/${dist.jar}"
basedir="${deploy.home}/classes"/>
</target>
</project>
+55
View File
@@ -0,0 +1,55 @@
Cocktail Mk3 -*- outline -*-
* What is it?
Cocktail is a platform on which multiplayer networked games may be
developed. The platform is (extremely) loosely based around the idea of a
cocktail party. For the most part, this just gives us a useful context
from which to choose names. The basic service of the Cocktail platform is
a simple information sharing mechanism based on the concept of distributed
objects. This layer is called "Cher" for reasons outlined in the Cher
design notes.
A distributed object has a set of subscribers. Whenever a modification is
made to that object, all of that object's subscribers are notified. This
has the beneficial effect of providing a framework in which to conceive a
distributed application based on who needs to know what. If information
need be shared among a set of clients, a distributed object can be created
to represent that information and those clients would subscribe. Then
modifications to that object (as well as simple notifications) can be
easily delivered to those clients and those clients only.
As one discovers after further use of the system, the distributed object
concept turns out to be a useful one for other reasons when designing
distributed applications (a subset of which are multiplayer networked
games). The distributed objects fit nicely as the model in the model,
view, controller pattern as well as into other useful patterns.
The primary value of the design is to bring the level of abstraction up
from network connections and packets, to objects and events.
** Getting the party started
Atop the distributed object layer, we further develop the concept of the
cocktail party. Parties tend to take place in rooms and be attended by
groups of people. This is the essence of the next layer of the system: a
framework for providing rooms, with occupants and mechanisms for the
people to move between those rooms. Within the rooms, we provide some
useful basic services like the ability to chat among the occupants of the
room, as well as some non-room-specific facilities like person to person
messaging from anywhere in the system and a location directory.
Not all games developed with the platform will want to use the room
concept, therefore we attempt at this layer and in all subsequent layers
to decouple our services as much as possible. This allows a game with
special needs or for whom our half-baked analogies don't apply, to
leverage some of the useful services without having to bend their design
in uncomfortable ways or hack up some additional interface to the services
we provide.
** Let's play
From here, we branch off into all sorts of interesting directions based on
the different kinds of games that are implemented with the system. We
provide matchmaking lobbies, an extension of the room concept to the game
room, on top of which is provided a framework for managing generic
turn-based games, and various other useful services. Again the philosophy
is to provide consistently designed, but decoupled services that can be
used within and along side whatever design works best for your game.
+38
View File
@@ -0,0 +1,38 @@
Cher Mk3 Design -*- outline -*-
* Why Cher?
The basic function of this layer is to allow the sharing (Cher-ing) of
information among different nodes in the network. Plus, I don't think Cher
has ever had a software system named after her and it's high time. Imagine
Cher as the social lubricant that allows the party goers to communicate.
* Client components
** DObjectManager
Manages object proxies; converts value change requests into events,
forwards them via the iomgr; dispatches events on incoming queue; reaps
proxies when last subscriber goes away
** UI (AWT/Swing)
Standard AWT/Swing UI
** UI (Controller)?
Provides a paradigm of controllers and commands; code can post commands
back to the controller queue for later execution; UI elements structured
to automatically generate commands; will probably opt not to use this in
favor of Swing's built-in paradigms
** I/O Manager
*** Reader
Reads incoming data from the socket; decodes messages; posts events to
domgr queue; notifies object subscription penders (this should be done
asychronously)
*** Writer
Encodes object subscription and event forwarding requests; writes them to
the outgoing socket
** Client object
Informs exo-client about connection state changes; provides interface to
connection + authentication (logon) and disconnection (logoff); provides
access to omgr and client dobj
+94
View File
@@ -0,0 +1,94 @@
Cher Mk3 Notes -*- outline -*-
* Server components:
+ Connection manager
+ Client manager
+ Event dispatch thread (DObjectManager)
+ Server side services?
+ Room/Game managers
+ Info repository for shared info?
* Client components:
+ Network client (auth + DObject services)
+ Applet shell
+ Panel management
+ Components: chatbox, occupant info, turn indicator?
+ Room/Game panels, controllers, etc.
* Client detail:
+ Supply credentials (username, password)
+ Establishes, maintains connection:
login (connect), logout (disconnect)
+ Creates, manages client-side DObjectManager
* Events in the client will be dispatched on the AWT thread
- Provide pluggable "event dispatcher" on the client
- Allow extension of Event as DispatchableEvent so that event objects
themselves can extend Runnable and be popped right into
SwingUtil.invokeLater().
* Client perform all network ops on own thread, will call back to main
code through Observer interface to notify of state changes in the
authentication process/connectedness:
public interface ClientObserver
{
public void didConnect ();
public void connectionFailed ();
public void didLogon ();
public void logonFailed ();
public void didDisconnect ();
public void didLogoff ();
}
* Three Rings Client flowlist:
Instantiate client
Obtain auth info
Obtain room info
Authenticate
* Distributed objects are defined like a class with a set of public data
members which is then converted into an actual class with get/set
methods for each member.
public dclass GameObject
{
public int[] players;
public String description;
}
becomes
public class GameObject extends DObject
{
public void setPlayers (int[] players);
public void setPlayersAt (int index, int value);
public int[] getPlayers ();
public int getPlayersAt (int index);
public void setDescription (String description);
public String getDescription ();
}
* Classes:
DObjectManager
DObject
DEvent
DTransaction
Subscriber
* Events:
ATTRIBUTE_CHANGED
ATTRIBUTES_CHANGED
ATTRIBUTES_CHANGED_SPLIT
ELEMENT_CHANGED
OBJECT_DESTROYED
ELEMENT_ADDED
ELEMENT_REMOVED
OID_ADDED
OID_REMOVED
+38
View File
@@ -0,0 +1,38 @@
//
// $Id: Log.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher;
/**
* A placeholder class that contains a reference to the log object used by
* the Spine package.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("robodj");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,197 @@
//
// $Id: Client.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.client;
import java.util.ArrayList;
import java.util.List;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.net.Credentials;
/**
* Through the client object, a connection to the system is established
* and maintained. The client object maintains two separate threads (a
* reader and a writer) through which all network traffic is managed.
*/
public class Client
{
/**
* Constructs a client object with the supplied credentials. These
* creds will be used to authenticate with any server to which this
* client attempts to connect.
*/
public Client (Credentials creds)
{
_creds = creds;
}
/**
* Registers the supplied observer with this client. While registered
* the observer will receive notifications of state changes within the
* client. The function will refuse to register an already registered
* observer.
*
* @see ClientObserver
*/
public void addObserver (ClientObserver observer)
{
synchronized (_observers) {
// disallow multiple instances of the same observer
if (!_observers.contains(observer)) {
_observers.add(observer);
}
}
}
/**
* Unregisters the supplied observer. Upon return of this function,
* the observer will no longer receive notifications of state changes
* within the client.
*/
public void removeObserver (ClientObserver observer)
{
synchronized (_observers) {
_observers.remove(observer);
}
}
/**
* Configures the client to communicate with the server on the
* supplied hostname/port combination.
*
* @see #logon
*/
public void setServer (String hostname, int port)
{
_hostname = hostname;
_port = port;
}
/**
* Returns the hostname of the server to which this client is
* currently configured to connect.
*/
public String getHostname ()
{
return _hostname;
}
/**
* Returns the port on which this client is currently configured to
* connect to the server.
*/
public int getPort ()
{
return _port;
}
/**
* Returns the credentials with which this client is currently
* configured to connect to the server.
*/
public Credentials getCredentials ()
{
return _creds;
}
/**
* Requests that this client connect and logon to the server with
* which it was previously configured.
*/
public synchronized void logon ()
{
// if we have a communicator reference, we're already logged on
if (_comm != null) {
return;
}
// otherwise create a new communicator instance and start it up.
// this will initiate the logon process
_comm = new Communicator(this);
_comm.logon();
}
/**
* Requests that the client log off of the server to which it is
* connected.
*
* @param abortable If true, the client will call
* <code>clientWillDisconnect</code> on all of the client observers
* and abort the logoff process if any of them return false. If false,
* <code>clientWillDisconnect</code> will not be called at all.
*
* @return true if the logoff succeeded, false if it failed due to a
* disagreeable observer.
*/
public boolean logoff (boolean abortable)
{
// if the request is abortable, let's run it past the observers
// before we act upon it
if (abortable && notifyObservers(CLIENT_WILL_LOGOFF, null)) {
return false;
}
// ask the communicator to send a logoff message and disconnect
// from the server
_comm.logoff();
return true;
}
boolean notifyObservers (int code, Exception cause)
{
boolean rejected = false;
synchronized (_observers) {
for (int i = 0; i < _observers.size(); i++) {
ClientObserver obs = (ClientObserver)_observers.get(i);
switch (code) {
case CLIENT_DID_LOGON:
obs.clientDidLogon(this);
break;
case CLIENT_FAILED_TO_LOGON:
obs.clientFailedToLogon(this, cause);
break;
case CLIENT_CONNETION_FAILED:
obs.clientConnectionFailed(this, cause);
break;
case CLIENT_WILL_LOGOFF:
if (!obs.clientWillLogoff(this)) {
rejected = true;
}
break;
case CLIENT_DID_LOGOFF:
obs.clientDidLogoff(this);
break;
default:
throw new RuntimeException("Invalid code supplied to " +
"notifyObservers: " + code);
}
}
}
return rejected;
}
synchronized void communicatorDidExit ()
{
// clear out our communicator reference
_comm = null;
}
protected Credentials _creds;
protected String _hostname;
protected int _port;
/** Our list of client observers. */
protected List _observers = new ArrayList();
/** The entity that manages our network communications. */
protected Communicator _comm;
// client observer codes
static final int CLIENT_DID_LOGON = 0;
static final int CLIENT_FAILED_TO_LOGON = 1;
static final int CLIENT_CONNETION_FAILED = 2;
static final int CLIENT_WILL_LOGOFF = 3;
static final int CLIENT_DID_LOGOFF = 4;
}
@@ -0,0 +1,72 @@
//
// $Id: ClientObserver.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.client;
/**
* A client observer is registered with the client instance to be notified
* when state changes happen within the client. Specifically, logon
* sucess/failure and logoff.
*
* <p> These callbacks happen on the client thread and should therefore
* not be used to perform any complex action or do much more than relay
* the signal to some other thread (like the AWT thread) to act more fully
* on the notice.
*
* <p> In the normal course of affairs, <code>clientDidLogon</code> will
* be called after the client successfully logs on to the server and
* <code>clientDidLogoff</code> will be called after the client logs off
* of the server. If logon fails for any reson,
* <code>clientFailedToLogon</code> will be called to explain the failure.
*
* <p> <code>clientWillLogoff</code> will only be called when an abortable
* logoff is requested (like when the user clicks on a logoff button of
* some sort). It will not be called during non-abortable logoff requests
* (like when the browser calls stop on the applet and is about to yank
* the rug out from under us). If an observer aborts the logoff request,
* it should notify the user in some way why the request was aborted
* (<em>but it shouldn't do so on the thread that calls
* <code>clientWillLogoff</code></em>).
*
* <p> If the client connection fails unexpectedly,
* <code>clientConnectionFailed</code> will be called to let the
* observers know that we lost our connection to the
* server. <code>clientDidLogoff</code> will be called immediately
* afterwards as a normal logoff procedure is effected.
*/
public interface ClientObserver
{
/**
* Called after the client successfully connected to and authenticated
* with the server. The entire object system is up and running by the
* time this method is called.
*/
public void clientDidLogon (Client client);
/**
* Called if anything fails during the logon attempt. This could be a
* network failure, authentication failure or otherwise. The exception
* provided will indicate the cause of the failure.
*/
public void clientFailedToLogon (Client client, Exception cause);
/**
* Called when the connection to the server went away for some
* unexpected reason. This will be followed by a call to
* <code>clientDidLogoff</code>.
*/
public void clientConnectionFailed (Client client, Exception cause);
/**
* Called when an abortable logoff requrest is made. If the observer
* returns false from this method, the client will abort the logoff
* request.
*/
public boolean clientWillLogoff (Client client);
/**
* Called after the client has been logged off of the server and has
* disconnected.
*/
public void clientDidLogoff (Client client);
}
@@ -0,0 +1,175 @@
//
// $Id: Communicator.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.client;
import java.io.*;
import java.net.Socket;
import java.net.InetAddress;
import com.samskivert.util.Queue;
import com.samskivert.cocktail.cher.Log;
import com.samskivert.cocktail.cher.net.*;
/**
* The client performs all network I/O on separate threads (one for
* reading and one for writing). The communicator class encapsulates that
* functionality.
*
* <pre>
* Logon synopsis:
*
* Client.logon():
* - Calls Communicator.start()
* Communicator.start():
* - spawn Reader thread
* Reader.run():
* { - connect
* - authenticate
* } if either fail, notify observers of failed logon
* - start writer thread
* - notify observers that we're logged on
* - read loop
* Writer.run():
* - write loop
* </pre>
*/
class Communicator
{
/**
* Creates a new communicator instance which is associated with the
* supplied client.
*/
public Communicator (Client client)
{
_client = client;
}
/**
* Logs on to the server and initiates our full-duplex message
* exchange.
*/
public void logon ()
{
// make sure things are copacetic
if (_reader != null) {
throw new RuntimeException("Communicator already started.");
}
// start up the reader thread. it will connect to the server and
// start up the writer thread if everything went successfully
_reader = new Reader();
_reader.start();
}
/**
* Delivers a logoff notification to the server and shuts down the
* network connection. Also causes all communication threads to
* terminate.
*/
public void logoff ()
{
}
/**
* Queues up the specified message for delivery to the server.
*/
public void postMessage (UpstreamMessage msg)
{
// simply append the message to the queue
_msgq.append(msg);
}
protected void startWriter ()
{
if (_writer != null) {
throw new RuntimeException("Writer already started!?");
}
// create a new writer thread and start it up
_writer = new Writer();
_writer.start();
}
protected synchronized void readerDidExit ()
{
// clear out our reader reference
_reader = null;
// let the client know when we finally go away
_client.communicatorDidExit();
}
protected class Reader extends Thread
{
public void run ()
{
try {
// first we connect and authenticate with the server
try {
// connect to the server
connect();
// then authenticate
logon();
} catch (Exception e) {
// let the observers know that we've failed
_client.notifyObservers(Client.CLIENT_FAILED_TO_LOGON, e);
// and terminate our communicator thread
return;
}
// once authenticated, we go into full-duplex mode,
// starting up another thread to listen for messages while
// we handle the delivery of messages
startWriter();
listen();
} finally {
// let the communicator know when we finally go away
readerDidExit();
}
}
protected void connect ()
throws IOException
{
// if we're already connected, we freak out
if (_socket != null) {
throw new IOException("Already connected.");
}
// look up the address of the target server
InetAddress host = InetAddress.getByName(_client.getHostname());
// establish a socket connection to said server
_socket = new Socket(host, _client.getPort());
}
protected void logon ()
throws LogonException
{
}
protected void listen ()
{
}
}
protected class Writer extends Thread
{
public void run ()
{
}
}
protected Client _client;
protected Reader _reader;
protected Writer _writer;
protected Socket _socket;
protected DataInputStream _din;
protected DataInputStream _dout;
protected Queue _msgq = new Queue();
}
@@ -0,0 +1,20 @@
//
// $Id: LogonException.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.client;
/**
* A logon exception is used to indicate a failure to log on to the
* server. The reason for failure is encoded as a string and stored in the
* message field of the exception.
*/
public class LogonException extends Exception
{
/**
* Constructs a logon exception with the supplied logon failure code.
*/
public LogonException (String code)
{
super(code);
}
}
@@ -0,0 +1,21 @@
//
// $Id: Event.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.dobj;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class Event
{
public void writeTo (DataOutputStream out)
throws IOException
{
}
public void readFrom (DataInputStream in)
throws IOException
{
}
}
@@ -0,0 +1,19 @@
//
// $Id: ObjectStreamException.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.io;
/**
* The object stream exception is used to communicate an error in
* processing a typed object stream. As any error is fatal, we don't
* differentiate exceptions with separate classes, but instead communicate
* the nature of the error in the exception message with the assumption
* that the caller will want to raise holy hell in every case.
*/
public class ObjectStreamException extends Exception
{
public ObjectStreamException (String message)
{
super(message);
}
}
@@ -0,0 +1,49 @@
//
// $Id: TypedObject.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.io;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
/**
* A typed object is one that is associated with a particular type code.
* The type code can be communicated on the wire and used by the receiving
* end to instantiate the proper typed object class for decoding.
*/
public abstract class TypedObject
{
/**
* Each typed object class must associate itself with a type value via
* the <code>TypedObjectFactory</code> so that the factory can
* instantiate the proper <code>TypedObject</code> derived class when
* decoding an incoming message.
*
* @return The type code associated with this object.
*/
public abstract short getType ();
/**
* Each typed object class must be able to write itself to a stream.
* It should first call <code>super.writeTo()</code> before writing
* its own fields to the stream.
*/
public void writeTo (DataOutputStream out)
throws IOException
{
out.writeShort(getType());
}
/**
* Each typed object class must be able to read itself from a stream.
* It should first call <code>super.readFrom()</code> before reading
* its own fields from the stream.
*/
public void readFrom (DataInputStream in)
throws IOException
{
// nothing to do because the TypedObjectFactory already read our
// type value from the stream
}
}
@@ -0,0 +1,77 @@
//
// $Id: TypedObjectFactory.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.io;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.HashMap;
/**
* The encodable factory is used to unserialize encodable object instances
* from a stream. It maintains a mapping of encodable object classes to
* identifier codes which it uses to determine what sort of object is on
* the stream. The encodable mechanism is used to associate a particular
* class with some compact code that can be transmitted on the wire to
* identify that class.
*/
public class TypedObjectFactory
{
/**
* Reads (unserializes) a typed object from the supplied data input
* stream.
*
* @return The unserialized typed object instance.
*/
public static TypedObject readFrom (DataInputStream din)
throws IOException, ObjectStreamException
{
// first determine the type of the incoming object
short type = din.readShort();
// now instantiate the proper object and decode the remainder
TypedObject msg = newObjectByType(type);
msg.readFrom(din);
return msg;
}
protected static TypedObject newObjectByType (short type)
throws ObjectStreamException
{
Class clazz = (Class)_classes.get(new Short(type));
if (clazz == null) {
String errmsg = "Unknown object type: " + type;
throw new ObjectStreamException(errmsg);
}
try {
return (TypedObject)clazz.newInstance();
} catch (Throwable t) {
String errmsg = "Unable to instantiate typed object " +
"[class=" + clazz.getName() + ", error=" + t + "].";
throw new ObjectStreamException(errmsg);
}
}
protected static void registerClass (short type, Class clazz)
{
Short key = new Short(type);
// make sure no funny business is afoot
if (_classes.containsKey(key)) {
Class incumbent = (Class)_classes.get(key);
String errmsg = "Cannot register " + clazz.getName() +
" as type " + type + " because " + incumbent.getName() +
" is already registered with that type.";
throw new RuntimeException(errmsg);
}
// set up the mapping
_classes.put(key, clazz);
}
/** Our type to class mapping table. */
protected static HashMap _classes = new HashMap();
}
@@ -0,0 +1,57 @@
//
// $Id: AuthRequest.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import com.samskivert.cocktail.cher.io.TypedObjectFactory;
public class AuthenticationRequest extends UpstreamMessage
{
/** The code for an object subscription request. */
public static final short TYPE = TYPE_BASE + 0;
/**
* Zero argument constructor used when unserializing an instance.
*/
public AuthenticationRequest ()
{
super();
}
/**
* Constructs a authentication request with the supplied credentials.
*/
public AuthenticationRequest (Credentials creds)
{
_creds = creds;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
_creds.writeTo(out);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
_creds = (Credentials)TypedObjectFactory.readFrom(in);
}
/**
* The object id of the distributed object to which we are
* subscribing.
*/
protected Credentials _creds;
}
@@ -0,0 +1,33 @@
//
// $Id: Credentials.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.net;
import com.samskivert.cocktail.cher.io.TypedObject;
import com.samskivert.cocktail.cher.io.TypedObjectFactory;
/**
* Credentials are supplied by the client implementation and sent along to
* the server during the authentication process. To provide support for a
* variety of authentication methods, the credentials class is meant to be
* subclassed for the particular method (ie. username + password) in use
* in a given system.
*
* <p> All derived classes should provide a no argument constructor so
* that they can be instantiated prior to reconstruction from a data input
* stream.
*/
public abstract class Credentials extends TypedObject
{
/**
* All credential derived classes should base their typed object code
* on this base value.
*/
public static final short TYPE_BASE = 300;
// register our credential classes
static {
TypedObjectFactory.registerClass(UsernamePasswordCreds.TYPE,
UsernamePasswordCreds.class);
}
}
@@ -0,0 +1,72 @@
//
// $Id: DownstreamMessage.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.net;
import com.samskivert.cocktail.cher.io.TypedObject;
import com.samskivert.cocktail.cher.io.TypedObjectFactory;
/**
* The <code>DownstreamMessage</code> class encapsulates a message in the
* Distributed Object Protocol that flows from the server to the
* client. Downstream messages include object subscription, event
* forwarding and session management.
*/
public class DownstreamMessage
{
/**
* All downstream message derived classes should base their typed
* object code on this base value.
*/
public static final short TYPE_BASE = 200;
/**
* The message id of the upstream message with which this downstream
* message is associated (or -1 if it is not associated with any
* upstream message). Because not every downstream message class cares
* to provide an upstream message id, this field is not serialized
* when the base downstream message class is serialized. Thus derived
* classes that care about message id should take care to initialize,
* serialize and unserialize the value theirselves.
*/
public short messageId = -1;
/**
* Each downstream message derived class must provide a zero argument
* constructor so that the <code>TypedObjectFactory</code> can create
* a new instance of said class prior to unserializing it.
*/
public DownstreamMessage ()
{
// nothing to do...
}
// register our downstream message classes
static {
TypedObjectFactory.registerClass(AuthenticationRequest.TYPE,
AuthenticationRequest.class);
TypedObjectFactory.registerClass(SubscribeRequest.TYPE,
SubscribeRequest.class);
TypedObjectFactory.registerClass(FetchRequest.TYPE,
FetchRequest.class);
TypedObjectFactory.registerClass(UnsubscribeNotification.TYPE,
UnsubscribeNotification.class);
TypedObjectFactory.registerClass(ForwardEventNotification.TYPE,
ForwardEventNotification.class);
TypedObjectFactory.registerClass(PingNotification.TYPE,
PingNotification.class);
TypedObjectFactory.registerClass(LogoffNotification.TYPE,
LogoffNotification.class);
}
/** The code for an event notification. */
public static final byte EVENT = 0x00;
/** The code for a fetch/subscribe object response. */
public static final byte OBJECT = 0x01;
/** The code for a request failure. */
public static final byte FAILURE = 0x02;
/** The code for a pong response. */
public static final byte PONG = 0x03;
/** The code for an end of transmission notification. */
public static final byte EOT = 0x04;
}
@@ -0,0 +1,48 @@
//
// $Id: EventNotification.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.net;
public class EventNotification extends DownstreamMessage
{
/** The code for an event notification. */
public static final short TYPE = TYPE_BASE + 1;
/**
* Zero argument constructor used when unserializing an instance.
*/
public EventNotification ()
{
super();
}
/**
* Constructs an event notification for the supplied event.
*/
public EventNotification (Event event)
{
_event = event;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
_event.writeTo(out);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
_event = EventFactory.readFrom(in);
}
/** The event which we are forwarding. */
protected Event _event;
}
@@ -0,0 +1,55 @@
//
// $Id: FetchRequest.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class FetchRequest extends UpstreamMessage
{
/** The code for an object fetch request. */
public static final short TYPE = TYPE_BASE + 2;
/**
* Zero argument constructor used when unserializing an instance.
*/
public FetchRequest ()
{
super();
}
/**
* Constructs a fetch request for the distributed object with the
* specified object id.
*/
public FetchRequest (int oid)
{
_oid = oid;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeInt(_oid);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
_oid = in.readInt();
}
/**
* The object id of the distributed object which we are fetching.
*/
protected int _oid;
}
@@ -0,0 +1,55 @@
//
// $Id: ForwardEventRequest.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import com.samskivert.cocktail.cher.dobj.Event;
import com.samskivert.cocktail.cher.dobj.EventFactory;
public class ForwardEventNotification extends UpstreamMessage
{
/** The code for a forward event notification. */
public static final short TYPE = TYPE_BASE + 4;
/**
* Zero argument constructor used when unserializing an instance.
*/
public ForwardEventNotification ()
{
super();
}
/**
* Constructs a forward event notification for the supplied event.
*/
public ForwardEventNotification (Event event)
{
_event = event;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
_event.writeTo(out);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
_event = EventFactory.readFrom(in);
}
/** The event which we are forwarding. */
protected Event _event;
}
@@ -0,0 +1,23 @@
//
// $Id: LogoffRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
package com.samskivert.cocktail.cher.net;
public class LogoffNotification extends UpstreamMessage
{
/** The code for a logoff notification. */
public static final short TYPE = TYPE_BASE + 6;
/**
* Zero argument constructor used when unserializing an instance.
*/
public LogoffNotification ()
{
super();
}
public short getType ()
{
return TYPE;
}
}
@@ -0,0 +1,23 @@
//
// $Id: PingRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
package com.samskivert.cocktail.cher.net;
public class PingNotification extends UpstreamMessage
{
/** The code for a ping notification. */
public static final short TYPE = TYPE_BASE + 5;
/**
* Zero argument constructor used when unserializing an instance.
*/
public PingNotification ()
{
super();
}
public short getType ()
{
return TYPE;
}
}
@@ -0,0 +1,56 @@
//
// $Id: SubscribeRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class SubscribeRequest extends UpstreamMessage
{
/** The code for an object subscription request. */
public static final short TYPE = TYPE_BASE + 1;
/**
* Zero argument constructor used when unserializing an instance.
*/
public SubscribeRequest ()
{
super();
}
/**
* Constructs a subscribe request for the distributed object with the
* specified object id.
*/
public SubscribeRequest (int oid)
{
_oid = oid;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeInt(_oid);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
_oid = in.readInt();
}
/**
* The object id of the distributed object to which we are
* subscribing.
*/
protected int _oid;
}
@@ -0,0 +1,56 @@
//
// $Id: UnsubscribeRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class UnsubscribeNotification extends UpstreamMessage
{
/** The code for an unsubscribe notification. */
public static final short TYPE = TYPE_BASE + 3;
/**
* Zero argument constructor used when unserializing an instance.
*/
public UnsubscribeNotification ()
{
super();
}
/**
* Constructs a unsubscribe notification for the distributed object
* with the specified object id.
*/
public UnsubscribeNotification (int oid)
{
_oid = oid;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeInt(_oid);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
_oid = in.readInt();
}
/**
* The object id of the distributed object from which we are
* unsubscribing.
*/
protected int _oid;
}
@@ -0,0 +1,105 @@
//
// $Id: UpstreamMessage.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import com.samskivert.cocktail.cher.io.TypedObject;
import com.samskivert.cocktail.cher.io.TypedObjectFactory;
/**
* The <code>UpstreamMessage</code> class encapsulates a message in the
* Distributed Object Protocol that flows from the client to the server.
* Upstream messages include object subscription, event forwarding and
* session management.
*/
public abstract class UpstreamMessage extends TypedObject
{
/**
* All upstream message derived classes should base their typed object
* code on this base value.
*/
public static final short TYPE_BASE = 100;
/**
* This is a unique (within the context of a reasonable period of
* time) identifier assigned to each upstream message. The message ids
* are used to correlate a downstream response message to the
* appropriate upstream request message.
*/
public short messageId;
/**
* Each upstream message derived class must provide a zero argument
* constructor so that the <code>TypedObjectFactory</code> can create
* a new instance of said class prior to unserializing it.
*/
public UpstreamMessage ()
{
// automatically generate a valid message id; on the client, this
// will be used, on the server it will be overwritten by the
// unserialized value
this.messageId = nextMessageId();
}
/**
* Derived classes should override this function to write their fields
* out to the supplied data output stream. They <em>must</em> be sure
* to first call <code>super.writeTo()</code>.
*/
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeShort(messageId);
}
/**
* Derived classes should override this function to read their fields
* from the supplied data input stream. They <em>must</em> be sure to
* first call <code>super.readFrom()</code>.
*/
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
messageId = in.readShort();
}
/**
* Returns the next message id suitable for use by an upstream
* message.
*/
protected static synchronized short nextMessageId ()
{
_nextMessageId = (short)((_nextMessageId + 1) % Short.MAX_VALUE);
return _nextMessageId;
}
/**
* This is used to generate monotonically increasing message ids on
* the client as new messages are generated.
*/
protected static short _nextMessageId;
// register our upstream message classes
static {
TypedObjectFactory.registerClass(AuthenticationRequest.TYPE,
AuthenticationRequest.class);
TypedObjectFactory.registerClass(SubscribeRequest.TYPE,
SubscribeRequest.class);
TypedObjectFactory.registerClass(FetchRequest.TYPE,
FetchRequest.class);
TypedObjectFactory.registerClass(UnsubscribeNotification.TYPE,
UnsubscribeNotification.class);
TypedObjectFactory.registerClass(ForwardEventNotification.TYPE,
ForwardEventNotification.class);
TypedObjectFactory.registerClass(PingNotification.TYPE,
PingNotification.class);
TypedObjectFactory.registerClass(LogoffNotification.TYPE,
LogoffNotification.class);
}
}
@@ -0,0 +1,52 @@
//
// $Id: UsernamePasswordCreds.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
package com.samskivert.cocktail.cher.net;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class UsernamePasswordCreds
{
public static final short TYPE = TYPE_BASE + 0;
/**
* Zero argument constructor used when unserializing an instance.
*/
public UsernamePasswordCreds ()
{
super();
}
/**
* Construct credentials with the supplied username and password.
*/
public UsernamePasswordCreds (String username, String password)
{
_username = username;
_password = password;
}
public short getType ()
{
return TYPE;
}
public void writeTo (DataOutputStream out)
throws IOException
{
out.writeUTF(_username);
out.writeUTF(_password);
}
public void readFrom (DataInputStream in)
throws IOException
{
_username = in.readUTF();
_password = in.readUTF();
}
protected String _username;
protected String _password;
}