//------------------------------------------------------------------------------
        //
        // Method: SetupAndReadMessage
        //
        //------------------------------------------------------------------------------
        /// <summary>
        /// Sets up variables and objects, and calls routines to read a message.
        /// </summary>
        /// <param name="parseState">The current state of parsing the message.</param>
        /// <param name="messageSequenceNumber">Populated with the sequence number of the received message.</param>
        /// <returns>The bytes of the message that were read.</returns>
        /// <remarks>In the usual case this routine will return the entire message.  It will only return a partial message if a cancel request occurs, or a pending connection is detected during reading.</remarks>
        private Queue <byte> SetupAndReadMessage(ref MessageParseState parseState, ref int messageSequenceNumber)
        {
            byte[] messageSequenceNumberBytes           = new byte[4];     // Holds the bytes of the message sequence number
            int    messageSequenceNumberCurrentPosition = 0;               // The current read position within the message sequence number

            byte[]         messageSizeHeaderBytes           = new byte[8]; // Holds the bytes of the message size header
            int            messageSizeHeaderCurrentPosition = 0;           // The current read position within the message size header
            long           messageSize   = -1;                             // The size of the message body
            Queue <byte>   messageBytes  = new Queue <byte>();             // Holds the bytes of the message body
            INetworkStream networkStream = client.GetStream();

            // Continue to read until a complete message has been received, unless a cancel request has been received or there is a pending connection (i.e. TcpRemoteSender has reconnected due to an error)
            while ((cancelRequest == false) && (listener.Pending() == false) && (parseState != MessageParseState.ReadCompleteMessage))
            {
                ReadAndParseMessageData(networkStream, ref parseState, messageSequenceNumberBytes, ref messageSequenceNumberCurrentPosition, ref messageSequenceNumber, messageSizeHeaderBytes, ref messageSizeHeaderCurrentPosition, ref messageSize, messageBytes);
            }

            // If a complete message has been received, send back the acknowledgement byte
            if ((cancelRequest == false) && (parseState == MessageParseState.ReadCompleteMessage))
            {
                networkStream.WriteByte(messageAcknowledgementByte);
            }

            return(messageBytes);
        }
        //------------------------------------------------------------------------------
        //
        // Method: ReadAndParseMessageData
        //
        //------------------------------------------------------------------------------
        /// <summary>
        /// Reads bytes from the inputted network stream and parses them, storing the results in the inputted parameters.
        /// </summary>
        /// <param name="networkStream">The network stream to read data from.</param>
        /// <param name="parseState">Represents the current state of parsing.</param>
        /// <param name="messageSequenceNumberBytes">The bytes containing the message sequence number.</param>
        /// <param name="messageSequenceNumberCurrentPosition">The current read position within the message sequence number bytes.</param>
        /// <param name="messageSequenceNumber">The message sequence number.</param>
        /// <param name="messageSizeHeaderBytes">The header bytes containing the message size.</param>
        /// <param name="messageSizeHeaderCurrentPosition">The current read position within the message size header bytes.</param>
        /// <param name="messageSize">The size of the message body.</param>
        /// <param name="messageBytes">The bytes containing the message body.</param>
        private void ReadAndParseMessageData(INetworkStream networkStream, ref MessageParseState parseState, byte[] messageSequenceNumberBytes, ref int messageSequenceNumberCurrentPosition, ref int messageSequenceNumber, byte[] messageSizeHeaderBytes, ref int messageSizeHeaderCurrentPosition, ref long messageSize, Queue <byte> messageBytes)
        {
            int availableBytes = client.Available;

            if (availableBytes > 0)
            {
                byte[] tempBuffer = new byte[availableBytes];  // Temporary buffer to store bytes read from the network before parsing
                networkStream.Read(ref tempBuffer, 0, availableBytes);

                // Iterate through the bytes in the buffer, advancing the parse state as successive sections (i.e. start delimiter, sequence number, size header, body, etc...) are read
                for (int i = 0; i < tempBuffer.Length; i = i + 1)
                {
                    switch (parseState)
                    {
                    case MessageParseState.StartOfMessage:
                        if (tempBuffer[i] != messageStartDelimiter)
                        {
                            throw new Exception("First byte of received message was expected to be " + messageStartDelimiter.ToString() + ", but was " + tempBuffer[i].ToString() + ".");
                        }
                        else
                        {
                            parseState = MessageParseState.ReadStartDelimiter;
                        }
                        break;

                    case MessageParseState.ReadStartDelimiter:
                        messageSequenceNumberBytes[messageSequenceNumberCurrentPosition] = tempBuffer[i];
                        messageSequenceNumberCurrentPosition++;
                        // If 4 bytes have been read into the sequence number byte array, then set the sequence number, and advance to the next parse state
                        if (messageSequenceNumberCurrentPosition == 4)
                        {
                            // Decode as little endian
                            if (BitConverter.IsLittleEndian == false)
                            {
                                Array.Reverse(messageSequenceNumberBytes);
                            }
                            messageSequenceNumber = BitConverter.ToInt32(messageSequenceNumberBytes, 0);
                            parseState            = MessageParseState.ReadSequenceNumber;
                        }
                        break;

                    case MessageParseState.ReadSequenceNumber:
                        messageSizeHeaderBytes[messageSizeHeaderCurrentPosition] = tempBuffer[i];
                        messageSizeHeaderCurrentPosition++;
                        // If 8 bytes have been read into the message size header byte array, then set the message size, and advance to the next parse state
                        if (messageSizeHeaderCurrentPosition == 8)
                        {
                            // Decode as little endian
                            if (BitConverter.IsLittleEndian == false)
                            {
                                Array.Reverse(messageSizeHeaderBytes);
                            }
                            messageSize = BitConverter.ToInt64(messageSizeHeaderBytes, 0);
                            parseState  = MessageParseState.ReadSizeHeader;
                        }
                        break;

                    case MessageParseState.ReadSizeHeader:
                        messageBytes.Enqueue(tempBuffer[i]);
                        // If the number of bytes read matches the size specified in the header, advance to the next parse state
                        if (messageBytes.Count == messageSize)
                        {
                            parseState = MessageParseState.ReadMessageBody;
                        }
                        break;

                    case MessageParseState.ReadMessageBody:
                        if (tempBuffer[i] != messageEndDelimiter)
                        {
                            throw new Exception("Last byte of received message was expected to be " + messageEndDelimiter.ToString() + ", but was " + tempBuffer[i].ToString() + ".");
                        }
                        else
                        {
                            parseState = MessageParseState.ReadCompleteMessage;
                        }
                        break;

                    case MessageParseState.ReadCompleteMessage:
                        throw new Exception("Surplus data encountered after message delimiter character, starting with " + tempBuffer[i].ToString() + ".");
                    }
                }
            }
        }
        //------------------------------------------------------------------------------
        //
        // Method: HandleExceptionAndRereadMessage
        //
        //------------------------------------------------------------------------------
        /// <summary>
        /// Handles an exception that occurred when attempting to read a message, before re-establishing the connection and repeating the read operation.
        /// </summary>
        /// <param name="readException">The exception that occurred when attempting to read the message.</param>
        /// <param name="parseState">The current state of parsing the message.</param>
        /// <param name="messageSequenceNumber">Populated with the seqence number of the received message.</param>
        /// <returns>The bytes of the message that were read.</returns>
        private Queue <byte> HandleExceptionAndRereadMessage(Exception readException, ref MessageParseState parseState, ref int messageSequenceNumber)
        {
            /*
             * In testing of this class, the only exhibited exception for probable real-world network issues (i.e. disconnecting network cable), was the System.IO.IOException.
             * However, the documentation states that numerous exceptions can potentially be thrown by the NetworkStream and TcpClient classes.  Hence the below code block handles all theoretically potential exceptions.
             * These exceptions, and the methods which can cause them are listed below...
             * TcpClient.Available
             *   ObjectDisposedException
             *   SocketException
             * TcpClient.GetStream()
             *   InvalidOperationException
             *   ObjectDisposedException
             * NetworkStream.Read(byte[] buffer, int offset, int size)
             *   IOException
             *   ObjectDisposedException
             * NetworkStream.WriteByte()
             *   IOException
             *   NotSupportedException
             *   ObjectDisposedException
             */
            Queue <byte> messageBytes = new Queue <byte>();

            try
            {
                if (readException is System.IO.IOException)
                {
                    logger.Log(this, LogLevel.Error, "IOException occurred whilst attempting to receive and acknowledge message.", readException);
                }
                else if ((readException is System.Net.Sockets.SocketException) ||
                         (readException is ObjectDisposedException) ||
                         (readException is InvalidOperationException) ||
                         (readException is NotSupportedException))
                {
                    logger.Log(this, LogLevel.Error, readException.GetType().Name + " occurred whilst attempting to receive and acknowledge message.", readException);
                }
                else
                {
                    throw new Exception("Error receiving message.  Unhandled exception while attempting to receive and acknowledge message.", readException);
                }

                logger.Log(this, LogLevel.Warning, "Attempting to reconnect to and re-receive.");

                AttemptConnect();
                metricsUtilities.Increment(new TcpRemoteReceiverReconnected());
                parseState = MessageParseState.StartOfMessage;
                try
                {
                    messageBytes = SetupAndReadMessage(ref parseState, ref messageSequenceNumber);
                }
                catch (Exception e)
                {
                    throw new Exception("Error receiving message.  Failed to read message after reconnecting.", e);
                }
            }
            catch (Exception e)
            {
                metricsUtilities.CancelBegin(new MessageReceiveTime());
                throw;
            }

            return(messageBytes);
        }
        /// <include file='InterfaceDocumentationComments.xml' path='doc/members/member[@name="M:MethodInvocationRemoting.IRemoteReceiver.Receive"]/*'/>
        public string Receive()
        {
            cancelRequest = false;
            CheckNotDisposed();
            CheckConnected();
            int    messageSequenceNumber = -1;
            string returnMessage         = "";

            while (cancelRequest == false)
            {
                // Check if there are any pending connections which would indicate the TcpRemoteSender has encountered an error and reconnected
                if (listener.Pending() == true)
                {
                    logger.Log(this, LogLevel.Warning, "New connection detected.  Attempting reconnect.");
                    AttemptConnect();
                    metricsUtilities.Increment(new TcpRemoteReceiverReconnected());
                }

                // Check if any data has been received from the TcpRemoteSender, and handle and retry if an exception occurs
                int availableData = 0;
                try
                {
                    availableData = client.Available;
                }
                catch (Exception e)
                {
                    availableData = HandleExceptionAndCheckAvailableData(e);
                }

                // If data has been received, attempt to read and parse it, and handle and retry if an exception occurs
                if (availableData != 0)
                {
                    metricsUtilities.Begin(new MessageReceiveTime());

                    MessageParseState parseState   = MessageParseState.StartOfMessage;
                    Queue <byte>      messageBytes = null; // Holds the bytes which form the body of the message received

                    try
                    {
                        messageBytes = SetupAndReadMessage(ref parseState, ref messageSequenceNumber);
                    }
                    catch (Exception e)
                    {
                        messageBytes = HandleExceptionAndRereadMessage(e, ref parseState, ref messageSequenceNumber);
                    }

                    // If the complete message has been read, break out of the current while loop
                    //   If the complete message was not read it would have been caused by a cancel request, or by a pending connection which is handled outside this block
                    if ((cancelRequest == false) && (parseState == MessageParseState.ReadCompleteMessage))
                    {
                        // If the sequence number of the message is the same as the last received message, then discard the message
                        //   This situation can be caused by the connection breaking before the sender received the last acknowledgment
                        if (messageSequenceNumber != lastMessageSequenceNumber)
                        {
                            lastMessageSequenceNumber = messageSequenceNumber;
                            returnMessage             = stringEncoding.GetString(messageBytes.ToArray());

                            metricsUtilities.End(new MessageReceiveTime());
                            metricsUtilities.Increment(new MessageReceived());
                            metricsUtilities.Add(new ReceivedMessageSize(returnMessage.Length));
                            loggingUtilities.LogMessageReceived(this, returnMessage);
                            break;
                        }
                        else
                        {
                            metricsUtilities.Increment(new TcpRemoteReceiverDuplicateSequenceNumber());
                            logger.Log(this, LogLevel.Warning, "Duplicate message with sequence number " + messageSequenceNumber + " received.  Message discarded.");
                            // Reset variables
                            messageSequenceNumber = -1;
                            returnMessage         = "";
                        }
                    }

                    metricsUtilities.End(new MessageReceiveTime());
                }

                waitingForRetry = true;
                if (receiveRetryInterval > 0)
                {
                    Thread.Sleep(receiveRetryInterval);
                }
                waitingForRetry = false;
            }

            return(returnMessage);
        }