简体   繁体   中英

Block incoming data from C# NetworkStream after timeout has passed

I have a strange question that involves NetworkStreams in C# .Net Core 2.2.101

My setup is as follows:

  • I have a list of meters
  • Each meter has a list of registers (a register saves a value, for example: the voltage or current)
  • Meters are connected to a GSM modem via RS485 (irrelevant for the question)
  • I send commands to the modem to read specific registers for specific meters

For each register of each meter, I send a command using stream.Write(bytesToSend, 0, bytesToSend.Length); to ask the meter to send me the data that is saved in a specific register in the meter. Directly after sending, I read the response using stream.Read(buffer, 0, buffer.Length); . I also set a read timeout of 5 seconds, which will block and wait for 5 seconds before moving on to the next register if no response has been received before the timeout.

The problem:

What is happening is that when I ask the meter for data, sometimes it will take too long and the timeout will be reached, after which I will move on to ask the next register for data, but then sometimes the first register will reply with data after I have moved on to the next register (meaning that the NetworkStream now has the data from the previous register). Since I have already moved on in my for -loop, my program thinks that the data I am reading from the stream is for the current register, when in fact it is for the previous register from the previous iteration. This messes up the data that goes into the database, because I am saving the wrong value for the wrong register.

My question is: Is there a clever way to ignore any incoming data from a previous iteration in my for -loop? Unfortunately there is no information in the data received that could be used to identify for which register the data is for.

Here is a snippet of what my write and read requests look like:

stream.ReadTimeout = 5000;
stream.WriteTimeout = 2000;

foreach (Meter meter in metersToRead)
{
  foreach (Register register in meter.Registers)
  {
    // Write the request to the meter
    stream.Write(bytesToSend, 0, bytesToSend.Length);

    // Read response from meter
    requestedReadingDataCount = stream.Read(buffer, 0, buffer.Length);

    // Extract the value from the response buffer and save the value to the database
    ...
  }
}

I want to try and clone the stream and use the cloned stream for communication regarding the current register iteration, so that when a response comes in after I have closed the cloned stream and moved on to the next register, the response will fail since the stream has been closed. However, I am not sure if you can clone a C# NetworkStream? Anybody know?

My last resort will be to make a call to the database after I read the data for each register, to check if the data I received is reasonable for that register, but I fear that this might slow the program down with all the database calls and I would have to build up some rules that will determine whether a value is reasonable for the current register.

Any ideas would be greatly appreciated. If you have any questions, please let me know and I will try my best to explain it further.

Edit

Here is an updated code snippet, as well as an image that will better explain the issue that I am having.

private async Task ReadMeterRegisters(List<MeterWithRegisters> metersWithRegisters, NetworkStream stream)
{
    stream.ReadTimeout = 5000; /* Read timeout set to 5 seconds */
    stream.WriteTimeout = 2000; /* Write timeout set to 2 seconds */

    foreach (Meter meter in metersToRead)
    {
          foreach (Register register in meter.Registers)
          {
                // Instantiate a new buffer to hold the response
                byte[] readingResponseDataBuffer = new byte[32];

                // Variable to hold number of bytes received
                int numBytesReceived = 0;

                try
                {
                    // Write the request to the meter
                    stream.Write(bytesToSend, 0, bytesToSend.Length);

                    // Read response from meter
                    numBytesReceived = stream.Read(buffer, 0, buffer.Length);
                }
                catch (IOException) /* catch read/write timeouts */
                {
                    // No response from meter, move on to next register of current meter
                    continue;
                }

                // Extract the value from the response buffer and save the value to the database
                ...
          }
    }
}

代码片段说明

It sounds like the issue here is that in a timeout scenario, the read operation is still completing at some point in the future but writing to the old buffer. If that is the case, perhaps the simplest option is to not reuse the read buffer in the event of a timeout (meaning: assign a new byte[] to buffer ), and consider that network-stream burned (since you now can't know what the internal state is).

An alternative approach would be to not read until you know there is data ; you can't do that from NetworkStream , but on Socket you can check .Available to see whether there is data to be read; that way, you won't be performing ambiguous reads. You can also perform a zero-length read on a socket (at least on most OS-es); if you pass a zero-length buffer, it will block until either the timeout or until data becomes available, but without consuming any data (the idea being that you follow a zero-length read with a non-zero-length read if you find that data has become available).

In the more general case: you might find you get better throughput here if you switch to asynchronous IO rather than synchronous IO; you could even use the array-pool for the buffers. For dealing with large volumes of connections, async IO is almost always the way to go.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM