Каков правильный способ чтения из NetworkStream в .NET.

Я боролся с этим и не могу найти причину, по которой мой код не умеет правильно читать с TCP-сервера, который я также написал. Я использую class TcpClient и его метод GetStream() но что-то не работает TcpClient . Либо операция блокируется бесконечно (последняя операция чтения не отключается, как ожидалось), либо данные обрезаны (по какой-либо причине операция чтения возвращает 0 и выходит из цикла, возможно, сервер не отвечает достаточно быстро). Это три попытки реализовать эту функцию:

 // this will break from the loop without getting the entire 4804 bytes from the server string SendCmd(string cmd, string ip, int port) { var client = new TcpClient(ip, port); var data = Encoding.GetEncoding(1252).GetBytes(cmd); var stm = client.GetStream(); stm.Write(data, 0, data.Length); byte[] resp = new byte[2048]; var memStream = new MemoryStream(); int bytes = stm.Read(resp, 0, resp.Length); while (bytes > 0) { memStream.Write(resp, 0, bytes); bytes = 0; if (stm.DataAvailable) bytes = stm.Read(resp, 0, resp.Length); } return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); } // this will block forever. It reads everything but freezes when data is exhausted string SendCmd(string cmd, string ip, int port) { var client = new TcpClient(ip, port); var data = Encoding.GetEncoding(1252).GetBytes(cmd); var stm = client.GetStream(); stm.Write(data, 0, data.Length); byte[] resp = new byte[2048]; var memStream = new MemoryStream(); int bytes = stm.Read(resp, 0, resp.Length); while (bytes > 0) { memStream.Write(resp, 0, bytes); bytes = stm.Read(resp, 0, resp.Length); } return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); } // inserting a sleep inside the loop will make everything work perfectly string SendCmd(string cmd, string ip, int port) { var client = new TcpClient(ip, port); var data = Encoding.GetEncoding(1252).GetBytes(cmd); var stm = client.GetStream(); stm.Write(data, 0, data.Length); byte[] resp = new byte[2048]; var memStream = new MemoryStream(); int bytes = stm.Read(resp, 0, resp.Length); while (bytes > 0) { memStream.Write(resp, 0, bytes); Thread.Sleep(20); bytes = 0; if (stm.DataAvailable) bytes = stm.Read(resp, 0, resp.Length); } return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); } 

Последний «работает», но он, безусловно, выглядит уродливым, чтобы ввести жесткий цикл внутри цикла, учитывая, что сокеты уже поддерживают таймауты чтения! Нужно ли мне TcpClient какое-либо свойство ( TcpClient ) на TcpClient из NetworkStream ? Проблема возникает на сервере? Сервер не закрывает соединения, это зависит от клиента. Вышеупомянутое также работает внутри контекста streamа пользовательского интерфейса (тестовая программа), возможно, оно имеет какое-то отношение к этому …

Кто-нибудь знает, как правильно использовать NetworkStream.Read для чтения данных, пока не будет доступно больше данных? Я предполагаю, что то, что я желаю, это что-то вроде старых свойств тайм-аута Win32 winsock … ReadTimeout и т. Д. Он пытается прочитать, пока не достигнут тайм-аут, а затем вернет 0 … Но иногда кажется, что возвращается 0, когда данные должен быть доступен (или по пути .. может прочитать возврат 0, если он доступен?), и затем он блокируется бесконечно на последнем чтении, когда данные недоступны …

Да, я в недоумении!

Сетевой код, как известно, трудно писать, тестировать и отлаживать.

У вас часто есть много вещей, чтобы рассмотреть такие, как:

  • какой «endian» вы будете использовать для обмена данными (Intel x86 / x64 основан на малоподобных) – системы, которые используют big-endian, могут по-прежнему читать данные, которые находятся в little-endian (и наоборот), но они должны переупорядочить данные. При документировании своего «протокола» просто дайте понять, какой из них вы используете.

  • есть ли какие-либо «настройки», которые были установлены в сокетах, которые могут влиять на поведение «streamа» (например, SO_LINGER) – вам может потребоваться включить или отключить некоторые из них, если ваш код очень чувствителен

  • как заторы в реальном мире, которые вызывают задержки в streamе, влияют на вашу логику чтения / записи

Если «сообщение», которое обменивается между клиентом и сервером (в любом направлении), может различаться по размеру, часто вам необходимо использовать страtagsю для того, чтобы это «сообщение» было обмениваться надежным образом (например, протокол).

