svcutil generate from create crear consumir consume clientbase wcf proxy

generate - wcf clientbase



Mejores prácticas para la vida útil del proxy WCF-¿o con qué frecuencia cerrar un proxy WCF? (4)

(Publicando una segunda respuesta completamente diferente)

Si hablamos de "mejores prácticas", la mejor práctica real con WCF es tener "métodos de grano grueso". Si un cliente llama a un método varias veces en un bucle, entonces toda la lógica comercial debe trasladarse al servicio en sí.

Por ejemplo.

[DataContract] class CustomerAndAddress { [DataMember] CustomerModel Customer; [DataMember] AddressModel Address; } [ServiceContract] class BillingService { [OperationContract] CustomerAndAddress[] GetAllCustomersAndAddresses(); }

o, más probablemente en el mundo real:

[ServiceContract] class BillingService { [OperationContract] CustomerReportData FetchCustomerReportInfo(CustomerReportParameters parameterSet); }

Habiendo dicho eso, todavía estoy interesado en ver si puedes lograr lo que estás intentando.

He estado trabajando en una aplicación WPF que usa WCF para acceder a la base de datos y la lógica del servidor.

Comencé con un solo objeto proxy de cliente WCF que estaba usando repetidamente para llamar a métodos en el servidor. Después de usar el proxy por un tiempo, el servidor eventualmente arrojaría una excepción: System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://.../Service/BillingService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://.../Service/BillingService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://.../Service/BillingService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full

Creo que esto se debe a que cada llamada de servicio abría un nuevo socket desde el proxy al servidor y nunca los cerraba. Finalmente, el servidor se inundó y comenzó a rechazar las solicitudes.

Después de una breve búsqueda, determiné que necesito cerrar () el proxy periódicamente. Las muestras que encontré son degeneradamente pequeñas. Este proporcionó algunos consejos útiles, pero realmente no responde la pregunta. También he visto recomendaciones para evitar el patrón using () (y aplicar try / catch / finally en su lugar) porque el método Dispose del proxy puede generar una excepción (yuck).

Parece que el patrón recomendado se está formando así:

[TestClass] public class WCFClientUnitTest { BillingServiceClient _service; [TestMethod] public void TestGetAddressModel() { List<CustomerModel> customers = null; try { _service = new BillingServiceClient(); customers = _service.GetCustomers().ToList(); } catch { _service.Abort(); _service = null; throw; } finally { if ((_service != null) && (_service.State == System.ServiceModel.CommunicationState.Opened)) _service.Close(); _service = null; } if (customers != null) foreach (CustomerModel customer in customers) { try { _service = new BillingServiceClient(); AddressModel address = (AddressModel)_service.GetAddressModel(customer.CustomerID); Assert.IsNotNull(address, "GetAddressModel returned null"); } catch { _service.Abort(); _service = null; throw; } finally { if ((_service != null) && (_service.State == System.ServiceModel.CommunicationState.Opened)) _service.Close(); _service = null; } } }

Entonces, mi pregunta todavía gira en torno a cuánto tiempo debería mantener vivo el proxy del cliente. ¿Debo abrirlo / cerrarlo para cada solicitud de servicio? Eso me parece excesivo. ¿No incurriré en un golpe de rendimiento significativo?

Lo que realmente quiero hacer es crear y abrir un canal y hacer una breve ráfaga de llamadas de servicio secuencial, cortas y repetidas a través del canal. Entonces cierra bien el canal.

Como nota al margen, aunque aún no lo he implementado, pronto agregaré un modelo de seguridad al servicio (SSL y ACL) para restringir quién puede llamar a los métodos de servicio. Una de las respuestas a esta publicación menciona que la renegociación del contexto de autenticación y seguridad hace que la reapertura del canal por cada llamada de servicio sea un desperdicio, pero simplemente recomienda evitar la construcción de un contexto de seguridad.

EDIT 03/11/2010: Esto parece importante, así que lo estoy agregando a la pregunta ...

En respuesta al comentario / sugerencia de Andrew Shepherd , volví a ejecutar mi prueba de unidad con mi cierre de TrendMicro AntiVirus mientras monitoreaba la salida de netstat -b. Netstat pudo registrar un crecimiento significativo de puertos abiertos que eran propiedad de WebDev.WebServer40.exe. La gran mayoría de los puertos estaban en el estado TIME_WAIT. Microsoft dice que los puertos pueden permanecer en NET_WAIT después de que el cliente cierra la conexión ...

NOTA: Es normal tener un socket en el estado TIME_WAIT durante un largo período de tiempo. La hora se especifica en RFC793 como dos veces la vida máxima del segmento (MSL). MSL está especificado para ser de 2 minutos. Entonces, un socket podría estar en un estado TIME_WAIT por hasta 4 minutos. Algunos sistemas implementan valores diferentes (menos de 2 minutos) para el MSL.

Esto me lleva a pensar que si cada llamada de servicio abre un nuevo socket en el servidor, y como estoy llamando al servicio en un circuito cerrado, podría inundar fácilmente el servidor, haciendo que se agote el espacio disponible y genere a su vez excepción que noté arriba.

Por lo tanto, debo seguir una de estas dos rutas: 1) intento de realizar llamadas de servicio por lotes para que reutilicen el socket del lado del servidor 2) cambie mi contrato de servicio para que pueda devolver grandes cantidades de datos con menos llamadas.

