Make ConnectionManager a general-purpose select server and implement a flash Policy server with it.
The presents-specific portions of ConnectionManager like reading Message subclasses and sending datagrams are now in PresentsConnectionManager. PresentsServer should behave identically, except during shutdown. Before, connections would be accepted and datagrams would be read until the OMgr thread exited. Now, both stop as soon as the server begins its shutdown. This seems like an improvement to me, but let me know if it causes issues and I can rework it. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6270 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -44,7 +44,7 @@ import com.threerings.presents.server.InvocationManager;
|
||||
import com.threerings.presents.server.PresentsSession;
|
||||
import com.threerings.presents.server.ServiceAuthenticator;
|
||||
import com.threerings.presents.server.SessionFactory;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
import com.threerings.presents.server.net.PresentsConnectionManager;
|
||||
|
||||
import com.threerings.bureau.data.AgentObject;
|
||||
import com.threerings.bureau.data.BureauAuthName;
|
||||
@@ -100,7 +100,7 @@ public class BureauRegistry
|
||||
* Creates an uninitialized registry.
|
||||
*/
|
||||
@Inject public BureauRegistry (
|
||||
InvocationManager invmgr, ConnectionManager conmgr, ClientManager clmgr)
|
||||
InvocationManager invmgr, PresentsConnectionManager conmgr, ClientManager clmgr)
|
||||
{
|
||||
invmgr.registerDispatcher(new BureauDispatcher(new BureauProvider() {
|
||||
public void bureauInitialized (ClientObject client, String bureauId) {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Lifecycle;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
|
||||
import com.threerings.nio.conman.Connection;
|
||||
import com.threerings.nio.conman.ConnectionManager;
|
||||
import com.threerings.nio.conman.ServerSocketChannelAcceptor;
|
||||
|
||||
import static com.threerings.NaryaLog.log;
|
||||
|
||||
/**
|
||||
* Binds to a port and responds to "xmlsocket" requests on it with a policy file allowing access
|
||||
* to a given set of ports on a given hostname.
|
||||
*/
|
||||
@Singleton
|
||||
public class PolicyServer extends ConnectionManager
|
||||
{
|
||||
@Inject
|
||||
public PolicyServer (Lifecycle cycle, ReportManager mgr)
|
||||
throws IOException
|
||||
{
|
||||
super(cycle, mgr);
|
||||
}
|
||||
|
||||
public void init (int socketPolicyPort, String publicServerHost, int[] serverPorts,
|
||||
int gameServerPort)
|
||||
throws IOException
|
||||
{
|
||||
_acceptor =
|
||||
new ServerSocketChannelAcceptor(publicServerHost, new int[] { socketPolicyPort }, this);
|
||||
|
||||
// build the XML once and for all
|
||||
StringBuilder policy = new StringBuilder("<?xml version=\"1.0\"?>\n").
|
||||
append("<cross-domain-policy>\n");
|
||||
// if we're running on 843, serve a master policy file
|
||||
if (socketPolicyPort == MASTER_PORT) {
|
||||
policy.append(" <site-control permitted-cross-domain-policies=\"master-only\"/>\n");
|
||||
}
|
||||
|
||||
for (String host : new String[] { publicServerHost }) {
|
||||
policy.append(" <allow-access-from domain=\"").append(host).append("\"");
|
||||
policy.append(" to-ports=\"");
|
||||
// allow Flash connections on our server & game ports
|
||||
/* TODO - for now we've just opened it up.
|
||||
for (int port : serverPorts) {
|
||||
policyBuilder.append(port).append(",");
|
||||
}
|
||||
policyBuilder.append(gameServerPort).append("\"/>\n");
|
||||
*/
|
||||
policy.append("*\"/>\n");
|
||||
}
|
||||
policy.append("</cross-domain-policy>\n");
|
||||
|
||||
_policy = policy.toString().getBytes(Charsets.UTF_8);
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void willStart ()
|
||||
{
|
||||
super.willStart();
|
||||
if (!_acceptor.bind()) {
|
||||
log.warning("Policy server failed to bind!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void didShutdown ()
|
||||
{
|
||||
super.didShutdown();
|
||||
_acceptor.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAcceptedSocket (SocketChannel channel)
|
||||
{
|
||||
Connection conn = new Connection() {
|
||||
@Override public int handleEvent (long when) {
|
||||
// Ignore incoming data. Should just be "<policy-file-request/>"
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
handleAcceptedSocket(channel, conn);
|
||||
_outq.append(Tuple.newTuple(conn, _policy));
|
||||
postAsyncClose(conn);
|
||||
}
|
||||
|
||||
protected ServerSocketChannelAcceptor _acceptor;
|
||||
|
||||
protected byte[] _policy;
|
||||
|
||||
protected static final int MASTER_PORT = 843;
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.nio.conman;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Implements the net event handler interface to check for delinquency and manages a client
|
||||
* connection. Subclasses must handle incoming data in {@link #handleEvent}.
|
||||
*/
|
||||
public abstract class Connection implements NetEventHandler
|
||||
{
|
||||
/** The key used by the NIO code to track this connection. */
|
||||
public SelectionKey selkey;
|
||||
|
||||
/**
|
||||
* Initializes a connection object with a socket and related info.
|
||||
*
|
||||
* @param cmgr The connection manager with which this connection is associated.
|
||||
* @param channel The socket channel from which we'll be reading messages.
|
||||
* @param createStamp The time at which this connection was created.
|
||||
*/
|
||||
public void init (ConnectionManager cmgr, SocketChannel channel, long createStamp)
|
||||
throws IOException
|
||||
{
|
||||
_cmgr = cmgr;
|
||||
_channel = channel;
|
||||
_lastEvent = createStamp;
|
||||
_connectionId = ++_lastConnectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection's unique identifier.
|
||||
*/
|
||||
public int getConnectionId ()
|
||||
{
|
||||
return _connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 + ".", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// unregister from the select set
|
||||
_cmgr.connectionClosed(this);
|
||||
|
||||
// close our socket
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a request to have this connection closed by the connection manager once all
|
||||
* messages in its queue have been written to its target.
|
||||
*/
|
||||
public void asyncClose ()
|
||||
{
|
||||
_cmgr.postAsyncClose(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an outgoing socket experiences a connect failure. The connection manager will
|
||||
* have cleaned up the partial registration needed during the connect process, so we are only
|
||||
* responsible for closing our socket.
|
||||
*/
|
||||
public void connectFailure (IOException ioe)
|
||||
{
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is a failure reading or writing to this connection. We notify the
|
||||
* connection manager and close ourselves down.
|
||||
*/
|
||||
public void networkFailure (IOException ioe)
|
||||
{
|
||||
// if we're already closed, then something is seriously funny
|
||||
if (isClosed()) {
|
||||
log.warning("Failure reported on closed connection " + this + ".", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// let the connection manager know we're hosed
|
||||
_cmgr.connectionFailed(this, ioe);
|
||||
|
||||
// and close our socket
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
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;
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
public void becameIdle ()
|
||||
{
|
||||
_cmgr.closeConnection(this);
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public String toString ()
|
||||
{
|
||||
return "[id=" + (hashCode() % 1000) + ", addr=" + getInetAddress() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
_channel = null;
|
||||
}
|
||||
|
||||
protected ConnectionManager _cmgr;
|
||||
protected SocketChannel _channel;
|
||||
|
||||
protected long _lastEvent;
|
||||
|
||||
protected int _connectionId;
|
||||
|
||||
/** The last connection id assigned. */
|
||||
protected static int _lastConnectionId;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
+37
-368
@@ -19,77 +19,52 @@
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
package com.threerings.nio.conman;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import com.samskivert.util.IntMap;
|
||||
import com.samskivert.util.IntMaps;
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
import com.samskivert.util.LoopingThread;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.ResultListener;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.io.ByteBufferInputStream;
|
||||
import com.threerings.io.FramingOutputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.UnreliableObjectInputStream;
|
||||
import com.threerings.io.UnreliableObjectOutputStream;
|
||||
import com.threerings.presents.data.ConMgrStats;
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
import com.threerings.nio.SelectorIterable;
|
||||
|
||||
import com.threerings.presents.annotation.AuthInvoker;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.ConMgrStats;
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
import com.threerings.presents.net.Transport;
|
||||
import com.threerings.presents.server.Authenticator;
|
||||
import com.threerings.presents.server.ChainedAuthenticator;
|
||||
import com.threerings.presents.server.ClientManager;
|
||||
import com.threerings.presents.server.DummyAuthenticator;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr;
|
||||
import com.threerings.presents.server.PresentsServer;
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
import com.threerings.presents.util.DatagramSequencer;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
import static com.threerings.NaryaLog.log;
|
||||
|
||||
/**
|
||||
* Manages socket connections. It creates connection objects for each socket 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.<p>
|
||||
*
|
||||
* ConnectionManager doesn't directly accept TCP connections; it expects a custom derived class or
|
||||
* external entity to do so and call its {@link #handleAcceptedSocket} method.
|
||||
* See {@link BindingConnectionManager} for the standalone implementation.
|
||||
* ConnectionManager doesn't directly accept TCP connections; it expects
|
||||
* {@link ServerSocketChannelAcceptor} or an external entity to do so and call its
|
||||
* {@link #handleAcceptedSocket} method
|
||||
*/
|
||||
@Singleton
|
||||
public class ConnectionManager extends LoopingThread
|
||||
public abstract class ConnectionManager extends LoopingThread
|
||||
implements Lifecycle.ShutdownComponent, ReportManager.Reporter
|
||||
{
|
||||
/**
|
||||
* Creates a connection manager instance.
|
||||
*/
|
||||
@Inject public ConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
public ConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
throws IOException
|
||||
{
|
||||
super("ConnectionManager");
|
||||
@@ -98,15 +73,6 @@ public class ConnectionManager extends LoopingThread
|
||||
_selector = Selector.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticator to the authentication chain. This authenticator will be offered a
|
||||
* chance to authenticate incoming connections before falling back to the main authenticator.
|
||||
*/
|
||||
public void addChainedAuthenticator (ChainedAuthenticator author)
|
||||
{
|
||||
_authors.add(author);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs us to execute the specified runnable when the connection manager thread exits.
|
||||
* <em>Note:</em> this will be executed on the connection manager thread, so don't do anything
|
||||
@@ -127,7 +93,6 @@ public class ConnectionManager extends LoopingThread
|
||||
// fill in our snapshot values
|
||||
_stats.connectionCount = _connections.size();
|
||||
_stats.handlerCount = _handlers.size();
|
||||
_stats.authQueueSize = _authq.size();
|
||||
_stats.deathQueueSize = _deathq.size();
|
||||
_stats.outQueueSize = _outq.size();
|
||||
if (_oflowqs.size() > 0) {
|
||||
@@ -139,6 +104,18 @@ public class ConnectionManager extends LoopingThread
|
||||
return _stats.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers <code>ops</code> on <code>chan</code> on this manager's selector and hooks
|
||||
* <code>netEventHandler</code> up to receive events whenever the selection occurs.
|
||||
*/
|
||||
public SelectionKey register (SelectableChannel chan, int ops, NetEventHandler netEventHandler)
|
||||
throws IOException
|
||||
{
|
||||
SelectionKey key = chan.register(_selector, ops);
|
||||
_handlers.put(key, netEventHandler);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Introduces a new active socket into Presents from off the ConnectionManager thread. If
|
||||
* Presents is embedded in another framework that handles socket acceptance, this will be
|
||||
@@ -149,32 +126,6 @@ public class ConnectionManager extends LoopingThread
|
||||
_acceptedq.append(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an outgoing connection to the supplied address. The connection will be opened in a
|
||||
* non-blocking manner and added to the connection manager's select set. Messages posted to the
|
||||
* connection prior to it being actually connected to its destination will remain in the queue.
|
||||
* If the connection fails those messages will be dropped.
|
||||
*
|
||||
* @param conn the connection to be initialized and opened. Callers may want to provide a
|
||||
* {@link Connection} derived class so that they may intercept calldown methods.
|
||||
* @param hostname the hostname of the server to which to connect.
|
||||
* @param port the port on which to connect to the server.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs creating our socket. Everything else
|
||||
* happens asynchronously. If the connection attempt fails, the Connection will be notified via
|
||||
* {@link Connection#networkFailure}.
|
||||
*/
|
||||
public void openOutgoingConnection (Connection conn, String hostname, int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a socket channel to use for this connection, initialize it and queue it up to
|
||||
// have the non-blocking connect process started
|
||||
SocketChannel sockchan = SocketChannel.open();
|
||||
sockchan.configureBlocking(false);
|
||||
conn.init(this, sockchan, System.currentTimeMillis());
|
||||
_connectq.append(Tuple.newTuple(conn, new InetSocketAddress(hostname, port)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a connection up to be closed on the conmgr thread.
|
||||
*/
|
||||
@@ -228,13 +179,6 @@ public class ConnectionManager extends LoopingThread
|
||||
report.append(bytesOut*1000/sinceLast).append(" bps\n");
|
||||
}
|
||||
|
||||
@Override // from LoopingThread
|
||||
public boolean isRunning ()
|
||||
{
|
||||
// Prevent exiting our thread until the object manager is done.
|
||||
return super.isRunning() || _omgr.isRunning();
|
||||
}
|
||||
|
||||
@Override // from LoopingThread
|
||||
protected void willStart ()
|
||||
{
|
||||
@@ -262,6 +206,7 @@ public class ConnectionManager extends LoopingThread
|
||||
_lastDebugStamp = iterStamp;
|
||||
}
|
||||
|
||||
|
||||
// close any connections that have been queued up to die
|
||||
Connection dconn;
|
||||
while ((dconn = _deathq.getNonBlocking()) != null) {
|
||||
@@ -289,21 +234,7 @@ public class ConnectionManager extends LoopingThread
|
||||
// those last moments we don't want to accept new connections or read any incoming messages
|
||||
if (super.isRunning()) {
|
||||
|
||||
SocketChannel accepted;
|
||||
while ((accepted = _acceptedq.getNonBlocking()) != null) {
|
||||
handleAcceptedSocket(accepted);
|
||||
}
|
||||
// start up any outgoing connections that need to be connected
|
||||
Tuple<Connection, InetSocketAddress> pconn;
|
||||
while ((pconn = _connectq.getNonBlocking()) != null) {
|
||||
startOutgoingConnection(pconn.left, pconn.right);
|
||||
}
|
||||
|
||||
// check for connections that have completed authentication
|
||||
processAuthedConnections(iterStamp);
|
||||
|
||||
// listen for and process incoming network events
|
||||
processIncomingEvents(iterStamp);
|
||||
handleIncoming(iterStamp);
|
||||
}
|
||||
|
||||
if (DEBUG_REPORT && generateDebugReport) {
|
||||
@@ -311,109 +242,31 @@ public class ConnectionManager extends LoopingThread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the authentication process on the specified connection. This is called by {@link
|
||||
* AuthingConnection} itself once it receives its auth request.
|
||||
*/
|
||||
protected void authenticateConnection (AuthingConnection conn)
|
||||
{
|
||||
Authenticator author = _author;
|
||||
for (ChainedAuthenticator cauthor : _authors) {
|
||||
if (cauthor.shouldHandleConnection(conn)) {
|
||||
author = cauthor;
|
||||
break;
|
||||
}
|
||||
protected void handleIncoming(long iterStamp) {
|
||||
|
||||
SocketChannel accepted;
|
||||
while ((accepted = _acceptedq.getNonBlocking()) != null) {
|
||||
handleAcceptedSocket(accepted);
|
||||
}
|
||||
|
||||
author.authenticateConnection(_authInvoker, conn, new ResultListener<AuthingConnection>() {
|
||||
public void requestCompleted (AuthingConnection conn) {
|
||||
_authq.append(conn);
|
||||
}
|
||||
public void requestFailed (Exception cause) {
|
||||
// this never happens
|
||||
}
|
||||
});
|
||||
// listen for and process incoming network events
|
||||
processIncomingEvents(iterStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a datagram sequencer for use by a {@link Connection}.
|
||||
* Adds a connection for the given socket to the managed set.
|
||||
*/
|
||||
protected DatagramSequencer createDatagramSequencer ()
|
||||
{
|
||||
return new DatagramSequencer(
|
||||
new UnreliableObjectInputStream(new ByteBufferInputStream(_databuf)),
|
||||
new UnreliableObjectOutputStream(_flattener));
|
||||
}
|
||||
protected abstract void handleAcceptedSocket (SocketChannel channel);
|
||||
|
||||
/**
|
||||
* Starts the connection process for an outgoing connection. This is called as part of the
|
||||
* conmgr tick for any pending outgoing connections.
|
||||
*/
|
||||
protected void startOutgoingConnection (final Connection conn, InetSocketAddress addr)
|
||||
{
|
||||
final SocketChannel sockchan = conn.getChannel();
|
||||
try {
|
||||
// register our channel with the selector (if this fails, we abandon ship immediately)
|
||||
conn.selkey = sockchan.register(_selector, SelectionKey.OP_CONNECT);
|
||||
|
||||
// start our connection process (now if we fail we need to clean things up)
|
||||
NetEventHandler handler;
|
||||
if (sockchan.connect(addr)) {
|
||||
// it is possible even for a non-blocking socket to connect immediately, in which
|
||||
// case we stick the connection in as its event handler immediately
|
||||
handler = conn;
|
||||
|
||||
} else {
|
||||
// otherwise we wire up a special event handler that will wait for our socket to
|
||||
// finish the connection process and then wire things up fully
|
||||
handler = new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
try {
|
||||
if (sockchan.finishConnect()) {
|
||||
// great, we're ready to roll, wire up the connection
|
||||
conn.selkey = sockchan.register(_selector, SelectionKey.OP_READ);
|
||||
_handlers.put(conn.selkey, conn);
|
||||
log.info("Outgoing connection ready", "conn", conn);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
handleError(ioe);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return conn.checkIdle(now);
|
||||
}
|
||||
public void becameIdle () {
|
||||
handleError(new IOException("Pending connection became idle."));
|
||||
}
|
||||
protected void handleError (IOException ioe) {
|
||||
_handlers.remove(conn.selkey);
|
||||
_oflowqs.remove(conn);
|
||||
conn.connectFailure(ioe);
|
||||
}
|
||||
};
|
||||
}
|
||||
_handlers.put(conn.selkey, handler);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to initiate connection for " + sockchan + ".", ioe);
|
||||
conn.connectFailure(ioe); // nothing else to clean up
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an accepted socket down the path to authorization.
|
||||
*/
|
||||
protected void handleAcceptedSocket (SocketChannel channel)
|
||||
protected void handleAcceptedSocket (SocketChannel channel, Connection conn)
|
||||
{
|
||||
try {
|
||||
// create a new authing connection object to manage the authentication of this client
|
||||
// connection and register it with our selection set
|
||||
channel.configureBlocking(false);
|
||||
AuthingConnection aconn = new AuthingConnection();
|
||||
aconn.selkey = channel.register(_selector, SelectionKey.OP_READ);
|
||||
aconn.init(this, channel, System.currentTimeMillis());
|
||||
_handlers.put(aconn.selkey, aconn);
|
||||
conn.init(this, channel, System.currentTimeMillis());
|
||||
conn.selkey = register(channel, SelectionKey.OP_READ, conn);
|
||||
_handlers.put(conn.selkey, conn);
|
||||
synchronized (this) {
|
||||
_stats.connects++;
|
||||
}
|
||||
@@ -430,48 +283,6 @@ public class ConnectionManager extends LoopingThread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts connections that have completed the authentication process into full running
|
||||
* connections and notifies the client manager that new connections have been established.
|
||||
*/
|
||||
protected void processAuthedConnections (long iterStamp)
|
||||
{
|
||||
AuthingConnection conn;
|
||||
while ((conn = _authq.getNonBlocking()) != null) {
|
||||
try {
|
||||
// construct a new running connection to handle this connections network traffic
|
||||
// from here on out
|
||||
Connection rconn = new Connection();
|
||||
rconn.init(this, conn.getChannel(), iterStamp);
|
||||
rconn.selkey = conn.selkey;
|
||||
|
||||
// we need to keep using the same object input and output streams from the
|
||||
// beginning of the session because they have context that needs to be preserved
|
||||
rconn.inheritStreams(conn);
|
||||
|
||||
// replace the mapping in the handlers table from the old conn with the new one
|
||||
_handlers.put(rconn.selkey, rconn);
|
||||
|
||||
// add a mapping for the connection id and set the datagram secret
|
||||
_connections.put(rconn.getConnectionId(), rconn);
|
||||
rconn.setDatagramSecret(conn.getAuthRequest().getCredentials().getDatagramSecret());
|
||||
|
||||
// transfer any overflow queue for that connection
|
||||
OverflowQueue oflowHandler = _oflowqs.remove(conn);
|
||||
if (oflowHandler != null) {
|
||||
_oflowqs.put(rconn, oflowHandler);
|
||||
}
|
||||
|
||||
// and let the client manager know about our new connection
|
||||
_clmgr.connectionEstablished(rconn, conn.getAuthName(), conn.getAuthRequest(),
|
||||
conn.getAuthResponse());
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure upgrading authing connection to running.", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for any network events on our set of sockets and passes those events down to their
|
||||
* associated {@link NetEventHandler}s for processing.
|
||||
@@ -568,11 +379,6 @@ public class ConnectionManager extends LoopingThread
|
||||
// otherwise write the message out to the client directly
|
||||
writeMessage(conn, tup.right, _oflowHandler);
|
||||
}
|
||||
|
||||
// send any datagrams
|
||||
while ((tup = _dataq.getNonBlocking()) != null) {
|
||||
writeDatagram(tup.left, tup.right);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -652,29 +458,6 @@ public class ConnectionManager extends LoopingThread
|
||||
return fully;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a datagram to the specified connection.
|
||||
*
|
||||
* @return true if the datagram was sent, false if we failed to send for any reason.
|
||||
*/
|
||||
protected boolean writeDatagram (Connection conn, byte[] data)
|
||||
{
|
||||
InetSocketAddress target = conn.getDatagramAddress();
|
||||
if (target == null) {
|
||||
log.warning("No address to send datagram", "conn", conn);
|
||||
return false;
|
||||
}
|
||||
|
||||
_databuf.clear();
|
||||
_databuf.put(data).flip();
|
||||
try {
|
||||
return conn.getDatagramChannel().send(_databuf, target) > 0;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to send datagram.", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Called by {@link #writeMessage} and friends when they write data over the network. */
|
||||
protected synchronized void noteWrite (int msgs, int bytes)
|
||||
{
|
||||
@@ -682,96 +465,6 @@ public class ConnectionManager extends LoopingThread
|
||||
_stats.bytesOut += bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a connection when it has a downstream message that needs to be delivered.
|
||||
* <em>Note:</em> this method is called as a result of a call to {@link Connection#postMessage}
|
||||
* which happens when forwarding an event to a client and at the completion of authentication,
|
||||
* both of which <em>must</em> happen only on the distributed object thread.
|
||||
*/
|
||||
protected void postMessage (Connection conn, Message msg)
|
||||
{
|
||||
if (!isRunning()) {
|
||||
log.warning("Posting message to inactive connection manager",
|
||||
"msg", msg, new Exception());
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (conn == null || msg == null) {
|
||||
log.warning("postMessage() bogosity", "conn", conn, "msg", msg, new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// more sanity check; messages must only be posted from the dobjmgr thread
|
||||
if (!_omgr.isDispatchThread()) {
|
||||
log.warning("Message posted on non-distributed object thread", "conn", conn,
|
||||
"msg", msg, "thread", Thread.currentThread(), new Exception());
|
||||
// let it through though as we don't want to break things unnecessarily
|
||||
}
|
||||
|
||||
try {
|
||||
// send it as a datagram if hinted and possible (pongs must be sent as part of the
|
||||
// negotation process)
|
||||
if (!msg.getTransport().isReliable() &&
|
||||
(conn.getTransmitDatagrams() || msg instanceof PongResponse) &&
|
||||
postDatagram(conn, msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note the actual transport
|
||||
msg.noteActualTransport(Transport.RELIABLE_ORDERED);
|
||||
|
||||
_framer.resetFrame();
|
||||
|
||||
// flatten this message using the connection's output stream
|
||||
ObjectOutputStream oout = conn.getObjectOutputStream(_framer);
|
||||
oout.writeObject(msg);
|
||||
oout.flush();
|
||||
|
||||
// now extract that data into a byte array
|
||||
ByteBuffer buffer = _framer.frameAndReturnBuffer();
|
||||
byte[] data = new byte[buffer.limit()];
|
||||
buffer.get(data);
|
||||
// log.info("Flattened " + msg + " into " + data.length + " bytes.");
|
||||
|
||||
// and slap both on the queue
|
||||
_outq.append(Tuple.newTuple(conn, data));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure flattening message", "conn", conn, "msg", msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for {@link #postMessage}; handles posting the message as a datagram.
|
||||
*
|
||||
* @return true if the datagram was successfully posted, false if it was too big.
|
||||
*/
|
||||
protected boolean postDatagram (Connection conn, Message msg)
|
||||
throws Exception
|
||||
{
|
||||
_flattener.reset();
|
||||
|
||||
// flatten the message using the connection's sequencer
|
||||
DatagramSequencer sequencer = conn.getDatagramSequencer();
|
||||
sequencer.writeDatagram(msg);
|
||||
|
||||
// if the message is too big, we must fall back to sending it through the stream channel
|
||||
if (_flattener.size() > Client.MAX_DATAGRAM_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// note the actual transport
|
||||
msg.noteActualTransport(Transport.UNRELIABLE_UNORDERED);
|
||||
|
||||
// extract as a byte array
|
||||
byte[] data = _flattener.toByteArray();
|
||||
|
||||
// slap it on the queue
|
||||
_dataq.append(Tuple.newTuple(conn, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a fake message to this connection's outgoing message queue that will cause the
|
||||
* connection to be closed when this message is reached. This is only used by outgoing
|
||||
@@ -798,9 +491,6 @@ public class ConnectionManager extends LoopingThread
|
||||
synchronized (this) {
|
||||
_stats.disconnects++;
|
||||
}
|
||||
|
||||
// let the client manager know what's up
|
||||
_clmgr.connectionFailed(conn, ioe);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -816,9 +506,6 @@ public class ConnectionManager extends LoopingThread
|
||||
synchronized (this) {
|
||||
_stats.closes++;
|
||||
}
|
||||
|
||||
// let the client manager know what's up
|
||||
_clmgr.connectionClosed(conn);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -963,12 +650,6 @@ public class ConnectionManager extends LoopingThread
|
||||
}
|
||||
};
|
||||
|
||||
/** Handles client authentication. The base authenticator is injected but optional services
|
||||
* like the PeerManager may replace this authenticator with one that intercepts certain types
|
||||
* of authentication and then passes normal authentications through. */
|
||||
@Inject(optional=true) protected Authenticator _author = new DummyAuthenticator();
|
||||
protected List<ChainedAuthenticator> _authors = Lists.newArrayList();
|
||||
|
||||
protected Selector _selector;
|
||||
protected SelectorIterable _selectorSelector;
|
||||
|
||||
@@ -979,17 +660,11 @@ public class ConnectionManager extends LoopingThread
|
||||
protected IntMap<Connection> _connections = IntMaps.newHashIntMap();
|
||||
|
||||
protected Queue<Connection> _deathq = Queue.newQueue();
|
||||
protected Queue<AuthingConnection> _authq = Queue.newQueue();
|
||||
protected Queue<Tuple<Connection, InetSocketAddress>> _connectq = Queue.newQueue();
|
||||
protected Queue<SocketChannel> _acceptedq = Queue.newQueue();
|
||||
|
||||
protected Queue<Tuple<Connection, byte[]>> _outq = Queue.newQueue();
|
||||
protected Queue<Tuple<Connection, byte[]>> _dataq = Queue.newQueue();
|
||||
|
||||
protected FramingOutputStream _framer = new FramingOutputStream();
|
||||
protected ByteArrayOutputStream _flattener = new ByteArrayOutputStream();
|
||||
protected ByteBuffer _outbuf = ByteBuffer.allocateDirect(64 * 1024);
|
||||
protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE);
|
||||
|
||||
protected Map<Connection, OverflowQueue> _oflowqs = Maps.newHashMap();
|
||||
|
||||
@@ -1012,12 +687,6 @@ public class ConnectionManager extends LoopingThread
|
||||
@Inject(optional=true) @Named("presents.net.selectLoopTime")
|
||||
protected int _selectLoopTime = 100;
|
||||
|
||||
// some dependencies
|
||||
@Inject @AuthInvoker protected Invoker _authInvoker;
|
||||
@Inject protected ClientManager _clmgr;
|
||||
@Inject protected PresentsDObjectMgr _omgr;
|
||||
@Inject protected PresentsServer _server;
|
||||
|
||||
/** Used to denote asynchronous close requests. */
|
||||
protected static final byte[] ASYNC_CLOSE_REQUEST = new byte[0];
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
package com.threerings.nio.conman;
|
||||
|
||||
/**
|
||||
* When a network event occurs, the connection manager calls the net event
|
||||
@@ -29,8 +29,8 @@ package com.threerings.presents.server.net;
|
||||
* client is authenticating and the one that processes messages from
|
||||
* authenticated connections.
|
||||
*
|
||||
* <p> Utilising this interface prevents us from having to do a bunch of
|
||||
* inefficient and ugly comparisons; instead we can simply call through an
|
||||
* <p> Using this interface prevents us from having to do a bunch of
|
||||
* inefficient and ugly comparisons; instead we can call through an
|
||||
* interface method to the proper code.
|
||||
*/
|
||||
public interface NetEventHandler
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.nio.conman;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.net.AddressUtil;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
|
||||
import static com.threerings.NaryaLog.log;
|
||||
|
||||
/**
|
||||
* Binds sockets on a given hostname for a set of ports for tcp connections, and passes accepted
|
||||
* connections into a connection manager.
|
||||
*/
|
||||
public class ServerSocketChannelAcceptor
|
||||
implements Lifecycle.ShutdownComponent
|
||||
{
|
||||
/**
|
||||
* Configures the connection manager with the hostname and ports on which it will listen for
|
||||
* socket connections. This must be called before the connection manager is started (via
|
||||
* {@via #start}) as the sockets will be bound at that time.
|
||||
*
|
||||
* @param socketHostname the hostname to which we bind our sockets or null to bind to all
|
||||
* interfaces.
|
||||
* @param socketPorts the ports on which to listen for TCP connection.
|
||||
*/
|
||||
public ServerSocketChannelAcceptor (String socketHostname, int[] socketPorts,
|
||||
ConnectionManager mgr)
|
||||
{
|
||||
Preconditions.checkNotNull(socketPorts, "Socket ports must be non-null.");
|
||||
_bindHostname = socketHostname;
|
||||
_ports = socketPorts;
|
||||
_conMan = mgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to the socket ports and return true if any of the binds succeeded.
|
||||
*/
|
||||
public boolean bind ()
|
||||
{
|
||||
// open our TCP listening ports
|
||||
int successes = 0;
|
||||
for (int port : _ports) {
|
||||
try {
|
||||
acceptConnections(port);
|
||||
successes++;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure listening to socket", "hostname", _bindHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
return successes > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind our listening sockets.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
for (ServerSocketChannel ssocket : _ssockets) {
|
||||
try {
|
||||
ssocket.socket().close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to close listening socket: " + ssocket, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void acceptConnections (int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a listening socket
|
||||
final ServerSocketChannel ssocket = ServerSocketChannel.open();
|
||||
ssocket.configureBlocking(false);
|
||||
InetSocketAddress isa = AddressUtil.getAddress(_bindHostname, port);
|
||||
ssocket.socket().bind(isa);
|
||||
|
||||
_conMan.register(ssocket, SelectionKey.OP_ACCEPT, new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
try {
|
||||
_conMan.handleAcceptedSocket(ssocket.accept());
|
||||
} catch (IOException ioe) {
|
||||
log.info("Failure accepting connected socket: " +ioe);
|
||||
}
|
||||
// there's no easy way to measure bytes read when accepting a connection, so we
|
||||
// claim nothing
|
||||
return 0;
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return false; // we're never idle
|
||||
}
|
||||
public void becameIdle () {
|
||||
// we're never idle
|
||||
}
|
||||
});
|
||||
_ssockets.add(ssocket);
|
||||
log.info("Server listening on " + isa + ".");
|
||||
}
|
||||
|
||||
protected final int[] _ports;
|
||||
protected final String _bindHostname;
|
||||
protected final ConnectionManager _conMan;
|
||||
|
||||
protected List<ServerSocketChannel> _ssockets = Lists.newArrayList();
|
||||
}
|
||||
@@ -57,6 +57,10 @@ import com.threerings.io.Streamable;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.EntryAddedEvent;
|
||||
import com.threerings.presents.dobj.EntryRemovedEvent;
|
||||
@@ -64,11 +68,13 @@ import com.threerings.presents.dobj.EntryUpdatedEvent;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.SetListener;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.peer.client.PeerService;
|
||||
import com.threerings.presents.peer.data.ClientInfo;
|
||||
import com.threerings.presents.peer.data.NodeObject;
|
||||
import com.threerings.presents.peer.data.PeerAuthName;
|
||||
import com.threerings.presents.peer.net.PeerCreds;
|
||||
import com.threerings.presents.peer.server.persist.NodeRecord;
|
||||
import com.threerings.presents.peer.server.persist.NodeRepository;
|
||||
import com.threerings.presents.server.ClientManager;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
@@ -77,15 +83,7 @@ import com.threerings.presents.server.PresentsSession;
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
import com.threerings.presents.server.ServiceAuthenticator;
|
||||
import com.threerings.presents.server.SessionFactory;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
|
||||
import com.threerings.presents.peer.client.PeerService;
|
||||
import com.threerings.presents.peer.data.ClientInfo;
|
||||
import com.threerings.presents.peer.data.NodeObject;
|
||||
import com.threerings.presents.peer.data.PeerAuthName;
|
||||
import com.threerings.presents.peer.net.PeerCreds;
|
||||
import com.threerings.presents.peer.server.persist.NodeRecord;
|
||||
import com.threerings.presents.peer.server.persist.NodeRepository;
|
||||
import com.threerings.presents.server.net.PresentsConnectionManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
@@ -1599,7 +1597,7 @@ public abstract class PeerManager
|
||||
// our service dependencies
|
||||
@Inject protected @MainInvoker Invoker _invoker;
|
||||
@Inject protected ClientManager _clmgr;
|
||||
@Inject protected ConnectionManager _conmgr;
|
||||
@Inject protected PresentsConnectionManager _conmgr;
|
||||
@Inject protected Injector _injector;
|
||||
@Inject protected InvocationManager _invmgr;
|
||||
@Inject protected NodeRepository _noderepo;
|
||||
|
||||
@@ -48,7 +48,7 @@ import com.threerings.presents.peer.data.NodeObject;
|
||||
import com.threerings.presents.peer.net.PeerBootstrapData;
|
||||
import com.threerings.presents.peer.server.persist.NodeRecord;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
import com.threerings.presents.server.net.PresentsConnectionManager;
|
||||
import com.threerings.presents.server.net.ServerCommunicator;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
@@ -354,7 +354,7 @@ public class PeerNode
|
||||
|
||||
@Inject protected PeerManager _peermgr;
|
||||
@Inject protected PresentsDObjectMgr _omgr;
|
||||
@Inject protected ConnectionManager _conmgr;
|
||||
@Inject protected PresentsConnectionManager _conmgr;
|
||||
|
||||
/** The amount of time after which a node record can be considered out of date and invalid. */
|
||||
protected static final long STALE_INTERVAL = 5L * 60L * 1000L;
|
||||
|
||||
@@ -46,7 +46,9 @@ import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.server.net.AuthingConnection;
|
||||
import com.threerings.presents.server.net.Connection;
|
||||
import com.threerings.presents.server.net.PresentsConnection;
|
||||
|
||||
import com.threerings.nio.conman.Connection;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
@@ -407,7 +409,7 @@ public class ClientManager
|
||||
* Called by the connection manager to let us know when a new connection has been established.
|
||||
*/
|
||||
public synchronized void connectionEstablished (
|
||||
Connection conn, Name authname, AuthRequest req, AuthResponse rsp)
|
||||
PresentsConnection conn, Name authname, AuthRequest req, AuthResponse rsp)
|
||||
{
|
||||
String type = authname.getClass().getSimpleName();
|
||||
|
||||
|
||||
@@ -41,11 +41,14 @@ import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr.LongRunnable;
|
||||
import com.threerings.presents.server.net.BindingConnectionManager;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
import com.threerings.presents.server.net.DatagramChannelReader;
|
||||
import com.threerings.presents.server.net.PresentsConnectionManager;
|
||||
|
||||
import com.threerings.crowd.server.PlaceManager;
|
||||
|
||||
import com.threerings.nio.conman.ConnectionManager;
|
||||
import com.threerings.nio.conman.ServerSocketChannelAcceptor;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
@@ -64,21 +67,13 @@ public class PresentsServer
|
||||
{
|
||||
@Override protected void configure () {
|
||||
bindInvokers();
|
||||
bindConnectionManager();
|
||||
bind(ConnectionManager.class).to(PresentsConnectionManager.class);
|
||||
bind(RunQueue.class).annotatedWith(EventQueue.class).to(PresentsDObjectMgr.class);
|
||||
bind(DObjectManager.class).to(PresentsDObjectMgr.class);
|
||||
bind(RootDObjectManager.class).to(PresentsDObjectMgr.class);
|
||||
bind(Lifecycle.class).toInstance(new Lifecycle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds just the connection manager so this can be overridden if desired.
|
||||
*/
|
||||
protected void bindConnectionManager()
|
||||
{
|
||||
bind(ConnectionManager.class).to(BindingConnectionManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds just the invokers so this can be overridden if desired.
|
||||
*/
|
||||
@@ -157,12 +152,13 @@ public class PresentsServer
|
||||
// provide our client manager with the injector it needs
|
||||
_clmgr.setInjector(injector);
|
||||
|
||||
// if we have a connection manager that handles its own socket accepting, initialize it
|
||||
// with the hostname and ports on which to listen
|
||||
if (_conmgr instanceof BindingConnectionManager) {
|
||||
((BindingConnectionManager)_conmgr).init(
|
||||
getBindHostname(), getDatagramHostname(), getListenPorts(), getDatagramPorts());
|
||||
}
|
||||
// Create our socket opening objects now that we know what ports we're using.
|
||||
_socketAcceptor =
|
||||
new ServerSocketChannelAcceptor(getBindHostname(), getListenPorts(), _conmgr);
|
||||
_lifecycle.addComponent(_socketAcceptor);
|
||||
_datagramReader =
|
||||
new DatagramChannelReader(getDatagramHostname(), getDatagramPorts(), _conmgr);
|
||||
_lifecycle.addComponent(_datagramReader);
|
||||
|
||||
// initialize the time base services
|
||||
TimeBaseProvider.init(_invmgr, _omgr);
|
||||
@@ -213,7 +209,12 @@ public class PresentsServer
|
||||
*/
|
||||
protected void openToThePublic ()
|
||||
{
|
||||
_conmgr.start();
|
||||
if (getListenPorts().length != 0 && !_socketAcceptor.bind()) {
|
||||
queueShutdown();
|
||||
} else {
|
||||
_datagramReader.bind();
|
||||
_conmgr.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,7 +258,9 @@ public class PresentsServer
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port on which the connection manager will listen for client connections.
|
||||
* Returns the port on which the connection manager will listen for client connections or an
|
||||
* empty array to skip binding and have an external entity transfer accepted sockets into
|
||||
* the connection manager.
|
||||
*/
|
||||
protected int[] getListenPorts ()
|
||||
{
|
||||
@@ -265,7 +268,8 @@ public class PresentsServer
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ports on which the connection manager will listen for datagrams.
|
||||
* Returns the ports on which the connection manager will listen for datagrams or an empty
|
||||
* array if no datagrams are desired.
|
||||
*/
|
||||
protected int[] getDatagramPorts ()
|
||||
{
|
||||
@@ -282,11 +286,15 @@ public class PresentsServer
|
||||
{
|
||||
}
|
||||
|
||||
protected ServerSocketChannelAcceptor _socketAcceptor;
|
||||
|
||||
protected DatagramChannelReader _datagramReader;
|
||||
|
||||
/** The manager of distributed objects. */
|
||||
@Inject protected PresentsDObjectMgr _omgr;
|
||||
|
||||
/** The manager of network connections. */
|
||||
@Inject protected ConnectionManager _conmgr;
|
||||
@Inject protected PresentsConnectionManager _conmgr;
|
||||
|
||||
/** The manager of clients. */
|
||||
@Inject protected ClientManager _clmgr;
|
||||
|
||||
@@ -67,8 +67,10 @@ import com.threerings.presents.net.TransmitDatagramsRequest;
|
||||
import com.threerings.presents.net.UnsubscribeRequest;
|
||||
import com.threerings.presents.net.UnsubscribeResponse;
|
||||
import com.threerings.presents.net.UpdateThrottleMessage;
|
||||
import com.threerings.presents.server.net.Connection;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
import com.threerings.presents.server.net.PresentsConnection;
|
||||
|
||||
import com.threerings.nio.conman.Connection;
|
||||
import com.threerings.nio.conman.ConnectionManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
@@ -84,7 +86,7 @@ import static com.threerings.presents.Log.log;
|
||||
* from the conmgr thread and therefore also need not be synchronized.
|
||||
*/
|
||||
public class PresentsSession
|
||||
implements Connection.MessageHandler, ClientResolutionListener
|
||||
implements PresentsConnection.MessageHandler, ClientResolutionListener
|
||||
{
|
||||
/** Used by {@link PresentsSession#setUsername} to report success or failure. */
|
||||
public static interface UserChangeListener
|
||||
@@ -169,7 +171,7 @@ public class PresentsSession
|
||||
*/
|
||||
public boolean getTransmitDatagrams ()
|
||||
{
|
||||
Connection conn = getConnection();
|
||||
PresentsConnection conn = getConnection();
|
||||
return conn != null && conn.getTransmitDatagrams();
|
||||
}
|
||||
|
||||
@@ -180,7 +182,7 @@ public class PresentsSession
|
||||
public void setClassLoader (ClassLoader loader)
|
||||
{
|
||||
_loader = loader;
|
||||
Connection conn = getConnection();
|
||||
PresentsConnection conn = getConnection();
|
||||
if (conn != null) {
|
||||
conn.setClassLoader(loader);
|
||||
}
|
||||
@@ -459,10 +461,11 @@ public class PresentsSession
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this client instance with the specified username, connection instance and client
|
||||
* object and begins a client session.
|
||||
* Initializes this client instance with the specified username, connection instance and
|
||||
* client object and begins a client session.
|
||||
*/
|
||||
protected void startSession (Name authname, AuthRequest req, Connection conn, Object authdata)
|
||||
protected void startSession (Name authname, AuthRequest req, PresentsConnection conn,
|
||||
Object authdata)
|
||||
{
|
||||
_authname = authname;
|
||||
_areq = req;
|
||||
@@ -480,7 +483,7 @@ public class PresentsSession
|
||||
* 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.
|
||||
*/
|
||||
protected void resumeSession (AuthRequest req, Connection conn)
|
||||
protected void resumeSession (AuthRequest req, PresentsConnection conn)
|
||||
{
|
||||
// check to see if we've already got a connection object, in which case it's probably stale
|
||||
Connection oldconn = getConnection();
|
||||
@@ -804,7 +807,7 @@ public class PresentsSession
|
||||
* Sets our connection reference in a thread safe way. Also establishes the back reference to
|
||||
* us as the connection's message handler.
|
||||
*/
|
||||
protected synchronized void setConnection (Connection conn)
|
||||
protected synchronized void setConnection (PresentsConnection conn)
|
||||
{
|
||||
// if our connection is being cleared out, record the amount of time we were connected to
|
||||
// our total connected time
|
||||
@@ -839,7 +842,7 @@ public class PresentsSession
|
||||
* @return The connection instance associated with this client or null if the client is not
|
||||
* currently connected.
|
||||
*/
|
||||
protected synchronized Connection getConnection ()
|
||||
protected synchronized PresentsConnection getConnection ()
|
||||
{
|
||||
return _conn;
|
||||
}
|
||||
@@ -860,7 +863,7 @@ public class PresentsSession
|
||||
/** Queues a message for delivery to the client. */
|
||||
protected boolean postMessage (DownstreamMessage msg)
|
||||
{
|
||||
Connection conn = getConnection();
|
||||
PresentsConnection conn = getConnection();
|
||||
if (conn != null) {
|
||||
conn.postMessage(msg);
|
||||
_messagesOut++; // count 'em up!
|
||||
@@ -1147,7 +1150,7 @@ public class PresentsSession
|
||||
protected AuthRequest _areq;
|
||||
protected Object _authdata;
|
||||
protected Name _authname;
|
||||
protected Connection _conn;
|
||||
protected PresentsConnection _conn;
|
||||
protected ClientObject _clobj;
|
||||
protected IntMap<ClientProxy> _subscrips = IntMaps.newHashIntMap();
|
||||
protected ClassLoader _loader;
|
||||
|
||||
@@ -33,7 +33,7 @@ import static com.threerings.presents.Log.log;
|
||||
* 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 PresentsConnection
|
||||
{
|
||||
public AuthingConnection ()
|
||||
{
|
||||
@@ -43,7 +43,7 @@ public class AuthingConnection extends Connection
|
||||
// keep a handle on our auth request
|
||||
_authreq = (AuthRequest)msg;
|
||||
// post ourselves for processing by the authmgr
|
||||
_cmgr.authenticateConnection(AuthingConnection.this);
|
||||
_pcmgr.authenticateConnection(AuthingConnection.this);
|
||||
} catch (ClassCastException cce) {
|
||||
log.warning("Received non-authreq message during authentication process",
|
||||
"conn", AuthingConnection.this, "msg", msg);
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.net.AddressUtil;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Binds tcp sockets and listens for datagrams in addition to ConnectionManager's normal duties.
|
||||
*/
|
||||
@Singleton
|
||||
public class BindingConnectionManager extends ConnectionManager
|
||||
{
|
||||
@Inject public BindingConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
throws IOException
|
||||
{
|
||||
super(cycle, repmgr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the connection manager with the hostname and ports on which it will listen for
|
||||
* socket connections and datagram packets. This must be called before the connection manager
|
||||
* is started (via {@via #start}) as the sockets will be bound at that time.
|
||||
*
|
||||
* @param socketHostname the hostname to which we bind our sockets or null to bind to all
|
||||
* interfaces.
|
||||
* @param datagramHostname the hostname to which we bind our datagram socket or null to bind
|
||||
* to all interfaces.
|
||||
* @param socketPorts the ports on which to listen for TCP connection.
|
||||
* @param datagramPorts the ports on which to listen for datagram packets.
|
||||
*/
|
||||
public void init (String socketHostname, String datagramHostname,
|
||||
int[] socketPorts, int[] datagramPorts)
|
||||
throws IOException
|
||||
{
|
||||
Preconditions.checkNotNull(socketPorts, "Socket ports must be non-null.");
|
||||
Preconditions.checkNotNull(datagramPorts, "Datagram ports must be non-null. " +
|
||||
"Pass a zero-length array to bind no datagram ports.");
|
||||
|
||||
_bindHostname = socketHostname;
|
||||
_ports = socketPorts;
|
||||
_datagramHostname = datagramHostname;
|
||||
_datagramPorts = datagramPorts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void willStart ()
|
||||
{
|
||||
super.willStart();
|
||||
|
||||
// open our TCP listening ports
|
||||
int successes = 0;
|
||||
for (int port : _ports) {
|
||||
try {
|
||||
acceptConnections(port);
|
||||
successes++;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure listening to socket", "hostname", _bindHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
if (successes == 0) {
|
||||
log.warning("ConnectionManager failed to bind to any ports. Shutting down.");
|
||||
_server.queueShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// open up the datagram ports as well
|
||||
for (int port : _datagramPorts) {
|
||||
try {
|
||||
acceptDatagrams(port);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure opening datagram channel", "hostname", _datagramHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void didShutdown ()
|
||||
{
|
||||
super.didShutdown();
|
||||
// TODO: consider closing the listen sockets earlier, like in the shutdown method
|
||||
|
||||
// unbind our listening sockets; note: because we wait for the objmgr to exit before we do,
|
||||
// we will still be accepting connections as long as there are events pending.
|
||||
for (ServerSocketChannel ssocket : _ssockets) {
|
||||
try {
|
||||
ssocket.socket().close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to close listening socket: " + ssocket, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// unbind datagram channels, if any
|
||||
for (DatagramChannel datagramChannel : _datagramChannels) {
|
||||
datagramChannel.socket().close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void acceptConnections (int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a listening socket
|
||||
final ServerSocketChannel ssocket = ServerSocketChannel.open();
|
||||
ssocket.configureBlocking(false);
|
||||
InetSocketAddress isa = AddressUtil.getAddress(_bindHostname, port);
|
||||
ssocket.socket().bind(isa);
|
||||
|
||||
// and add it to the select set
|
||||
SelectionKey sk = ssocket.register(_selector, SelectionKey.OP_ACCEPT);
|
||||
_handlers.put(sk, new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
try {
|
||||
handleAcceptedSocket(ssocket.accept());
|
||||
} catch (IOException ioe) {
|
||||
log.info("Failure accepting connected socket: " +ioe);
|
||||
}
|
||||
// there's no easy way to measure bytes read when accepting a connection, so we
|
||||
// claim nothing
|
||||
return 0;
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return false; // we're never idle
|
||||
}
|
||||
public void becameIdle () {
|
||||
// we're never idle
|
||||
}
|
||||
});
|
||||
_ssockets.add(ssocket);
|
||||
log.info("Server listening on " + isa + ".");
|
||||
}
|
||||
|
||||
protected void acceptDatagrams (int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a channel
|
||||
final DatagramChannel channel = DatagramChannel.open();
|
||||
channel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY
|
||||
channel.configureBlocking(false);
|
||||
InetSocketAddress isa = AddressUtil.getAddress(_datagramHostname, port);
|
||||
channel.socket().bind(isa);
|
||||
|
||||
// and add it to the select set
|
||||
SelectionKey sk = channel.register(_selector, SelectionKey.OP_READ);
|
||||
_handlers.put(sk, new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
return handleDatagram(channel, when);
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return false;// Can't be idle
|
||||
}
|
||||
public void becameIdle () {}
|
||||
});
|
||||
_datagramChannels.add(channel);
|
||||
log.info("Server accepting datagrams on " + isa + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a datagram message is ready to be read off its channel.
|
||||
*/
|
||||
protected int handleDatagram (DatagramChannel listener, long when)
|
||||
{
|
||||
InetSocketAddress source;
|
||||
_databuf.clear();
|
||||
try {
|
||||
source = (InetSocketAddress)listener.receive(_databuf);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure receiving datagram.", ioe);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// make sure we actually read a packet
|
||||
if (source == null) {
|
||||
log.info("Psych! Got READ_READY, but no datagram.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// flip the buffer and record the size (which must be at least 14 to contain the connection
|
||||
// id, authentication hash, and a class reference)
|
||||
int size = _databuf.flip().remaining();
|
||||
if (size < 14) {
|
||||
log.warning("Received undersized datagram", "source", source, "size", size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// the first four bytes are the connection id
|
||||
int connectionId = _databuf.getInt();
|
||||
Connection conn = _connections.get(connectionId);
|
||||
if (conn != null) {
|
||||
conn.handleDatagram(source, listener, _databuf, when);
|
||||
} else {
|
||||
log.debug("Received datagram for unknown connection", "id", connectionId,
|
||||
"source", source);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
protected int[] _ports, _datagramPorts;
|
||||
protected String _bindHostname, _datagramHostname;
|
||||
|
||||
protected List<ServerSocketChannel> _ssockets = Lists.newArrayList();
|
||||
protected List<DatagramChannel> _datagramChannels = Lists.newArrayList();
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.net.AddressUtil;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
|
||||
import com.threerings.nio.conman.NetEventHandler;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Binds datagram connections on a given hostname for a set of ports, and passes datagrams read
|
||||
* off those ports into a connection manager.
|
||||
*/
|
||||
public class DatagramChannelReader
|
||||
implements Lifecycle.ShutdownComponent
|
||||
{
|
||||
public DatagramChannelReader (String datagramHostname,
|
||||
int[] datagramPorts, PresentsConnectionManager conMgr)
|
||||
{
|
||||
Preconditions.checkNotNull(datagramPorts, "Datagram ports must be non-null. " +
|
||||
"Pass a zero-length array to bind no datagram ports.");
|
||||
|
||||
_datagramHostname = datagramHostname;
|
||||
_datagramPorts = datagramPorts;
|
||||
_conMan = conMgr;
|
||||
}
|
||||
|
||||
public void bind()
|
||||
{
|
||||
for (int port : _datagramPorts) {
|
||||
try {
|
||||
acceptDatagrams(port);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure opening datagram channel", "hostname", _datagramHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown ()
|
||||
{
|
||||
for (DatagramChannel datagramChannel : _datagramChannels) {
|
||||
datagramChannel.socket().close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void acceptDatagrams (int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a channel
|
||||
final DatagramChannel channel = DatagramChannel.open();
|
||||
channel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY
|
||||
channel.configureBlocking(false);
|
||||
InetSocketAddress isa = AddressUtil.getAddress(_datagramHostname, port);
|
||||
channel.socket().bind(isa);
|
||||
|
||||
// and add it to the select set
|
||||
_conMan.register(channel, SelectionKey.OP_READ, new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
return _conMan.handleDatagram(channel, when);
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return false;// Can't be idle
|
||||
}
|
||||
public void becameIdle () {}
|
||||
});
|
||||
_datagramChannels.add(channel);
|
||||
log.info("Server accepting datagrams on " + isa + ".");
|
||||
}
|
||||
|
||||
protected final int[] _datagramPorts;
|
||||
protected final String _datagramHostname;
|
||||
protected final List<DatagramChannel> _datagramChannels = Lists.newArrayList();
|
||||
protected final PresentsConnectionManager _conMan;
|
||||
}
|
||||
+15
-173
@@ -23,11 +23,9 @@ package com.threerings.presents.server.net;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -38,17 +36,18 @@ import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.util.DatagramSequencer;
|
||||
|
||||
import com.threerings.nio.conman.Connection;
|
||||
import com.threerings.nio.conman.ConnectionManager;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The base connection class implements the net event handler interface and processes raw incoming
|
||||
* network data into a stream of parsed {@link Message} objects. It also provides the means to send
|
||||
* messages to the client and facilities for checking delinquency.
|
||||
* Parses incoming network data into a stream of {@link Message} objects, sends messages to the
|
||||
* client and adds datagram support to a connection.
|
||||
*/
|
||||
public class Connection implements NetEventHandler
|
||||
public class PresentsConnection extends Connection
|
||||
{
|
||||
/** Used with {@link Connection#setMessageHandler}. */
|
||||
public static interface MessageHandler {
|
||||
@@ -56,23 +55,16 @@ public class Connection implements NetEventHandler
|
||||
void handleMessage (Message message);
|
||||
}
|
||||
|
||||
/** The key used by the NIO code to track this connection. */
|
||||
public SelectionKey selkey;
|
||||
|
||||
/**
|
||||
* Initializes a connection object with a socket and related info.
|
||||
*
|
||||
* @param cmgr The connection manager with which this connection is associated.
|
||||
* @param channel The socket channel from which we'll be reading messages.
|
||||
* @param createStamp The time at which this connection was created.
|
||||
* Initializes the connection with its channel. Must be called with a
|
||||
* {@link PresentsConnectionManager} as <code>cmgr</code>.
|
||||
*/
|
||||
@Override
|
||||
public void init (ConnectionManager cmgr, SocketChannel channel, long createStamp)
|
||||
throws IOException
|
||||
{
|
||||
_cmgr = cmgr;
|
||||
_channel = channel;
|
||||
_lastEvent = createStamp;
|
||||
_connectionId = ++_lastConnectionId;
|
||||
super.init(cmgr, channel, createStamp);
|
||||
_pcmgr = (PresentsConnectionManager)cmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,31 +87,6 @@ public class Connection implements NetEventHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection's unique identifier.
|
||||
*/
|
||||
public int getConnectionId ()
|
||||
{
|
||||
return _connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether we should transmit datagrams.
|
||||
*/
|
||||
@@ -166,42 +133,6 @@ public class Connection implements NetEventHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 + ".", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// unregister from the select set
|
||||
_cmgr.connectionClosed(this);
|
||||
|
||||
// close our socket
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a request to have this connection closed by the connection manager once all
|
||||
* messages in its queue have been written to its target.
|
||||
*/
|
||||
public void asyncClose ()
|
||||
{
|
||||
_cmgr.postAsyncClose(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a message for delivery to this connection. The message will be delivered by the conmgr
|
||||
* thread as soon as it gets to it.
|
||||
@@ -209,36 +140,7 @@ public class Connection implements NetEventHandler
|
||||
public void postMessage (Message msg)
|
||||
{
|
||||
// pass this along to the connection manager
|
||||
_cmgr.postMessage(this, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an outgoing socket experiences a connect failure. The connection manager will
|
||||
* have cleaned up the partial registration needed during the connect process, so we are only
|
||||
* responsible for closing our socket.
|
||||
*/
|
||||
public void connectFailure (IOException ioe)
|
||||
{
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is a failure reading or writing to this connection. We notify the
|
||||
* connection manager and close ourselves down.
|
||||
*/
|
||||
public void networkFailure (IOException ioe)
|
||||
{
|
||||
// if we're already closed, then something is seriously funny
|
||||
if (isClosed()) {
|
||||
log.warning("Failure reported on closed connection " + this + ".", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// let the connection manager know we're hosed
|
||||
_cmgr.connectionFailed(this, ioe);
|
||||
|
||||
// and close our socket
|
||||
closeSocket();
|
||||
_pcmgr.postMessage(this, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,7 +157,7 @@ public class Connection implements NetEventHandler
|
||||
log.warning("Missing MD5 algorithm.");
|
||||
return;
|
||||
}
|
||||
_sequencer = _cmgr.createDatagramSequencer();
|
||||
_sequencer = _pcmgr.createDatagramSequencer();
|
||||
}
|
||||
|
||||
// verify the hash
|
||||
@@ -291,7 +193,6 @@ public class Connection implements NetEventHandler
|
||||
}
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
public int handleEvent (long when)
|
||||
{
|
||||
// make a note that we received an event as of this time
|
||||
@@ -345,32 +246,6 @@ public class Connection implements NetEventHandler
|
||||
return bytesIn;
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
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;
|
||||
}
|
||||
|
||||
// from interface NetEventHandler
|
||||
public void becameIdle ()
|
||||
{
|
||||
_cmgr.closeConnection(this);
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public String toString ()
|
||||
{
|
||||
return "[id=" + (hashCode() % 1000) + ", addr=" + getInetAddress() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object input stream associated with this connection. This should only be used
|
||||
* by the connection manager.
|
||||
@@ -385,7 +260,7 @@ public class Connection implements NetEventHandler
|
||||
* 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)
|
||||
protected void inheritStreams (PresentsConnection other)
|
||||
{
|
||||
_fin = other._fin;
|
||||
_oin = other._oin;
|
||||
@@ -427,34 +302,6 @@ public class Connection implements NetEventHandler
|
||||
return _sequencer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
_channel = null;
|
||||
}
|
||||
|
||||
protected ConnectionManager _cmgr;
|
||||
protected SocketChannel _channel;
|
||||
|
||||
protected long _lastEvent;
|
||||
|
||||
protected int _connectionId;
|
||||
|
||||
protected FramedInputStream _fin;
|
||||
protected ObjectInputStream _oin;
|
||||
protected ObjectOutputStream _oout;
|
||||
@@ -470,10 +317,5 @@ public class Connection implements NetEventHandler
|
||||
protected MessageHandler _handler;
|
||||
protected ClassLoader _loader;
|
||||
|
||||
/** The last connection id assigned. */
|
||||
protected static int _lastConnectionId;
|
||||
|
||||
/** 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;
|
||||
protected PresentsConnectionManager _pcmgr;
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.server.net;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.Lifecycle;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.ResultListener;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.io.ByteBufferInputStream;
|
||||
import com.threerings.io.FramingOutputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.UnreliableObjectInputStream;
|
||||
import com.threerings.io.UnreliableObjectOutputStream;
|
||||
|
||||
import com.threerings.presents.annotation.AuthInvoker;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.ConMgrStats;
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
import com.threerings.presents.net.Transport;
|
||||
import com.threerings.presents.server.Authenticator;
|
||||
import com.threerings.presents.server.ChainedAuthenticator;
|
||||
import com.threerings.presents.server.ClientManager;
|
||||
import com.threerings.presents.server.DummyAuthenticator;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr;
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
import com.threerings.presents.util.DatagramSequencer;
|
||||
|
||||
import com.threerings.nio.conman.Connection;
|
||||
import com.threerings.nio.conman.ConnectionManager;
|
||||
import com.threerings.nio.conman.NetEventHandler;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
@Singleton
|
||||
public class PresentsConnectionManager extends ConnectionManager
|
||||
{
|
||||
|
||||
@Inject
|
||||
public PresentsConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
throws IOException
|
||||
{
|
||||
super(cycle, repmgr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ConMgrStats getStats ()
|
||||
{
|
||||
_stats.authQueueSize = _authq.size();
|
||||
return super.getStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticator to the authentication chain. This authenticator will be offered a
|
||||
* chance to authenticate incoming connections before falling back to the main authenticator.
|
||||
*/
|
||||
public void addChainedAuthenticator (ChainedAuthenticator author)
|
||||
{
|
||||
_authors.add(author);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a datagram message is ready to be read off its channel.
|
||||
*/
|
||||
protected int handleDatagram (DatagramChannel listener, long when)
|
||||
{
|
||||
InetSocketAddress source;
|
||||
_databuf.clear();
|
||||
try {
|
||||
source = (InetSocketAddress)listener.receive(_databuf);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure receiving datagram.", ioe);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// make sure we actually read a packet
|
||||
if (source == null) {
|
||||
log.info("Psych! Got READ_READY, but no datagram.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// flip the buffer and record the size (which must be at least 14 to contain the connection
|
||||
// id, authentication hash, and a class reference)
|
||||
int size = _databuf.flip().remaining();
|
||||
if (size < 14) {
|
||||
log.warning("Received undersized datagram", "source", source, "size", size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// the first four bytes are the connection id
|
||||
int connectionId = _databuf.getInt();
|
||||
Connection conn = _connections.get(connectionId);
|
||||
if (conn != null) {
|
||||
((PresentsConnection)conn).handleDatagram(source, listener, _databuf, when);
|
||||
} else {
|
||||
log.debug("Received datagram for unknown connection", "id", connectionId,
|
||||
"source", source);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a connection when it has a downstream message that needs to be delivered.
|
||||
* <em>Note:</em> this method is called as a result of a call to
|
||||
* {@link PresentsConnection#postMessage} which happens when forwarding an event to a client
|
||||
* and at the completion of authentication, both of which <em>must</em> happen only on the
|
||||
* distributed object thread.
|
||||
*/
|
||||
protected void postMessage (PresentsConnection conn, Message msg)
|
||||
{
|
||||
if (!isRunning()) {
|
||||
log.warning("Posting message to inactive connection manager",
|
||||
"msg", msg, new Exception());
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (conn == null || msg == null) {
|
||||
log.warning("postMessage() bogosity", "conn", conn, "msg", msg, new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// more sanity check; messages must only be posted from the dobjmgr thread
|
||||
if (!_omgr.isDispatchThread()) {
|
||||
log.warning("Message posted on non-distributed object thread", "conn", conn,
|
||||
"msg", msg, "thread", Thread.currentThread(), new Exception());
|
||||
// let it through though as we don't want to break things unnecessarily
|
||||
}
|
||||
|
||||
try {
|
||||
// send it as a datagram if hinted and possible (pongs must be sent as part of the
|
||||
// negotation process)
|
||||
if (!msg.getTransport().isReliable() &&
|
||||
(conn.getTransmitDatagrams() || msg instanceof PongResponse) &&
|
||||
postDatagram(conn, msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note the actual transport
|
||||
msg.noteActualTransport(Transport.RELIABLE_ORDERED);
|
||||
|
||||
_framer.resetFrame();
|
||||
|
||||
// flatten this message using the connection's output stream
|
||||
ObjectOutputStream oout = conn.getObjectOutputStream(_framer);
|
||||
oout.writeObject(msg);
|
||||
oout.flush();
|
||||
|
||||
// now extract that data into a byte array
|
||||
ByteBuffer buffer = _framer.frameAndReturnBuffer();
|
||||
byte[] data = new byte[buffer.limit()];
|
||||
buffer.get(data);
|
||||
// log.info("Flattened " + msg + " into " + data.length + " bytes.");
|
||||
|
||||
// and slap both on the queue
|
||||
_outq.append(Tuple.<Connection, byte[]>newTuple(conn, data));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure flattening message", "conn", conn, "msg", msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for {@link #postMessage}; handles posting the message as a datagram.
|
||||
*
|
||||
* @return true if the datagram was successfully posted, false if it was too big.
|
||||
*/
|
||||
protected boolean postDatagram (PresentsConnection conn, Message msg)
|
||||
throws Exception
|
||||
{
|
||||
_flattener.reset();
|
||||
|
||||
// flatten the message using the connection's sequencer
|
||||
DatagramSequencer sequencer = conn.getDatagramSequencer();
|
||||
sequencer.writeDatagram(msg);
|
||||
|
||||
// if the message is too big, we must fall back to sending it through the stream channel
|
||||
if (_flattener.size() > Client.MAX_DATAGRAM_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// note the actual transport
|
||||
msg.noteActualTransport(Transport.UNRELIABLE_UNORDERED);
|
||||
|
||||
// extract as a byte array
|
||||
byte[] data = _flattener.toByteArray();
|
||||
|
||||
// slap it on the queue
|
||||
_dataq.append(Tuple.newTuple(conn, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a datagram sequencer for use by a {@link Connection}.
|
||||
*/
|
||||
protected DatagramSequencer createDatagramSequencer ()
|
||||
{
|
||||
return new DatagramSequencer(
|
||||
new UnreliableObjectInputStream(new ByteBufferInputStream(_databuf)),
|
||||
new UnreliableObjectOutputStream(_flattener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an outgoing connection to the supplied address. The connection will be opened in a
|
||||
* non-blocking manner and added to the connection manager's select set. Messages posted to the
|
||||
* connection prior to it being actually connected to its destination will remain in the queue.
|
||||
* If the connection fails those messages will be dropped.
|
||||
*
|
||||
* @param conn the connection to be initialized and opened. Callers may want to provide a
|
||||
* {@link Connection} derived class so that they may intercept calldown methods.
|
||||
* @param hostname the hostname of the server to which to connect.
|
||||
* @param port the port on which to connect to the server.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs creating our socket. Everything else
|
||||
* happens asynchronously. If the connection attempt fails, the Connection will be notified via
|
||||
* {@link Connection#networkFailure}.
|
||||
*/
|
||||
public void openOutgoingConnection (Connection conn, String hostname, int port)
|
||||
throws IOException
|
||||
{
|
||||
// create a socket channel to use for this connection, initialize it and queue it up to
|
||||
// have the non-blocking connect process started
|
||||
SocketChannel sockchan = SocketChannel.open();
|
||||
sockchan.configureBlocking(false);
|
||||
conn.init(this, sockchan, System.currentTimeMillis());
|
||||
_connectq.append(Tuple.newTuple(conn, new InetSocketAddress(hostname, port)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the connection process for an outgoing connection. This is called as part of the
|
||||
* conmgr tick for any pending outgoing connections.
|
||||
*/
|
||||
protected void startOutgoingConnection (final Connection conn, InetSocketAddress addr)
|
||||
{
|
||||
final SocketChannel sockchan = conn.getChannel();
|
||||
try {
|
||||
// register our channel with the selector (if this fails, we abandon ship immediately)
|
||||
conn.selkey = sockchan.register(_selector, SelectionKey.OP_CONNECT);
|
||||
|
||||
// start our connection process (now if we fail we need to clean things up)
|
||||
NetEventHandler handler;
|
||||
if (sockchan.connect(addr)) {
|
||||
// it is possible even for a non-blocking socket to connect immediately, in which
|
||||
// case we stick the connection in as its event handler immediately
|
||||
handler = conn;
|
||||
|
||||
} else {
|
||||
// otherwise we wire up a special event handler that will wait for our socket to
|
||||
// finish the connection process and then wire things up fully
|
||||
handler = new NetEventHandler() {
|
||||
public int handleEvent (long when) {
|
||||
try {
|
||||
if (sockchan.finishConnect()) {
|
||||
// great, we're ready to roll, wire up the connection
|
||||
conn.selkey = sockchan.register(_selector, SelectionKey.OP_READ);
|
||||
_handlers.put(conn.selkey, conn);
|
||||
log.info("Outgoing connection ready", "conn", conn);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
handleError(ioe);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public boolean checkIdle (long now) {
|
||||
return conn.checkIdle(now);
|
||||
}
|
||||
public void becameIdle () {
|
||||
handleError(new IOException("Pending connection became idle."));
|
||||
}
|
||||
protected void handleError (IOException ioe) {
|
||||
_handlers.remove(conn.selkey);
|
||||
_oflowqs.remove(conn);
|
||||
conn.connectFailure(ioe);
|
||||
}
|
||||
};
|
||||
}
|
||||
_handlers.put(conn.selkey, handler);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to initiate connection for " + sockchan + ".", ioe);
|
||||
conn.connectFailure(ioe); // nothing else to clean up
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from LoopingThread
|
||||
public boolean isRunning ()
|
||||
{
|
||||
// Prevent exiting our thread until the object manager is done.
|
||||
return super.isRunning() || _omgr.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIncoming (long iterStamp)
|
||||
{
|
||||
super.handleIncoming(iterStamp);
|
||||
|
||||
// start up any outgoing connections that need to be connected
|
||||
Tuple<Connection, InetSocketAddress> pconn;
|
||||
while ((pconn = _connectq.getNonBlocking()) != null) {
|
||||
startOutgoingConnection(pconn.left, pconn.right);
|
||||
}
|
||||
|
||||
// check for connections that have completed authentication
|
||||
processAuthedConnections(iterStamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed (Connection conn, IOException ioe)
|
||||
{
|
||||
super.connectionFailed(conn, ioe);
|
||||
|
||||
// let the client manager know what's up
|
||||
_clmgr.connectionFailed(conn, ioe);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionClosed (Connection conn)
|
||||
{
|
||||
super.connectionClosed(conn);
|
||||
|
||||
// let the client manager know what's up
|
||||
_clmgr.connectionClosed(conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the authentication process on the specified connection. This is called by {@link
|
||||
* AuthingConnection} itself once it receives its auth request.
|
||||
*/
|
||||
protected void authenticateConnection (AuthingConnection conn)
|
||||
{
|
||||
Authenticator author = _author;
|
||||
for (ChainedAuthenticator cauthor : _authors) {
|
||||
if (cauthor.shouldHandleConnection(conn)) {
|
||||
author = cauthor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
author.authenticateConnection(_authInvoker, conn, new ResultListener<AuthingConnection>() {
|
||||
public void requestCompleted (AuthingConnection conn) {
|
||||
_authq.append(conn);
|
||||
}
|
||||
public void requestFailed (Exception cause) {
|
||||
// this never happens
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an accepted socket down the path to authorization.
|
||||
*/
|
||||
@Override
|
||||
protected void handleAcceptedSocket (SocketChannel channel)
|
||||
{
|
||||
handleAcceptedSocket(channel, new AuthingConnection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts connections that have completed the authentication process into full running
|
||||
* connections and notifies the client manager that new connections have been established.
|
||||
*/
|
||||
protected void processAuthedConnections (long iterStamp)
|
||||
{
|
||||
AuthingConnection conn;
|
||||
while ((conn = _authq.getNonBlocking()) != null) {
|
||||
try {
|
||||
// construct a new running connection to handle this connections network traffic
|
||||
// from here on out
|
||||
PresentsConnection rconn = new PresentsConnection();
|
||||
rconn.init(this, conn.getChannel(), iterStamp);
|
||||
rconn.selkey = conn.selkey;
|
||||
|
||||
// we need to keep using the same object input and output streams from the
|
||||
// beginning of the session because they have context that needs to be preserved
|
||||
rconn.inheritStreams(conn);
|
||||
|
||||
// replace the mapping in the handlers table from the old conn with the new one
|
||||
_handlers.put(rconn.selkey, rconn);
|
||||
|
||||
// add a mapping for the connection id and set the datagram secret
|
||||
_connections.put(rconn.getConnectionId(), rconn);
|
||||
rconn.setDatagramSecret(conn.getAuthRequest().getCredentials().getDatagramSecret());
|
||||
|
||||
// transfer any overflow queue for that connection
|
||||
OverflowQueue oflowHandler = _oflowqs.remove(conn);
|
||||
if (oflowHandler != null) {
|
||||
_oflowqs.put(rconn, oflowHandler);
|
||||
}
|
||||
|
||||
// and let the client manager know about our new connection
|
||||
_clmgr.connectionEstablished(rconn, conn.getAuthName(), conn.getAuthRequest(),
|
||||
conn.getAuthResponse());
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure upgrading authing connection to running.", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendOutgoingMessages (long iterStamp)
|
||||
{
|
||||
super.sendOutgoingMessages(iterStamp);
|
||||
|
||||
// send any datagrams
|
||||
Tuple<PresentsConnection, byte[]> tup;
|
||||
while ((tup = _dataq.getNonBlocking()) != null) {
|
||||
writeDatagram(tup.left, tup.right);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a datagram to the specified connection.
|
||||
*
|
||||
* @return true if the datagram was sent, false if we failed to send for any reason.
|
||||
*/
|
||||
protected boolean writeDatagram (PresentsConnection conn, byte[] data)
|
||||
{
|
||||
InetSocketAddress target = conn.getDatagramAddress();
|
||||
if (target == null) {
|
||||
log.warning("No address to send datagram", "conn", conn);
|
||||
return false;
|
||||
}
|
||||
|
||||
_databuf.clear();
|
||||
_databuf.put(data).flip();
|
||||
try {
|
||||
return conn.getDatagramChannel().send(_databuf, target) > 0;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to send datagram.", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles client authentication. The base authenticator is injected but optional services
|
||||
* like the PeerManager may replace this authenticator with one that intercepts certain types
|
||||
* of authentication and then passes normal authentications through. */
|
||||
@Inject(optional=true) protected Authenticator _author = new DummyAuthenticator();
|
||||
protected List<ChainedAuthenticator> _authors = Lists.newArrayList();
|
||||
|
||||
protected Queue<AuthingConnection> _authq = Queue.newQueue();
|
||||
protected Queue<Tuple<Connection, InetSocketAddress>> _connectq = Queue.newQueue();
|
||||
|
||||
protected FramingOutputStream _framer = new FramingOutputStream();
|
||||
protected ByteArrayOutputStream _flattener = new ByteArrayOutputStream();
|
||||
|
||||
// some dependencies
|
||||
@Inject @AuthInvoker protected Invoker _authInvoker;
|
||||
@Inject protected ClientManager _clmgr;
|
||||
@Inject protected PresentsDObjectMgr _omgr;
|
||||
|
||||
protected Queue<Tuple<PresentsConnection, byte[]>> _dataq = Queue.newQueue();
|
||||
protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE);
|
||||
}
|
||||
@@ -45,7 +45,8 @@ import static com.threerings.presents.Log.log;
|
||||
*/
|
||||
public class ServerCommunicator extends Communicator
|
||||
{
|
||||
public ServerCommunicator (Client client, ConnectionManager conmgr, RootDObjectManager rootmgr)
|
||||
public ServerCommunicator (Client client, PresentsConnectionManager conmgr,
|
||||
RootDObjectManager rootmgr)
|
||||
{
|
||||
super(client);
|
||||
_conmgr = conmgr;
|
||||
@@ -62,7 +63,7 @@ public class ServerCommunicator extends Communicator
|
||||
|
||||
// we assume server entities have no firewall issues and can connect on the first port
|
||||
try {
|
||||
Connection conn = new Connection() {
|
||||
PresentsConnection conn = new PresentsConnection() {
|
||||
@Override public void postMessage (Message msg) {
|
||||
super.postMessage(msg);
|
||||
// outgoing traffic on this connection is used to prevent idleness
|
||||
@@ -92,7 +93,7 @@ public class ServerCommunicator extends Communicator
|
||||
shutdown();
|
||||
}
|
||||
};
|
||||
conn.setMessageHandler(new Connection.MessageHandler() {
|
||||
conn.setMessageHandler(new PresentsConnection.MessageHandler() {
|
||||
public void handleMessage (Message message) {
|
||||
try {
|
||||
// our first message will always be an auth response
|
||||
@@ -177,7 +178,7 @@ public class ServerCommunicator extends Communicator
|
||||
super.logonSucceeded(data);
|
||||
|
||||
// now we can route all messages to the ClientDObjectMgr
|
||||
_conn.setMessageHandler(new Connection.MessageHandler() {
|
||||
_conn.setMessageHandler(new PresentsConnection.MessageHandler() {
|
||||
public void handleMessage (Message message) {
|
||||
if (PING_DEBUG && message instanceof PongResponse) {
|
||||
log.info("Got pong from server " + message);
|
||||
@@ -200,9 +201,9 @@ public class ServerCommunicator extends Communicator
|
||||
clientCleanup(_logonError);
|
||||
}
|
||||
|
||||
protected ConnectionManager _conmgr;
|
||||
protected PresentsConnectionManager _conmgr;
|
||||
protected RootDObjectManager _rootmgr;
|
||||
protected Connection _conn;
|
||||
protected PresentsConnection _conn;
|
||||
protected ClassLoader _loader;
|
||||
protected Exception _logonError;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user