ConnectionManager allowed a server to listen on multiple tcp and datagram ports, but it only closed

the last opened.  Close all opened listening sockets.

Also, the last opened datagram socket would be used to send all datagram messages instead of the one
datagrams were received on.  It now goes through the same channel where datagrams were last
received.



git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6156 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Charlie Groves
2010-09-14 22:47:45 +00:00
parent c2ebf2884b
commit 0d4837e71d
2 changed files with 50 additions and 41 deletions
@@ -21,17 +21,16 @@
package com.threerings.presents.server.net; package com.threerings.presents.server.net;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.threerings.io.FramedInputStream; import com.threerings.io.FramedInputStream;
import com.threerings.io.FramingOutputStream; import com.threerings.io.FramingOutputStream;
@@ -146,6 +145,15 @@ public class Connection implements NetEventHandler
return _datagramAddress; return _datagramAddress;
} }
/**
* Returns the channel through which datagrams should be sent or null if no datagram channel
* has been established.
*/
public DatagramChannel getDatagramChannel ()
{
return _datagramChannel;
}
/** /**
* Sets the secret string used to authenticate datagrams from the client. * Sets the secret string used to authenticate datagrams from the client.
*/ */
@@ -236,7 +244,8 @@ public class Connection implements NetEventHandler
/** /**
* Processes a datagram sent to this connection. * Processes a datagram sent to this connection.
*/ */
public void handleDatagram (InetSocketAddress source, ByteBuffer buf, long when) public void handleDatagram (InetSocketAddress source, DatagramChannel channel,
ByteBuffer buf, long when)
{ {
// lazily create our various bits and bobs // lazily create our various bits and bobs
if (_digest == null) { if (_digest == null) {
@@ -263,6 +272,7 @@ public class Connection implements NetEventHandler
// update our target address // update our target address
_datagramAddress = source; _datagramAddress = source;
_datagramChannel = channel;
// read the contents through the sequencer // read the contents through the sequencer
try { try {
@@ -450,6 +460,7 @@ public class Connection implements NetEventHandler
protected ObjectOutputStream _oout; protected ObjectOutputStream _oout;
protected InetSocketAddress _datagramAddress; protected InetSocketAddress _datagramAddress;
protected DatagramChannel _datagramChannel;
protected byte[] _datagramSecret; protected byte[] _datagramSecret;
protected boolean _transmitDatagrams; protected boolean _transmitDatagrams;
@@ -34,7 +34,6 @@ import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel; import java.nio.channels.DatagramChannel;
import java.nio.channels.NotYetConnectedException; import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.Selector; import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel; import java.nio.channels.ServerSocketChannel;
@@ -116,20 +115,15 @@ public class ConnectionManager extends LoopingThread
String bindHostname, String datagramHostname, int[] ports, int[] datagramPorts) String bindHostname, String datagramHostname, int[] ports, int[] datagramPorts)
throws IOException throws IOException
{ {
Preconditions.checkArgument(ports != null, "Ports must be non-null."); Preconditions.checkNotNull(ports, "Ports must be non-null.");
Preconditions.checkArgument(datagramPorts != null, "Datagram 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.");
Preconditions.checkState(!super.isRunning(), "Must initialize before starting.");
_bindHostname = bindHostname; _bindHostname = bindHostname;
_datagramHostname = datagramHostname; _datagramHostname = datagramHostname;
_ports = ports; _ports = ports;
_datagramPorts = datagramPorts; _datagramPorts = datagramPorts;
_selector = SelectorProvider.provider().openSelector(); _selector = SelectorProvider.provider().openSelector();
// create our stats record
_stats = new ConMgrStats();
_lastStats = new ConMgrStats();
} }
/** /**
@@ -360,18 +354,20 @@ public class ConnectionManager extends LoopingThread
@Override @Override
protected void willStart () protected void willStart ()
{ {
Preconditions.checkNotNull(_ports, "Must call init before starting.");
int successes = 0; int successes = 0;
for (int port : _ports) { for (int port : _ports) {
try { try {
// create a listening socket and add it to the select set // create a listening socket and add it to the select set
_ssocket = ServerSocketChannel.open(); ServerSocketChannel ssocket = ServerSocketChannel.open();
_ssocket.configureBlocking(false); ssocket.configureBlocking(false);
InetSocketAddress isa = getAddress(_bindHostname, port); InetSocketAddress isa = getAddress(_bindHostname, port);
_ssocket.socket().bind(isa); ssocket.socket().bind(isa);
registerChannel(_ssocket); registerChannel(ssocket);
successes++; successes++;
log.info("Server listening on " + isa + "."); log.info("Server listening on " + isa + ".");
_ssockets.add(ssocket);
} catch (IOException ioe) { } catch (IOException ioe) {
log.warning("Failure listening to socket", "hostname", _bindHostname, log.warning("Failure listening to socket", "hostname", _bindHostname,
@@ -416,12 +412,13 @@ public class ConnectionManager extends LoopingThread
for (int port : _datagramPorts) { for (int port : _datagramPorts) {
try { try {
// create a channel and add it to the select set // create a channel and add it to the select set
_datagramChannel = DatagramChannel.open(); DatagramChannel channel = DatagramChannel.open();
_datagramChannel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY channel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY
_datagramChannel.configureBlocking(false); channel.configureBlocking(false);
InetSocketAddress isa = getAddress(_datagramHostname, port); InetSocketAddress isa = getAddress(_datagramHostname, port);
_datagramChannel.socket().bind(isa); channel.socket().bind(isa);
registerChannel(_datagramChannel); registerChannel(channel);
_datagramChannels.add(channel);
log.info("Server accepting datagrams on " + isa + "."); log.info("Server accepting datagrams on " + isa + ".");
} catch (IOException ioe) { } catch (IOException ioe) {
@@ -815,7 +812,7 @@ public class ConnectionManager extends LoopingThread
_databuf.clear(); _databuf.clear();
_databuf.put(data).flip(); _databuf.put(data).flip();
try { try {
return _datagramChannel.send(_databuf, target) > 0; return conn.getDatagramChannel().send(_databuf, target) > 0;
} catch (IOException ioe) { } catch (IOException ioe) {
log.warning("Failed to send datagram.", ioe); log.warning("Failed to send datagram.", ioe);
return false; return false;
@@ -849,10 +846,9 @@ public class ConnectionManager extends LoopingThread
// create a new authing connection object to manage the authentication of this client // create a new authing connection object to manage the authentication of this client
// connection and register it with our selection set // connection and register it with our selection set
SelectableChannel selchan = channel; channel.configureBlocking(false);
selchan.configureBlocking(false);
AuthingConnection aconn = new AuthingConnection(); AuthingConnection aconn = new AuthingConnection();
aconn.selkey = selchan.register(_selector, SelectionKey.OP_READ); aconn.selkey = channel.register(_selector, SelectionKey.OP_READ);
aconn.init(this, channel, System.currentTimeMillis()); aconn.init(this, channel, System.currentTimeMillis());
_handlers.put(aconn.selkey, aconn); _handlers.put(aconn.selkey, aconn);
synchronized (this) { synchronized (this) {
@@ -909,7 +905,7 @@ public class ConnectionManager extends LoopingThread
int connectionId = _databuf.getInt(); int connectionId = _databuf.getInt();
Connection conn = _connections.get(connectionId); Connection conn = _connections.get(connectionId);
if (conn != null) { if (conn != null) {
conn.handleDatagram(source, _databuf, when); conn.handleDatagram(source, listener, _databuf, when);
} else { } else {
log.debug("Received datagram for unknown connection", "id", connectionId, log.debug("Received datagram for unknown connection", "id", connectionId,
"source", source); "source", source);
@@ -1074,16 +1070,18 @@ public class ConnectionManager extends LoopingThread
// unbind our listening socket // unbind our listening socket
// Note: because we wait for the object manager to exit before we do, we will still be // Note: because we wait for the object manager to exit before we do, we will still be
// accepting connections as long as there are events pending. // accepting connections as long as there are events pending.
// TODO: consider shutting down the listen socker earlier, like in the shutdown method // TODO: consider closing the listen sockets earlier, like in the shutdown method
try { for (ServerSocketChannel ssocket : _ssockets) {
_ssocket.socket().close(); try {
} catch (IOException ioe) { ssocket.socket().close();
log.warning("Failed to close listening socket.", ioe); } catch (IOException ioe) {
log.warning("Failed to close listening socket: " + ssocket, ioe);
}
} }
// and the datagram socket, if any // and the datagram sockets, if any
if (_datagramChannel != null) { for (DatagramChannel datagramChannel : _datagramChannels) {
_datagramChannel.socket().close(); datagramChannel.socket().close();
} }
// report if there's anything left on the outgoing message queue // report if there's anything left on the outgoing message queue
@@ -1224,8 +1222,8 @@ public class ConnectionManager extends LoopingThread
protected int[] _ports, _datagramPorts; protected int[] _ports, _datagramPorts;
protected String _bindHostname, _datagramHostname; protected String _bindHostname, _datagramHostname;
protected Selector _selector; protected Selector _selector;
protected ServerSocketChannel _ssocket; protected List<ServerSocketChannel> _ssockets = Lists.newArrayList();
protected DatagramChannel _datagramChannel; protected List<DatagramChannel> _datagramChannels = Lists.newArrayList();
/** Counts consecutive runtime errors in select(). */ /** Counts consecutive runtime errors in select(). */
protected int _runtimeExceptionCount; protected int _runtimeExceptionCount;
@@ -1251,10 +1249,10 @@ public class ConnectionManager extends LoopingThread
protected Map<Connection, OverflowQueue> _oflowqs = Maps.newHashMap(); protected Map<Connection, OverflowQueue> _oflowqs = Maps.newHashMap();
/** Our current runtime stats. */ /** Our current runtime stats. */
protected ConMgrStats _stats; protected ConMgrStats _stats = new ConMgrStats();
/** A snapshot of our runtime stats as of our last report. */ /** A snapshot of our runtime stats as of our last report. */
protected ConMgrStats _lastStats; protected ConMgrStats _lastStats = new ConMgrStats();
/** Used to periodically report connection manager activity when in debug mode. */ /** Used to periodically report connection manager activity when in debug mode. */
protected long _lastDebugStamp; protected long _lastDebugStamp;