All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xsocket.connection.spi.IoSocketHandler Maven / Gradle / Ivy

There is a newer version: 2.8.15
Show newest version
/*
 *  Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
 * The latest copy of this software may be found on http://www.xsocket.org/
 */
package org.xsocket.connection.spi;

import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xsocket.IDispatcher;
import org.xsocket.IHandle;
import org.xsocket.DataConverter;


/**
 * Socket based io handler
 *
 * @author [email protected]
 */
final class IoSocketHandler extends ChainableIoHandler implements IHandle {

	private static final Logger LOG = Logger.getLogger(IoSocketHandler.class.getName());


	private static final int MAXSIZE_LOG_READ = 2000;

	@SuppressWarnings("unchecked")
	private static final Map SUPPORTED_OPTIONS = new HashMap();

	static {
		SUPPORTED_OPTIONS.put(IClientIoProvider.SO_RCVBUF, Integer.class);
		SUPPORTED_OPTIONS.put(IClientIoProvider.SO_SNDBUF, Integer.class);
		SUPPORTED_OPTIONS.put(IClientIoProvider.SO_REUSEADDR, Boolean.class);
		SUPPORTED_OPTIONS.put(IClientIoProvider.SO_KEEPALIVE, Boolean.class);
		SUPPORTED_OPTIONS.put(IClientIoProvider.TCP_NODELAY, Boolean.class);
		SUPPORTED_OPTIONS.put(IClientIoProvider.SO_LINGER, Integer.class);
	}


	// flag
	private boolean isLogicalOpen = true;
	private boolean isDisconnect = false;

	// socket
	private SocketChannel channel = null;


	// dispatcher
	private IoSocketDispatcher dispatcher = null;


	// memory management
	private IMemoryManager memoryManager = null;


	// receive & send queue
	private final IoQueue sendQueue = new IoQueue();


    // id
	private String id = null;

	
	// retry read
	private boolean isRetryRead = true;
	

	// timeouts
	private long idleTimeoutMillis = IClientIoProvider.DEFAULT_IDLE_TIMEOUT_MILLIS;
	private long idleTimeoutDateMillis = Long.MAX_VALUE;
	private long connectionTimeoutMillis = IClientIoProvider.DEFAULT_CONNECTION_TIMEOUT_MILLIS;
	private long connectionTimeoutDateMillis = Long.MAX_VALUE;


	// suspend flag
	private boolean suspendRead = false;


	// socket param
	private int soRcvbuf = 0;


	// statistics
	private long openTime = -1;
	private long lastTimeReceivedMillis = System.currentTimeMillis();
//	private long lastTimeSent = System.currentTimeMillis();
	private long receivedBytes = 0;
	private long sendBytes = 0;


	/**
	 * constructor
	 *
	 * @param channel         the underlying channel
	 * @param idLocalPrefix   the id namespace prefix
	 * @param dispatcher      the dispatcher
	 * @throws IOException If some other I/O error occurs
	 */
    @SuppressWarnings("unchecked")
	IoSocketHandler(SocketChannel channel, IoSocketDispatcher dispatcher, String connectionId) throws IOException {
   	   	super(null);

    	assert (channel != null);
    	this.channel = channel;

    	openTime = System.currentTimeMillis();

		channel.configureBlocking(false);

		this.dispatcher = dispatcher;
    	this.id = connectionId;

    	soRcvbuf = (Integer) getOption(DefaultIoProvider.SO_RCVBUF);
	}


    public void init(IIoHandlerCallback callbackHandler) throws IOException, SocketTimeoutException {
    	setPreviousCallback(callbackHandler);

		blockUntilIsConnected();
		dispatcher.register(this, SelectionKey.OP_READ);
    }
    
    
    void setRetryRead(boolean isRetryRead) {
    	this.isRetryRead = isRetryRead;
    }


    /**
     * {@inheritDoc}
     */
    public boolean reset() {
    	try {
	    	sendQueue.drain();
	    	resumeRead();

			return super.reset();
    	} catch (Exception e) {
    		return false;
    	}
    }


