package com.esotericsoftware.kryonet; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import static com.esotericsoftware.minlog.Log.*; /** @author Nathan Sweet <misc@n4te.com> */ class UdpConnection { InetSocketAddress connectedAddress; DatagramChannel datagramChannel; int keepAliveMillis = 19000; final ByteBuffer readBuffer, writeBuffer; private final Serialization serialization; private SelectionKey selectionKey; private final Object writeLock = new Object(); private long lastCommunicationTime; public UdpConnection (Serialization serialization, int bufferSize) { this.serialization = serialization; readBuffer = ByteBuffer.allocate(bufferSize); writeBuffer = ByteBuffer.allocateDirect(bufferSize); } public void bind (Selector selector, InetSocketAddress localPort) throws IOException { close(); readBuffer.clear(); writeBuffer.clear(); try { datagramChannel = selector.provider().openDatagramChannel(); datagramChannel.socket().bind(localPort); datagramChannel.configureBlocking(false); selectionKey = datagramChannel.register(selector, SelectionKey.OP_READ); lastCommunicationTime = System.currentTimeMillis(); } catch (IOException ex) { close(); throw ex; } } public void connect (Selector selector, InetSocketAddress remoteAddress) throws IOException { close(); readBuffer.clear(); writeBuffer.clear(); try { datagramChannel = selector.provider().openDatagramChannel(); datagramChannel.socket().bind(null); datagramChannel.socket().connect(remoteAddress); datagramChannel.configureBlocking(false); selectionKey = datagramChannel.register(selector, SelectionKey.OP_READ); lastCommunicationTime = System.currentTimeMillis(); connectedAddress = remoteAddress; } catch (IOException ex) { close(); IOException ioEx = new IOException("Unable to connect to: " + remoteAddress); ioEx.initCause(ex); throw ioEx; } } public InetSocketAddress readFromAddress () throws IOException { DatagramChannel datagramChannel = this.datagramChannel; if (datagramChannel == null) throw new SocketException("Connection is closed."); lastCommunicationTime = System.currentTimeMillis(); return (InetSocketAddress)datagramChannel.receive(readBuffer); } public Object readObject (Connection connection) { readBuffer.flip(); try { try { Object object = serialization.read(connection, readBuffer); if (readBuffer.hasRemaining()) throw new KryoNetException("Incorrect number of bytes (" + readBuffer.remaining() + " remaining) used to deserialize object: " + object); return object; } catch (Exception ex) { throw new KryoNetException("Error during deserialization.", ex); } } finally { readBuffer.clear(); } } /** This method is thread safe. */ public int send (Connection connection, Object object, SocketAddress address) throws IOException { DatagramChannel datagramChannel = this.datagramChannel; if (datagramChannel == null) throw new SocketException("Connection is closed."); synchronized (writeLock) { try { try { serialization.write(connection, writeBuffer, object); } catch (Exception ex) { throw new KryoNetException("Error serializing object of type: " + object.getClass().getName(), ex); } writeBuffer.flip(); int length = writeBuffer.limit(); datagramChannel.send(writeBuffer, address); lastCommunicationTime = System.currentTimeMillis(); boolean wasFullWrite = !writeBuffer.hasRemaining(); return wasFullWrite ? length : -1; } finally { writeBuffer.clear(); } } } public void close () { connectedAddress = null; try { if (datagramChannel != null) { datagramChannel.close(); datagramChannel = null; if (selectionKey != null) selectionKey.selector().wakeup(); } } catch (IOException ex) { if (DEBUG) debug("kryonet", "Unable to close UDP connection.", ex); } } public boolean needsKeepAlive (long time) { return connectedAddress != null && keepAliveMillis > 0 && time - lastCommunicationTime > keepAliveMillis; } }