/* * Created by Andrey Cherkashin (acherkashin) * http://acherkashin.me * * License * Copyright (c) 2015 Andrey Cherkashin * The project released under the MIT license: http://opensource.org/licenses/MIT */ package ragefist.core.network; import com.juniform.IJUniformPacker; import com.juniform.JUniform; import com.juniform.JUniformObject; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.SelectionKey; import static java.nio.channels.SelectionKey.OP_WRITE; import java.nio.channels.SocketChannel; import java.nio.channels.WritableByteChannel; import java.util.LinkedList; import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author acherkashin */ public class Connection extends SocketClient { private static final Logger _log = Logger.getLogger(Connection.class.getName()); private static int _nextId = 0; private boolean _isClosed = false; private final int _id; private final Server _server; private final Socket _socket; private final SelectionKey _selectionKey; private final SocketChannel _socketChannel; private final ReadableByteChannel _readableByteChannel; private final WritableByteChannel _writableByteChannel; private Object _packetBuffer; private final Queue<byte[]> _sendQueue = new LinkedList<>(); private Object _attachment = null; private IJUniformPacker _packer; private final ISocketStrategy _socketStrategy; // ---------------------------------------------------------------------- // // BUILDER // ---------------------------------------------------------------------- // public final static class ConnectionBuilder { public Server server = null; public Socket socket = null; public SelectionKey selectionKey = null; public IJUniformPacker packerPacker = null; public ISocketStrategy socketStrategy = null; // BUILD public final Connection build() throws IllegalArgumentException { if (server == null) { throw new IllegalArgumentException("ConnectionBuilder.server parameter is null"); } if (socket == null) { throw new IllegalArgumentException("ConnectionBuilder.socket parameter is null"); } if (selectionKey == null) { throw new IllegalArgumentException("ConnectionBuilder.selectionKey parameter is null"); } if (socketStrategy == null) { throw new IllegalArgumentException("ConnectionBuilder.socketStrategy parameter is null"); } return new Connection(this); } } private Connection(ConnectionBuilder builder) { _id = ++_nextId; if (_nextId == Integer.MAX_VALUE) { _nextId = 0; } _server = builder.server; _socket = builder.socket; _selectionKey = builder.selectionKey; _socketChannel = (SocketChannel)_selectionKey.channel(); _readableByteChannel = _socket.getChannel(); _writableByteChannel = _socket.getChannel(); _packer = builder.packerPacker; if (_packer == null) { _packer = JUniform.getPackerInstance(JUniform.PACKER_TYPE_JSON); } _socketStrategy = builder.socketStrategy; } // ---------------------------------------------------------------------- // // PUBLIC // ---------------------------------------------------------------------- // // GETTERS / SETTERS public final int getId() { return _id; } public final boolean isClosed() { return _isClosed; } public final Queue<byte[]> getSendQueue() { return _sendQueue; } public final Object getAttachment() { return _attachment; } public void setAttachment(Object object) { _attachment = object; } public IJUniformPacker getPacker() { return _packer; } public void setPacker(IJUniformPacker packer) { _packer = packer; } /** * Sending bytes into a socket * @param bytes */ @Override public void sendPacket(byte[] bytes) { _log.info("Sending a packet..."); try { int written = 0; synchronized(this) { written = _socketStrategy.writePacket(this, bytes); } if (written == 0) { synchronized(getSendQueue()) { getSendQueue().add(bytes); _selectionKey.interestOps(_selectionKey.interestOps() | OP_WRITE); _selectionKey.selector().wakeup(); } } } catch(ClosedChannelException ex) { close(); } catch(IOException ex) { Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "Connection is closed due to unknown reason: " + ex.getMessage()); close(); } } /** * Sending a packet into the socket * @param packet */ @Override public void sendPacket(JUniformObject packet) { byte[] bytes = _packer.fromUniformObjectToByteArray(packet); sendPacket(bytes); } /** * Returns an array of new packets or NULL if there are no new packets * @param readBuffer * @return array | null * @throws IOException */ @Override public JUniformObject[] readPackets(ByteBuffer readBuffer) throws IOException { byte[][] packets = _socketStrategy.readPackets(this, readBuffer); if (packets.length == 0) { return null; } JUniformObject[] objects = new JUniformObject[packets.length]; for(int i = 0; i < packets.length; i++) { objects[i] = _packer.toUniformObjectFromBytes(packets[i]); } return objects; } @Override public final void close() { _isClosed = true; try { _socket.close(); } catch (IOException ex) { Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "Failed to close socket", ex); } } // package API @Override public int getPacketBufferSize() { return _server.getPacketBufferSize(); } @Override public final int read(final ByteBuffer buffer) throws IOException { return _readableByteChannel.read(buffer); } @Override public final int write(final ByteBuffer buffer) throws IOException { return _writableByteChannel.write(buffer); } }