    void setMemoryManager(IMemoryManager memoryManager) {
    	this.memoryManager = memoryManager;
    }

    @Override
    public String getId() {
    	return id;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public int getPendingWriteDataSize() {
    	return sendQueue.getSize() + super.getPendingWriteDataSize();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasDataToSend() {
    	return !sendQueue.isEmpty();
    }



	/**
	 * {@inheritDoc}
	 */
	public void setOption(String name, Object value) throws IOException {
		DefaultIoProvider.setOption(channel.socket(), name, value);

		if (name.equals(DefaultIoProvider.SO_RCVBUF)) {
			soRcvbuf = (Integer) value;
		}
	}



	/**
	 * {@inheritDoc}
	 */
	public Object getOption(String name) throws IOException {
		return DefaultIoProvider.getOption(channel.socket(), name);
	}


	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public Map getOptions() {
		return Collections.unmodifiableMap(SUPPORTED_OPTIONS);
	}


    /**
     * {@inheritDoc}
     */
    public void setIdleTimeoutMillis(long timeoutMillis) {
		if (timeoutMillis <= 0) {
			LOG.warning("connection timeout " + timeoutMillis + " millis is invalid");
			return;
		}

		this.idleTimeoutMillis = timeoutMillis;
		this.idleTimeoutDateMillis = System.currentTimeMillis() + idleTimeoutMillis;
		
		if (idleTimeoutDateMillis < 0) {
			idleTimeoutDateMillis = Long.MAX_VALUE;
		}

		long period = idleTimeoutMillis;
		if (idleTimeoutMillis > 500) {
			period = idleTimeoutMillis / 5;
		}

		dispatcher.updateTimeoutCheckPeriod(period);
	}




	/**
	 * sets the connection timeout
	 *
	 * @param timeout the connection timeout
	 */
	public void setConnectionTimeoutMillis(long timeoutMillis) {

		if (timeoutMillis <= 0) {
			LOG.warning("connection timeout " + timeoutMillis + " millis is invalid");
			return;
		}

		this.connectionTimeoutMillis = timeoutMillis;
		this.connectionTimeoutDateMillis = System.currentTimeMillis() + connectionTimeoutMillis;

	
		long period = connectionTimeoutMillis;
		if (connectionTimeoutMillis > 500) {
			period = connectionTimeoutMillis / 5;
		}
		
		dispatcher.updateTimeoutCheckPeriod(period);
	}


	/**
	 * gets the connection timeout
	 *
	 * @return the connection timeout
	 */
	public long getConnectionTimeoutMillis() {
		return connectionTimeoutMillis;
	}


	/**
     * {@inheritDoc}
     */
	public long getIdleTimeoutMillis() {
		return idleTimeoutMillis;
	}





	/**
	 * check the  timeout
	 *
	 * @param currentMillis   the current time
	 * @return true, if the connection has been timed out
	 */
	boolean checkIdleTimeout(Long currentMillis) {
		if (getRemainingMillisToIdleTimeout(currentMillis) <= 0) {
			getPreviousCallback().onIdleTimeout();
			return true;
		}
		return false;
	}


	/**
	 * {@inheritDoc}
	 */
	public long getRemainingMillisToIdleTimeout() {
		return getRemainingMillisToIdleTimeout(System.currentTimeMillis());
	}


	private long getRemainingMillisToIdleTimeout(long currentMillis) {
		long remaining = idleTimeoutDateMillis - currentMillis;

		// time out received
		if (remaining > 0) {
			return remaining;

		// ... yes
		} else {

			// ... but check if meantime data has been received!
			return (lastTimeReceivedMillis + idleTimeoutMillis) - currentMillis;
		}
	}



	/**
	 * check if the underlying connection is timed out
	 *
	 * @param currentMillis   the current time
	 * @return true, if the connection has been timed out
	 */
	boolean checkConnectionTimeout(Long currentMillis) {
		if (getRemainingMillisToConnectionTimeout(currentMillis) <= 0) {
			getPreviousCallback().onConnectionTimeout();
			return true;
		}
		return false;
	}


	/**
	 * {@inheritDoc}
	 */
	public long getRemainingMillisToConnectionTimeout() {
		return getRemainingMillisToConnectionTimeout(System.currentTimeMillis());
	}


	private long getRemainingMillisToConnectionTimeout(long currentMillis) {
		return connectionTimeoutDateMillis - currentMillis;
	}


	/**
	 * check if the underyling connection is timed out
	 *
	 * @param current   the current time
	 * @return true, if the connection has been timed out
	 */
	void checkConnection() {
		if (!channel.isOpen()) {
			getPreviousCallback().onConnectionAbnormalTerminated();
		}
	}





	void onConnectEvent() throws IOException {
		getPreviousCallback().onConnect();
	}


	int onReadableEvent() throws IOException {
		assert (IoSocketDispatcher.isDispatcherThread()) : "receiveQueue can only be accessed by the dispatcher thread";

		int read = 0;

		try {
			// read data from socket
			ByteBuffer [] received  = readSocket();

			// handle the data
			if (received != null) {
				getPreviousCallback().onData(received);
			}

			// increase preallocated read memory if not sufficient
			checkPreallocatedReadMemory();

		} catch (ClosedChannelException ce) {
			close(false);

		} catch (Exception t) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("[" + getId() + "] error occured by handling readable event. reason: " + t.toString());
			}
			close(false);

		} catch (Error e) {
			close(false);
			throw e;
		}


		return read;
	}



