Блокировать входящие данные из C# NetworkStream после истечения времени ожидания - PullRequest
3 голосов
/ 12 марта 2020

У меня странный вопрос, который включает NetworkStreams в C#. Net Core 2.2.101

Моя настройка следующая:

  • У меня есть список метров
  • Каждый метр имеет список регистров (регистр сохраняет значение, например: напряжение или ток)
  • Измерители подключены к модему GSM через RS485 (не имеет значения для вопроса)
  • Я посылаю команды модему для чтения определенных c регистров для указанных c метров

Для каждого регистра каждого счетчика я посылаю команду, используя stream.Write(bytesToSend, 0, bytesToSend.Length);, чтобы попросить счетчик выслать мне данные, которые сохранены в указанном регистре c счетчика. Сразу после отправки я прочитал ответ, используя stream.Read(buffer, 0, buffer.Length);. Я также установил read timeout 5 секунд, который будет блокировать и ждать 5 секунд, прежде чем перейти к следующему регистру, если до тайм-аута не было получено никакого ответа.

Проблема:

Что происходит, когда я запрашиваю данные у счетчика, иногда это занимает слишком много времени, и время ожидания истекает, после чего я перехожу к следующему регистру для запроса data, но иногда первый регистр ответит данными после того, как я перейду к следующему регистру (это означает, что NetworkStream теперь имеет данные из предыдущего регистра). Поскольку в моем for -l oop я уже перешел, моя программа считает, что данные, которые я читаю из потока, предназначены для текущего регистра, тогда как на самом деле это данные для предыдущего регистра предыдущей итерации. Это портит данные, которые поступают в базу данных, потому что я сохраняю неправильное значение для неправильного регистра.

Мой вопрос: есть ли умный способ игнорировать любые входящие данные из предыдущей итерации в моем for -l oop? К сожалению, в полученных данных нет информации, которую можно было бы использовать для определения, для какого регистра эти данные нужны.

Вот фрагмент того, что мой запросы на запись и чтение выглядят следующим образом:

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
    ...
  }
}

Я хочу попытаться клонировать поток и использовать клонированный поток для связи относительно текущей итерации регистра, чтобы при поступлении ответа после того, как я закрыл клонированный поток и перешел к следующему регистру, ответ не удастся, так как поток был закрыт. Однако я не уверен, что вы можете клонировать C# NetworkStream? Кто-нибудь знает?

Мое последнее средство будет сделать вызов в базу данных после того, как я прочитал данные для каждого регистра, чтобы проверить, являются ли полученные мной данные приемлемыми для этого регистра, но я боюсь, что это может замедлить работу программы со всеми вызовами базы данных, и мне придется создать некоторые правила, которые будут определять, является ли значение разумным для текущего регистра.

Любые идеи будут высоко оценены. Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать, и я постараюсь объяснить это подробнее.

Редактировать

Вот обновленный фрагмент кода, а также изображение, которое лучше объяснит проблема, которая у меня возникла.

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
                ...
          }
    }
}

Description of code snippet

1 Ответ

1 голос
/ 12 марта 2020

Похоже, проблема в том, что в сценарии тайм-аута операция чтения все еще завершается в некоторый момент в будущем, но выполняется запись в старый буфер. Если это так, возможно, самый простой вариант - не использовать буфер чтения в случае тайм-аута (имеется в виду: назначить новый byte[] для buffer) и считать, что сетевой поток сгорел (поскольку теперь вы можете не знаю, что такое внутреннее состояние).

Альтернативным подходом будет не читать, пока вы не узнаете, что есть данные ; вы не можете сделать это с NetworkStream, но на Socket вы можете проверить .Available, чтобы увидеть, есть ли данные для чтения; Таким образом, вы не будете выполнять неоднозначные чтения. Вы также можете выполнить чтение нулевой длины в сокете (по крайней мере, в большинстве ОС); если вы пропустите буфер нулевой длины, он будет блокироваться до истечения времени ожидания или до тех пор, пока данные не станут доступными, но без использования каких-либо данных (идея заключается в том, что вы читаете чтение нулевой длины с чтением ненулевой длины, если вы найдете эти данные становятся доступными ).

В более общем случае вы можете обнаружить, что здесь вы получаете лучшую пропускную способность, если переключаетесь на асинхронный ввод-вывод, а не на синхронный ввод-вывод; Вы могли бы даже использовать массив массивов для буферов. Для работы с большими объемами соединений asyn c IO - почти всегда путь к go.

...