.NET блокирует чтение сокета до тех пор, пока X байтов не будет доступно?

Предположим, у меня есть простой протокол, реализованный через TCP, где каждое сообщение состоит из:

  1. int указывает длину данных.
  2. Двоичные данные, длина которых указана в 1.

Читая такое сообщение, я хотел бы что-то вроде:

int length = input.ReadInt();
byte[] data = input.ReadBytes(length);

С помощью Socket.Receive или NetworkStream.Read считывается доступное количество байтов. Я хочу, чтобы вызов ReadBytes блокировался до тех пор, пока не будет доступно length байт.

Есть ли простой способ сделать это без необходимости зацикливания чтения, перезапуска со смещением в ожидании оставшихся данных?

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

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

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


person Anders Abel    schedule 22.07.2011    source источник


Ответы (6)


Что-то, где-то придется зацикливаться. В конце концов, может потребоваться несколько операций чтения сокетов.

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

person Jon Skeet    schedule 22.07.2011
comment
В документе BinaryReader.Read говорится, что он возвращает количество прочитанных байтов и что это может быть меньше запрошенного количества байтов, если такое количество байтов недоступно. Я думаю, мне нужно проверить, означает ли это, что байты доступны прямо сейчас или байты, полученные до закрытия сокета. - person Anders Abel; 22.07.2011
comment
@Anders: Да, документация неясна. Я верю, что он будет зацикливаться, но эта двусмысленность — еще одна причина для написания собственного метода. Это около 7 строк кода — просто сделайте это :) - person Jon Skeet; 22.07.2011
comment
очень жаль, что вы не в Java, чтение (новый байт [9]) будет блокироваться до тех пор, пока не будут прочитаны 9 байтов .... Я в той же лодке ищу что-то простое. обратите внимание, что я НЕ зацикливаюсь на своем сервере моделирования. мы просто читаем (новый байт [1]), чтобы получить заголовок размера кадра, а затем читаем (новый байт [длина]), чтобы получить сам пакет ... без цикла, всего два чтения после того, как мы отправили запрос. - person Dean Hiller; 15.12.2011
comment
@DeanHiller: это зависит от входного потока, но в целом InputStream.read не блокируется, пока не заполнит буфер. DataInputStream.readFully будет, но это другое дело. Если вы используете чтение для общего InputStream без проверки возвращаемого значения, вы подвергаете себя опасности. - person Jon Skeet; 15.12.2011
comment
@JonSkeet, извините, я думал о BufferedInputStream, который обычно используется, поэтому разработчику не нужно писать цикл ... да, базовые InputStreams этого не делают ... C # это нужно, было бы неплохо. очевидно, вы просто обертываете любой InputStream буферизованным и читаете из BufferInputStream - person Dean Hiller; 22.12.2011

Socket.Available подойдет, если вы не возражаете против жесткого цикла с ожиданием?

http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.available%28VS.71%29.aspx

person Kieren Johnstone    schedule 22.07.2011
comment
Будьте осторожны - если вы не читаете данные, это остановит сокет, так что вы можете ждать вечно, если вы ждете больше, чем просто крошечный фрагмент данных. Поэтому я бы НЕ использовал это, чтобы ждать, пока не будет получено определенное количество данных. - person Lucero; 22.07.2011

Вам нужно самостоятельно буферизовать данные, спецификация чтения заключается в том, что он может считывать любое количество между 1 байтом и размером буфера при возврате.

person Lucero    schedule 22.07.2011

похоже, что оператор yield может прекрасно подойти для этого сценария.

то есть, скажем, есть цикл, наблюдающий за входящим потоком, и как только вы нажмете каждый номер длины, вы возвращаете управление вызывающей стороне через «выход» в IEnumerable/foreach.

Возможно, yield может, в свою очередь, сигнализировать через событие об альтернативном отделении от IEnumerable. Я считаю IEnumerable удобным.

person sgtz    schedule 22.07.2011

Взгляните на следующую ссылку: http://blog.stephencleary.com/2009/04/tcpip-net-sockets-faq.html

По этой ссылке вы найдете отличную информацию о сокетах, а также библиотеку.

Единственное, что я бы обязательно реализовал, — это механизм тайм-аута, чтобы избежать зависания, когда сеть выходит из строя.

person Klinger    schedule 22.07.2011

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

        int numBytes = 0;
        int i = 0;
        while(numBytes != length)
        {
            numBytes += latestClient.Receive(body, numBytes, length-numBytes, SocketFlags.None);                
            if(i == 10000)
                throw new Exception("Could not read enough data.  read="+numBytes+" but expected="+length);
        }
person Dean Hiller    schedule 15.12.2011