Basic support for datagram messaging. Hope I haven't fucked

anything up!


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5090 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Andrzej Kapolka
2008-05-13 21:12:39 +00:00
parent 02654017d0
commit 9bafa5e7c8
20 changed files with 1397 additions and 109 deletions
@@ -21,9 +21,11 @@
package com.threerings.presents.server.net;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
@@ -44,10 +46,12 @@ import com.samskivert.util.*;
import com.threerings.io.FramingOutputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.ConMgrStats;
import com.threerings.presents.net.AuthRequest;
import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.util.DatagramSequencer;
import com.threerings.presents.server.Authenticator;
import com.threerings.presents.server.PresentsServer;
@@ -79,8 +83,19 @@ public class ConnectionManager extends LoopingThread
*/
public ConnectionManager (int[] ports)
throws IOException
{
this(ports, new int[0]);
}
/**
* Constructs and initialized a connection manager (binding socket on which it will listen for
* client connections to each of the specified ports).
*/
public ConnectionManager (int[] ports, int[] datagramPorts)
throws IOException
{
_ports = ports;
_datagramPorts = datagramPorts;
_selector = SelectorProvider.provider().openSelector();
// create our stats record
@@ -311,8 +326,27 @@ public class ConnectionManager extends LoopingThread
return;
}
// we'll use this for sending messages to clients
// open up the datagram ports as well
for (int port : _datagramPorts) {
try {
// create a channel and add it to the select set
_datagramChannel = DatagramChannel.open();
_datagramChannel.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(port);
_datagramChannel.socket().bind(isa);
registerChannel(_datagramChannel);
log.info("Server accepting datagrams on " + isa + ".");
} catch (IOException ioe) {
log.log(Level.WARNING, "Failure opening datagram channel on port '" +
port + "'.", ioe);
}
}
// we'll use these for sending messages to clients
_framer = new FramingOutputStream();
_flattener = new ByteArrayOutputStream();
// notify our startup listener, if we have one
if (_startlist != null) {
@@ -340,6 +374,30 @@ public class ConnectionManager extends LoopingThread
});
}
/** Helper function for {@link #willStart}. */
protected void registerChannel (final DatagramChannel listener)
throws IOException
{
SelectionKey sk = listener.register(_selector, SelectionKey.OP_READ);
_handlers.put(sk, new NetEventHandler() {
public int handleEvent (long when) {
return readDatagram(listener, when);
}
public boolean checkIdle (long now) {
return false; // we're never idle
}
});
}
/**
* Returns a reference to the output stream used to flatten messages into byte arrays. Should
* only be called by {@link Connection}.
*/
protected ByteArrayOutputStream getFlattener ()
{
return _flattener;
}
/**
* Performs the select loop. This is the body of the conmgr thread.
*/
@@ -385,6 +443,11 @@ public class ConnectionManager extends LoopingThread
// replace the mapping in the handlers table from the old conn with the new one
_handlers.put(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) {
@@ -526,6 +589,11 @@ 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);
}
}
/**
@@ -595,6 +663,29 @@ 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 _datagramChannel.send(_databuf, target) > 0;
} catch (IOException ioe) {
log.log(Level.WARNING, "Failed to send datagram.", ioe);
return false;
}
}
/** Called by {@link #writeMessage} and friends when they write data over the network. */
protected final synchronized void noteWrite (int msgs, int bytes)
{
@@ -624,6 +715,11 @@ public class ConnectionManager extends LoopingThread
log.log(Level.WARNING, "Failed to close listening socket.", ioe);
}
// and the datagram socket, if any
if (_datagramChannel != null) {
_datagramChannel.socket().close();
}
// report if there's anything left on the outgoing message queue
if (_outq.size() > 0) {
log.warning("Connection Manager failed to deliver " + _outq.size() + " message(s).");
@@ -684,6 +780,51 @@ public class ConnectionManager extends LoopingThread
}
}
/**
* Called by our net event handler when a datagram is ready to be read from the channel.
*
* @return the size of the datagram.
*/
protected int readDatagram (DatagramChannel listener, long when)
{
InetSocketAddress source;
_databuf.clear();
try {
source = (InetSocketAddress)listener.receive(_databuf);
} catch (IOException ioe) {
log.log(Level.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, _databuf, when);
} else {
log.warning("Received datagram for unknown connection [id=" + connectionId +
", source=" + source + "].");
}
// return the size of the datagram
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 Connection#postMessage}
@@ -708,6 +849,12 @@ public class ConnectionManager extends LoopingThread
}
try {
// send it as a datagram if hinted and possible
if (msg.datagram && conn.getDatagramAddress() != null) {
postDatagram(conn, msg);
return;
}
_framer.resetFrame();
// flatten this message using the connection's output stream
@@ -731,14 +878,34 @@ public class ConnectionManager extends LoopingThread
}
}
/**
* Helper function for {@link #postMessage}; handles posting the message as a datagram.
*/
void postDatagram (Connection conn, DownstreamMessage msg)
throws Exception
{
_flattener.reset();
// flatten the message using the connection's sequencer
DatagramSequencer sequencer = conn.getDatagramSequencer();
sequencer.writeDatagram(msg);
// extract as a byte array
byte[] data = _flattener.toByteArray();
// slap it on the queue
_dataq.append(new Tuple<Connection, byte[]>(conn, data));
}
/**
* Called by a connection if it experiences a network failure.
*/
void connectionFailed (Connection conn, IOException ioe)
{
// remove this connection from our mapping (it is automatically removed from the Selector
// remove this connection from our mappings (it is automatically removed from the Selector
// when the socket is closed)
_handlers.remove(conn.getSelectionKey());
_connections.remove(conn.getConnectionId());
_oflowqs.remove(conn);
synchronized (this) {
_stats.disconnects++;
@@ -753,9 +920,10 @@ public class ConnectionManager extends LoopingThread
*/
void connectionClosed (Connection conn)
{
// remove this connection from our mapping (it is automatically removed from the Selector
// remove this connection from our mappings (it is automatically removed from the Selector
// when the socket is closed)
_handlers.remove(conn.getSelectionKey());
_connections.remove(conn.getConnectionId());
_oflowqs.remove(conn);
// let our observers know what's up
@@ -860,10 +1028,11 @@ public class ConnectionManager extends LoopingThread
protected int _msgs, _partials;
}
protected int[] _ports;
protected int[] _ports, _datagramPorts;
protected Authenticator _author;
protected Selector _selector;
protected ServerSocketChannel _ssocket;
protected DatagramChannel _datagramChannel;
protected ResultListener<Object> _startlist;
/** Counts consecutive runtime errors in select(). */
@@ -873,12 +1042,18 @@ public class ConnectionManager extends LoopingThread
protected HashMap<SelectionKey,NetEventHandler> _handlers =
new HashMap<SelectionKey,NetEventHandler>();
/** Connections mapped by identifier. */
protected HashIntMap<Connection> _connections = new HashIntMap<Connection>();
protected Queue<Connection> _deathq = new Queue<Connection>();
protected Queue<AuthingConnection> _authq = new Queue<AuthingConnection>();
protected Queue<Tuple<Connection,byte[]>> _outq = new Queue<Tuple<Connection,byte[]>>();
protected Queue<Tuple<Connection,byte[]>> _dataq = new Queue<Tuple<Connection,byte[]>>();
protected FramingOutputStream _framer;
protected ByteArrayOutputStream _flattener;
protected ByteBuffer _outbuf = ByteBuffer.allocateDirect(64 * 1024);
protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE);
protected HashMap<Connection,OverflowQueue> _oflowqs = new HashMap<Connection,OverflowQueue>();