Files
narya/src/java/com/threerings/presents/server/net/ConnectionManager.java
T
Michael Bayne a0e9044e93 Config clean up and unification. The base servers were doing more than
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
2002-10-21 20:56:21 +00:00

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;
}