package games.strategy.net.nio; import static com.google.common.base.Preconditions.checkNotNull; import games.strategy.net.INode; import games.strategy.net.IObjectStreamFactory; import games.strategy.net.MessageHeader; import java.io.IOException; import java.net.Socket; import java.nio.channels.SocketChannel; import java.util.logging.Level; import lombok.extern.java.Log; /** * The threads needed for a group of sockets using NIO. One thread reds socket data, one thread * writes socket data and one thread deserializes (decodes) packets read by the read thread. * serializing (encoding) objects to be written across the network is done by threads calling this * object. */ @Log public class NioSocket implements ErrorReporter { private final Encoder encoder; private final Decoder decoder; private final NioWriter writer; private final NioReader reader; private final NioSocketListener listener; public NioSocket(final IObjectStreamFactory factory, final NioSocketListener listener) { this.listener = listener; writer = new NioWriter(this); reader = new NioReader(this); decoder = new Decoder(this, reader, this, factory); encoder = new Encoder(writer, factory); } INode getLocalNode() { return listener.getLocalNode(); } /** Stop our threads. This does not close the sockets we are connected to. */ public void shutDown() { writer.shutDown(); reader.shutDown(); decoder.shutDown(); } /** * Sends the specified message header through the specified channel. * * @param to The destination channel. * @param header The message header to send. */ public void send(final SocketChannel to, final MessageHeader header) { checkNotNull(to); checkNotNull(header); checkNotNull(header.getFrom()); encoder.write(to, header); } /** Add this channel. The channel will either be unquarantined, or an error will be reported */ public void add(final SocketChannel channel, final QuarantineConversation conversation) { if (channel.isBlocking()) { throw new IllegalArgumentException("Channel is blocking"); } // add the decoder first, so it can quarantine the messages! decoder.add(channel, conversation); reader.add(channel); } void unquarantine(final SocketChannel channel, final QuarantineConversation conversation) { listener.socketUnquarantined(channel, conversation); } @Override public void error(final SocketChannel channel, final Exception e) { close(channel); listener.socketError(channel, e); } /** Close the channel, and clean up any data associated with it. */ public void close(final SocketChannel channel) { try { final Socket s = channel.socket(); if (!s.isInputShutdown()) { s.shutdownInput(); } if (!s.isOutputShutdown()) { s.shutdownOutput(); } if (!s.isClosed()) { s.close(); } channel.close(); } catch (final IOException e1) { log.log(Level.FINE, "error closing channel", e1); } decoder.close(channel); writer.close(channel); reader.close(channel); } void messageReceived(final MessageHeader header, final SocketChannel channel) { listener.messageReceived(header, channel); } }