Split tcp socket opening and datagram listening out of ConnectionManager to allow sockets to remain
open when a ConnectionManager using them shuts down. BindingConnectionManager takes on the duties of opening sockets that ConnectionManager used to handle, and it's injected automatically by PresentsServer. Hopefully that means this won't change anything for existing users of PresentsServer. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6157 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package com.threerings.nio;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
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 extends SelectAcceptor
|
||||
{
|
||||
public interface SocketChannelHandler
|
||||
{
|
||||
void handleSocketChannel (SocketChannel channel, long when);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an acceptor that passes socket connections on to the given handler.
|
||||
*
|
||||
* @param failureHandler - called when the selector is irredemably broken.
|
||||
* @param hostname - the hostname to bind to or null for all interfaces
|
||||
* @param ports - the ports to bind to, or an empty array to skip binding
|
||||
*/
|
||||
public SocketChannelAcceptor (SocketChannelHandler connectionHandler,
|
||||
SelectFailureHandler failureHandler, String bindHostname, int[] ports)
|
||||
throws IOException
|
||||
{
|
||||
super(failureHandler, bindHostname, ports);
|
||||
|
||||
_connHandler = connectionHandler;
|
||||
}
|
||||
|
||||
protected void acceptConnection (ServerSocketChannel listener, long when)
|
||||
{
|
||||
SocketChannel channel = null;
|
||||
try {
|
||||
channel = listener.accept();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
|
||||
// log.debug("Accepted connection " + channel + ".");
|
||||
|
||||
_connHandler.handleSocketChannel(channel, when);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listens for socket connections on the configured addresses.
|
||||
*/
|
||||
public boolean listen ()
|
||||
{
|
||||
int successes = 0;
|
||||
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(port);
|
||||
ssocket.socket().bind(isa);
|
||||
SelectionKey sk = ssocket.register(_selector, SelectionKey.OP_ACCEPT);
|
||||
_handlers.put(sk, new SelectionHandler() {
|
||||
public void handle (long when) {
|
||||
acceptConnection(ssocket, when);
|
||||
}
|
||||
});
|
||||
successes++;
|
||||
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 successes > 0;
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// unbind our listening socket
|
||||
// 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.
|
||||
for (ServerSocketChannel ssocket : _ssockets) {
|
||||
try {
|
||||
ssocket.socket().close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to close listening socket: " + ssocket, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final List<ServerSocketChannel> _ssockets = Lists.newArrayList();
|
||||
protected final SocketChannelHandler _connHandler;
|
||||
}
|
||||
Reference in New Issue
Block a user