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

net.sf.eBus.client.EReliableUDPConnection Maven / Gradle / Ivy

The newest version!
//
// Copyright 2023 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.lang.invoke.MethodHandle;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import static net.sf.eBus.client.EAbstractConnection.HEARTBEAT;
import static net.sf.eBus.client.EAbstractConnection.HEARTBEAT_REPLY;
import static net.sf.eBus.client.EAbstractConnection.MAX_MESSAGE_SIZE;
import static net.sf.eBus.client.EAbstractConnection.MESSAGE_HEADER_SIZE;
import static net.sf.eBus.client.EAbstractConnection.MESSAGE_SIZE_SIZE;
import static net.sf.eBus.client.EClient.sCoreExecutor;
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 static net.sf.eBus.messages.EMessageHeader.NO_ID;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.net.AsyncDatagramSocket;
import net.sf.eBus.net.DatagramBufferWriter;
import net.sf.eBus.timer.EScheduledExecutor.IETimer;
import net.sf.eBus.util.HexDump;
import net.sf.eBus.util.LazyString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Extends {@link EUDPConnection} providing reliability to UDP
 * packet transmission. Using UDP to communicate has two
 * problems:
 * 
    *
  1. * lost packets (low probability in today's networks) and *
  2. *
  3. * packets arriving out-of-order (real possibility). *
  4. *
*

* This connection solves these two problems using the simplest * type of reliability: allowing only one packet in flight at a * time. A second packet is held in a queue and transmitted only * when the first packet is acknowledged. This solution is * definitely simpler than TCP reliability but avoids the * non-trivial TCP's complexity. The simple solution was chosen * because message transmission rate is on the magnitude of * one message or less per millisecond which nothing compared to * TCP segments. *

*

* Given this connections limitations it is recommended that * this connection type not be used for high rate * messages as the reliability technique is low throughput. *

*

* This connection type provides both secure and insecure * communication via the {@code EUDPConnection} super class. *

* * @author Charles W. Rapp */ /* package */ final class EReliableUDPConnection extends EUDPConnection { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * Reliable UDP acknowledgement messages are denoted by this * negative "message length" value. */ private static final int ACK_ID = 0xeb050000; /** * Sequence number position in the acknowledge message is * at 4. */ private static final int SEQ_NUM_POSITION = Integer.BYTES; //----------------------------------------------------------- // Statics. // /** * Access point for logging subsystem. */ private static final Logger sLogger = LoggerFactory.getLogger(EReliableUDPConnection.class); //----------------------------------------------------------- // Locals. // /** * Expected sequence number of the next inbound application * message. Initialized to zero. */ private int mNextInboundSequenceNumber; /** * When this task expires, retransmit application on front * of transmit queue if it matches the sequence * number stored in the task. */ private final AtomicReference mRetransmitTask; /** * Serialized reliable UDP acknowledgement message. */ private final byte[] mAckIdData; /** * Wrap {@link #mAckIdData} in this buffer and use it to set * the acknowledged sequence number. */ private final ByteBuffer mAckBuffer; /** * Re-transmit application messages at this millisecond * rate. */ private Duration mRetransmitDelay; /** * Re-transmit a single application message this many times * before declaring a disconnect. */ private int mRetransmitLimit; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new reliable UDP connection instance of the * given connection type. * @param connType reliable UDP connection type. * @param connRole either accepted or initiated. * @param dtlsContext DTLS context used for a secure UDP * connection type. * @param remoteApp eBus remote application instance owning * this connection. */ private EReliableUDPConnection(final ConnectionType connType, final ConnectionRole connRole, final SSLContext dtlsContext, final ERemoteApp remoteApp) { super (connType, connRole, dtlsContext, remoteApp); mNextInboundSequenceNumber = 0; mRetransmitTask = new AtomicReference<>(); final int ackSize = (Integer.BYTES * 2); mAckIdData = new byte[ackSize]; mAckBuffer = ByteBuffer.wrap(mAckIdData); } // end of EReliableUDPConnection(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Overrides. // /** * Retrieves the re-transmit delay and limit from connection * configuration. * @param config reliable UDP connection configuration. */ @Override protected void doInitialize(final RemoteConnection config) { mRetransmitDelay = config.retransmitDelay(); mRetransmitLimit = config.retransmitLimit(); // Set the message acknowledgement byte order. mAckBuffer.order(config.byteOrder()); mAckBuffer.putInt(ACK_ID); super.doInitialize(config); } // end of doInitialize(RemoteConnection) /** * Retrieves the re-transmit delay and limit from connection * configuration. * @param config reliable UDP service configuration. */ @Override protected void doInitialize(final Service config) { mRetransmitDelay = config.retransmitDelay(); mRetransmitLimit = config.retransmitLimit(); // Set the message acknowledgement byte order. mAckBuffer.order(config.byteOrder()); mAckBuffer.putInt(ACK_ID); super.doInitialize(config); } // end of doInitialize(final Service config) /** * Returns output writer for this connection type. * @param config remote connection configuration. * @return connection output writer. */ @Override protected AbstractMessageWriter createWriter( final AbstractConfig config) { return (new ReliableMessageWriter(config, this)); } // end of createWriter(AbstractConfig) // // end of Abstract Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Timeout Handlers. // /** * Re-transmits first application message on transmit queue * if: *
    *
  1. * re-transmit timer is still running and *
  2. *
  3. * first application message's sequence number matches * the re-transmit timer's sequence number. *
  4. *
* Otherwise this task does nothing. * @param task expired re-transmit timer task. */ @SuppressWarnings({"java:S1172"}) private void onRetransmitTimeout(final int seqNum) { // Get the latest re-transmit timer and set to null // since this is a one shot timer. final IETimer timer = mRetransmitTask.getAndSet(null); // Was the timer set? if (timer != null) { // Yes, the timer is valid. final ReliableMessageWriter writer = (ReliableMessageWriter) mOutputWriter; try { // Is there an application message enqueued with // a matching sequence number? // Note: the message writer will throw an I/O // exception if the message transmit limit has // been exceeded. if (writer.okToRetransmit(seqNum, mRetransmitLimit)) { sLogger.trace( "{}: re-transmitting sequence number {}.", mRemoteAddress, seqNum); doSend(writer); } } catch (IOException ioex) { // Either re-transmit limit was reached or // re-transmit failed. Drop the connection. dropConnection(ioex); } } } // end of onRetransmitTimeout(TimerEvent) // // end of Timeout Handlers. //----------------------------------------------------------- /** * Returns a new, closed eBus reliable UDP connection * configured according to {@code config}. * @param config remote connection configuration. * @param remoteApp pass messages to this eBus remote * application interface. * @return a closed reliable UDP connection. * * @see #create(InetSocketAddress, Service, ERemoteApp) */ /* package */ static EReliableUDPConnection create(final RemoteConnection config, final ERemoteApp remoteApp) { final EReliableUDPConnection retval = new EReliableUDPConnection(config.connectionType(), ConnectionRole.INITIATOR, config.sslContext(), remoteApp); // Initialize the socket, message writer, and message // readers map. retval.initialize(config); return (retval); } // end of create(RemoteConnection, ERemoteApp) /** * Returns a new, open eBus reliable UDP connection for the * given "accepted" connection from {@code address}. This * means that the UDP "server" received a connect request * from a UDP connection at that address and port. * @param address far end UDP connection. * @param config eBus service configuration. * @param remoteApp connection belongs to this remote * application proxy. * @return open eBus reliable UDP connection. * * @see #create(RemoteConnection, ERemoteApp) */ /* package */ static EReliableUDPConnection create(final InetSocketAddress address, final Service config, final ERemoteApp remoteApp) { final EReliableUDPConnection retval = new EReliableUDPConnection(config.connectionType(), ConnectionRole.ACCEPTOR, config.sslContext(), remoteApp); // Initialize the socket, message writer, and message // readers map. retval.initialize(config); retval.mTarget = address; return (retval); } // end of create(InetSocketAddress, Service, ERemoteApp) /** * Returns input reader for given message type and subject * used to decode inbound messages. Returned reader is for * reliable UDP message header. * @param keyId unique message class identifier from remote * eBus application. * @param subject message key subject. * @param mt message type, used for de-serialization. * @param mh callback target method. * @param mn callback method name. * @return reliable UDP message input message decoder. */ @Override protected MessageReader createReader(final int keyId, final String subject, final MessageType mt, final MethodHandle mh, final String mn) { return ( new ReliableMessageReader( keyId, subject, mt, mh, mn)); } // end of createReader(...) @Override protected void processInput(final ByteBuffer buffer, final InetSocketAddress address) { // 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 = buffer.getInt(pos); final byte[] data = new byte[rem]; buffer.mark(); buffer.get(data); buffer.reset(); sLogger.trace( "{}: {} bytes available (start={}, msg size={}):\n{}", address, rem, pos, ms, new LazyString( () -> HexDump.dump(data, 0, rem, " "))); } else { sLogger.debug("{}: {} bytes available.", address, buffer.remaining()); } final int messageSize = buffer.getInt(); boolean hbReplyFlag = false; // 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. } // Is this a reliable UDP message acknowledgement? else if (messageSize == ACK_ID) { // Yes. Get the sequence number and verify this // acknowledgement. receiveAck(buffer.getInt()); } // 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))); } else { // Have the message reader parse the message and // forward it to the remote application interface. decode(buffer, address); } // Do we need to send a heartbeat reply? if (hbReplyFlag) { // Yes, send the reply on its way. try { sLogger.trace("{}: sending heartbeat reply.", mRemoteAddress); ((AsyncDatagramSocket) mAsocket).send( mHeartbeatReplyData, 0, mHeartbeatReplyData.length); } catch (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 doClose // 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, InetSocketAddress) /** * Handles {@link ReliableUdpAck} system messages. If this * is an application message, then checks if inbound sequence * number matches expected value, and then acknowledges * message receipt to the far-end. If a reliable UDP protocol * violation is detected (acknowledge message or inbound * application message contains an invalid sequence number), * then the reliable UDP connection is dropped. * @param keyId message key identifier extracted from message * header. * @param address far-end address. * @param header decoded message header. * @param reader message reader used to decode message. */ @Override protected void processMessage(final int keyId, final InetSocketAddress address, final EMessageHeader header, final MessageReader reader) { final boolean isSystemMessage = header.isSystemMessage(); final int sequenceNumber = header.sequenceNumber(); // Always forward system messages. boolean forwardFlag = isSystemMessage; // Is this an application message? if (!isSystemMessage) { // This is an application message. // Does the sequence number match the expected value? forwardFlag = (sequenceNumber == mNextInboundSequenceNumber); if (forwardFlag) { // Yes. Acknowledge receipt and advance the // expected inbound sequence number. sendAck(sequenceNumber); ++mNextInboundSequenceNumber; } // Is this the previously received message (that is, // sequence number is one less than expected)? else if (sequenceNumber == (mNextInboundSequenceNumber - 1)) { // Yes. Far-end may have missed our // acknowledgement. Re-send that ack. sendAck(sequenceNumber); } // No. Either the far-end acknowledged a much // older message or a future message. Drop the // connection. else { dropConnection( new ProtocolException( String.format( "received application message with invalid seq num (expected %,d, received %,d)", mNextInboundSequenceNumber, sequenceNumber))); } } // Good to forward this message? // Does is sequence number match the expected value? if (forwardFlag) { // Yes. super.processMessage(keyId, address, header, reader); } } // end of processMessage(...) /** * Processes a reliable UDP application message * acknowledgement. If the acknowledged sequence number * matches the expected sequence number and there is * another message in the queue, then sends next message. * @param sequenceNumber acknowledge sequence number. */ private void receiveAck(final int sequenceNumber) { sLogger.trace("{}: received UDP ack seq num {}.", mRemoteAddress, sequenceNumber); try { final ReliableMessageWriter writer = (ReliableMessageWriter) mOutputWriter; // Is there another message to send? if (writer.acknowledgeReceived(sequenceNumber)) { // Yes. Send pending message now. doSend(writer); ++mMsgOutCount; ++sTotalOutCount; } } catch (IOException ioex) { // If transmit fails, then close UDP connection, // and let ERemoteApp know the connection is // gone. Attempt to reconnect if so configured. dropConnection(ioex); } } // end of receiveAck(int) /** * Sends a reliable UDP acknowledgement to far-end with the * given sequence number. Since an acknowledgement is a * system message, it may be sent immediately. * @param sequenceNumber acknowledging receipt of application * message with this sequence number. */ private void sendAck(final int sequenceNumber) { sLogger.trace("{}: acknowledging seq num {}.", mRemoteAddress, sequenceNumber); try { mAckBuffer.position(0) .putInt(SEQ_NUM_POSITION, sequenceNumber); ((AsyncDatagramSocket) mAsocket).send( mAckIdData, 0, mAckIdData.length); } catch(IOException ioex) { // If transmit fails, then close UDP connection, // and let ERemoteApp know the connection is // gone. Attempt to reconnect if so configured. dropConnection(ioex); } } // end of sendAck(final int) /** * Starts application message re-transmit timer. When timer * expires and the next message on the transmit queue * matches the given sequence number, then send that message * again. * @param seqNum when time expires, re-transmit application * message with this sequence number. If first message on * transmit queue does not have a matching sequence number, * then do not transmit message. */ @SuppressWarnings({"java:S3398"}) private void startRetransmitTimer(final int seqNum) { mRetransmitTask.set( sCoreExecutor.schedule( () -> onRetransmitTimeout(seqNum), this, mRetransmitDelay)); } // end of startRetransmitTimer(int) /** * Stops re-transmit timer if currently set. */ private void stopRetransmitTimer() { final IETimer timer = mRetransmitTask.getAndSet(null); if (timer != null) { try { timer.close(); } catch (Exception jex) {} } } // end of stopRetransmitTimer() /** * Stops re-transmit timer, closes underlying UDP socket, and * passes exception to * {@link EAbstractConnection#disconnected(Throwable)}. * @param t exception causing disconnect. */ private void dropConnection(final Throwable t) { // Stop re-transmit timer since we are no longer // connected. stopRetransmitTimer(); mAsocket.closeNow(); disconnected(t); } // end of dropConnection(Throwable) //--------------------------------------------------------------- // Inner classes. // /** * Extends {@code EAbstractConnection.MessageReader} and * includes 4-byte sequence number extraction from reliable * UDP message header. */ private final class ReliableMessageReader extends MessageReader { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Constants. // /** * Reliable UDP header size sans message size is * {@value} bytes. */ private static final int HEADER_SIZE = 12; // bytes. //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new reliable message reader for the given * parameters. * @param keyId unique message class identifier from * remote eBus application. * @param subject message key subject. * @param mt message type, used for de-serialization. * @param mh callback target method. * @param mn callback method name. */ private ReliableMessageReader(final int keyId, final String subject, final MessageType mt, final MethodHandle mh, final String mn) { super (keyId, subject, mt, mh, mn, HEADER_SIZE); } // end of ReliableMessageReader(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // MessageWriter Method Overrides. // @Override protected EMessageHeader extractHeader(final ByteBuffer buffer, final SocketAddress address, final EMessage msg) { final int fromFeedId = buffer.getInt(); final int toFeedId = buffer.getInt(); final int sequenceNumber = buffer.getInt(); return (new EMessageHeader(mKeyId, fromFeedId, toFeedId, sequenceNumber, address, msg)); } // end of extractHeader(...) // // end of MessageWriter Method Overrides. //------------------------------------------------------- } // end of class ReliableMessageReader /** * Responsible for encoding outbound eBus messages to the * underlying {@link AsyncDatagramSocket} outbound buffer. * Unlike {@code EUDPConnection.MessageWriter} the eBus * message header sequence number is encoded. */ private final class ReliableMessageWriter extends AbstractMessageWriter implements DatagramBufferWriter { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Set outbound application message's sequence number to * this value and increment after. Note that system * messages do not have a sequence number. Initialized to * zero. */ private int mNextSequenceNumber; /** * Sequence number of latest unacknowledged application * transmitted. If there is no expected acknowledgement, * then set to a value < zero. */ private int mUnackedSequenceNumber; /** * Tracks the number of times a particular application * message has been transmitted. If the count exceeds the * re-transmit limit, then the reliable UDP connection is * declared lost. */ private int mRetransmitCount; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates reliable datagram buffer message writer for * the reliable UDP connection. * @param config connection configuration. * @param connection reliable UDP connection. */ private ReliableMessageWriter(final EConfigure.AbstractConfig config, final EReliableUDPConnection connection) { super (config, connection); mNextSequenceNumber = 0; mUnackedSequenceNumber = NO_ID; } // end of ReliableMessageWriter() // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // DatagramBufferWriter Interface Implementation. // @Override public SocketAddress fill(final ByteBuffer buffer) { final EMessageHeader header = mTransmitQueue.peek(); final DataType msgType = header.dataType(); final int sizePosition = buffer.position(); // Move past the message size bytes and fill in the // rest of the header. // Note: buffer.position() returns Buffer and not // ByteBuffer. (buffer.position(sizePosition + MESSAGE_SIZE_SIZE)) .putInt(header.classId()) .putInt(header.fromFeedId()) .putInt(header.toFeedId()) .putInt(header.sequenceNumber()); msgType.serialize(header.message(), null, buffer); // Now write out the message size. buffer.putInt(sizePosition, (buffer.position() - sizePosition)); // If this is system message, then remove it from // the queue because system messages are not // acknowledged. if (header.isSystemMessage()) { mTransmitQueue.poll(); // Is the transmit queue now empty? if (mTransmitQueue.isEmpty()) { // Yes. Set unacknowledge message sequence // number to < zero because we are not // expecting an ack. mUnackedSequenceNumber = NO_ID; mRetransmitCount = 0; } // No, not empty. That means the transmit queue // contains an application message waiting to be // acknowledged. Do *NOT* update the un-acked // sequence number or re-transmit count. They // still apply to the enqueued application // message. } // This is an application message. Store away the // message sequence number and start the re-transmit // timer. else { mUnackedSequenceNumber = header.sequenceNumber(); ++mRetransmitCount; startRetransmitTimer(mUnackedSequenceNumber); } ++mTransmitCount; sLogger.trace( "{}: queued message sent to {} (seq num={}, re-transmit count={} size={}, transmited={}, discarded={}).", mConnection.remoteSocketAddress(), (mTarget == null ? "connected" : mTarget), header.sequenceNumber(), mRetransmitCount, mTransmitQueueSize.get(), mTransmitCount, mDiscardCount); return (mTarget); } // end of fill(ByteBuffer) // // end of DatagramBufferWriter Interface Implementation. //------------------------------------------------------- /** * Returns {@code true} if {@code header} contains a * system message or this is the sole application message * on the transmit queue. Place system message at the * transmit queue's front and application message at the * transmit queue's read. * @param header enqueue this message. * @param queueSize message queue size as a result of * adding message. * @return {@code true} if clear to transmit. */ @Override protected boolean enqueue(final EMessageHeader header, final int queueSize) { final boolean retcode; // Is this a system message? if (header.isSystemMessage()) { // Yes. Place this message at transmit queue's // *front* and return ready-to-transmit. mTransmitQueue.addFirst(header); retcode = true; } // This is an application message. Set message's // sequence number and place message at transmit // queue's rear. // Return true if this is the only message on the // queue. else { header.sequenceNumber(mNextSequenceNumber); ++mNextSequenceNumber; mTransmitQueue.addLast(header); retcode = (queueSize == 1); } return (retcode); } // end of enqueue(EMessageHeader, int) /** * In addition to {@link AbstractMessageWriter#closed()} * resets next sequence number to zero and unacknowledged * sequence number to {@link #NO_ID}. */ @Override protected void closed() { super.closed(); mNextSequenceNumber = 0; mUnackedSequenceNumber = NO_ID; mRetransmitCount = 0; } // end of closed() /** * Returns {@code true} if given sequence number matches * expected value and there is another message on the * transmit queue. * @param seqNum acknowledge sequence number. * @return {@code true} if previously transmitted * application message is now acknowledged and there is * another message waiting to be transmitted. * @throws ProtocolException * if acknowledged sequence number is either unexpected * or is > expected value. */ private boolean acknowledgeReceived(final int seqNum) throws ProtocolException { boolean retcode = false; // Is an acknowledgement expected? if (mUnackedSequenceNumber < 0) { // No. Is this an application message sequence // number? if (seqNum >= 0) { // Yes. Log this error. throw ( new ProtocolException( String.format( "received unexpected ack (seq num %,d)", seqNum))); } // A system message was acknowledged. Report // this mistake. else { throw ( new ProtocolException( "received ack for system message")); } } // Yes, an ackowledgement is expected. // Is it the expected sequence number? else if (mUnackedSequenceNumber == seqNum) { final int queueSize; // Yes. Then: // 1) Stop re-transmit timer. // 2) Decrement transmit queue size. // 3) Remove acknowledged application message // from the queue. // 4) Reset the unacked sequence number. stopRetransmitTimer(); queueSize = mTransmitQueueSize.decrementAndGet(); mTransmitQueue.poll(); mUnackedSequenceNumber = NO_ID; mRetransmitCount = 0; sLogger.trace( "{}: received acknowledge seq num {}, queue size={}.", mConnection.remoteSocketAddress(), seqNum, queueSize); // Is there another enqueued message waiting to // be transmitted? // If so, send this message now. retcode = (queueSize > 0); } // Is this for a previous message? else if (seqNum < mUnackedSequenceNumber) { // Yes, it is less than the expected sequence // number but that is OK. It may be a message ack // re-transmit. Log this but do nothing else. sLogger.warn( "{}: received re-transmitted acknowledement, expected {}, received {}.", mConnection.remoteSocketAddress(), mUnackedSequenceNumber, seqNum); } // Acknowledged sequence number is for a future, // yet-to-be-transmitted message. else { throw ( new ProtocolException( String.format( "ack seq num > expected (expected %,d, received %,d)", mUnackedSequenceNumber, seqNum))); } return (retcode); } // end of acknowledgeReceived(int) /** * Returns {@code true} if: *
    *
  • * transmit queue is not empty, *
  • *
  • * first message on the queue is an application * message with a sequence number matching * {@code seqNum}, and *
  • *
  • * application message's re-transmit count has not * exceeded {@code retransmitLimit}. *
  • *
* @param seqNum re-transmit message with this sequence * number. * @param retransmitLimit message may be re-transmitted * at most this many times. * @return if there is an application message ready for * re-transmit with the given sequence number and within * re-transmit limit. * @throws IOException * if application message with given sequence number has * exceeded re-transmit limit. */ private boolean okToRetransmit(final int seqNum, final int retransmitLimit) throws IOException { final EMessageHeader header = mTransmitQueue.peek(); boolean retcode = false; // Is there a message to re-transmit? if (header != null) { // Yes. retcode = (header.sequenceNumber() == seqNum); // Does the message's sequence number match the // desired sequence number? // Has the re-transmit limit been exceeded? if (retcode && mRetransmitCount > retransmitLimit) { // Yes. Throw an I/O exception to let // the reliable UDP connection know that // this connection is down. throw ( new IOException( String.format( "re-transmit seq num %,d reached %,d limit", seqNum, retransmitLimit))); } } // No message on the transmit queue. return (retcode); } // end of okToRetransmit(int, int) } // end of class ReliableMessageWriter } // end of class EReliableUDPConnection




© 2015 - 2024 Weber Informatics LLC | Privacy Policy