2011-01-31 13 views
6

Me disculpo por esta larga publicación. Lo hice lo más pequeño posible mientras seguía transmitiendo el problema.Problema muy extraño al enviar datos a través de Sockets en C#

Ok, esto me está volviendo loco. Tengo un cliente y un programa de servidor, ambos en C#. El servidor envía datos al cliente a través de Socket.Send(). El cliente recibe los datos a través de Socket.BeginReceive y Socket.Receive. Mi pseudo protocolo es el siguiente: el servidor envía un valor de dos bytes (corto) que indica la longitud de los datos reales seguidos inmediatamente por los datos reales. El cliente lee los dos primeros byes de forma asincrónica, convierte los bytes en un corto, e inmediatamente lee tantos bytes desde el socket de forma síncrona.

Ahora esto funciona bien para un ciclo cada pocos segundos más o menos, pero cuando aumento la velocidad, las cosas se ponen raras. Parece que el cliente leerá aleatoriamente los datos reales cuando intente leer desde la longitud de dos bytes. A continuación, intenta convertir estos dos bytes arbitrarios en un corto que conduce a un valor completamente incorrecto, lo que provoca un bloqueo. El siguiente código es de mi programa, pero recortado para mostrar solo las líneas importantes. Método del lado del servidor

para enviar datos:

private static object myLock = new object(); 
private static bool sendData(Socket sock, String prefix, byte[] data) 
{ 
    lock(myLock){ 
     try 
     { 
      // prefix is always a 4-bytes string 
      // encoder is an ASCIIEncoding object  
      byte[] prefixBytes = encoder.GetBytes(prefix); 
      short length = (short)(prefixBytes.Length + data.Length); 

      sock.Send(BitConverter.GetBytes(length)); 
      sock.Send(prefixBytes); 
      sock.Send(data); 

      return true; 
     } 
     catch(Exception e){/*blah blah blah*/} 
    } 
} 

método del lado del cliente para recibir los datos: El código de servidor

private static object myLock = new object(); 
private void receiveData(IAsyncResult result) 
{ 
    lock(myLock){ 
     byte[] buffer = new byte[1024]; 
     Socket sock = result.AsyncState as Socket; 
     try 
     { 
      sock.EndReceive(result); 
      short n = BitConverter.ToInt16(smallBuffer, 0); 
      // smallBuffer is a 2-byte array 

      // Receive n bytes 
      sock.Receive(buffer, n, SocketFlags.None); 

      // Determine the prefix. encoder is an ASCIIEncoding object 
      String prefix = encoder.GetString(buffer, 0, 4); 

      // Code to process the data goes here 

      sock.BeginReceive(smallBuffer, 0, 2, SocketFlags.None, receiveData, sock); 
     } 
     catch(Exception e){/*blah blah blah*/} 
    } 
} 

para recrear el problema de forma fiable:

byte[] b = new byte[1020]; // arbitrary length 

for (int i = 0; i < b.Length; i++) 
    b[i] = 7; // arbitrary value of 7 

while (true) 
{ 
    sendData(socket, "PRFX", b); 
    // socket is a Socket connected to a client running the same code as above 
    // "PRFX" is an arbitrary 4-character string that will be sent 
} 

Al observar el código anterior, se puede determinar que el servidor enviará para siempre el número 1024, la longitud de los datos totales, incluido el prefijo, como un resumen (0x400) seguido de "PRFX" en ASCII binario seguido de un grupo de 7 (0x07). El cliente leerá los dos primeros bytes (0x400), interpretará que es 1024, almacena ese valor como n y luego lee 1024 byes de la secuencia.

Esto es de hecho lo que hace durante las primeras 40 o más iteraciones pero, espontáneamente, el cliente leerá los dos primeros byes e interpretará que son 1799, no 1024. ¡1799 en hex es 0x0707 que son dos 7 consecutivos! Esa es la información, no la longitud! ¿Qué pasó con esos dos bytes? Esto sucede con cualquier valor que puse en la matriz de bytes, simplemente elegí 7 porque es fácil ver la correlación con 1799.

Si todavía estás leyendo en este punto, aplaudo tu dedicación.

Algunas observaciones importantes:

  • La disminución de la longitud de b aumentará el número de iteraciones antes de que ocurra el problema, pero no impide que se produzca el problema
  • Adición de un retraso significativo entre cada iteración del bucle puede evitar que ocurra el problema
  • Esto NO ocurre cuando se utiliza tanto el cliente como el servidor en el mismo host y se conecta a través de una dirección de bucle invertido.

