c# - receive - ¿Cuál es la forma correcta de leer desde NetworkStream en.NET?
tcp server c# (3)
De acuerdo con sus requisitos, 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 tu función, esto podría ayudarte 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!
He estado luchando con esto y no puedo encontrar una razón por la que mi código no puede leer correctamente desde un servidor TCP que también he escrito. Estoy usando la clase TcpClient
y su método GetStream()
pero algo no está funcionando como se esperaba. O bien la operación se bloquea indefinidamente (la última operación de lectura no se agota 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 intentos de 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 bucle, ¡considerando que los enchufes ya admiten tiempos de espera de lectura! ¿Necesito configurar algunas propiedades en el TcpClient
de NetworkStream
? ¿El problema reside en el servidor? El servidor no cierra las conexiones, es responsabilidad del cliente hacerlo. Lo anterior también se está ejecutando dentro del contexto de la interfaz de usuario (programa de prueba), quizás tenga 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? Supongo que lo que deseo es algo como las antiguas propiedades de tiempo de espera de ReadTimeout
... ReadTimeout
, etc. Intenta leer hasta que se alcanza el tiempo de espera, y luego devolver 0 ... Pero a veces parece devolver 0 cuando los datos debería estar disponible (o en el camino ... ¿puede Read leer 0 si está disponible?) y luego se bloquea indefinidamente en la última lectura cuando los datos no están disponibles ...
Sí, estoy en una pérdida!
El código de red es muy difícil de escribir, probar y depurar.
A menudo tienes muchas cosas que considerar como:
qué "endian" usará para los datos que se intercambian (Intel x86 / x64 se basa en little-endian): los sistemas que usan big-endian todavía pueden leer datos que están en little-endian (y viceversa), pero Hay que reorganizar los datos. Cuando documente su "protocolo" simplemente deje en claro cuál está utilizando.
¿hay alguna "configuración" que se haya establecido en los sockets que pueda afectar el comportamiento de la "transmisión" (por ejemplo, SO_LINGER)? Es posible que deba activar o desactivar algunos de ellos si su código es muy sensible.
¿De qué manera la congestión en el mundo real que causa retrasos en la transmisión afecta 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, entonces a menudo necesita usar una estrategia para que ese "mensaje" se intercambie 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 del mensaje), o podría ser un "encabezado" más exótico
use 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 "fin de marcador"
use un tiempo de espera ... es decir, un cierto período de no recibir 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 ser fácilmente afectados en flujos congestionados.
tenga un canal de "comando" y "datos" en "conexiones" separadas ... este es el enfoque que utiliza el protocolo FTP (la ventaja es una clara separación de los datos de los comandos ... a costa de una segunda conexión)
Cada enfoque tiene sus pros y sus contras para la "corrección".
El código siguiente utiliza el método de "tiempo de espera", ya que parece ser el que usted desea.
Consulte http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx . Puede obtener acceso a NetworkStream
en el TCPClient
para que pueda cambiar el 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 al pie para otras variaciones en este código de red de escritura ... cuando realice una Read
en la que desee evitar un "bloque", puede verificar el indicador de disponibilidad de datos y luego SOLO leer lo que está en el búfer, verificando la propiedad .Length
, por ejemplo, stm.Read(resp, 0, stm.Length);
El establecimiento de la propiedad ReceiveTimeout
socket subyacente hizo el truco. Puede acceder a él de esta manera: yourTcpClient.Client.ReceiveTimeout
. Puedes leer los docs para más información.
Ahora el código solo se "suspenderá" mientras sea necesario para que algunos datos lleguen al zócalo, o generará una excepción si no llega ningún dato, al comienzo de una operación de lectura, durante más de 20 ms. Puedo modificar este tiempo de espera si es necesario. Ahora no pago el precio de 20 ms en cada iteración, solo lo pago en la última operación de lectura. Dado que tengo la longitud del contenido del mensaje en los primeros bytes leídos del servidor, puedo usarlo para modificarlo aún más y no intentar leer si ya se han recibido todos los datos esperados.
Encuentro que el uso de 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());
}