La primera opción me parece mejor, y voy a seguir buscándola. Volveré a publicar lo que descubrí y recibiré más comentarios, preguntas y respuestas.


Escribí (bueno, encontré y modifiqué) un envoltorio para ayudar a deshacerse del servicio correctamente. Perdón por el VB. Tiene algunas cosas adicionales, como un MessageViewerInspector para poder interceptar el XML que MessageViewerInspector y sale del servicio. Solo ignóralo por ahora.

EDITAR : Sé que esto realmente no responde a su pregunta de cómo mantener ráfagas de solicitudes, pero ciertamente haría que el uso del servicio sea un poco más limpio, en cuanto al código.

Imports System.ServiceModel Namespace WCF '''''' <summary> '''''' This helper fixes an issue with WCF services where an exception gets thrown '''''' during the Dispose() call of the client, so it doesn''t actually get disposed. '''''' </summary> '''''' <typeparam name="TProxy"></typeparam> '''''' <typeparam name="TChannel"></typeparam> '''''' <remarks></remarks> Public Class ServiceProxyHelper(Of TProxy As {ClientBase(Of TChannel), New}, TChannel As Class) Implements IDisposable Private _proxy As TProxy Private _inspector As MessageViewerInspector Public ReadOnly Property ServiceProxy() As TProxy Get If Not _proxy Is Nothing Then Return _proxy Else Throw New ObjectDisposedException("ServiceProxyHelper") End If End Get End Property Public ReadOnly Property Inspector() As MessageViewerInspector Get If _inspector Is Nothing Then _inspector = New MessageViewerInspector() End If Return _inspector End Get End Property Public Sub New() _proxy = New TProxy() _proxy.Endpoint.Behaviors.Add(Me.Inspector) End Sub Public Sub New(ByVal endpointAddress As String) Me.New() If Not Me._proxy Is Nothing AndAlso Not String.IsNullOrEmpty(endpointAddress) Then Me._proxy.Endpoint.Address = New EndpointAddress(endpointAddress) End If End Sub Public Sub Dispose() Implements IDisposable.Dispose Try If Not _proxy Is Nothing Then If _proxy.State <> CommunicationState.Faulted Then _proxy.Close() Else _proxy.Abort() End If End If Catch ex As CommunicationException _proxy.Abort() Catch ex As TimeoutException _proxy.Abort() Catch ex As Exception _proxy.Abort() Throw Finally _proxy = Nothing End Try End Sub End Class End Namespace

