2009-01-16 12 views
10

El siguiente método (en el que afortunadamente no cometí ningún error al simplificarlo dramáticamente para esta publicación) funciona correctamente y configuro usando el modo de transferencia de transmisión por streaming sobre el protocolo net.tcp. El problema es que el rendimiento es significativamente peor que la descarga del mismo archivo a través de IIS a través de http. ¿Por qué sería esto y qué cosas puedo cambiar para mejorar el rendimiento?Descarga de archivos a través de WCF más lento que a través de IIS

Stream WebSiteStreamedServiceContract.DownloadFile(string filePath) { 
    return File.OpenRead(filePath); 
} 

Por último, se WCF asumir la responsabilidad de la eliminación adecuada de mi flujo, y es lo que hace un buen trabajo en esto? Si no, ¿qué se supone que debo hacer en su lugar?

Gracias.

+0

Hi Greg! Estoy luchando por configurar un canal transmitido a través de netTcp. Con tu pregunta, parece que has tenido éxito en hacer lo mismo. ¿Puedes compartir información sobre las configuraciones del servidor y del cliente? Muchas gracias de antemano! – Nayan

+0

@Nayan Creo que deberías hacer una nueva pregunta y señalarme. La respuesta probablemente terminará siendo demasiado grande para los comentarios. –

Respuesta

31

Después de 8 meses de trabajar en este problema, 3 de ellos con Microsoft, esta es la solución. La respuesta corta es que el lado del servidor (el lado que envía el archivo grande) necesarios para utilizar el siguiente para una unión:

<customBinding> 
    <binding name="custom_tcp"> 
    <binaryMessageEncoding /> 
    <tcpTransport connectionBufferSize="256192" maxOutputDelay="00:00:30" transferMode="Streamed"> 
    </tcpTransport> 
    </binding> 
</customBinding> 

La clave aquí es el atributo connectionBufferSize. Es posible que se necesiten varios otros atributos (maxReceivedMessageSize, etc.), pero connectionBufferSize fue el culpable.

No se tuvo que cambiar el código en el lado del servidor.

No se tuvo que cambiar el código en el lado del cliente.

No se tuvo que cambiar la configuración en el lado del cliente.

Aquí está la respuesta larga:

sospeché desde el principio que la razón net.tcp sobre WCF fue lento era porque estaba enviando pequeños trozos de información con mucha frecuencia en lugar de grandes trozos de información con menos frecuencia, y que esto hizo que funcionara mal en redes de alta latencia (Internet). Esto resultó ser cierto, pero fue un largo camino para llegar allí.

Hay varios atributos en netTcpBinding que suenan prometedores: maxBufferSize es el más obvio, y maxBytesPerRead y otros suenan esperanzados también. Además de estos, es posible crear flujos más complicados que el de la pregunta original; también puede especificar el tamaño del búfer allí, tanto en el lado del cliente como del servidor. El problema es que nada de esto tiene ningún impacto. Una vez que utilizas un NetTcpBinding, te dan una manguera.

El motivo es que al ajustar maxBufferSize en un netTcpBinding se ajusta el búfer en la capa de protocolo. Pero nada de lo que pueda hacer con un NetTcpBinding ajustará la capa de transporte subyacente. Es por eso que hemos fallado por tanto tiempo para avanzar.

El enlace personalizado resuelve el problema porque aumentar el connectionBufferSize en la capa de transporte aumenta la cantidad de información enviada a la vez, y por lo tanto, la transferencia es mucho menos susceptible a la latencia.

Al resolver este problema, me di cuenta de que maxBufferSize y maxBytesPerRead tuvieron un impacto en el rendimiento sobre redes de baja latencia (y localmente). Microsoft me dice que maxBufferSize y connectionBufferSize son independientes y que todas las combinaciones de sus valores (iguales entre sí, maxBufferSize mayor que connectionBufferSize, maxBufferSize menor que connectionBufferSize) son válidas. Estamos teniendo éxito con maxBufferSize y maxBytesPerRead de 65536 bytes. De nuevo, sin embargo, esto tuvo muy poco impacto en el rendimiento de la red de alta latencia (el problema original).

Si se está preguntando para qué es maxOutputDelay, es la cantidad de tiempo que se asigna para llenar el búfer de conexión antes de que el marco genere una excepción de IO. Debido a que aumentamos el tamaño del búfer, también aumentamos la cantidad de tiempo asignada para llenar el búfer.

Con esta solución, nuestro rendimiento aumentó alrededor del 400% y ahora es ligeramente mejor que IIS. Hay varios otros factores que afectan el rendimiento relativo y absoluto sobre IIS sobre HTTP y WCF sobre net.tcp (y WCF sobre http, para el caso), pero esta fue nuestra experiencia.

+0

Greg, dices No hubo que cambiar la configuración en el lado del cliente, ¿cómo? Actualmente estoy utilizando basicHttpBinding tanto en el servidor como en el cliente, y Mtom, que requiere configuraciones tanto para el cliente como para el servicio, por lo que no veo cómo el cliente puede reconocer mágicamente un nuevo nodo CustomBinding. – joedotnot

+0

@joe, anteriormente estábamos usando un NetTcpBinding (creamos un enlace y luego lo pasamos a ChannelFactory, no usamos el modo de proxy autogenerado para hacer las cosas), y seguimos usando un NetTcpBinding. Estoy bastante seguro de que nada ha cambiado en absoluto, pero voy a parar de prometer eso y decir cuál fue mi punto real, que fue que ninguno de los valores de parámetro (MaxBytesPerRead, etc.) en el lado del cliente fueron la causa del problema original –

3

No conozco la respuesta a su primera pregunta (creo que debe proporcionar más código para mostrar lo que está haciendo para ambas pruebas), pero su segunda pregunta con respecto a la eliminación de la transmisión, la respuesta es que necesitas hacerlo tú mismo

Here is an excellent blog entry que tiene un gran código para este propósito.

1

Tomé @ Greg-Smalter consejos y cambios ConnectionBufferSize en NetTcpBinding usando la reflexión y eso resolvió mi problema. Transmitir documentos grandes ahora está gritando rápido. Aquí está el código.

 var transport = binding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance) 
      ?.GetValue(binding); 

     transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192); 
0

Aquí hay otra forma de hacerlo sin tener que lidiar con Reflection. Simplemente envuelva NetTcpBinding en CustomBinding.

var binding = new CustomBinding(new NetTcpBinding 
{ 
    MaxReceivedMessageSize = 2147483647, 
    MaxBufferSize = 2147483647, 
    MaxBufferPoolSize = 2147483647, 
    ReceiveTimeout = new TimeSpan(4, 1, 0), 
    OpenTimeout = new TimeSpan(4, 1, 0), 
    SendTimeout = new TimeSpan(4, 1, 0), 
    CloseTimeout = new TimeSpan(4, 1, 0), 
    ReaderQuotas = XmlDictionaryReaderQuotas.Max, 
    Security = 
      { 
       Mode = SecurityMode.None, 
       Transport = {ClientCredentialType = TcpClientCredentialType.None} 
      }, 
      TransferMode = TransferMode.Streamed, 
      HostNameComparisonMode = HostNameComparisonMode.StrongWildcard 
     }); 

binding.Elements.Find<TcpTransportBindingElement>().ConnectionBufferSize = 665600; 
Cuestiones relacionadas