Starting to wire up client/server dobj stuff.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@22 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-06-02 01:30:37 +00:00
parent 2c3e4d9528
commit 1c6b292edc
18 changed files with 320 additions and 64 deletions
+11
View File
@@ -1,5 +1,16 @@
Cher Mk3 Notes -*- outline -*-
* Server-side event concentrator
The client objects will not subscribe directly, but will subscribe through
the concentrator so that, at least, it can create a single
ForwardEventNotification for each Event being dispatched to a group of
clients. Optimally, it would be able to flatten that notification as well
and the byte array can be written to the socket of each of the individual
clients rather than creating a separate byte array for each client. This
will require a special "flattened notification" that can be inserted into
the queue to preserve message ordering but then is simply sent rather than
flattened and sent.
* Marshaller
Consider how the dobject marshaller deals with classes loaded and reloaded
using flushable classloaders.
@@ -1,5 +1,5 @@
//
// $Id: AttributesChangedEvent.java,v 1.1 2001/06/01 07:12:13 mdb Exp $
// $Id: AttributesChangedEvent.java,v 1.2 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.dobj;
@@ -139,6 +139,19 @@ public class AttributesChangedEvent extends DEvent
return ((Double)_values[index]).doubleValue();
}
/**
* Applies this attribute change to the object.
*/
public boolean applyToObject (DObject target)
throws ObjectAccessException
{
// pass the new values on to the object
for (int i = 0; i < _count; i++) {
target.setAttribute(_names[i], _values[i]);
}
return true;
}
protected int _count;
protected String[] _names;
protected Object[] _values;
@@ -1,5 +1,5 @@
//
// $Id: Subscriber.java,v 1.2 2001/06/01 05:17:16 mdb Exp $
// $Id: Subscriber.java,v 1.3 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.dobj;
@@ -34,7 +34,7 @@ public interface Subscriber
* @see DObjectManager.subscribeToObject
* @see DObjectManager.fetchObject
*/
public void requestFailed (ObjectAccessException cause);
public void requestFailed (int oid, ObjectAccessException cause);
/**
* Called when an event has been dispatched on an object. The event
@@ -1,5 +1,5 @@
//
// $Id: AuthRequest.java,v 1.4 2001/05/30 23:58:31 mdb Exp $
// $Id: AuthRequest.java,v 1.5 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -35,6 +35,11 @@ public class AuthRequest extends UpstreamMessage
return TYPE;
}
public Credentials getCredentials ()
{
return _creds;
}
public void writeTo (DataOutputStream out)
throws IOException
{
@@ -1,5 +1,5 @@
//
// $Id: Credentials.java,v 1.4 2001/05/30 23:58:31 mdb Exp $
// $Id: Credentials.java,v 1.5 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -14,8 +14,11 @@ import com.threerings.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.
* subclassed for the particular method (ie. password, auth digest, etc.)
* in use in a given system.
*
* <p> All credentials must provide a username as the username is used to
* associate network connections with sessions.
*
* <p> All derived classes should provide a no argument constructor so
* that they can be instantiated prior to reconstruction from a data input
@@ -29,6 +32,27 @@ public abstract class Credentials implements TypedObject
*/
public static final short TYPE_BASE = 300;
/**
* Constructs a credentials instance with the specified username.
*/
public Credentials (String username)
{
_username = username;
}
/**
* Constructs a blank credentials instance in preparation for
* unserializing from the network.
*/
public Credentials ()
{
}
public String getUsername ()
{
return _username;
}
/**
* Derived classes should override this function to write their fields
* out to the supplied data output stream. They <em>must</em> be sure
@@ -37,7 +61,7 @@ public abstract class Credentials implements TypedObject
public void writeTo (DataOutputStream out)
throws IOException
{
// we don't do anything here, but we may want to some day
out.writeUTF(_username);
}
/**
@@ -48,6 +72,8 @@ public abstract class Credentials implements TypedObject
public void readFrom (DataInputStream in)
throws IOException
{
// we don't do anything here, but we may want to some day
_username = in.readUTF();
}
protected String _username;
}
@@ -1,5 +1,5 @@
//
// $Id: EventNotification.java,v 1.4 2001/05/30 23:59:16 mdb Exp $
// $Id: EventNotification.java,v 1.5 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -7,7 +7,7 @@ import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import com.threerings.cocktail.cher.dobj.Event;
import com.threerings.cocktail.cher.dobj.DEvent;
public class EventNotification extends DownstreamMessage
{
@@ -25,7 +25,7 @@ public class EventNotification extends DownstreamMessage
/**
* Constructs an event notification for the supplied event.
*/
public EventNotification (Event event)
public EventNotification (DEvent event)
{
_event = event;
}
@@ -50,5 +50,5 @@ public class EventNotification extends DownstreamMessage
}
/** The event which we are forwarding. */
protected Event _event;
protected DEvent _event;
}
@@ -1,5 +1,5 @@
//
// $Id: FailureResponse.java,v 1.2 2001/05/30 23:58:31 mdb Exp $
// $Id: FailureResponse.java,v 1.3 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -21,12 +21,12 @@ public class FailureResponse extends DownstreamMessage
}
/**
* Constructs a failure response that is associated with the specified
* upstream message id.
* Constructs a failure response in response to a request for the
* specified oid.
*/
public FailureResponse (short messageId)
public FailureResponse (int oid)
{
this.messageId = messageId;
_oid = oid;
}
public short getType ()
@@ -34,17 +34,24 @@ public class FailureResponse extends DownstreamMessage
return TYPE;
}
public int getOid ()
{
return _oid;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeShort(messageId);
out.writeInt(_oid);
}
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
messageId = in.readShort();
_oid = in.readInt();
}
protected int _oid;
}
@@ -1,5 +1,5 @@
//
// $Id: ObjectResponse.java,v 1.5 2001/05/30 23:58:31 mdb Exp $
// $Id: ObjectResponse.java,v 1.6 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -24,12 +24,10 @@ public class ObjectResponse extends DownstreamMessage
}
/**
* Constructs an object response with supplied distributed object that
* is associated with the specified upstream message id.
* Constructs an object response with supplied distributed object.
*/
public ObjectResponse (short messageId, DObject dobj)
public ObjectResponse (DObject dobj)
{
this.messageId = messageId;
_dobj = dobj;
}
@@ -42,7 +40,6 @@ public class ObjectResponse extends DownstreamMessage
throws IOException
{
super.writeTo(out);
out.writeShort(messageId);
DObjectFactory.writeTo(out, _dobj);
}
@@ -50,7 +47,6 @@ public class ObjectResponse extends DownstreamMessage
throws IOException
{
super.readFrom(in);
messageId = in.readShort();
_dobj = (DObject)DObjectFactory.readFrom(in);
}
@@ -1,5 +1,5 @@
//
// $Id: UsernamePasswordCreds.java,v 1.4 2001/05/30 23:58:31 mdb Exp $
// $Id: UsernamePasswordCreds.java,v 1.5 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -24,7 +24,7 @@ public class UsernamePasswordCreds extends Credentials
*/
public UsernamePasswordCreds (String username, String password)
{
_username = username;
super(username);
_password = password;
}
@@ -33,11 +33,15 @@ public class UsernamePasswordCreds extends Credentials
return TYPE;
}
public String getPassword ()
{
return _password;
}
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeUTF(_username);
out.writeUTF(_password);
}
@@ -45,10 +49,13 @@ public class UsernamePasswordCreds extends Credentials
throws IOException
{
super.readFrom(in);
_username = in.readUTF();
_password = in.readUTF();
}
protected String _username;
public String toString ()
{
return "[username=" + _username + ", password=" + _password + "]";
}
protected String _password;
}
@@ -1,11 +1,13 @@
//
// $Id: ClientManager.java,v 1.1 2001/06/01 22:12:03 mdb Exp $
// $Id: ClientManager.java,v 1.2 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server;
import java.io.IOException;
import java.util.HashMap;
import com.threerings.cocktail.cher.Log;
import com.threerings.cocktail.cher.net.Credentials;
import com.threerings.cocktail.cher.server.net.*;
/**
@@ -13,6 +15,11 @@ import com.threerings.cocktail.cher.server.net.*;
* surprise) which are slightly more than just connections. Clients
* persist in the absence of connections in case a user goes bye bye
* unintentionally and wants to reconnect and continue their session.
*
* <p> The client manager operates with thread safety because it is called
* both from the conmgr thread (to notify of connections showing up or
* going away) and from the dobjmgr thread (when clients are given the
* boot for application-defined reasons).
*/
public class ClientManager implements ConnectionObserver
{
@@ -29,9 +36,22 @@ public class ClientManager implements ConnectionObserver
*
* @param conn The newly established connection.
*/
public void connectionEstablished (Connection conn)
public synchronized
void connectionEstablished (Connection conn, Credentials creds)
{
Log.info("Connection established: " + conn);
String username = creds.getUsername();
// see if there's a client already registered with this username
Client client = (Client)_clients.get(username);
if (client != null) {
Log.info("Session resumed [username=" + username +
", conn=" + conn + "].");
} else {
Log.info("Session initiated [username=" + username +
", conn=" + conn + "].");
}
}
/**
@@ -43,7 +63,8 @@ public class ClientManager implements ConnectionObserver
* @param conn The connection in that failed.
* @param fault The exception associated with the failure.
*/
public void connectionFailed (Connection conn, IOException fault)
public synchronized
void connectionFailed (Connection conn, IOException fault)
{
Log.info("Connection failed: " + conn + ": " + fault);
}
@@ -53,8 +74,10 @@ public class ClientManager implements ConnectionObserver
*
* @param conn The recently closed connection.
*/
public void connectionClosed (Connection conn)
public synchronized void connectionClosed (Connection conn)
{
Log.info("Connection closed: " + conn);
}
protected HashMap _clients = new HashMap();
}
@@ -0,0 +1,97 @@
//
// $Id: PresentsClient.java,v 1.1 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server;
import com.threerings.cocktail.cher.Log;
import com.threerings.cocktail.cher.dobj.*;
import com.threerings.cocktail.cher.net.*;
import com.threerings.cocktail.cher.server.net.*;
/**
* A client object represents a client session in the server. It is
* associated with a connection instance (while the client is connected)
* and acts as the intermediary for the remote client in terms of passing
* along events forwarded by the client, ensuring that subscriptions are
* maintained on behalf of the client and that events are forwarded to the
* client.
*
* <p><em>A note on synchronization:</em> the client object is structured
* so that its <code>Subscriber</code> implementation (which is called
* from the dobjmgr thread) can proceed without synchronization. This does
* not overlap with its other client duties which are called from the
* conmgr thread and therefore also need not be synchronized.
*/
public class Client implements Subscriber, MessageHandler
{
/**
* Constructs a new client instance bound to the specified username
* and initially associated with the specified connection instance.
*/
public Client (String username, Connection conn)
{
_username = username;
setConnection(conn);
}
/**
* Called by the client manager when a new connection arrives that
* authenticates as this already established client. This must only be
* called from the congmr thread.
*/
public void resumeSession (Connection conn)
{
// check to see if we've already got a connection object, in which
// case it's probably stale
if (_conn != null) {
Log.info("Closing stale connection [old=" + _conn +
", new=" + conn + "].");
// close the old connection (which results in everything being
// properly unregistered)
_conn.close();
}
// start using the new connection
setConnection(conn);
Log.info("Session resumed [client=" + this + "].");
}
protected void setConnection (Connection conn)
{
// keep a handle to the new connection
_conn = conn;
// tell the connection to pass messages on to us
_conn.setMessageHandler(this);
}
// documentation inherited from interface
public void handleMessage (UpstreamMessage message)
{
}
// documentation inherited from interface
public void objectAvailable (DObject object)
{
// queue up an object response
_conn.postMessage(new ObjectResponse(object));
}
// documentation inherited from interface
public void requestFailed (int oid, ObjectAccessException cause)
{
_conn.postMessage(new FailureResponse(oid));
}
// documentation inherited from interface
public boolean handleEvent (DEvent event, DObject target)
{
// forward the event to the client
_conn.postMessage(new EventNotification(event));
return true;
}
protected String _username;
protected transient Connection _conn;
}
@@ -1,5 +1,5 @@
//
// $Id: PresentsDObjectMgr.java,v 1.2 2001/06/01 20:35:39 mdb Exp $
// $Id: PresentsDObjectMgr.java,v 1.3 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server;
@@ -175,10 +175,11 @@ public class CherDObjectMgr implements DObjectManager
public boolean applyToObject (DObject target)
throws ObjectAccessException
{
int oid = getNextOid();
try {
// create a new instance of this object
DObject obj = (DObject)_class.newInstance();
int oid = getNextOid();
// initialize this object
obj.init(oid, CherDObjectMgr.this);
@@ -205,7 +206,8 @@ public class CherDObjectMgr implements DObjectManager
// let the subscriber know shit be fucked
if (_target != null) {
String errmsg = "Object instantiation failed: " + e;
_target.requestFailed(new ObjectAccessException(errmsg));
_target.requestFailed(
oid, new ObjectAccessException(errmsg));
}
}
@@ -243,14 +245,14 @@ public class CherDObjectMgr implements DObjectManager
// if it don't exist, let them know
if (obj == null) {
_target.requestFailed(new NoSuchObjectException(_oid));
_target.requestFailed(_oid, new NoSuchObjectException(_oid));
return false;
}
// check permissions
if (!obj.checkPermissions(_target)) {
String errmsg = "m.access_denied\t" + _oid;
_target.requestFailed(new ObjectAccessException(errmsg));
_target.requestFailed(_oid, new ObjectAccessException(errmsg));
return false;
}
@@ -1,5 +1,5 @@
//
// $Id: AuthingConnection.java,v 1.2 2001/05/30 23:58:31 mdb Exp $
// $Id: AuthingConnection.java,v 1.3 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -14,7 +14,8 @@ import com.threerings.cocktail.cher.net.AuthRequest;
* The authing connection manages the client connection until
* authentication has completed (for better or for worse).
*/
public class AuthingConnection extends Connection
public class AuthingConnection
extends Connection implements MessageHandler
{
/**
* Creates a new authing connection object that will manage the
@@ -25,6 +26,8 @@ public class AuthingConnection extends Connection
throws IOException
{
super(cmgr, socket);
// we are our own message handler
setMessageHandler(this);
}
/**
@@ -55,6 +58,11 @@ public class AuthingConnection extends Connection
return _authreq;
}
public String toString ()
{
return "[mode=AUTHING, addr=" + _socket.getInetAddress() + "]";
}
protected int _state = AWAITING_AUTH_REQUEST;
protected AuthRequest _authreq;
@@ -1,5 +1,5 @@
//
// $Id: Connection.java,v 1.3 2001/06/01 22:12:03 mdb Exp $
// $Id: Connection.java,v 1.4 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -45,6 +45,16 @@ public abstract class Connection implements NetEventHandler
_din = new DataInputStream(_fin);
}
/**
* Instructs the connection to pass parsed messages on to this handler
* for processing. This should be done before the connection is turned
* loose to process messages.
*/
public void setMessageHandler (MessageHandler handler)
{
_handler = handler;
}
/**
* Returns the select item associated with this connection.
*/
@@ -71,6 +81,33 @@ public abstract class Connection implements NetEventHandler
return _socket;
}
/**
* Closes this connection and unregisters it from the connection
* manager. This should only be called from the conmgr thread.
*/
public void close ()
{
// we shouldn't be closed twice
if (_socket == null) {
Log.warning("Attempted to re-close connection.");
return;
}
// unregister from the select set
_cmgr.connectionClosed(this);
// close our socket
try {
_socket.close();
} catch (IOException ioe) {
Log.warning("Error closing connection [conn=" + this +
", error=" + ioe + "].");
}
// clear out our socket reference to prevent repeat closings
_socket = null;
}
/**
* Called when our client socket has data available for reading.
*/
@@ -80,8 +117,8 @@ public abstract class Connection implements NetEventHandler
// read the available data and see if we have a whole frame
if (_fin.readFrame(_in)) {
// parse the message and pass it on
handleMessage((UpstreamMessage)
TypedObjectFactory.readFrom(_din));
_handler.handleMessage((UpstreamMessage)
TypedObjectFactory.readFrom(_din));
}
} catch (EOFException eofe) {
@@ -96,12 +133,6 @@ public abstract class Connection implements NetEventHandler
}
}
/**
* Called when a complete message has been parsed from incoming
* network data.
*/
public abstract void handleMessage (UpstreamMessage msg);
/**
* Posts a downstream message for delivery to this connection. The
* message will be delivered by the conmgr thread as soon as it gets
@@ -121,4 +152,6 @@ public abstract class Connection implements NetEventHandler
protected OutputStream _out;
protected FramedInputStream _fin;
protected DataInputStream _din;
protected MessageHandler _handler;
}
@@ -1,5 +1,5 @@
//
// $Id: ConnectionManager.java,v 1.3 2001/06/01 22:12:03 mdb Exp $
// $Id: ConnectionManager.java,v 1.4 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -15,6 +15,7 @@ import com.samskivert.util.Queue;
import com.threerings.cocktail.cher.Log;
import com.threerings.cocktail.cher.io.FramingOutputStream;
import com.threerings.cocktail.cher.io.TypedObjectFactory;
import com.threerings.cocktail.cher.net.Credentials;
import com.threerings.cocktail.cher.net.DownstreamMessage;
import com.threerings.cocktail.cher.net.Registry;
@@ -102,8 +103,7 @@ public class ConnectionManager extends LoopingThread
* Notifies the connection observers of a connection event. Used
* internally.
*/
protected void notifyObservers (int code, Connection conn,
IOException cause)
protected void notifyObservers (int code, Connection conn, Object arg)
{
synchronized (_observers) {
for (int i = 0; i < _observers.size(); i++) {
@@ -111,10 +111,10 @@ public class ConnectionManager extends LoopingThread
(ConnectionObserver)_observers.get(i);
switch (code) {
case CONNECTION_ESTABLISHED:
obs.connectionEstablished(conn);
obs.connectionEstablished(conn, (Credentials)arg);
break;
case CONNECTION_FAILED:
obs.connectionFailed(conn, cause);
obs.connectionFailed(conn, (IOException)arg);
break;
case CONNECTION_CLOSED:
obs.connectionClosed(conn);
@@ -154,8 +154,8 @@ public class ConnectionManager extends LoopingThread
}
// check for connections that have completed authentication
Connection conn;
while ((conn = (Connection)_authq.getNonBlocking()) != null) {
AuthingConnection conn;
while ((conn = (AuthingConnection)_authq.getNonBlocking()) != null) {
// remove the old connection from the select set
_selset.remove(conn.getSelectItem());
@@ -168,7 +168,8 @@ public class ConnectionManager extends LoopingThread
_selset.add(rconn.getSelectItem());
// and let our observers know about our new connection
notifyObservers(CONNECTION_ESTABLISHED, rconn, null);
notifyObservers(CONNECTION_ESTABLISHED, rconn,
conn.getAuthRequest().getCredentials());
} catch (IOException ioe) {
Log.warning("Failure upgrading authing connection to " +
@@ -1,9 +1,10 @@
//
// $Id: ConnectionObserver.java,v 1.3 2001/06/01 22:12:03 mdb Exp $
// $Id: ConnectionObserver.java,v 1.4 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server.net;
import java.io.IOException;
import com.threerings.cocktail.cher.net.Credentials;
/**
* A connection observer can be registered with the connection manager to
@@ -23,8 +24,10 @@ public interface ConnectionObserver
* the connection observer.
*
* @param conn The newly established connection.
* @param creds The credentials with which this connection
* (successfully) authenticated.
*/
public void connectionEstablished (Connection conn);
public void connectionEstablished (Connection conn, Credentials creds);
/**
* Called if a connection fails for any reason. If a connection fails,
@@ -0,0 +1,19 @@
//
// $Id: MessageHandler.java,v 1.1 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server.net;
import com.threerings.cocktail.cher.net.UpstreamMessage;
/**
* After the connection object has parsed an entire upstream message, it
* passes it on to its message handler.
*/
public interface MessageHandler
{
/**
* Called when a complete message has been parsed from incoming
* network data.
*/
public void handleMessage (UpstreamMessage message);
}
@@ -1,5 +1,5 @@
//
// $Id: RunningConnection.java,v 1.2 2001/05/30 23:58:31 mdb Exp $
// $Id: RunningConnection.java,v 1.3 2001/06/02 01:30:37 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -30,4 +30,9 @@ public class RunningConnection extends Connection
public void handleMessage (UpstreamMessage msg)
{
}
public String toString ()
{
return "[mode=RUNNING, addr=" + _socket.getInetAddress() + "]";
}
}