Como he mencionado, ¡esto me está volviendo loco! Siempre puedo resolver mis problemas de programación, pero este me ha dejado perplejo por completo. Así que estoy aquí abogando por cualquier consejo o conocimiento sobre este tema.

Gracias.

Respuesta

3

Sospecho que está asumiendo que el mensaje completo se entrega en una llamada al receiveData. Esto no es, en general, el caso. La fragmentación, los retrasos, etc. pueden terminar significando que los datos llegan en dribbles, por lo que puede que se llame a la función receiveData cuando solo hay, p. 900 bytes listos. Su llamada sock.Receive(buffer, n, SocketFlags.None); dice "Dame los datos en el búfer, hasta n bytes" - es posible que reciba menos, y el número de bytes realmente leídos es devuelto por Receive.

Esto explica por qué disminuir b, agregar más demoras o usar el mismo host parece "arreglar" el problema - las probabilidades aumentan enormemente de que todo el mensaje llegue de una vez usando estos métodos (menor b, datos más pequeños; agregar un retraso significa que hay menos datos generales en la tubería, no hay una red en el camino para la dirección local).

Para ver si este es realmente el problema, registre el valor de retorno de Receive. Ocasionalmente será menos de 1024 si mi diagnóstico es correcto. Para solucionarlo, debe esperar hasta que el búfer de la pila de red local tenga todos sus datos (no ideal), o simplemente recibir datos cuando llegue, almacenarlos localmente hasta que todo esté listo y procesarlo.

+0

Este es un problema que tuve en un momento. Intente leer los datos de la red en un ciclo while hasta que lea la cantidad de datos especificada en el parámetro "longitud" – Kurru

+0

Reiniciando el 1024; por supuesto, podría fallar al leer el encabezado de 2 bytes –

+0

@Marc - Quiero decir que al registrar el valor de retorno de Recibir, verá menos de 1024 en el caso de prueba dado si la red subyacente proporciona menos que todo el mensaje en uno Reciba. –

0

Supongo que smallBuffer está declarado fuera del método de recepción y se está reutilizando desde varios subprocesos. En otras palabras, eso no es seguro para subprocesos.

Escribir un buen código de socket es difícil. Ya que está escribiendo tanto el cliente como el servidor, es posible que desee echarle un vistazo al Windows Communication Foundation (WCF) que le permite enviar y recibir objetos completos con mucha menos molestia.

+0

Gracias por la Respuesta. Olvidé mencionar que ambos métodos están en un bloque de bloqueo. Actualizaré el código de manera adecuada para que otros puedan ver si incluso lo estoy bloqueando correctamente. –

1

Cuando usa conectores, debe anticipar que el socket podría transferir menos bytes de los que esperaba. Debe realizar un bucle en el método .Receive para obtener el resto de los bytes.

Esto también es cierto cuando envía bytes a través de un socket. Debe verificar cuántos bytes se enviaron y realizar un bucle en el envío hasta que se hayan enviado todos los bytes.

Este comportamiento se debe a las capas de red que dividen los mensajes en varios paquetes. Si sus mensajes son cortos, entonces es menos probable que encuentre esto. Pero siempre debes codificarlo.

Con más bytes por búfer, es muy probable que vea el mensaje del remitente en varios paquetes. Cada operación de lectura recibirá un paquete, que es solo parte de su mensaje. Pero los pequeños buffers también se pueden dividir.

2

Me parece que el principal problema aquí es sin comprobar el resultado de EndReceive que es el número de bytes leídos. Si el socket está abierto, puede ser cualquier cosa > 0 y <= el máximo que haya especificado. No es seguro suponer que debido a que solicitó 2 bytes, se leyeron 2 bytes. Lo mismo cuando lee los datos reales.

Siempre debe realizar un bucle, acumulando la cantidad de datos que espera (y disminuyendo el conteo restante, y aumentando el desplazamiento, según corresponda).

La mezcla de síncrono/asíncrono no va a ayudar a hacer más fácil, ya sea:

£
2
sock.Receive(buffer, n, SocketFlags.None); 

No comprobar el valor de retorno de la función. El socket devolverá hasta 'n' bytes, pero solo devolverá lo que está disponible. Es probable que no esté recibiendo la totalidad de los 'datos' con esta lectura.Por lo tanto, cuando realiza la siguiente lectura de socket, en realidad está obteniendo los dos primeros bytes del resto de los datos y no la longitud de la siguiente transmisión.

Cuestiones relacionadas