Entonces puedes usarlo así:

Using myService As New ServiceProxyHelper(Of MyService.MyServiceClient, MyService.IMyService) Try '' Do work myService.ServiceProxy.DoWork() Catch ex As FaultException(Of MyService.MyServiceException) '' Log exception End Try End Using


Has puesto tu propia respuesta en la pregunta.

"Lo que realmente quiero hacer es crear y abrir un canal y hacer una breve ráfaga de llamadas de servicio secuencial, breves y repetidas a través del canal. Luego cerrar bien el canal"

Eso es correcto.

Aplicando esto al ejemplo que ha dado, puede simplificarlo como:

try { _service = new BillingServiceClient(); customers = _service.GetCustomers().ToList(); if (customers != null) foreach (CustomerModel customer in customers) { AddressModel address = (AddressModel)_service.GetAddressModel(customer.CustomerID); Assert.IsNotNull(address, "GetAddressModel returned null"); } } catch { _service.Abort(); _service = null; throw; } finally { if ((_service != null) && (_service.State == System.ServiceModel.CommunicationState.Opened)) _service.Close(); _service = null; }

Más tarde Editar: Oh, espera, acabo de volver a leer tu pregunta. Has dicho que fallaría después de múltiples llamadas repetidas. Supongo que el código anterior en realidad causa la falla?


Parece que hay varias cosas en juego aquí.

Primero, la prueba unitaria que ejecuté y supervisé con netstat -b señaló que Cassini (WebDev.WebServer40.exe) era el propietario de los puertos que se estaban acumulando en el estado TIME_WAIT. Como se menciona en el artículo referenciado de MSFT kb, es normal que los puertos permanezcan después del saludo de FIN mientras la aplicación espera a que se entreguen los paquetes lentos en la red y se agote la cola de mensajes. La configuración predeterminada de 2 minutos explica por qué vi los puertos fingir después de completar la prueba de mi unidad. Si bien es posible cambiar el MSL a través de la configuración de registro, no es recomendable.

Pero, el punto importante que casi lo olvido es que el servicio se estaba ejecutando bajo la dirección de Cassini. Cuando cambio mi punto final de servidor para que funcione bajo IIS7, ¡no experimento ningún crecimiento de puerto! No puedo explicar si eso significa que el cliente está reutilizando los puertos o si IIS7 es mejor que Cassini para limpiar los puertos después de que terminen. Ahora, eso no responde totalmente a mi pregunta con respecto a la frecuencia con la que debo cerrar el proxy de WCF. Simplemente significa que no tengo que cerrar el proxy con frecuencia.

Puedo ver que todavía existe una compensación de recursos para mantener el proxy abierto durante largos períodos de tiempo.

Si tiene muchos (es decir, miles) clientes que acceden a su servicio WCF, puede tener sentido liberar los recursos del servidor entre llamadas o pequeños lotes de llamadas. En ese caso, asegúrese de usar el mecanismo try / catch / finally y no usar () porque aunque el proxy de servicio implemente IDisposable, el método close () puede lanzar una excepción si el servicio está en estado de falla.

Por otro lado (como en mi caso particular), si solo espera que unos pocos clientes accedan a su servicio WCF, no necesita la complejidad adicional de abrir y cerrar el servidor proxy de manera frecuente y explícita. Por lo tanto, intento abrir el proxy una vez cuando se inicie mi aplicación y dejarlo abierto hasta que la aplicación finalice. Tengo la intención de implementar un método de invocación de ayuda de servicio (similar a: ¿ Renovar un cliente WCF cuando SCT ha expirado? ) Que reciclará la conexión, por si alguna vez entra en estado de falla. Entonces, no tengo que preocuparme por administrar la vida útil del proxy.

Por favor, avíseme si cree que estoy malinterpretando los resultados de mi prueba o si tiene una mejor solución.