Вот несколько способов обработки обмена:

  • имеют размер сообщения, закодированный в заголовке, который предшествует данным – это может быть просто «число» в первых отправленных 2/4/8 байтах (в зависимости от вашего максимального размера сообщения) или может быть более экзотическим «заголовком»,

  • используйте специальный маркер «конца сообщения» (дозорный), с реальными данными, закодированными / экранированными, если есть вероятность того, что реальные данные будут путаться с «концом маркера»,

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

  • имеют канал «команда» и «данные» на отдельных «соединениях» … это подход, который использует протокол FTP (преимущество заключается в четком разделении данных от команд … за счет 2-го соединения)

Каждый подход имеет свои плюсы и минусы за «правильность».

В приведенном ниже коде используется метод «тайм-аута», который, по-видимому, тот, который вы хотите.

См. http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx . Вы можете получить доступ к NetworkStream на TCPClient чтобы вы могли изменить ReadTimeout .

 string SendCmd(string cmd, string ip, int port) { var client = new TcpClient(ip, port); var data = Encoding.GetEncoding(1252).GetBytes(cmd); var stm = client.GetStream(); // Set a 250 millisecond timeout for reading (instead of Infinite the default) stm.ReadTimeout = 250; stm.Write(data, 0, data.Length); byte[] resp = new byte[2048]; var memStream = new MemoryStream(); int bytesread = stm.Read(resp, 0, resp.Length); while (bytesread > 0) { memStream.Write(resp, 0, bytesread); bytesread = stm.Read(resp, 0, resp.Length); } return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); } 

В качестве примечания для других вариантов этого кода сетевой записи … при DataAvailable где вы хотите избежать «блока», вы можете проверить флаг DataAvailable а затем ТОЛЬКО читать то, что находится в буфере, проверяющее свойство .Length например stm.Read(resp, 0, stm.Length);

Настройка базового сокета. ReceiveTimeout выполнило трюк. Вы можете получить доступ к нему следующим образом: yourTcpClient.Client.ReceiveTimeout . Вы можете прочитать документы для получения дополнительной информации.

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

Я считаю, что использование ReceiveTimeout намного проще, чем реализация асинхронного чтения … Вот рабочий код:

 string SendCmd(string cmd, string ip, int port) { var client = new TcpClient(ip, port); var data = Encoding.GetEncoding(1252).GetBytes(cmd); var stm = client.GetStream(); stm.Write(data, 0, data.Length); byte[] resp = new byte[2048]; var memStream = new MemoryStream(); var bytes = 0; client.Client.ReceiveTimeout = 20; do { try { bytes = stm.Read(resp, 0, resp.Length); memStream.Write(resp, 0, bytes); } catch (IOException ex) { // if the ReceiveTimeout is reached an IOException will be raised... // with an InnerException of type SocketException and ErrorCode 10060 var socketExept = ex.InnerException as SocketException; if (socketExept == null || socketExept.ErrorCode != 10060) // if it's not the "expected" exception, let's not hide the error throw ex; // if it is the receive timeout, then reading ended bytes = 0; } } while (bytes > 0); return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); } 

Согласно вашему требованию, Thread.Sleep отлично подходит для использования, поскольку вы не уверены, когда данные будут доступны, поэтому вам может потребоваться дождаться появления данных. Я немного изменил логику вашей функции, это может помочь вам немного дальше.

 string SendCmd(string cmd, string ip, int port) { var client = new TcpClient(ip, port); var data = Encoding.GetEncoding(1252).GetBytes(cmd); var stm = client.GetStream(); stm.Write(data, 0, data.Length); byte[] resp = new byte[2048]; var memStream = new MemoryStream(); int bytes = 0; do { bytes = 0; while (!stm.DataAvailable) Thread.Sleep(20); // some delay bytes = stm.Read(resp, 0, resp.Length); memStream.Write(resp, 0, bytes); } while (bytes > 0); return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); } 

Надеюсь это поможет!

  • Почему невозможно, не пытаясь выполнить ввод-вывод, обнаружить, что TCP-сокет был изящно закрыт одноранговым узлом?
  • Невозможно присвоить запрошенный адрес: JVM_Bind
  • Установка таймаута для операций сокета
  • сокет программирует несколько клиентов на один сервер
  • Передача изображения OpenCV на C ++ через сокет
  • Сколько накладных расходов накладывает SSL?
  • Что вызывает мой java.net.SocketException: сброс соединения?
  • Потоковый ввод в System.Speech.Recognition.SpeechRecognitionEngine
  • Как работает функция accept API ()?
  • Как использовать сокет-клиент с WCF (net.tcp)?
  • Установка IP-адреса источника для гнезда UDP
  • Давайте будем гением компьютера.