Замена NetworkStream.Length

Я использую сетевой поток для передачи коротких строк по сети.
Теперь на принимающей стороне я столкнулся с проблемой:
Обычно я бы читал так:

  1. посмотреть, доступны ли данные вообще
  2. получить количество доступных данных
  3. прочитать столько байтов в буфер
  4. преобразовать содержимое буфера в строку.

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

NetworkStream stream = someTcpClient.GetStream();
while(!stream.DataAvailable)
    ;

byte[] bufferByte;
stream.Read(bufferByte, 0, stream.Lenght);
AsciiEncoding enc = new AsciiEncoding();
string result = enc.GetString(bufferByte);

Однако в MSDN указано, что NetworkStream.Length на самом деле не реализован и всегда будет вызывать исключение при вызове.
Поскольку входящие данные имеют разную длину, я не могу жестко закодировать количество ожидаемых байтов (что также было бы случай антипаттерна магического числа).

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

EDIT:
Хотя предоставленный ответ приводит к улучшению общего кода, я все же хочу поделиться другим вариантом, с которым я столкнулся:
TCPClient.Available предоставляет байты, доступные для чтения. Я знал, что должен быть способ подсчитать байты в собственном почтовом ящике.


person Mark    schedule 12.05.2014    source источник
comment
Это должно помочь   -  person Sriram Sakthivel    schedule 12.05.2014


Ответы (2)


Нет никакой гарантии, что вызовы Read на одной стороне соединения совпадут 1-1 с вызовами Write на другой стороне. Если вы имеете дело с сообщениями переменной длины, вам нужно предоставить эту информацию принимающей стороне.

Один из распространенных способов сделать это — сначала определить длину сообщения, которое вы собираетесь отправить, а затем сначала отправить информацию об этой длине. На принимающей стороне вы сначала получаете длину, а затем знаете, какой размер буфера выделить. Затем вы вызываете Read в цикле, пока не прочитаете правильное количество байтов. Обратите внимание, что в исходном коде вы в настоящее время игнорируете возвращаемое значение из Read, которое говорит вам, сколько байтов было фактически прочитано. В одном вызове и возврате это может быть всего 1, даже если вы запрашиваете более 1 байта.

Другой распространенный способ - выбрать «форматы» сообщений, где, например. сообщение номер 1 всегда имеет длину 32 байта и структуру X, а сообщение номер 2 имеет длину 51 байт и структуру Y. При таком подходе вместо того, чтобы отправлять сообщение length перед отправкой сообщения, вместо этого вы отправляете информацию о формате — сначала вы отправляете «здесь приходит сообщение типа 1», а затем отправляете сообщение.

Еще один распространенный способ, если применимо, заключается в использовании какой-либо формы часовых. Если ваши сообщения никогда не будут содержать, скажем, байт со значением 0xff, вы сканируете полученные байты, пока не получите байт 0xff, а затем все, что было до этого. byte было сообщением, которое вы хотели получить.

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


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

person Damien_The_Unbeliever    schedule 12.05.2014
comment
Я использую кодировку ascii, поэтому я мог бы, например, использовать 0x04 End of Transmission. Какое совпадение, что такой персонаж существует. Большое тебе спасибо. Я подумал, что поскольку чтение — это моя часть работы, я мог бы просто заглянуть в мою ленту и прочитать столько-то. - person Mark; 12.05.2014
comment
@Mark, да, это почти похоже на то, что ASCII был основан на протоколе, который изначально работал в сетях. - person Scott Chamberlain; 12.05.2014

Вы можете использовать StreamReader для чтения потока до конца

var streamReader = new StreamReader(someTcpClient.GetStream(), Encoding.ASCII);

string result = streamReader.ReadToEnd();
person Vladimir Sachek    schedule 12.05.2014
comment
Это будет блокироваться, пока сокет не будет закрыт. - person Sriram Sakthivel; 12.05.2014
comment
Как еще узнать, что стрим закончился? - person Vladimir Sachek; 12.05.2014
comment
@VladimirSachek Я действительно хочу использовать это для связи, поэтому хочу держать сокет открытым в течение неопределенного периода времени. - person Mark; 12.05.2014