	int onWriteableEvent() throws IOException {
		assert (IoSocketDispatcher.isDispatcherThread());

		int sent = 0;

		if (suspendRead) {
			if (LOG.isLoggable(Level.FINEST)) {
				LOG.finest("[" + getId() + "] writeable event occured. update interested to none (because suspendRead is set) and write data to socket");
			}
			updateInterestedSetNonen();

		} else {
//			if (LOG.isLoggable(Level.FINEST)) {
//				LOG.finest("[" + getId() + "] writeable event occured. update interested to read and write data to socket");
//			}
			updateInterestedSetRead();
		}


		// write data to socket
		sent = writeSocket();


		// all data send? -> check for close
		if (sendQueue.isEmpty()) {
			if (shouldClosedPhysically()) {
				realClose();
			}

		// .. no, remaining data to send
		} else {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("[" + id + "] remaining data to send. initiate sending of the remaining (" + DataConverter.toFormatedBytesSize(sendQueue.getSize()) + ")");
			}

			updateInterestedSetWrite();
		}


		if (LOG.isLoggable(Level.FINEST)) {
			LOG.finest("[" + getId() + "] writeable event handled");
		}

		return sent;
	}


	private void blockUntilIsConnected() throws IOException, SocketTimeoutException {
		// check/wait until channel is connected
		while (!getChannel().finishConnect()) {
			getChannel().configureBlocking(true);
			getChannel().finishConnect();
			getChannel().configureBlocking(false);
		}
	}


	private boolean shouldClosedPhysically() {
		// close handling (-> close() leads automatically to write, if there is data to write)
		if (!isLogicalOpen) {

			// send queue is emtpy -> close can be completed
			if (sendQueue.isEmpty()) {
				return true;
			}
		}

		return false;
	}


	/**
	 * {@inheritDoc}
	 */
	public void write(ByteBuffer[] buffers) throws IOException {
		if (buffers != null) {
			sendQueue.append(buffers);
			updateInterestedSetWrite();
		}
	}




	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public void close(boolean immediate) throws IOException {
		if (immediate || sendQueue.isEmpty()) {
			realClose();

		} else {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("postpone close until remaning data to write (" + sendQueue.getSize() + ") has been written");
			}

			isLogicalOpen = false;
			updateInterestedSetWrite();
		}
	}


	private void realClose() {
		try {
			getDispatcher().deregister(this);
		} catch (Exception e) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by deregistering connection " + id + " on dispatcher. reason: " + e.toString());
			}
		}

		try {
			channel.close();
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("connection " + id + " has been closed");
			}
		} catch (Exception e) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by closing connection " + id + " reason: " + e.toString());
			}
		}


		if (!isDisconnect) {
			isDisconnect = true;
			getPreviousCallback().onDisconnect();
		}
	}



	void onDispatcherClose() {
		getPreviousCallback().onConnectionAbnormalTerminated();
	}

	private void updateInterestedSetWrite() throws ClosedChannelException {
		try {
			dispatcher.updateInterestSet(this, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("couldn`t update interested set to write data on socket. Reason: " + ioe.toString());
			}

			try {
				dispatcher.deregister(this);
			} catch (Exception ignore) { }

			throw new ClosedChannelException();
		}
	}

	private void updateInterestedSetRead() throws ClosedChannelException {
		try {
			dispatcher.updateInterestSet(this, SelectionKey.OP_READ);
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("couldn`t update interested set to read data. Reason: " + ioe.toString());
			}

			try {
				dispatcher.deregister(this);
			} catch (Exception ignore) { }

			throw new ClosedChannelException();
		}
	}




	private void updateInterestedSetNonen() throws ClosedChannelException {
		try {
			dispatcher.updateInterestSet(this, 0);
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("could not update interested set to nonen. Reason: " + ioe.toString());
			}

			try {
				dispatcher.deregister(this);
			} catch (Exception ignore) { }

			throw new ClosedChannelException();
		}
	}




	/**
	 * {@inheritDoc}
	 */
	public boolean isOpen() {
		return channel.isOpen();
	}



	/**
	 * return the underlying channel
	 *
	 * @return the underlying channel
	 */
	public SocketChannel getChannel() {
		return channel;
	}


	IDispatcher getDispatcher() {
		return dispatcher;
	}


	@Override
	public void suspendRead() throws IOException {
		suspendRead = true;

		// update to write (why?). Reason:
		//  * avoid race conditions in which current write need will be swallowed
		//  * write falls back to `none interested set`
		updateInterestedSetWrite();
	}



	@Override
	public void resumeRead() throws IOException {
		if (suspendRead) {
			suspendRead = false;

			// update to write (why not read?). Reason:
			//  * avoid race conditions in which current write need will be swallowed
			//  * write falls back to `read interested set` if there is no data to write
			updateInterestedSetWrite();
		}
	}


	/**
	 * reads socket into read queue
	 *
	 * @return the received data or null
	 * @throws IOException If some other I/O error occurs
	 * @throws ClosedChannelException if the underlying channel is closed
	 */
	private ByteBuffer[] readSocket() throws IOException {
		assert (IoSocketDispatcher.isDispatcherThread()) : "receiveQueue can only be accessed by the dispatcher thread";


		ByteBuffer[] received = null;


		int read = 0;
		lastTimeReceivedMillis = System.currentTimeMillis();


		if (isOpen() && !suspendRead) {

			assert (memoryManager instanceof UnsynchronizedMemoryManager);

			ByteBuffer readBuffer = memoryManager.acquireMemoryStandardSizeOrPreallocated(soRcvbuf);
			int pos = readBuffer.position();
			int limit = readBuffer.limit();

			// read from channel
			try {
				read = channel.read(readBuffer);
				
						
			// exception occured while reading
			} catch (IOException ioe) {
				readBuffer.position(pos);
				readBuffer.limit(limit);
				memoryManager.recycleMemory(readBuffer);

	 			if (LOG.isLoggable(Level.FINE)) {
	 				LOG.fine("[" + id + "] error occured while reading channel: " + ioe.toString());
	 			}
	 			
				throw ioe;
			}


			// handle read
			switch (read) {

				// end-of-stream has been reached -> throw an exception
				case -1:
					memoryManager.recycleMemory(readBuffer);
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("[" + id + "] channel has reached end-of-stream (maybe closed by peer)");
					}
					ClosedChannelException cce = new ClosedChannelException();
					throw cce;

				// no bytes read recycle read buffer and do nothing
				case 0:
					memoryManager.recycleMemory(readBuffer);
					return null;

                // bytes available (read < -1 is not handled)
				default:
					int remainingFreeSize = readBuffer.remaining();
					ByteBuffer dataBuffer = memoryManager.extractAndRecycleMemory(readBuffer, read);

					if (received == null) {
						received = new ByteBuffer[1];
						received[0] = dataBuffer;
					}


					receivedBytes += read;

					if (LOG.isLoggable(Level.FINE)) {
	 	 				LOG.fine("[" + id + "] received (" + (dataBuffer.limit() - dataBuffer.position()) + " bytes, total " + (receivedBytes + read) + " bytes): " + DataConverter.toTextOrHexString(new ByteBuffer[] {dataBuffer.duplicate() }, "UTF-8", MAXSIZE_LOG_READ));
		 			}


					// whole read buffer has been required -> repeat the read, because there could be more data to read
					if ((remainingFreeSize == 0) && isRetryRead) {
							
						// but just i case, if already read size is smaller than the preallocation size
						if (read < memoryManager.gettPreallocationBufferSize()) {
							if (LOG.isLoggable(Level.FINE)) {
								LOG.fine("[" + id + "] complete read buffer has been used, initiating repeated read");
							}

							ByteBuffer[] repeatedReceived = readSocket();
							if (repeatedReceived != null) {
								ByteBuffer[] newReceived = new ByteBuffer[received.length + 1];
								newReceived[0] = dataBuffer;
								System.arraycopy(repeatedReceived, 0, newReceived, 1, repeatedReceived.length);
								received = newReceived;

								return received;

							} else {
								return received;
							}

						} else  {
							return received;
						}
					}


					return received;
			}

		} else {
			if (LOG.isLoggable(Level.FINEST)) {
				if (!isOpen()) {
					LOG.finest("["  + getId() + "] couldn't read socket because socket is already closed");
				}

				if (suspendRead) {
					LOG.finest("["  + getId() + "] read is suspended, do nothing");
				}
			}

			return null;
		}
	}


	/**
	 * check if preallocated read buffer size is sufficient. if not increase it
	 */
	private void checkPreallocatedReadMemory() {
		assert (IoSocketDispatcher.isDispatcherThread());

		memoryManager.preallocate();
	}

	/**
	 * writes the content of the send queue to the socket
	 *
	 * @throws IOException If some other I/O error occurs
	 * @throws ClosedChannelException if the underlying channel is closed
	 */
	@SuppressWarnings("unchecked")
	private int writeSocket() throws IOException {
		assert (IoSocketDispatcher.isDispatcherThread());

		int sent = 0;

		////////////////////////////////////////////////////////////
		// Why hasn`t channel.write(ByteBuffer[]) been used??
		//
		// sendBytes += channel.write(data.toArray(new ByteBuffer[data.size()])) doesn`t
		// work correct under WinXP_SP2 & Sun JDK 1.6.0_01-b06 (and other configurations?).
		// The channel reports that x bytes have been written, but in some situations duplicated
		// data appears on the line (caused by the channel impl?!)
		// This behaviour doesn`t appear under Suse9.1/Intel & Sun JDK 1.5.0_08
		////////////////////////////////////////////////////////////


		if (isOpen()) {
			ByteBuffer[] buffers = sendQueue.drain();
			if (buffers == null) {
				return 0;
			}

			boolean hasUnwrittenBuffers = false;
			try {
				for (int i = 0; i < buffers.length; i++) {

					if (buffers[i] != null) {
		 				int writeSize = buffers[i].remaining();

		 				// data to write for this buffer?
		 				if (writeSize > 0) {
			 				if (LOG.isLoggable(Level.FINE)) {
					 			if (LOG.isLoggable(Level.FINE)) {
			 						LOG.fine("[" + id + "] sending (" + writeSize + " bytes): " + DataConverter.toTextOrHexString(buffers[i].duplicate(), "UTF-8", 500));
			 		 			}
			 				}

			 				// write to socket (internal out buffer)
			 				try {
				 				int written = channel.write(buffers[i]);
				 				sent += written;
				 				sendBytes += written;
	 				
				 				// all data written?
				 				if (written == writeSize) {
				 					try {
				 						// notify the io handler that data has been written
				 						getPreviousCallback().onWritten(buffers[i]);
				 					} catch (Exception e) {
				 						if (LOG.isLoggable(Level.FINE)) {
				 							LOG.fine("error occured by notifying that buffer has been written " + e.toString());
				 						}
				 					}

				 					buffers[i] = null;

				 				// ... no, return byte buffer to send queue
				 				} else {
				 					hasUnwrittenBuffers = true;  // see finally block

				 					if (LOG.isLoggable(Level.FINE)) {
				 						LOG.fine("[" + id + "] " + written + " of " + (writeSize - written) + " bytes has been sent (" + DataConverter.toFormatedBytesSize((writeSize - written)) + ")");
				 		 			}
				 					break;
				 				}

			 				} catch(IOException ioe)  {

			 					if (LOG.isLoggable(Level.FINE)) {
			 						LOG.fine("error " + ioe.toString() + " occured by writing " + DataConverter.toTextOrHexString(buffers[i].duplicate(), "US-ASCII", 500));
			 					}


			 					try {
			 						getPreviousCallback().onWriteException(ioe, buffers[i]);
			 					} catch (Exception e) {
			 						if (LOG.isLoggable(Level.FINE)) {
			 							LOG.fine("error occured by notifying that write exception (" + e.toString() + ") has been occured " + e.toString());
			 						}
			 					}
			 					buffers[i] = null;

			 					return sent;
			 				}
		 				}
					}
				}
			} finally {

				// not all data written -> return array into (head of) queue
				if (hasUnwrittenBuffers) {
					sendQueue.addFirst(buffers);
				}
			}

		} else {
			if (LOG.isLoggable(Level.FINEST)) {
				if (!isOpen()) {
					LOG.finest("["  + getId() + "] couldn't write send queue to socket because socket is already closed (sendQueuesize=" + DataConverter.toFormatedBytesSize(sendQueue.getSize()) + ")");
				}

				if (sendQueue.isEmpty()) {
					LOG.finest("["  + getId() + "] nothing to write, because send queue is empty ");
				}
			}
		}

		return sent;
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public final InetAddress getLocalAddress() {
		return channel.socket().getLocalAddress();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public final int getLocalPort() {
		return channel.socket().getLocalPort();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public final InetAddress getRemoteAddress() {
		return channel.socket().getInetAddress();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public final int getRemotePort() {
		return channel.socket().getPort();
	}


	/**
	 * {@inheritDoc}
	 */
	public void flushOutgoing() {

	}


	/**
	 * {@inheritDoc}
	 */
   	@Override
	public String toString() {
   		try {
	   		return "(" + channel.socket().getInetAddress().toString() + ":" + channel.socket().getPort()
	   			   + " -> " + channel.socket().getLocalAddress().toString() + ":" + channel.socket().getLocalPort() + ")"
	   		       + " received=" + DataConverter.toFormatedBytesSize(receivedBytes)
	   		       + ", sent=" + DataConverter.toFormatedBytesSize(sendBytes)
	   		       + ", age=" + DataConverter.toFormatedDuration(System.currentTimeMillis() - openTime)
	   		       + ", lastReceived=" + DataConverter.toFormatedDate(lastTimeReceivedMillis)
	   		       + ", sendQueueSize=" + DataConverter.toFormatedBytesSize(sendQueue.getSize())
	   		       + " [" + id + "]";
   		} catch (Throwable e) {
   			return super.toString();
   		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy