TcpClient.GetStream (). DataAvailable возвращает false, но поток содержит больше данных - PullRequest
10 голосов
/ 24 ноября 2010

Итак, может показаться, что блокирующее Read () может вернуться до того, как оно завершится, получая все данные, отправляемые на него. В свою очередь, мы заключаем Read () в цикл, который управляется значением DataAvailable из рассматриваемого потока. Проблема в том, что вы можете получить больше данных в этом цикле while, но за кулисами не происходит обработки, позволяющей системе знать об этом. Большинство решений, которые я нашел для этого в сети, не были так или иначе применимы ко мне.

То, что я в итоге сделал, это последний шаг в моем цикле, я делаю простой Thread.Sleep (1) после чтения каждого блока из потока. Похоже, что это дает системе время для обновления, и я не получаю точных результатов, но это кажется немного хакерским и довольно «косвенным» для решения.

Вот список обстоятельств, с которыми я сталкиваюсь: Одно TCP-соединение между приложением IIS и автономным приложением, оба написаны на C # для отправки / получения связи. Он отправляет запрос, а затем ждет ответа. Этот запрос инициируется запросом HTTP, но у меня нет этой проблемы при чтении данных из запроса HTTP, это по факту.

Вот базовый код для обработки входящего соединения

protected void OnClientCommunication(TcpClient oClient)
{
    NetworkStream stream = oClient.GetStream();
    MemoryStream msIn = new MemoryStream();

    byte[] aMessage = new byte[4096];
    int iBytesRead = 0;

    while ( stream.DataAvailable )
    {
        int iRead = stream.Read(aMessage, 0, aMessage.Length);
        iBytesRead += iRead;
        msIn.Write(aMessage, 0, iRead);
        Thread.Sleep(1);
    }
    MemoryStream msOut = new MemoryStream();

    // .. Do some processing adding data to the msOut stream

    msOut.WriteTo(stream);
    stream.Flush();

    oClient.Close();
}

Приветствуются все отзывы о лучшем решении или просто большое спасибо за необходимость дать Sleep (1) возможность обновиться, прежде чем мы проверим значение DataAvailable.

Полагаю, я надеюсь, что через 2 года ответ на на этот вопрос не такой, как все есть :)

Ответы [ 4 ]

9 голосов
/ 24 ноября 2010

Я вижу проблему с этим.
Вы ожидаете, что связь будет быстрее, чем цикл while(), что маловероятно.
Цикл while() завершится, как толькобольше нет данных, что может не произойти через несколько миллисекунд сразу после их выхода.

Ожидаете ли вы определенного количества байтов?
Как часто запускается OnClientCommunication()?Кто это запускает?

Что вы делаете с данными после цикла while()?Вы продолжаете добавлять к предыдущим данным?

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

8 голосов
/ 01 февраля 2012

Вы должны знать, сколько данных вам нужно прочитать;вы не можете просто зациклить чтение данных, пока не останется больше данных, потому что вы никогда не можете быть уверены, что больше не будет.

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

Вот два решения для вас в зависимости от того,у вас есть контроль над тем, что отправляет другая сторона:

  1. Используйте символы «кадрирования»: (SB) данные (EB), где SB и EB - символы начального и конечного блоков(по вашему выбору), но которые НЕ МОГУТ происходить внутри данных.Когда вы «видите» EB, вы знаете, что все готово.

  2. Реализуйте поле длины перед каждым сообщением, чтобы указать, сколько данных следует: (len) data.Прочитайте (len), затем прочитайте (len) байтов;при необходимости повторите.

Это не похоже на чтение из файла, где чтение нулевой длины означает конец данных (это означает, что другая сторона отключилась, но это другоеистория).

Третье (не рекомендуемое) решение заключается в том, что вы можете реализовать таймер. Как только вы начнете получать данные, установите таймер.Если цикл приема простаивает в течение некоторого периода времени (например, несколько секунд, если данные поступают не часто), вы, вероятно, можете предположить, что больше не поступает никаких данных.Этот последний метод является последним средством ... он не очень надежен, его сложно настроить, и он хрупок.

