¿Cuál es la forma correcta de leer de NetworkStream en .NET

He estado luchando con esto y no puedo encontrar una razón por la cual mi código no lee correctamente desde un servidor TCP que también he escrito. Estoy usando la clase TcpClient y su método GetStream() , pero algo no funciona como se esperaba. O la operación bloquea indefinidamente (la última operación de lectura no expira como se esperaba) o los datos se recortan (por alguna razón, una operación de lectura devuelve 0 y sale del ciclo, tal vez el servidor no responde lo suficientemente rápido). Estos son tres bashs para implementar esta función:

 // 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()); } 

El último “funciona”, pero ciertamente parece feo poner un sueño codificado dentro del ciclo considerando que los sockets ya soportan tiempos muertos de lectura. ¿Debo configurar algunas propiedades en el TcpClient de NetworkStream ? ¿El problema reside en el servidor? El servidor no cierra las conexiones, depende del cliente hacerlo. Lo anterior también se ejecuta dentro del contexto del hilo de la interfaz de usuario (progtwig de prueba), tal vez tiene algo que ver con eso …

¿Alguien sabe cómo usar NetworkStream.Read correctamente? ¿Leer para leer datos hasta que no haya más datos disponibles? Creo que lo que estoy deseando es algo así como las antiguas propiedades Win32 de tiempo de espera ReadTimeoutReadTimeout , etc. Trata de leer hasta que se alcanza el tiempo de espera, y luego devuelve 0 … Pero a veces parece devolver 0 cuando los datos debería estar disponible (o en el camino … ¿puede leer return 0 si está disponible?) y luego bloquea indefinidamente en la última lectura cuando los datos no están disponibles …

Sí, estoy perdido

El código de red es notoriamente difícil de escribir, probar y depurar.

A menudo tiene muchas cosas para considerar, tales como:

  • qué “endian” usará para los datos que se intercambian (Intel x86 / x64 se basa en little-endian) – los sistemas que usan big-endian aún pueden leer datos que están en little-endian (y viceversa), pero tiene que reorganizar los datos. Cuando documente su “protocolo” simplemente deje en claro cuál está usando.

  • ¿Hay alguna “configuración” que se haya establecido en los sockets que pueda afectar el comportamiento de la “secuencia” (por ejemplo, SO_LINGER)? Es posible que deba activar o desactivar algunas si su código es muy sensible.

  • ¿Cómo afecta la congestión en el mundo real que causa retrasos en la secuencia a su lógica de lectura / escritura?

Si el “mensaje” que se intercambia entre un cliente y un servidor (en cualquier dirección) puede variar en tamaño, a menudo necesita usar una estrategia para intercambiar ese “mensaje” de manera confiable (también conocido como Protocolo).

Aquí hay varias maneras diferentes de manejar el intercambio:

  • tener el tamaño del mensaje codificado en un encabezado que precede a los datos; esto podría ser simplemente un “número” en los primeros 2/4/8 bytes enviados (dependiendo del tamaño máximo de su mensaje), o podría ser un “encabezado” más exótico

  • utilice un marcador especial de “fin de mensaje” (centinela), con los datos reales codificados / escapados si existe la posibilidad de que los datos reales se confundan con un “final de marcador”

  • use un tiempo de espera … es decir, un cierto período de no recepción de bytes significa que no hay más datos para el mensaje; sin embargo, esto puede ser propenso a errores con tiempos de espera cortos, que pueden afectar fácilmente las transmisiones congestionadas.

  • tiene un “comando” y un “canal de datos” en “conexiones” separadas … este es el enfoque que usa el protocolo FTP (la ventaja es una separación clara de los datos de los comandos … a expensas de una segunda conexión)

Cada enfoque tiene sus pros y sus contras para la “corrección”.

El siguiente código utiliza el método de “tiempo de espera”, ya que parece ser el que desea.

Vea http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx . Puede obtener acceso a NetworkStream en TCPClient para que pueda cambiar 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()); } 

Como nota a pie de página para otras variaciones en este código de red de escritura … al hacer una Read donde desea evitar un “bloque”, puede verificar el indicador DataAvailable y luego SOLAMENTE leer lo que está en el búfer comprobando la propiedad .Length , por ejemplo, stm.Read(resp, 0, stm.Length);

Configurar el socket subyacente ReceiveTimeout propiedad ReceiveTimeout hizo el truco. Puede acceder de esta manera: yourTcpClient.Client.ReceiveTimeout . Puede leer los documentos para obtener más información.

Ahora el código solo “dormirá” el tiempo que sea necesario para que algunos datos lleguen al socket, o generará una excepción si no se reciben datos, al comienzo de una operación de lectura, durante más de 20 ms. Puedo ajustar este tiempo de espera si es necesario. Ahora no estoy pagando el precio de 20 ms en cada iteración, solo lo estoy pagando en la última operación de lectura. Como tengo la longitud de contenido del mensaje en los primeros bytes leídos desde el servidor, puedo usarlo para ajustarlo aún más y no intentar leer si ya se han recibido todos los datos esperados.

Creo que usar ReceiveTimeout es mucho más fácil que implementar la lectura asíncrona … Aquí está el código de trabajo:

 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()); } 

Según su requerimiento, Thread.Sleep está perfectamente bien de usar porque no está seguro de cuándo estarán disponibles los datos, por lo que es posible que deba esperar a que los datos estén disponibles. He cambiado ligeramente la lógica de su función, esto podría ayudarlo un poco más.

 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()); } 

¡Espero que esto ayude!