
net.sf.eBus.client.ETCPConnection Maven / Gradle / Ivy
//
// Copyright 2015, 2016 Charles W. Rapp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package net.sf.eBus.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.net.StandardSocketOptions;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SocketChannel;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.config.EConfigure.AbstractConfig;
import net.sf.eBus.config.EConfigure.ConnectionRole;
import net.sf.eBus.config.EConfigure.ConnectionType;
import net.sf.eBus.config.EConfigure.RemoteConnection;
import net.sf.eBus.config.EConfigure.Service;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.InvalidMessageException;
import net.sf.eBus.messages.UnknownMessageException;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.net.AbstractAsyncSocket;
import net.sf.eBus.net.AsyncChannel;
import net.sf.eBus.net.AsyncSecureSocket;
import net.sf.eBus.net.AsyncSecureSocket.SecureSocketBuilder;
import net.sf.eBus.net.AsyncSocket;
import net.sf.eBus.net.AsyncSocket.SocketBuilder;
import net.sf.eBus.net.BufferWriter;
import net.sf.eBus.net.SocketListener;
import net.sf.eBus.util.HexDump;
import net.sf.eBus.util.LazyString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements communication between eBus applications using a
* TCP session. This class implements the eBus message protocol,
* handling heart beating and automatic reconnect upon
* disconnect, according to {@link EConfigure} settings. There is
* a one-to-one relationship between {@code ETCPConnection} and
* {@link ERemoteApp}. All inbound messages are posted to the
* {@code ERemoteApp} instance and only that {@code ERemoteApp}
* instance may send outbound messages.
*
* @see ERemoteApp
*
* @author Charles W. Rapp
*/
/* package */ final class ETCPConnection
extends EAbstractConnection
implements SocketListener
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Statics.
//
/**
* Access point for logging.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(ETCPConnection.class);
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a closed socket with the specified input and
* output buffer sizes and the buffer byte order. The
* {@link AsyncSocket} and {@link MessageWriter} are
* instantiated in
* {@link #initialize(int, int, ByteOrder, int, String)}
* because {@code this} is used as a constructor parameter.
* @param connType remote connection type.
* @param connRole either accepted or initiated.
* @param remoteApp pass events to this eBus remote
* application interface.
* @throws IllegalArgumentException
* if {@code remoteApp} is {@code null}.
*/
private ETCPConnection(final ConnectionType connType,
final ConnectionRole connRole,
final ERemoteApp remoteApp)
{
super (connType, connRole, remoteApp, true);
} // end of ETCPConnection(...)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Implementations.
//
/**
* Initializes the {@link AsyncSocket TCP socket} and
* {@link MessageWriter message writer} instances. This is
* done outside the constructor because both classes require
* {@code this} as a constructor parameter.
* @param config remote connection configuration.
*/
@Override
protected void doInitialize(final RemoteConnection config)
{
switch (config.connectionType())
{
case TCP:
mAsocket = createClearTextTCP(config);
break;
case SECURE_TCP:
mAsocket = createSecureTCP(config);
break;
default:
}
} // end of doInitialize(RemoteConnection)
/**
* Initializes a newly accepted remote connection with the
* given service configuration.
* @param config initialize connection with this
* configuration.
*/
@Override
protected void doInitialize(final Service config)
{
switch (config.connectionType())
{
case TCP:
mAsocket = createClearTextTCP(config);
break;
case SECURE_TCP:
mAsocket = createSecureTCP(config);
break;
default:
}
} // end of doInitialize(Service)
/**
* Returns the output writer for this connection type.
* @param queueSize maximum message queue size for this
* writer.
* @return connection output writer.
*/
@Override
protected AbstractMessageWriter createWriter(final AbstractConfig config)
{
return (new MessageWriter(config, this));
} // end of createWriter(AbstractConfig)
/**
* Closes the TCP socket connection if open. Performs a
* "slow" close on the socket which means the socket closes
* only after any and all pending output is transmitted.
*
* @see #doCloseNow()
* @see #doOpen(RemoteConnection)
* @see #doOpen(SelectableChannel)
*/
@Override
protected void doClose()
{
if (mAsocket.isOpen())
{
sLogger.debug("{}: closing connection.",
mRemoteAddress);
mAsocket.close();
}
} // end of doClose()
/**
* Closes the TCP socket connection immediately if open.
* Performs a "fast" socket close which means all pending
* output is discarded and not transmitted before closing.
*
* @see #doOpen(RemoteConnection)
* @see #doOpen(SelectableChannel)
* @see #doClose()
*/
@Override
protected void doCloseNow()
{
if (mAsocket.isOpen())
{
sLogger.debug("{}: closing connection now.",
mRemoteAddress);
mAsocket.closeNow();
}
} // end of doCloseNow()
/**
* Establishes a TCP socket connection to the given address
* and bind port. Returns {@code true} if the
* connection is immediately established and {@code false} if
* the connection is in-progress. If {@code false} is
* returned, then {@link #handleOpen(AbstractAsyncSocket)}
* will be called when the connection process completes.
* @param config remote connection configuration.
* @return {@code true} if connection is established and
* {@code false} if the connection is in-progress.
* @exception IOException
* if socket connection attempt threw an exception and
* {@code reconnectFlag} is {@code false}.
*
* @see #doOpen(SelectableChannel)
* @see #doClose()
* @see #doCloseNow()
*/
@Override
protected boolean doOpen(final SocketAddress address,
final SocketAddress bindAddress)
throws IOException
{
return (
((AbstractAsyncSocket) mAsocket).open(
address, bindAddress));
} // end of doOpen(SocketAddress, SocketAddress)
/**
* Encapsulates an already connected socket accept by an
* {@link ETCPServer} instance. Will not reconnect
* if the connection is lost.
* @param socket the accepted socket connection.
* @return {@code true} because this TCP socket is fully
* connected once opened.
* @exception IOException
* if {@code socket} is closed.
*
* @see #doOpen(InetSocketAddress, int)
* @see #doClose()
* @see #doCloseNow()
*/
@Override
protected boolean doOpen(final SelectableChannel socket)
throws IOException
{
((AbstractAsyncSocket) mAsocket).open(socket);
return (true);
} // end of doOpen(SelectableChannel)
/**
* Turns off Nagle's algorithm on the TCP socket which
* delays TCP transmit.
* @throws IOException
* if an I/O failure occurs while configuring the socket.
*/
@Override
protected void doConnected()
throws IOException
{
mAsocket.setOption(
StandardSocketOptions.TCP_NODELAY, true);
} // end of doConnected()
/**
* Returns {@code true} if connected.
* @return {@code true} if clear to transmit a message.
*/
@Override
protected boolean maySend()
{
return (mState.get() == ConnectState.CONNECTED);
} // end of maySend()
/**
* Sends message stored in {@code writer} to remote eBus
* application. Since messages are sent asynchronously, then
* it is likely that the message was not transmitted
* prior to this method returning. If an application must be
* certain that all messages are transmitted prior to closing
* the connection, then {@link #close} should be used rather
* than {@link #closeNow} as {@link #close} waits for the
* pending outgoing messages to be transmitted before closing
* the socket connection. Once a connection is closed, no
* further messages may be sent.
* @param writer outbound messages are stored in this writer.
* @exception IOException
* if there is an I/O failure when transmitting the message.
*/
@Override
protected void doSend(final AbstractMessageWriter writer)
throws IOException
{
((AbstractAsyncSocket) mAsocket).send(
(BufferWriter) writer);
} // end of doSend(AbstractMessageWriter)
/**
* Sends the heartbeat bytes to the far-end.
* @throws IOException
* if an I/O failure occurs when transmitting the heartbeat
* bytes.
*/
@Override
protected void doSendHeartbeat()
throws IOException
{
((AbstractAsyncSocket) mAsocket).send(
mHeartbeatData, 0, mHeartbeatData.length);
} // end of doSendHeartbeat()
//
// end of Abstract Method Implementations.
//-----------------------------------------------------------
//-----------------------------------------------------------
// SocketListener Interface Implementation
//
/**
* The underlying socket is open. Starts the timer and
* inform the listener.
* @param socket The now open socket.
*/
@Override
public final void handleOpen(final AbstractAsyncSocket socket)
{
connected();
} // end of handleOpen(AbstractAsyncSocket)
/**
* Finds the eBus messages in the newly arrived data. If the
* connection was closed prior to this new input arriving,
* the input is ignored. If this input overflows the
* input buffer, the connection is closed an the listener
* informed. Otherwise, {@code data} is appended to the input
* buffer and messages are extracted.
*
* DO NOT USE THIS METHOD!
* This method is part of the {@code SocketListener}
* interface and should not be called by an application.
* @param buffer the incoming bytes.
* @param asocket bytes read from this socket.
*/
@Override
public void handleInput(final ByteBuffer buffer,
final AbstractAsyncSocket asocket)
{
// Are we still connected?
if (mState.get() == ConnectState.CONNECTED)
{
// Yes, carry on.
processInput(buffer);
}
// No, ignore the input but do clear it out.
else
{
final int position = buffer.position();
final int remaining = buffer.remaining();
buffer.position(position + remaining);
}
} // end of handleInput(ByteBuffer, AbstractAsyncSocket)
/**
* The socket's output queue is no longer full and the socket
* is ready to accept output again.
*
* DO NOT USE THIS METHOD!
* This method is part of the {@code SocketListener}
* interface and should not be called by an application.
* @param s The socket making this call.
*/
@Override
public final void handleOutputAvailable(final AbstractAsyncSocket s)
{
// If the async socket throws a buffer overflow
// exception, do NOT allow that to flow up to the
// caller because this is not an exceptional
// circumstance. Rather, it is the normal progression
// in socket transmissions.
try
{
s.send((BufferWriter) mOutputWriter);
}
catch (BufferOverflowException bufx)
{
// Ignore
}
catch (IOException ioex)
{
sLogger.warn("{}: message transmit failed.",
mRemoteAddress,
ioex);
}
} // end of handleOutputAvailable(AbstractAsyncSocket)
/**
* Cleans up the connection data due to an unexpected,
* asynchronous socket closing.
*
* DO NOT USE THIS METHOD!
* This method is part of the
* {@code net.sf.eBus.net.SocketListener} interface and should
* not be called by an application.
* @param t The exception causing this disconnect. May be
* {@code null}.
* @param asocket The now disconnected socket.
*/
@Override
public final void handleClose(final Throwable t,
final AbstractAsyncSocket asocket)
{
disconnected(t);
} // end of handleClose(Throwable, AbstractAsyncSocket)
//
// end of SocketListener Interface Implementation
//-----------------------------------------------------------
/**
* Returns a new, closed eBus connection configured according
* to the given configuration.
* @param config remote connection configuration.
* @param remoteApp pass events to this eBus remote
* application interface.
* @return a closed eBus connection.
* @throws NullPointerException
* if either {@code config} or {@code remoteApp} is
* {@code null}.
*
* @see #create(EConfigure.Service, ERemoteApp)
* @see #open(SocketChannel, EConfigure.Service)
* @see #open(SocketAddress, EConfigure.RemoteConnection)
*/
/* package */ static ETCPConnection create(final RemoteConnection config,
final ERemoteApp remoteApp)
{
final ETCPConnection retval =
new ETCPConnection(config.connectionType(),
ConnectionRole.INITIATOR,
remoteApp);
// Initialize the socket, message writer, and message
// readers map.
retval.initialize(config);
return (retval);
} // end of create(RemoteConnection, ERemoteApp)
/**
* Returns a new, closed eBus connection configured according
* to the given configuration.
* @param config accepted connection configuration.
* @param remoteApp pass events to this eBus remote
* application interface.
* @return a closed eBus connection.
* @throws NullPointerException
* if either {@code config} or {@code remoteApp} is
* {@code null}.
*
*
*/
/* package */ static ETCPConnection create(final Service config,
final ERemoteApp remoteApp)
{
final ETCPConnection retval =
new ETCPConnection(config.connectionType(),
ConnectionRole.ACCEPTOR,
remoteApp);
// Initialize the socket, message writer, and message
// readers map.
retval.initialize(config);
return (retval);
} // end of create(Service, ERemoteApp)
/**
* Extract the eBus messages from the buffer and forward them
* to the remote eBus application connection.
* @param buffer extract the messages from this buffer.
*/
@SuppressWarnings({"java:S3776", "java:S3457"})
private void processInput(final ByteBuffer buffer)
{
int remaining;
int startPosition;
int messageSize = -1;
boolean hbReplyFlag = false;
int keyId = Integer.MIN_VALUE;
MessageReader reader;
EMessageHeader header;
// Stop whichever heart timer is running while processing
// input.
stopHeartbeatTimer();
// The fact that we received input of any kind serves
// purpose of a heartbeat reply. We know that the
// connection is still alive. So turn off the heartbeat
// reply flag.
mHeartbeatReplyFlag = false;
if (sLogger.isTraceEnabled())
{
final int rem = buffer.remaining();
final int pos = buffer.position();
final int ms =
(rem >= Integer.BYTES ? buffer.getInt(pos) : -1);
final byte[] data = new byte[rem];
buffer.mark();
buffer.get(data);
buffer.reset();
sLogger.trace(
"{}: {} bytes available (start={}, msg size={}):\n{}",
mRemoteAddress,
rem,
pos,
ms,
new LazyString(
() -> HexDump.dump(data, 0, rem, " ")));
}
else
{
sLogger.debug("{}: {} bytes available.",
mRemoteAddress,
buffer.remaining());
}
// Iterate over the buffer until either it is
// entirely consumed or it contains a partial
// serialized message.
for (startPosition = buffer.position(),
remaining = buffer.remaining(),
buffer.mark();
remaining >= Integer.BYTES &&
(messageSize = buffer.getInt()) <= remaining;
startPosition = buffer.position(),
remaining = buffer.remaining(),
buffer.mark())
{
sLogger.trace(
"{}: {} bytes remaining, position is {}, message size is {} bytes.",
mRemoteAddress,
remaining,
startPosition,
messageSize);
// Heartbeat and heartbeat reply messages are
// simply two-byte integers that are < 0.
if (messageSize == HEARTBEAT)
{
hbReplyFlag = true;
}
else if (messageSize == HEARTBEAT_REPLY)
{
// That's nice. A reply to our heartbeat.
// But the heartbeat flag is already turned off.
// So there is nothing else to do.
}
// The message size must be at least big enough for
// the message header or is within the maximum
// allowed message size. If not, disconnect; there is
// not way to recover from this protocol error.
else if (messageSize < MESSAGE_HEADER_SIZE ||
messageSize > MAX_MESSAGE_SIZE)
{
logError(
new ProtocolException(
"invalid message size - " +
Integer.toString(messageSize)));
mAsocket.closeNow();
disconnected(
new IOException("invalid message size"));
}
else
{
try
{
// Have the reader parse the message and
// forward it to its callback.
keyId = buffer.getInt();
reader = mInputReaders.get(keyId);
header =
reader.extractMessage(
buffer,
mRemoteAddress);
reader.forwardMessage(header, mRemoteApp);
}
catch (NullPointerException nullex)
{
sLogger.warn(
"received unsupported key ID {}",
keyId,
nullex);
}
catch (UnknownMessageException |
InvalidMessageException |
BufferUnderflowException jex)
{
logError(jex);
}
finally
{
// Whether the message was successfully
// deserialized or not, move to past this
// message and to the start of the next.
startPosition += messageSize;
buffer.position(startPosition);
}
}
}
// In case the input buffer contains an incomplete
// message, reset to the mark.
buffer.reset();
// Do we need to send a heartbeat reply?
if (hbReplyFlag)
{
// Yes, send the reply on its way.
try
{
sLogger.trace("{}: sending heartbeat reply.",
mRemoteAddress);
((AbstractAsyncSocket) mAsocket).send(mHeartbeatReplyData,
0,
mHeartbeatReplyData.length);
}
catch (BufferOverflowException | IOException bufex)
{
// Ignore.
// Why?
// Because a full output buffer means that we
// have output waiting to be sent and that output
// will serve as a heartbeat reply. See the top
// of this method.
// On the other hand, if the other side is
// sending us a heartbeat while our output buffer
// is full up, then something is desparately
// wrong. We're pitching bytes and they keep
// missing them. If max output message queue is
// set, we will likely hit that max and close
// the connection. If not set, then this JVM will
// run out of heap and crash.
}
}
// Now that we have finished processing input,
// start the appropriate heartbeat timer.
startHeartbeatTimer();
} // end of processInput(ByteBuffer)
/**
* The output message writer is reporting that the outbound
* message queue is empty. If this connection is waiting on
* this condition to complete a slow close, then complete it
* now.
*/
@SuppressWarnings({"java:S3398"})
private void outboundQueueEmpty()
{
// Is this eBus connection in the process of closing?
if (mState.get() == ConnectState.CLOSING)
{
// Yes, complete the closing process by closing the
// socket.
setState(ConnectState.CLOSED);
if (mAsocket.isOpen())
{
sLogger.debug("{}: closing connection.",
mRemoteAddress);
mAsocket.close();
}
}
} // end of outboundQueueEmpty()
/**
* Returns a clear text TCP connection based on the given
* remote connection configuration.
* @param config remote connection configuration.
* @return un-secure TCP connection.
*/
private AsyncChannel createClearTextTCP(final RemoteConnection config)
{
final SocketBuilder builder = AsyncSocket.builder();
sLogger.debug(
"Creating clear text TCP client connection.");
return (builder.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.selector())
.listener(this)
.build());
} // end of createClearTextTCP(RemoteConnection)
/**
* Returns an SSL/TLS secure TCP connection based on the
* given remote connection configuration.
* @param config remote connection configuration.
* @return secure TCP connection.
*/
private AsyncChannel createSecureTCP(final RemoteConnection config)
{
final SecureSocketBuilder builder =
AsyncSecureSocket.builder();
sLogger.debug(
"Creating secure TCP client connection.");
return (builder.sslContext(config.sslContext())
.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.selector())
.listener(this)
.build());
} // end of createSecureTCP(RemoteConnection)
/**
* Returns a clear text TCP connection based on the given
* local service configuration.
* @param config local service configuration.
* @return un-secure TCP connection.
*/
// Yes this method is used.
@SuppressWarnings({"java:S1144"})
private AsyncChannel createClearTextTCP(final Service config)
{
final SocketBuilder builder = AsyncSocket.builder();
sLogger.debug(
"Creating clear text TCP accepted connection.");
return (builder.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.connectionSelector())
.listener(this)
.build());
} // end of createClearTextTCP(Service)
/**
* Returns an SSL/TLS secure TCP connection based on the
* given remote connection configuration.
* @param config remote connection configuration.
* @return secure TCP connection.
*/
// Yes this method is used.
@SuppressWarnings({"java:S1144"})
private AsyncChannel createSecureTCP(final Service config)
{
final SecureSocketBuilder builder =
AsyncSecureSocket.builder();
sLogger.debug(
"Creating secure TCP accepted connection.");
return (builder.sslContext(config.sslContext())
.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.connectionSelector())
.listener(this)
.build());
} // end of createSecureTCP(Service)
//---------------------------------------------------------------
// Inner classes.
//
/**
* This class is responsible for serializing messages
* directly to a given {@link AsyncSocket socket}
* {@link ByteBuffer output buffer}.
*/
private static final class MessageWriter
extends AbstractMessageWriter
implements BufferWriter
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Statics.
//
/**
* Logging subsystem interface.
*/
private static final Logger sSublogger =
LoggerFactory.getLogger(MessageWriter.class);
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a message writer instance with the given
* maximum outbound message queue size and eBus
* connection.
* @param config connection configuration.
* @param connection eBus connection.
*/
public MessageWriter(final AbstractConfig config,
final ETCPConnection connection)
{
super (config, connection);
} // end of MessageWriter(AbstractConfig, EConnection)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// BufferWriter Interface Implementations.
//
/**
* Returns {@code true} if there are messages in the
* transmit queue waiting to be transmitted; otherwise
* returns {@code false}.
* @return {@code true} if there are messages in the
* transmit queue.
*/
@Override
public boolean hasOutput()
{
return (!mTransmitQueue.isEmpty());
} // end of hasOutput()
/**
* Serializes as many queued messages as possible. This
* loop will terminate when either the message queue is
* empty or {@code buffer} overflows. If an overflow
* occurs, then
* {@link #handleOutputAvailable(AsyncSocket)} will pass
* this message writer back to the socket so that the
* remaining messages may be serialized.
*
* The serialized information consists of the from proxy
* identifier, the to proxy identifier, and the eBus
* message.
*
* @param buffer the output buffer.
* @throws BufferOverflowException
* if a message serialization overflows {@code buffer}
* available bytes.
*/
@Override
public void fill(final ByteBuffer buffer)
throws BufferOverflowException
{
EMessageHeader header;
DataType msgType;
int sizePosition;
int queueSize;
sSublogger.trace(
"{} queue: sending messages (size={}, remaining={}).",
mConnection.remoteSocketAddress(),
mTransmitQueueSize.get(),
buffer.remaining());
while (!mTransmitQueue.isEmpty())
{
// If the buffer does not contain sufficient
// space to store the message header, then throw
// a buffer overflow exception because setting
// the buffer position past its limit results in
// an illegal argument exception.
if (buffer.remaining() < MESSAGE_HEADER_SIZE)
{
throw (new BufferOverflowException());
}
// Do not remove this message from the queue
// until successfully transmitted.
header = mTransmitQueue.peek();
msgType = header.dataType();
// While AsyncSocket marks the buffer before
// making this call, we still need to mark the
// buffer again before serializing each message.
// If we did not do that, all the successfully
// serialized messages before the buffer overflow
// would be lost.
buffer.mark();
// Remember where the message size is written so
// we can come back there and write the size.
sizePosition = buffer.position();
buffer.position(
sizePosition + MESSAGE_SIZE_SIZE);
// Serialize the header then the message.
buffer.putInt(header.classId())
.putInt(header.fromFeedId())
.putInt(header.toFeedId());
msgType.serialize(
header.message(), null, buffer);
// Now write out the message size.
buffer.putInt(
sizePosition,
(buffer.position() - sizePosition));
// Now dequeue the message - one way or another
// we are finished with it.
mTransmitQueue.poll();
// Decrement the transmit queue size
// if-and-only-if the message was *not* system.
if (header.messageType() ==
EMessage.MessageType.SYSTEM)
{
queueSize = mTransmitQueueSize.get();
}
else
{
queueSize =
mTransmitQueueSize.decrementAndGet();
}
++mTransmitCount;
sSublogger.trace(
"{}: queued message sent (size={}, transmited={}, discarded={}).",
mConnection.remoteSocketAddress(),
queueSize,
mTransmitCount,
mDiscardCount);
}
// Is the eBus connection doing a slow close?
// Note: the message queue is empty at this point
// because we wouldn't get here if a buffer overflow
// happened.
if (mClosingFlag)
{
// Yes, report the outbound queue being empty.
((ETCPConnection) mConnection).outboundQueueEmpty();
}
} // end of fill(ByteBuffer)
//
// end of BufferWriter Interface Implementations.
//-------------------------------------------------------
} // end of class MessageWriter
} // end of class ETCPConnection