package rocks.inspectit.shared.all.kryonet; import static com.esotericsoftware.minlog.Log.DEBUG; import static com.esotericsoftware.minlog.Log.debug; import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import com.esotericsoftware.kryonet.KryoNetException; import rocks.inspectit.shared.all.storage.nio.stream.ExtendedByteBufferOutputStream; import rocks.inspectit.shared.all.storage.nio.stream.SocketExtendedByteBufferInputStream; import rocks.inspectit.shared.all.storage.nio.stream.StreamProvider; /** * <b>IMPORTANT:</b> The class code is copied/taken/based from * <a href="https://github.com/EsotericSoftware/kryonet">kryonet</a>. Original author is Nathan * Sweet. License info can be found * <a href="https://github.com/EsotericSoftware/kryonet/blob/master/license.txt">here</a>. * * @author Nathan Sweet <misc@n4te.com> */ @SuppressWarnings("all") // NOCHKALL class TcpConnection { static private final int IPTOS_LOWDELAY = 0x10; /** * Amount of output streams to create in the empty queue. */ // Added by ISE private static final int MAX_OUTPUT_STREAMS = 10; /** * {@link StreamProvider} for creating streams. */ // Added by ISE private StreamProvider streamProvider; /** * Write lock for write synch. */ // Added by ISE private Lock writeReentrantLock = new ReentrantLock(); /** * Queue of {@link ExtendedByteBufferOutputStream}s to be sent to the socket channel. */ // Added by ISE private LinkedBlockingQueue<ExtendedByteBufferOutputStream> writeQueue = new LinkedBlockingQueue<ExtendedByteBufferOutputStream>(); /** * Queue of {@link ExtendedByteBufferOutputStream}s to be written to. */ // Added by ISE private LinkedBlockingQueue<ExtendedByteBufferOutputStream> idleQueue = new LinkedBlockingQueue<ExtendedByteBufferOutputStream>(); /** * {@link SocketExtendedByteBufferInputStream} to read data with. */ // Added by ISE private SocketExtendedByteBufferInputStream socketInputStream; SocketChannel socketChannel; int keepAliveMillis = 8000; final ByteBuffer readBuffer, writeBuffer; boolean bufferPositionFix; int timeoutMillis = 20000; // ISE: increased to 20s float idleThreshold = 0.1f; final IExtendedSerialization serialization; // Changed by ISE private SelectionKey selectionKey; private long lastWriteTime, lastReadTime; private int currentObjectLength; private final Object writeLock = new Object(); // Changed by ISE: added StreamProvider public TcpConnection(IExtendedSerialization serialization, int writeBufferSize, int objectBufferSize, StreamProvider streamProvider) { this.serialization = serialization; this.streamProvider = streamProvider; // Added by ISE writeBuffer = ByteBuffer.allocate(writeBufferSize); readBuffer = ByteBuffer.allocate(objectBufferSize); readBuffer.flip(); for (int i = 0; i < MAX_OUTPUT_STREAMS; i++) { try { idleQueue.add(streamProvider.getExtendedByteBufferOutputStream()); } catch (IOException e) { throw new KryoNetException("Can not initalize the output streams for the TCP Connection.", e); } } } public SelectionKey accept(Selector selector, SocketChannel socketChannel) throws IOException { // writeBuffer.clear(); Commented out by ISE readBuffer.clear(); readBuffer.flip(); currentObjectLength = 0; try { this.socketChannel = socketChannel; socketChannel.configureBlocking(false); Socket socket = socketChannel.socket(); socket.setTcpNoDelay(true); // Added by ISE socketInputStream = streamProvider.getSocketExtendedByteBufferInputStream(socketChannel); selectionKey = socketChannel.register(selector, SelectionKey.OP_READ); if (DEBUG) { debug("kryonet", "Port " + socketChannel.socket().getLocalPort() + "/TCP connected to: " + socketChannel.socket().getRemoteSocketAddress()); } lastReadTime = lastWriteTime = System.currentTimeMillis(); return selectionKey; } catch (IOException ex) { close(); throw ex; } } public void connect(Selector selector, SocketAddress remoteAddress, int timeout) throws IOException { close(); // writeBuffer.clear(); Commented out by ISE readBuffer.clear(); readBuffer.flip(); currentObjectLength = 0; try { SocketChannel socketChannel = selector.provider().openSocketChannel(); Socket socket = socketChannel.socket(); socket.setTcpNoDelay(true); // socket.setTrafficClass(IPTOS_LOWDELAY); socket.connect(remoteAddress, timeout); // Connect using blocking mode for simplicity. socketChannel.configureBlocking(false); this.socketChannel = socketChannel; // Added by ISE socketInputStream = streamProvider.getSocketExtendedByteBufferInputStream(socketChannel); selectionKey = socketChannel.register(selector, SelectionKey.OP_READ); selectionKey.attach(this); if (DEBUG) { debug("kryonet", "Port " + socketChannel.socket().getLocalPort() + "/TCP connected to: " + socketChannel.socket().getRemoteSocketAddress()); } lastReadTime = lastWriteTime = System.currentTimeMillis(); } catch (IOException ex) { close(); IOException ioEx = new IOException("Unable to connect to: " + remoteAddress); ioEx.initCause(ex); throw ioEx; } } public Object readObject(Connection connection) throws IOException { SocketChannel socketChannel = this.socketChannel; if (socketChannel == null) { throw new SocketException("Connection is closed."); } // Change by ISE from here to end of method // we use the read buffer to read the size of the length if (currentObjectLength == 0) { // Read the length of the next object from the socket. int lengthLength = serialization.getLengthLength(); if (readBuffer.remaining() < lengthLength) { readBuffer.compact(); int bytesRead = socketChannel.read(readBuffer); readBuffer.flip(); if (bytesRead == -1) { throw new SocketException("Connection is closed."); } lastReadTime = System.currentTimeMillis(); if (readBuffer.remaining() < lengthLength) { return null; } } currentObjectLength = serialization.readLength(readBuffer); if (currentObjectLength <= 0) { throw new KryoNetException("Invalid object length: " + currentObjectLength); } } int length = currentObjectLength; // reset stream socketInputStream.reset(length); currentObjectLength = 0; // read object Object object; try { object = serialization.read(connection, socketInputStream); } catch (Exception ex) { throw new KryoNetException("Error during deserialization.", ex); } lastReadTime = System.currentTimeMillis(); return object; } // Changed completely by ISE public void writeOperation() throws IOException { writeReentrantLock.lock(); try { if (writeToSocket()) { // Write successful, clear OP_WRITE. selectionKey.interestOps(SelectionKey.OP_READ); } lastWriteTime = System.currentTimeMillis(); } finally { writeReentrantLock.unlock(); } } private boolean writeToSocket() throws IOException { SocketChannel socketChannel = this.socketChannel; if (socketChannel == null) { throw new SocketException("Connection is closed."); } // Change by ISE from here to end of method outerloop: while (!writeQueue.isEmpty()) { ExtendedByteBufferOutputStream outputStream = writeQueue.peek(); if (null == outputStream) { break; } // calculate how much we have already written long written = 0; List<ByteBuffer> buffers = outputStream.getAllByteBuffers(); for (ByteBuffer buffer : buffers) { written += buffer.position(); } // then try to write until the end while (written < outputStream.getTotalWriteSize()) { long writeSize = socketChannel.write(buffers.toArray(new ByteBuffer[buffers.size()])); if (0 == writeSize) { // if we can not write any more we go out break outerloop; } written += writeSize; } // here we have done with this output stream // remove it from the write queue, prepare for new usage and return to the idle queue writeQueue.remove(outputStream); outputStream.prepare(); idleQueue.offer(outputStream); } return writeQueue.isEmpty(); } /** This method is thread safe. */ public int send(Connection connection, Object object) throws IOException { SocketChannel socketChannel = this.socketChannel; if (socketChannel == null) { throw new SocketException("Connection is closed."); } // Change by ISE from here to end of method // acquire the output stream to write from the queue ExtendedByteBufferOutputStream outputStream = null; try { outputStream = idleQueue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Sending was interrupted."); } // we are locking here as the serialization is not thread-safe writeReentrantLock.lock(); try { int lengthLength = serialization.getLengthLength(); // make space for the length // just write empty byte array in correct size outputStream.write(new byte[lengthLength]); // Write data and flush when done try { serialization.write(connection, outputStream, object); } catch (KryoNetException ex) { // NOPMD outputStream.close(); throw new KryoNetException("Error serializing object of type: " + object.getClass().getName(), ex); } outputStream.flush(false); // rewrite the size to the first buffer long writeSize = outputStream.getTotalWriteSize() - lengthLength; ByteBuffer buffer = outputStream.getAllByteBuffers().iterator().next(); int position = buffer.position(); buffer.position(0); serialization.writeLength(buffer, (int) writeSize); buffer.position(position); // Write to socket if no data was queued. boolean hasQueuedData = hasQueuedData(); writeQueue.add(outputStream); if (!hasQueuedData && !writeToSocket()) { // A partial write, set OP_WRITE to be notified when more writing can occur. selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } else { // Full write, wake up selector so idle event will be fired. selectionKey.selector().wakeup(); } lastWriteTime = System.currentTimeMillis(); return (int) writeSize; } finally { writeReentrantLock.unlock(); } } /** * @return Returns if any data is queued for writing. */ // Added by ISE private boolean hasQueuedData() { return !writeQueue.isEmpty(); } public void close() { try { if (socketChannel != null) { socketChannel.close(); socketChannel = null; if (selectionKey != null) { selectionKey.selector().wakeup(); } } // Added by ISE Start // close input stream if (null != socketInputStream) { socketInputStream.close(); } // and all output streams while (!writeQueue.isEmpty()) { ExtendedByteBufferOutputStream outputStream = writeQueue.poll(); if (null != outputStream) { outputStream.close(); } } // Added by ISE End } catch (IOException ex) { if (DEBUG) { debug("kryonet", "Unable to close TCP connection.", ex); } } } public boolean needsKeepAlive(long time) { return (socketChannel != null) && (keepAliveMillis > 0) && ((time - lastWriteTime) > keepAliveMillis); } public boolean isTimedOut(long time) { return (socketChannel != null) && (timeoutMillis > 0) && ((time - lastReadTime) > timeoutMillis); } /** * Returns current size to be written. * * @return Current size to be written. */ // Added by ISE public int getWriteBuffersSize() { if (writeQueue.isEmpty()) { return 0; } else { int size = 0; for (ExtendedByteBufferOutputStream stream : writeQueue) { size += stream.getTotalWriteSize(); } return size; } } }