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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user