//------------------------------------------------------------------------------ // // 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); }