2011-05-25 33 views
6

Estoy escribiendo una aplicación cliente/servidor en C#, y está funcionando muy bien. Por ahora, todo funciona y es bastante robusto. Mi problema es que me encuentro con algunas demoras al enviar paquetes a través de la conexión.¿Forma más rápida de comunicarse usando TcpClient?

En el lado del cliente que esté haciendo esto:

NetworkStream ns = tcpClient.GetStream(); 

// Send packet 

byte[] sizePacket = BitConverter.GetBytes(request.Length); 
byte[] requestWithHeader = new byte[sizePacket.Length + request.Length]; 
sizePacket.CopyTo(requestWithHeader, 0); 
request.CopyTo(requestWithHeader, sizePacket.Length); 

ns.Write(requestWithHeader, 0, requestWithHeader.Length); 

// Receive response 

ns.Read(sizePacket, 0, sizePacket.Length); 
int responseLength = BitConverter.ToInt32(sizePacket, 0); 
byte[] response = new byte[responseLength]; 

int bytesReceived = 0; 
while (bytesReceived < responseLength) 
{ 
    int bytesRead = ns.Read(response, bytesReceived, responseLength - bytesReceived); 
    bytesReceived += bytesRead; 
} 

(Izquierda a cabo la captura de alguna excepción, etc.) El servidor hace lo contrario, es decir, que bloquea en NetworkStream.Read() hasta que tenga en su conjunto solicitar, luego lo procesa y envía una respuesta usando Write().

La velocidad bruta de Write()/Read() no es un problema (es decir, enviar paquetes grandes es rápido), pero enviar varios paquetes pequeños, uno tras otro, sin cerrar la conexión puede ser terriblemente lento (retrasos de 50-100 ms). Lo extraño es que estos retrasos aparecen en las conexiones LAN con tiempos de ping típicos < 1 ms, pero no ocurren si el servidor se ejecuta en localhost, aunque el tiempo de ping sería efectivamente el mismo (al menos la diferencia no debería ser del orden de 100 ms). Eso tendría sentido para mí si volviera a abrir la conexión en cada paquete, causando mucho apretón de manos, pero no lo hago. Es como si el servidor entrara en estado de espera y fuera de sincronización con el cliente, y luego tropieza un poco, ya que restablece lo que esencialmente es una conexión perdida.

Entonces, ¿lo estoy haciendo mal? ¿Hay alguna forma de mantener sincronizada la conexión entre TcpServer y TcpClient para que el servidor siempre esté listo para recibir datos? (Y viceversa: a veces el procesamiento de la solicitud del cliente tarda unos pocos ms, y luego el cliente no parece estar listo para recibir la respuesta del servidor hasta que ha tenido unos minutos para despertar después de bloquear en Read() .)

+0

¿Solo por curiosidad intentó usar un StreamReader? –

+1

¿Alguna razón en particular por la que no usa [WCF] (http://msdn.microsoft.com/en-us/netframework/aa663324)? Este marco tiene todo lo que intenta hacer y más incorporado. – Jay

+2

; por el contrario, IMO; WCF está muy hinchado, y la mayoría de la gente solo usa una pequeña fracción de él. Los sockets crudos a menudo están bien, y generalmente son significativamente más eficientes. –

Respuesta

7

Resulta que mi servidor y el cliente no eran completamente simétricos después de todo. Me había dado cuenta, pero no pensé que importara en absoluto. Aparentemente es un gran trato. En concreto, el servidor hizo esto:

ns.Write(sizePacket, 0, sizePacket.Length); 
ns.Write(response, 0, response.Length); 

Qué me puse esto:

// ... concatenate sizePacket and response into one array, same as client code above 
ns.Write(responseWithHeader, 0, responseWithHeader.Length); 

Y ahora el retraso ha desaparecido por completo, o al menos ya no es medible en milisegundos. Entonces eso es algo así como una aceleración de 100x allí mismo. \ o/

Todavía es extraño porque está escribiendo exactamente los mismos datos en el socket que antes, así que supongo que el socket recibe algunos metadatos secretos durante la operación de escritura, que de alguna manera se comunica al socket remoto, lo que puede interpretar es una oportunidad para tomar una siesta. O eso, o la primera escritura pone el zócalo en modo de recepción, causando que se dispare cuando se le pide que envíe de nuevo antes de que haya recibido algo.

Supongo que la implicación sería que todo este código de ejemplo se encuentra por ahí, que muestra cómo escribir y leer desde sockets en fragmentos de tamaño fijo (a menudo precedido por una sola int que describe el tamaño del paquete follow, igual que mi primera versión), no está mencionando que hay una penalización de rendimiento muy grande por hacerlo.

+9

Solo para explicar los retrasos, creo que aplica el algoritmo Nagles en los sockets de Windows: los paquetes pequeños se colocarán en un búfer para mejorar el rendimiento general de la red. Cada paquete individual puede ver un Retardo de 200 ms antes de llegar al par de la red: http://support.microsoft.com/kb/214397/en-us http://msdn.microsoft.com/en-us/library/system.net .sockets.tcpclient.nodelay.aspx –

+1

Bueno, gracias por eso, fue bastante informativo. Desearía que Microsoft fuera mejor enlazando artículos excelentes como ese de MSDN. O tal vez solo necesito saber dónde mirar. De cualquier manera, me siento más inteligente ahora. Todavía es un poco extraño que estuviera midiendo ~ 100 ms de retraso, nada como el tiempo de espera de 200 ms para el búfer de Winsock, pero tal vez estaba midiéndolo mal. – ReturningTarzan

+4

No es realmente específico de Microsoft (aparte de su propia elección de algoritmo). Cualquier pila TCP de producción favorecerá el envío de paquetes grandes sobre pequeños para reducir la sobrecarga. Por lo tanto, cuando escribe una pequeña cantidad de datos en la secuencia TCP, se almacenará en búfer con la esperanza de que ocurran otras escrituras. Un buen algoritmo de pila TCP se esfuerza por equilibrar la baja latencia (mediante el envío inmediato) y el ancho de banda alto (utilizando el mayor tamaño de paquete posible). La mayoría de las pilas ofrecen una manera de vaciar manualmente el comando de búfer, o para un mejor rendimiento no especifique demoras en un zócalo específico. – David

Cuestiones relacionadas