Restructured BindingConnectionManager so that the listen sockets are included
with all the other sockets and we do only a single select. Nixed the SocketChannelAcceptor as it relies on having its own Selector which is not desirable here or in Samsara. I'd also like to move SelectorIterable to samskivert since it's a generally useful addition to NIO, but it depends on Google Collections. It should probably stay in Presents anyway since it has a Presents-inspired if not Presents-specific catastrophic error handling approach. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6170 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,198 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/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.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.nio.SelectorIterable.SelectFailureHandler;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Listens for socket connections on a set of ports for a hostname and passes those connected
|
||||
* sockets on to a listener when they're ready. {@link #listen()} must be called after
|
||||
* creating the acceptor to open the channels, and then tick must be called periodically to process
|
||||
* new connections.
|
||||
*/
|
||||
public class SocketChannelAcceptor
|
||||
{
|
||||
/** Used to pass freshly accepted sockets to entities that want them. */
|
||||
public interface SocketChannelHandler
|
||||
{
|
||||
/** Called when a new connection has been accepted and is ready for use. */
|
||||
void handleSocketChannel (SocketChannel channel, long when);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a address to the given host, or the wildcard host if the hostname is
|
||||
* {@link StringUtil#blank}.
|
||||
*/
|
||||
public static InetSocketAddress getAddress (String hostname, int port)
|
||||
{
|
||||
return StringUtil.isBlank(hostname) ?
|
||||
new InetSocketAddress(port) : new InetSocketAddress(hostname, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an acceptor that passes socket connections on to the given handler.
|
||||
*
|
||||
* @param failureHandler - called when the selector is irredemably broken.
|
||||
* @param bindHostname - the hostname to bind to or null for all interfaces
|
||||
* @param ports - the ports to bind to, or an empty array to skip binding
|
||||
* @param selectLoopTime - the amount of time to wait in select, or 0 to skip the wait at all.
|
||||
*/
|
||||
public SocketChannelAcceptor (SocketChannelHandler connectionHandler,
|
||||
SelectFailureHandler failureHandler, String bindHostname, int[] ports, int selectLoopTime)
|
||||
throws IOException
|
||||
{
|
||||
Preconditions.checkNotNull(ports, "Ports must be non-null.");
|
||||
_bindHostname = bindHostname;
|
||||
_ports = ports;
|
||||
_selectorSelector = new SelectorIterable(_selector, selectLoopTime, failureHandler);
|
||||
_connHandler = connectionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the selector for ready keys and passes any through to the handlers.
|
||||
*/
|
||||
public void tick (long when)
|
||||
{
|
||||
for (SelectionKey key : _selectorSelector) {
|
||||
ServerSocketChannel ssocket = _channels.get(key);
|
||||
SocketChannel channel = null;
|
||||
try {
|
||||
channel = ssocket.accept();
|
||||
} catch (IOException e) {
|
||||
log.warning("Got exception on accept", e);
|
||||
continue;
|
||||
}
|
||||
if (channel == null) {
|
||||
// in theory this shouldn't happen because we got an ACCEPT_READY event...
|
||||
log.info("Psych! Got ACCEPT_READY, but no connection.");
|
||||
continue;
|
||||
}
|
||||
// log.debug("Accepted connection " + channel + ".");
|
||||
_connHandler.handleSocketChannel(channel, when);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ports on which we are listening for connections.
|
||||
*/
|
||||
public Iterable<Integer> getPorts ()
|
||||
{
|
||||
return Ints.asList(_ports);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for socket connections on the configured addresses.
|
||||
*/
|
||||
public boolean listen ()
|
||||
{
|
||||
for (int port : _ports) {
|
||||
try {
|
||||
// create a listening socket and add it to the select set
|
||||
final ServerSocketChannel ssocket = ServerSocketChannel.open();
|
||||
ssocket.configureBlocking(false);
|
||||
|
||||
InetSocketAddress isa = getAddress(_bindHostname, port);
|
||||
ssocket.socket().bind(isa);
|
||||
SelectionKey sk = ssocket.register(_selector, SelectionKey.OP_ACCEPT);
|
||||
_channels.put(sk, ssocket);
|
||||
log.info("Server listening on " + isa + ".");
|
||||
_ssockets.add(ssocket);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failure listening to socket", "hostname", _bindHostname,
|
||||
"port", port, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this is not currently working; it works but for whatever inscrutable reason the
|
||||
// inherited channel claims to be readable immediately every time through the select() loop
|
||||
// which causes the server to consume 100% of the CPU repeatedly ignoring the inherited
|
||||
// channel (except when an actual connection comes in in which case it does the right
|
||||
// thing)
|
||||
|
||||
// // now look to see if we were passed a socket inetd style by a
|
||||
// // privileged parent process
|
||||
// try {
|
||||
// Channel inherited = System.inheritedChannel();
|
||||
// if (inherited instanceof ServerSocketChannel) {
|
||||
// _ssocket = (ServerSocketChannel)inherited;
|
||||
// _ssocket.configureBlocking(false);
|
||||
// registerChannel(_ssocket);
|
||||
// successes++;
|
||||
// log.info("Server listening on " +
|
||||
// _ssocket.socket().getInetAddress() + ":" +
|
||||
// _ssocket.socket().getLocalPort() + ".");
|
||||
|
||||
// } else if (inherited != null) {
|
||||
// log.warning("Inherited non-server-socket channel " + inherited + ".");
|
||||
// }
|
||||
// } catch (IOException ioe) {
|
||||
// log.warning("Failed to check for inherited channel.");
|
||||
// }
|
||||
|
||||
// if we failed to listen on at least one port, give up the ghost
|
||||
return !_channels.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all listening sockets and shuts down.
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final int[] _ports;
|
||||
protected final String _bindHostname;
|
||||
protected final Map<SelectionKey, ServerSocketChannel> _channels = Maps.newHashMap();
|
||||
protected final Selector _selector = Selector.open();
|
||||
protected final SelectorIterable _selectorSelector;
|
||||
protected final List<ServerSocketChannel> _ssockets = Lists.newArrayList();
|
||||
protected final SocketChannelHandler _connHandler;
|
||||
}
|
||||
@@ -27,18 +27,19 @@ 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.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
|
||||
import com.threerings.nio.SocketChannelAcceptor;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
@@ -47,11 +48,10 @@ import static com.threerings.presents.Log.log;
|
||||
@Singleton
|
||||
public class BindingConnectionManager extends ConnectionManager
|
||||
{
|
||||
@Inject public BindingConnectionManager (Lifecycle cycle, ReportManager repmgr,
|
||||
IncomingEventWaitHolder incomingEventWait)
|
||||
@Inject public BindingConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
throws IOException
|
||||
{
|
||||
super(cycle, repmgr, incomingEventWait);
|
||||
super(cycle, repmgr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,85 +66,127 @@ public class BindingConnectionManager extends ConnectionManager
|
||||
* @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)
|
||||
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.");
|
||||
"Pass a zero-length array to bind no datagram ports.");
|
||||
|
||||
// Listen for socket connections, but don't wait to select on them. The connection check
|
||||
// occurs as part of ConnectionManager's incoming event loop, which already has a wait.
|
||||
_socketAcceptor = new SocketChannelAcceptor(this, _failureHandler, socketHostname,
|
||||
socketPorts, 0);
|
||||
|
||||
_datagramPorts = datagramPorts;
|
||||
_bindHostname = socketHostname;
|
||||
_ports = socketPorts;
|
||||
_datagramHostname = datagramHostname;
|
||||
_datagramPorts = datagramPorts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void willStart ()
|
||||
{
|
||||
if (!_socketAcceptor.listen()) {
|
||||
log.warning("ConnectionManager failed to bind to any ports. Shutting down.");
|
||||
_server.queueShutdown();
|
||||
super.willStart();
|
||||
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processIncomingEvents (long iterStamp)
|
||||
{
|
||||
// first check for any new sockets, ready to be accepted
|
||||
_socketAcceptor.tick(iterStamp);
|
||||
|
||||
// then do our standard processing for existing connected sockets
|
||||
super.processIncomingEvents(iterStamp);
|
||||
// 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
|
||||
_socketAcceptor.shutdown();
|
||||
|
||||
// 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 and add it to the select set
|
||||
// create a channel
|
||||
final DatagramChannel channel = DatagramChannel.open();
|
||||
channel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY
|
||||
channel.configureBlocking(false);
|
||||
InetSocketAddress isa = SocketChannelAcceptor.getAddress(_datagramHostname, port);
|
||||
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);
|
||||
@@ -192,9 +234,9 @@ public class BindingConnectionManager extends ConnectionManager
|
||||
return size;
|
||||
}
|
||||
|
||||
protected SocketChannelAcceptor _socketAcceptor;
|
||||
protected int[] _datagramPorts;
|
||||
protected String _datagramHostname;
|
||||
protected int[] _ports, _datagramPorts;
|
||||
protected String _bindHostname, _datagramHostname;
|
||||
|
||||
protected final List<DatagramChannel> _datagramChannels = Lists.newArrayList();
|
||||
protected List<ServerSocketChannel> _ssockets = Lists.newArrayList();
|
||||
protected List<DatagramChannel> _datagramChannels = Lists.newArrayList();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ 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;
|
||||
@@ -52,6 +54,7 @@ import com.threerings.io.FramingOutputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.UnreliableObjectInputStream;
|
||||
import com.threerings.io.UnreliableObjectOutputStream;
|
||||
import com.threerings.nio.SelectorIterable;
|
||||
|
||||
import com.threerings.presents.annotation.AuthInvoker;
|
||||
import com.threerings.presents.client.Client;
|
||||
@@ -68,10 +71,6 @@ import com.threerings.presents.server.PresentsServer;
|
||||
import com.threerings.presents.server.ReportManager;
|
||||
import com.threerings.presents.util.DatagramSequencer;
|
||||
|
||||
import com.threerings.nio.SocketChannelAcceptor;
|
||||
import com.threerings.nio.SelectorIterable;
|
||||
import com.threerings.nio.SelectorIterable.SelectFailureHandler;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
@@ -79,29 +78,24 @@ import static com.threerings.presents.Log.log;
|
||||
* 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 an external entity to do
|
||||
* so and call its {@link #handleSocketChannel} method.
|
||||
*
|
||||
* @see BindingConnectionManager
|
||||
* @see SocketChannelAcceptor
|
||||
* 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.
|
||||
*/
|
||||
@Singleton
|
||||
public class ConnectionManager extends LoopingThread
|
||||
implements Lifecycle.ShutdownComponent, ReportManager.Reporter,
|
||||
SocketChannelAcceptor.SocketChannelHandler
|
||||
implements Lifecycle.ShutdownComponent, ReportManager.Reporter
|
||||
{
|
||||
/**
|
||||
* Creates a connection manager instance.
|
||||
*/
|
||||
@Inject public ConnectionManager (Lifecycle cycle, ReportManager repmgr,
|
||||
IncomingEventWaitHolder incomingEventWait)
|
||||
public ConnectionManager (Lifecycle cycle, ReportManager repmgr)
|
||||
throws IOException
|
||||
{
|
||||
super("ConnectionManager");
|
||||
cycle.addComponent(this);
|
||||
repmgr.registerReporter(this);
|
||||
_selectorSelector = new SelectorIterable(
|
||||
_selector, incomingEventWait.value, _failureHandler);
|
||||
_selector = Selector.open();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,6 +139,39 @@ public class ConnectionManager extends LoopingThread
|
||||
return _stats.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called to introduce a new active socket into the system. For Presents systems
|
||||
* handling their own socket listening, this is called by the {@link BindingConnectionManager}.
|
||||
* If Presents is embedded in another framework that handles socket acceptance, this will be
|
||||
* called by a custom ConnectionManager derived class that integrates Presents with that
|
||||
* system.
|
||||
*/
|
||||
public void handleAcceptedSocket (SocketChannel channel)
|
||||
{
|
||||
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);
|
||||
synchronized (this) {
|
||||
_stats.connects++;
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
// no need to generate a warning because this happens in the normal course of events
|
||||
log.info("Failure accepting new connection: " + ioe);
|
||||
// make sure we don't leak a socket if something went awry
|
||||
try {
|
||||
channel.socket().close();
|
||||
} catch (IOException ioe2) {
|
||||
log.warning("Failed closing aborted connection: " + ioe2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -224,33 +251,6 @@ public class ConnectionManager extends LoopingThread
|
||||
report.append(bytesOut*1000/sinceLast).append(" bps\n");
|
||||
}
|
||||
|
||||
// from interface SocketChannelAcceptor.SocketChannelHandler
|
||||
public void handleSocketChannel (SocketChannel channel, long when)
|
||||
{
|
||||
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);
|
||||
synchronized (this) {
|
||||
_stats.connects++;
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
// no need to generate a warning because this happens in the normal course of events
|
||||
log.info("Failure accepting new connection: " + ioe);
|
||||
// make sure we don't leak a socket if something went awry
|
||||
try {
|
||||
channel.socket().close();
|
||||
} catch (IOException ioe2) {
|
||||
log.warning("Failed closing aborted connection: " + ioe2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from LoopingThread
|
||||
public boolean isRunning ()
|
||||
{
|
||||
@@ -258,6 +258,78 @@ public class ConnectionManager extends LoopingThread
|
||||
return super.isRunning() || _omgr.isRunning();
|
||||
}
|
||||
|
||||
@Override // from LoopingThread
|
||||
protected void willStart ()
|
||||
{
|
||||
super.willStart();
|
||||
|
||||
log.info("Creating selector selector!");
|
||||
_selectorSelector = new SelectorIterable(
|
||||
_selector, _selectLoopTime, new SelectorIterable.SelectFailureHandler() {
|
||||
public void handleSelectFailure (Exception e) {
|
||||
log.error("One of our selectors crapped out completely. " +
|
||||
"Shutting down the connection manager.", e);
|
||||
shutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override // from LoopingThread
|
||||
protected void iterate ()
|
||||
{
|
||||
// performs the select loop; this is the body of the conmgr thread
|
||||
final long iterStamp = System.currentTimeMillis();
|
||||
|
||||
// note whether or not we're generating a debug report
|
||||
boolean generateDebugReport = (iterStamp - _lastDebugStamp > DEBUG_REPORT_INTERVAL);
|
||||
if (DEBUG_REPORT && generateDebugReport) {
|
||||
_lastDebugStamp = iterStamp;
|
||||
}
|
||||
|
||||
// close any connections that have been queued up to die
|
||||
Connection dconn;
|
||||
while ((dconn = _deathq.getNonBlocking()) != null) {
|
||||
// it's possible that we caught an EOF trying to read from this connection even after
|
||||
// it was queued up for death, so let's avoid trying to close it twice
|
||||
if (!dconn.isClosed()) {
|
||||
dconn.close();
|
||||
}
|
||||
}
|
||||
|
||||
// close connections that have had no network traffic for too long
|
||||
for (NetEventHandler handler : _handlers.values()) {
|
||||
if (handler.checkIdle(iterStamp)) {
|
||||
// this will queue the connection for closure on our next tick
|
||||
handler.becameIdle();
|
||||
}
|
||||
}
|
||||
|
||||
// send any messages that are waiting on the outgoing overflow and message queues
|
||||
sendOutgoingMessages(iterStamp);
|
||||
|
||||
// we may be in the middle of shutting down (in which case super.isRunning() is false but
|
||||
// isRunning() is true); this is because we stick around until the dobject manager is
|
||||
// totally done so that we can send shutdown-related events out to our clients; during
|
||||
// those last moments we don't want to accept new connections or read any incoming messages
|
||||
if (super.isRunning()) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (DEBUG_REPORT && generateDebugReport) {
|
||||
log.info("CONMGR status " + getStats());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the authentication process on the specified connection. This is called by {@link
|
||||
* AuthingConnection} itself once it receives its auth request.
|
||||
@@ -347,64 +419,6 @@ public class ConnectionManager extends LoopingThread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the select loop. This is the body of the conmgr thread.
|
||||
*/
|
||||
@Override
|
||||
protected void iterate ()
|
||||
{
|
||||
final long iterStamp = System.currentTimeMillis();
|
||||
|
||||
// note whether or not we're generating a debug report
|
||||
boolean generateDebugReport = (iterStamp - _lastDebugStamp > DEBUG_REPORT_INTERVAL);
|
||||
if (DEBUG_REPORT && generateDebugReport) {
|
||||
_lastDebugStamp = iterStamp;
|
||||
}
|
||||
|
||||
// close any connections that have been queued up to die
|
||||
Connection dconn;
|
||||
while ((dconn = _deathq.getNonBlocking()) != null) {
|
||||
// it's possible that we caught an EOF trying to read from this connection even after
|
||||
// it was queued up for death, so let's avoid trying to close it twice
|
||||
if (!dconn.isClosed()) {
|
||||
dconn.close();
|
||||
}
|
||||
}
|
||||
|
||||
// close connections that have had no network traffic for too long
|
||||
for (NetEventHandler handler : _handlers.values()) {
|
||||
if (handler.checkIdle(iterStamp)) {
|
||||
// this will queue the connection for closure on our next tick
|
||||
handler.becameIdle();
|
||||
}
|
||||
}
|
||||
|
||||
// send any messages that are waiting on the outgoing overflow and message queues
|
||||
sendOutgoingMessages(iterStamp);
|
||||
|
||||
// we may be in the middle of shutting down (in which case super.isRunning() is false but
|
||||
// isRunning() is true); this is because we stick around until the dobject manager is
|
||||
// totally done so that we can send shutdown-related events out to our clients; during
|
||||
// those last moments we don't want to accept new connections or read any incoming messages
|
||||
if (super.isRunning()) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (DEBUG_REPORT && generateDebugReport) {
|
||||
log.info("CONMGR status " + getStats());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts connections that have completed the authentication process into full running
|
||||
* connections and notifies the client manager that new connections have been established.
|
||||
@@ -929,14 +943,6 @@ public class ConnectionManager extends LoopingThread
|
||||
protected int _msgs, _partials;
|
||||
}
|
||||
|
||||
protected final SelectFailureHandler _failureHandler = new SelectFailureHandler() {
|
||||
public void handleSelectFailure (Exception e) {
|
||||
log.error("One of our selectors crapped out completely. " +
|
||||
"Shutting down the connection manager.", e);
|
||||
shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
/** Used to create an overflow queue on the first partial write. */
|
||||
protected PartialWriteHandler _oflowHandler = new PartialWriteHandler() {
|
||||
public void handlePartialWrite (Connection conn, ByteBuffer msgbuf) {
|
||||
@@ -946,19 +952,13 @@ public class ConnectionManager extends LoopingThread
|
||||
}
|
||||
};
|
||||
|
||||
/** Helper for Guice to allow the incoming event wait to be injected optionally. */
|
||||
protected static class IncomingEventWaitHolder
|
||||
{
|
||||
@Inject(optional=true) @IncomingEventWait int value = SELECT_LOOP_TIME;
|
||||
}
|
||||
|
||||
/** 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 = Selector.open();
|
||||
protected Selector _selector;
|
||||
protected SelectorIterable _selectorSelector;
|
||||
|
||||
/** Maps selection keys to network event handlers. */
|
||||
@@ -993,18 +993,19 @@ public class ConnectionManager extends LoopingThread
|
||||
/** A runnable to execute when the connection manager thread exits. */
|
||||
protected volatile Runnable _onExit;
|
||||
|
||||
/** Duration in milliseconds for which we wait for network events before checking our running
|
||||
* flag to see if we should still be running. We don't want to loop too tightly, but we need to
|
||||
* make sure we don't sit around listening for incoming network events too long when there are
|
||||
* outgoing messages in the queue. */
|
||||
@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;
|
||||
|
||||
/** How long we wait for network events before checking our running flag to see if we should
|
||||
* still be running. We don't want to loop too tightly, but we need to make sure we don't sit
|
||||
* around listening for incoming network events too long when there are outgoing messages in
|
||||
* the queue. */
|
||||
protected static final int SELECT_LOOP_TIME = 100;
|
||||
|
||||
/** Used to denote asynchronous close requests. */
|
||||
protected static final byte[] ASYNC_CLOSE_REQUEST = new byte[0];
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/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.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
|
||||
/**
|
||||
* Identifies the amount of time to wait for incoming events in ConnectionManager's select.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.PARAMETER })
|
||||
@BindingAnnotation
|
||||
public @interface IncomingEventWait
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user