Files
narya/src/java/com/threerings/presents/server/net/Connection.java
T
Michael Bayne d618d3c771 Eclipse suggested cleanup.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2971 542714f4-19e9-0310-aa3c-eee0fc999fb1
2004-02-25 14:50:28 +00:00

308 lines
9.3 KiB
Java

//
// $Id: Connection.java,v 1.18 2004/02/25 14:45:16 mdb Exp $
package com.threerings.presents.server.net;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import com.samskivert.io.NestableIOException;
import com.threerings.io.FramedInputStream;
import com.threerings.io.FramingOutputStream;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.presents.Log;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.PingRequest;
import com.threerings.presents.net.UpstreamMessage;
/**
* The base connection class implements the net event handler interface
* and processes raw incoming network data into a stream of parsed
* <code>UpstreamMessage</code> objects. It also provides the means to
* send messages to the client and facilities for checking delinquency.
*/
public abstract class Connection implements NetEventHandler
{
/**
* Constructs a connection object that is associated with the supplied
* socket.
*
* @param cmgr The connection manager with which this connection is
* associated.
* @param selkey the key used by the NIO code to track this
* connection.
* @param channel The socket channel from which we'll be reading
* messages.
* @param createStamp The time at which this connection was created.
*/
public Connection (ConnectionManager cmgr, SelectionKey selkey,
SocketChannel channel, long createStamp)
throws IOException
{
_cmgr = cmgr;
_selkey = selkey;
_channel = channel;
_lastEvent = createStamp;
}
/**
* 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 selection key associated with our socket channel.
*/
public SelectionKey getSelectionKey ()
{
return _selkey;
}
/**
* Returns the non-blocking socket object used to construct this
* connection.
*/
public SocketChannel getChannel ()
{
return _channel;
}
/**
* Returns the address associated with this connection or null if it
* has no underlying socket channel.
*/
public InetAddress getInetAddress ()
{
return (_channel == null) ? null : _channel.socket().getInetAddress();
}
/**
* Returns true if this connection is closed.
*/
public boolean isClosed ()
{
return (_channel == null);
}
/**
* 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 (isClosed()) {
Log.warning("Attempted to re-close connection " + this + ".");
Thread.dumpStack();
return;
}
// unregister from the select set
_cmgr.connectionClosed(this);
// close our socket
closeSocket();
}
/**
* Called when there is a failure reading or writing on this
* connection. We notify the connection manager and close ourselves
* down.
*/
public void handleFailure (IOException ioe)
{
// if we're already closed, then something is seriously funny
if (isClosed()) {
Log.warning("Failure reported on closed connection " + this + ".");
Thread.dumpStack();
return;
}
// let the connection manager know we're hosed
_cmgr.connectionFailed(this, ioe);
// and close our socket
closeSocket();
}
/**
* Returns the object input stream associated with this connection.
* This should only be used by the connection manager.
*/
protected ObjectInputStream getObjectInputStream ()
{
return _oin;
}
/**
* Instructs this connection to inherit its streams from the supplied
* connection object. This is called by the connection manager when
* the time comes to pass streams from the authing connection to the
* running connection.
*/
protected void inheritStreams (Connection other)
{
_fin = other._fin;
_oin = other._oin;
_oout = other._oout;
}
/**
* Returns the object output stream associated with this connection
* (creating it if necessary). This should only be used by the
* connection manager.
*/
protected ObjectOutputStream getObjectOutputStream (
FramingOutputStream fout)
{
// we're lazy about creating our output stream because we may be
// inheriting it from our authing connection and we don't want to
// unnecessarily create it in that case
if (_oout == null) {
_oout = new ObjectOutputStream(fout);
}
return _oout;
}
/**
* Sets the object output stream used by this connection. This should
* only be called by the connection manager.
*/
protected void setObjectOutputStream (ObjectOutputStream oout)
{
_oout = oout;
}
/**
* Closes the socket associated with this connection. This happens
* when we receive EOF, are requested to close down or when our
* connection fails.
*/
protected void closeSocket ()
{
if (_channel != null) {
Log.debug("Closing channel " + this + ".");
try {
_channel.close();
} catch (IOException ioe) {
Log.warning("Error closing connection [conn=" + this +
", error=" + ioe + "].");
}
// clear out our references to prevent repeat closings
_selkey = null;
_channel = null;
}
}
/**
* Called when our client socket has data available for reading.
*/
public int handleEvent (long when)
{
// make a note that we received an event as of this time
_lastEvent = when;
int bytesIn = 0;
try {
// we're lazy about creating our input streams because we may
// be inheriting them from our authing connection and we don't
// want to unnecessarily create them in that case
if (_fin == null) {
_fin = new FramedInputStream();
_oin = new ObjectInputStream(_fin);
}
// there may be more than one frame in the buffer, so we keep
// reading them until we run out of data
while (_fin.readFrame(_channel)) {
// make a note of how many bytes are in this frame
// (including the frame length bytes which aren't reported
// in available())
bytesIn = _fin.available() + 4;
// parse the message and pass it on
UpstreamMessage msg = (UpstreamMessage)_oin.readObject();
msg.received = when;
// Log.info("Read message " + msg + ".");
_handler.handleMessage(msg);
}
} catch (EOFException eofe) {
// close down the socket gracefully
close();
} catch (ClassNotFoundException cnfe) {
Log.warning("Error reading message from socket " +
"[channel=" + _channel + ", error=" + cnfe + "].");
// deal with the failure
handleFailure(new NestableIOException(
"Unable to decode incoming message.", cnfe));
} catch (IOException ioe) {
Log.warning("Error reading message from socket " +
"[channel=" + _channel + ", error=" + ioe + "].");
// deal with the failure
handleFailure(ioe);
}
return bytesIn;
}
// documentation inherited from interface
public boolean checkIdle (long now)
{
long idleMillis = now - _lastEvent;
if (idleMillis < PingRequest.PING_INTERVAL + LATENCY_GRACE) {
return false;
}
if (isClosed()) {
return true;
}
Log.info("Disconnecting non-communicative client [conn=" + this +
", idle=" + idleMillis + "ms].");
return true;
}
/**
* Posts a downstream message for delivery to this connection. The
* message will be delivered by the conmgr thread as soon as it gets
* to it.
*/
public void postMessage (DownstreamMessage msg)
{
// pass this along to the connection manager
_cmgr.postMessage(this, msg);
}
protected ConnectionManager _cmgr;
protected SelectionKey _selkey;
protected SocketChannel _channel;
protected long _lastEvent;
protected FramedInputStream _fin;
protected ObjectInputStream _oin;
protected ObjectOutputStream _oout;
protected MessageHandler _handler;
/** The number of milliseconds beyond the ping interval that we allow
* a client's network connection to be idle before we forcibly
* disconnect them. */
protected static final long LATENCY_GRACE = 30 * 1000L;
}