2 голосов
/ 14 апреля 2017

Когда у меня есть этот код:

    var readBuffer = new byte[1024];
    using (var memoryStream = new MemoryStream())
    {
        do
        {
            int numberOfBytesRead = networkStream.Read(readBuffer, 0, readBuffer.Length);
            memoryStream.Write(readBuffer, 0, numberOfBytesRead);
        }
        while (networkStream.DataAvailable);
    }

Из того, что я могу наблюдать:

  • Когда отправитель отправляет 1000 байтов, а читатель хочет их прочитать. Тогда я подозреваю, что NetworkStream каким-то образом «знает», что он должен получить 1000 байтов.
  • Когда я вызываю .Read до того, как какие-либо данные поступают из NetworkStream, тогда .Read должен блокироваться, пока не получит более 0 байтов (или больше, если .NoDelay имеет значение false для networkStream)
  • Затем, когда я читаю первую порцию данных, я подозреваю, что .Read каким-то образом обновляет по своему результату счетчик этих 1000 байтов в NetworkStream, и я подозреваю, что до этого момента .DataAvailable устанавливается в значение false, а после счетчик обновляется, затем .DataAvailable устанавливается на правильное значение, если данные счетчика меньше 1000 байтов. Это имеет смысл, когда вы думаете об этом. Потому что в противном случае он перейдет к следующему циклу перед проверкой того, что поступило 1000 байтов, и метод .Read будет блокировать бесконечно, потому что считыватель мог уже прочитать 1000 байтов, и больше не поступало бы данных.
  • Здесь я думаю, что это точка отказа, как уже сказал Джеймс:

Да, именно так работают эти библиотеки. Им нужно дать время на запуск, чтобы полностью проверить поступающие данные. - Джеймс 20 апреля 16 года в 5:24

  • Я подозреваю, что обновление внутреннего счетчика между концом .Read и перед доступом к .DataAvailable не является атомарной операцией (транзакцией), поэтому TcpClient требуется больше времени для правильной установки DataAvailable.

Когда у меня есть этот код:

    var readBuffer = new byte[1024];
    using (var memoryStream = new MemoryStream())
    {
        do
        {
            int numberOfBytesRead = networkStream.Read(readBuffer, 0, readBuffer.Length);
            memoryStream.Write(readBuffer, 0, numberOfBytesRead);

            if (!networkStream.DataAvailable)
                System.Threading.Thread.Sleep(1); //Or 50 for non-believers ;)
        }
        while (networkStream.DataAvailable);
    }

Тогда у NetworkStream будет достаточно времени для правильной установки .DataAvailable, и этот метод должен работать правильно.

Забавный факт ... Это как-то зависит от версии ОС. Потому что первая функция без сна у меня работала на Win XP и Win 10, но не получала целых 1000 байт на Win 7. Не спрашивайте меня, почему, но я проверил это довольно тщательно, и его легко воспроизвести.

2 голосов
/ 19 августа 2012

Я пытался проверить DataAvailable перед чтением данных из сетевого потока, и он вернул бы false, хотя после чтения одного байта он вернул бы true.Поэтому я проверил документацию MSDN, и они также прочитали перед проверкой.Я бы переставил цикл while в цикл do, чтобы следовать этому шаблону.

http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.dataavailable.aspx

        // Check to see if this NetworkStream is readable. 
        if(myNetworkStream.CanRead){
            byte[] myReadBuffer = new byte[1024];
            StringBuilder myCompleteMessage = new StringBuilder();
            int numberOfBytesRead = 0;

            // Incoming message may be larger than the buffer size. 
            do{
                 numberOfBytesRead = myNetworkStream.Read(myReadBuffer, 0, myReadBuffer.Length);

                 myCompleteMessage.AppendFormat("{0}", Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));

            }
            while(myNetworkStream.DataAvailable);

            // Print out the received message to the console.
            Console.WriteLine("You received the following message : " +
                                         myCompleteMessage);
        }
        else{
             Console.WriteLine("Sorry.  You cannot read from this NetworkStream.");
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...