a0e9044e93
they needed to and when all that was stripped away, they didn't really need their own config files. Now what little config they need is provided by the users of these basic services so that said users can make their own decisions about where to obtain configuration information. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1820 542714f4-19e9-0310-aa3c-eee0fc999fb1
387 lines
13 KiB
Java
387 lines
13 KiB
Java
//
|
|
// $Id: ConnectionManager.java,v 1.22 2002/10/21 20:56:21 mdb Exp $
|
|
|
|
package com.threerings.presents.server.net;
|
|
|
|
import java.io.IOException;
|
|
import java.net.SocketException;
|
|
import java.util.ArrayList;
|
|
|
|
import ninja2.core.io_core.nbio.*;
|
|
import com.samskivert.util.*;
|
|
|
|
import com.threerings.io.FramingOutputStream;
|
|
import com.threerings.io.ObjectOutputStream;
|
|
|
|
import com.threerings.presents.Log;
|
|
import com.threerings.presents.client.Client;
|
|
|
|
import com.threerings.presents.net.AuthRequest;
|
|
import com.threerings.presents.net.AuthResponse;
|
|
import com.threerings.presents.net.DownstreamMessage;
|
|
|
|
import com.threerings.presents.server.Authenticator;
|
|
import com.threerings.presents.server.PresentsServer;
|
|
|
|
/**
|
|
* The connection manager manages the socket on which connections are
|
|
* received. It creates connection objects to manage each individual
|
|
* connection, but those connection objects interact closely with the
|
|
* connection manager because network I/O is done via a poll()-like
|
|
* mechanism rather than via threads.
|
|
*/
|
|
public class ConnectionManager extends LoopingThread
|
|
{
|
|
/**
|
|
* Constructs and initialized a connection manager (binding the socket
|
|
* on which it will listen for client connections).
|
|
*/
|
|
public ConnectionManager (int port)
|
|
throws IOException
|
|
{
|
|
_port = port;
|
|
_selset = new SelectSet();
|
|
|
|
try {
|
|
// create our listening socket and add it to the select set
|
|
_listener = new NonblockingServerSocket(_port);
|
|
} catch (SocketException se) {
|
|
Log.warning("Failure creating listen socket " +
|
|
"[port=" + _port + "].");
|
|
throw se;
|
|
}
|
|
|
|
_litem = new SelectItem(_listener, Selectable.ACCEPT_READY);
|
|
// when an ACCEPT_READY event happens, we do this:
|
|
_litem.obj = new NetEventHandler() {
|
|
public void handleEvent (Selectable item, short events) {
|
|
acceptConnection();
|
|
}
|
|
};
|
|
_selset.add(_litem);
|
|
|
|
// we'll use this for sending messages to clients
|
|
_framer = new FramingOutputStream();
|
|
}
|
|
|
|
/**
|
|
* Specifies the authenticator that should be used by the connection
|
|
* manager to authenticate logon requests.
|
|
*/
|
|
public void setAuthenticator (Authenticator author)
|
|
{
|
|
// say hello to our new authenticator
|
|
_author = author;
|
|
_author.setConnectionManager(this);
|
|
}
|
|
|
|
/**
|
|
* Returns the entity that is being used to authenticate connections.
|
|
*/
|
|
public Authenticator getAuthenticator ()
|
|
{
|
|
return _author;
|
|
}
|
|
|
|
/**
|
|
* Adds the specified connection observer to the observers list.
|
|
* Connection observers will be notified of connection-related
|
|
* events. An observer will not be added to the list twice.
|
|
*
|
|
* @see ConnectionObserver
|
|
*/
|
|
public void addConnectionObserver (ConnectionObserver observer)
|
|
{
|
|
synchronized (_observers) {
|
|
if (!_observers.contains(observer)) {
|
|
_observers.add(observer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the specified connection observer from the observers list.
|
|
*/
|
|
public void removeConnectionObserver (ConnectionObserver observer)
|
|
{
|
|
synchronized (_observers) {
|
|
_observers.remove(observer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queues a connection up to be closed on the conmgr thread.
|
|
*/
|
|
public void closeConnection (Connection conn)
|
|
{
|
|
_deathq.append(conn);
|
|
}
|
|
|
|
/**
|
|
* Called by the authenticator to indicate that a connection was
|
|
* successfully authenticated.
|
|
*/
|
|
public void connectionDidAuthenticate (Connection conn)
|
|
{
|
|
// slap this sucker onto the authenticated connections queue
|
|
_authq.append(conn);
|
|
}
|
|
|
|
/**
|
|
* Notifies the connection observers of a connection event. Used
|
|
* internally.
|
|
*/
|
|
protected void notifyObservers (
|
|
int code, Connection conn, Object arg1, Object arg2)
|
|
{
|
|
synchronized (_observers) {
|
|
for (int i = 0; i < _observers.size(); i++) {
|
|
ConnectionObserver obs =
|
|
(ConnectionObserver)_observers.get(i);
|
|
switch (code) {
|
|
case CONNECTION_ESTABLISHED:
|
|
obs.connectionEstablished(conn, (AuthRequest)arg1,
|
|
(AuthResponse)arg2);
|
|
break;
|
|
case CONNECTION_FAILED:
|
|
obs.connectionFailed(conn, (IOException)arg1);
|
|
break;
|
|
case CONNECTION_CLOSED:
|
|
obs.connectionClosed(conn);
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Invalid code supplied to " +
|
|
"notifyObservers: " + code);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// documentation inherited
|
|
protected void willStart ()
|
|
{
|
|
Log.info("Connection Manager listening [port=" + _port + "].");
|
|
}
|
|
|
|
/**
|
|
* Performs the select loop. This is the body of the conmgr thread.
|
|
*/
|
|
protected void iterate ()
|
|
{
|
|
// close any connections that have been queued up to die
|
|
Connection dconn;
|
|
while ((dconn = (Connection)_deathq.getNonBlocking()) != null) {
|
|
// it's possible that we caught an EOF trying to read from
|
|
// this connection even after it was queued up for death, so
|
|
// let's avoid trying to close it twice
|
|
if (!dconn.isClosed()) {
|
|
dconn.close();
|
|
}
|
|
}
|
|
|
|
// send any messages that are waiting on the outgoing queue
|
|
Tuple tup;
|
|
while ((tup = (Tuple)_outq.getNonBlocking()) != null) {
|
|
Connection conn = (Connection)tup.left;
|
|
// if the connection to which this message is destined is
|
|
// closed, drop the message and move along quietly; this is
|
|
// perfectly legal, a user can logoff whenever they like, even
|
|
// if we still have things to tell them; such is life in a
|
|
// fully asynchronous distributed system
|
|
if (conn.isClosed()) {
|
|
continue;
|
|
}
|
|
|
|
DownstreamMessage outmsg = (DownstreamMessage)tup.right;
|
|
try {
|
|
// write the message via the connection's object output
|
|
// stream (which is configured to write data to our
|
|
// framing output stream)
|
|
ObjectOutputStream oout = conn.getObjectOutputStream(_framer);
|
|
// Log.info("Sending " + outmsg + ".");
|
|
oout.writeObject(outmsg);
|
|
oout.flush();
|
|
|
|
// then write framed message to real output stream
|
|
_framer.writeFrameAndReset(conn.getOutputStream());
|
|
|
|
} catch (IOException ioe) {
|
|
// instruct the connection to deal with its failure
|
|
conn.handleFailure(ioe);
|
|
}
|
|
}
|
|
|
|
// check for connections that have completed authentication
|
|
AuthingConnection conn;
|
|
while ((conn = (AuthingConnection)_authq.getNonBlocking()) != null) {
|
|
// remove the old connection from the select set
|
|
_selset.remove(conn.getSelectItem());
|
|
|
|
// construct a new running connection to handle this
|
|
// connections network traffic from here on out
|
|
try {
|
|
RunningConnection rconn =
|
|
new RunningConnection(this, conn.getSocket());
|
|
// we need to keep using the same object input and output
|
|
// streams from the beginning of the session because they
|
|
// have contextual state that needs to be preserved
|
|
rconn.inheritStreams(conn);
|
|
// wire this connection up to receive network events
|
|
_selset.add(rconn.getSelectItem());
|
|
|
|
// and let our observers know about our new connection
|
|
notifyObservers(CONNECTION_ESTABLISHED, rconn,
|
|
conn.getAuthRequest(), conn.getAuthResponse());
|
|
|
|
} catch (IOException ioe) {
|
|
Log.warning("Failure upgrading authing connection to " +
|
|
"running connection.");
|
|
Log.logStackTrace(ioe);
|
|
}
|
|
}
|
|
|
|
// check for incoming network events
|
|
int ecount = _selset.select(SELECT_LOOP_TIME);
|
|
if (ecount == 0) {
|
|
return;
|
|
}
|
|
|
|
// process those events
|
|
SelectItem[] active = _selset.getEvents();
|
|
for (int i = 0; i < active.length; i++) {
|
|
try {
|
|
SelectItem item = active[i];
|
|
NetEventHandler handler = (NetEventHandler)item.obj;
|
|
handler.handleEvent(item.getSelectable(), item.revents);
|
|
|
|
} catch (Exception e) {
|
|
Log.warning("Error processing network data.");
|
|
Log.logStackTrace(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// documentation inherited
|
|
protected void handleIterateFailure (Exception e)
|
|
{
|
|
// log the exception
|
|
Log.warning("ConnectionManager.iterate() uncaught exception.");
|
|
Log.logStackTrace(e);
|
|
}
|
|
|
|
// documentation inherited
|
|
protected void didShutdown ()
|
|
{
|
|
Log.info("Connection Manager thread exited.");
|
|
}
|
|
|
|
/**
|
|
* Called by our net event handler when a new connection is ready to
|
|
* be accepted on our listening socket.
|
|
*/
|
|
protected void acceptConnection ()
|
|
{
|
|
NonblockingSocket socket = null;
|
|
|
|
try {
|
|
socket = _listener.nbAccept();
|
|
if (socket == null) {
|
|
// in theory this shouldn't happen because we got an
|
|
// ACCEPT_READY event, but better safe than sorry
|
|
// Log.info("Psych! Got ACCEPT_READY, but no connection.");
|
|
return;
|
|
}
|
|
|
|
// create a new authing connection object to manage the
|
|
// authentication of this client connection
|
|
AuthingConnection acon = new AuthingConnection(this, socket);
|
|
|
|
// wire this connection into the select set
|
|
_selset.add(acon.getSelectItem());
|
|
|
|
} catch (IOException ioe) {
|
|
Log.warning("Failure accepting new connection: " + ioe);
|
|
|
|
// make sure we don't leak a socket if something went awry
|
|
if (socket != null) {
|
|
try {
|
|
socket.close();
|
|
} catch (IOException ioe2) {
|
|
Log.warning("Failed closing aborted connection: " + ioe2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by a connection when it has a downstream message that needs
|
|
* to be delivered.
|
|
*/
|
|
void postMessage (Connection conn, DownstreamMessage msg)
|
|
{
|
|
// sanity check
|
|
if (conn == null || msg == null) {
|
|
Log.warning("Bogosity.");
|
|
Thread.dumpStack();
|
|
|
|
} else {
|
|
// slap both these suckers onto the outgoing message queue
|
|
_outq.append(new Tuple(conn, msg));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by a connection if it experiences a network failure.
|
|
*/
|
|
void connectionFailed (Connection conn, IOException ioe)
|
|
{
|
|
// remove this connection from the select set
|
|
_selset.remove(conn.getSelectItem());
|
|
|
|
// let our observers know what's up
|
|
notifyObservers(CONNECTION_FAILED, conn, ioe, null);
|
|
}
|
|
|
|
/**
|
|
* Called by a connection when it discovers that it's closed.
|
|
*/
|
|
void connectionClosed (Connection conn)
|
|
{
|
|
// remove this connection from the select set
|
|
_selset.remove(conn.getSelectItem());
|
|
|
|
// let our observers know what's up
|
|
notifyObservers(CONNECTION_CLOSED, conn, null, null);
|
|
}
|
|
|
|
protected int _port;
|
|
protected Authenticator _author;
|
|
protected SelectSet _selset;
|
|
|
|
protected NonblockingServerSocket _listener;
|
|
protected SelectItem _litem;
|
|
|
|
protected Queue _deathq = new Queue();
|
|
|
|
protected Queue _outq = new Queue();
|
|
protected FramingOutputStream _framer;
|
|
|
|
protected Queue _authq = new Queue();
|
|
|
|
protected ArrayList _observers = new ArrayList();
|
|
|
|
/**
|
|
* How long we wait for network events before checking our running
|
|
* flag to see if we should still be running.
|
|
*/
|
|
// protected static final int SELECT_LOOP_TIME = 30 * 1000;
|
|
|
|
// while we're testing, use a short loop time
|
|
protected static final int SELECT_LOOP_TIME = 10;
|
|
|
|
// codes for notifyObservers()
|
|
protected static final int CONNECTION_ESTABLISHED = 0;
|
|
protected static final int CONNECTION_FAILED = 1;
|
|
protected static final int CONNECTION_CLOSED = 2;
|
|
}
|