2011-01-10 16 views
5

He seguido la demostración de Tomek Janczuk en silverlight tv para crear un programa de chat que utiliza el servicio web WCF Duplex Polling. El cliente se suscribe al servidor y luego el servidor inicia las notificaciones a todos los clientes conectados para publicar eventos.interbloqueo al utilizar WCF Duplex Polling con Silverlight

La idea es simple, en el cliente, hay un botón que permite que el cliente se conecte. Un cuadro de texto donde el cliente puede escribir un mensaje y publicarlo, y un cuadro de texto más grande que presenta todas las notificaciones recibidas del servidor.

He conectado 3 clientes (en diferentes navegadores - IE, Firefox y Chrome) y todo funciona muy bien. Envían mensajes y los reciben sin problemas. El problema comienza cuando cierro uno de los navegadores. Tan pronto como un cliente sale, los otros clientes se quedan atascados. Dejan de recibir notificaciones.

Supongo que el bucle en el servidor que pasa por todos los clientes y les envía las notificaciones está atascado en el cliente que ahora falta. Intenté capturar la excepción y eliminarla de la lista de clientes (ver código) pero todavía no ayuda.

¿Alguna idea?

El código del servidor es la siguiente:

using System; 
using System.Linq; 
using System.Runtime.Serialization; 
using System.ServiceModel; 
using System.ServiceModel.Activation; 
using System.Collections.Generic; 
using System.Runtime.Remoting.Channels; 

namespace ChatDemo.Web 
{ 
    [ServiceContract] 
    public interface IChatNotification 
    { 
     // this will be used as a callback method, therefore it must be one way 
     [OperationContract(IsOneWay=true)] 
     void Notify(string message); 

     [OperationContract(IsOneWay = true)] 
     void Subscribed(); 
    } 

    // define this as a callback contract - to allow push 
    [ServiceContract(Namespace="", CallbackContract=typeof(IChatNotification))] 
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] 
    public class ChatService 
    { 
     SynchronizedCollection<IChatNotification> clients = new SynchronizedCollection<IChatNotification>(); 

     [OperationContract(IsOneWay=true)] 
     public void Subscribe() 
     { 
      IChatNotification cli = OperationContext.Current.GetCallbackChannel<IChatNotification>(); 
      this.clients.Add(cli); 
      // inform the client it is now subscribed 
      cli.Subscribed(); 

      Publish("New Client Connected: " + cli.GetHashCode()); 

     } 

     [OperationContract(IsOneWay = true)] 
     public void Publish(string message) 
     { 
      SynchronizedCollection<IChatNotification> toRemove = new SynchronizedCollection<IChatNotification>(); 

      foreach (IChatNotification channel in this.clients) 
      { 
       try 
       { 
        channel.Notify(message); 
       } 
       catch 
       { 
        toRemove.Add(channel); 
       } 
      } 

      // now remove all the dead channels 
      foreach (IChatNotification chnl in toRemove) 
      { 
       this.clients.Remove(chnl); 
      } 
     } 
    } 
} 

El código de cliente es el siguiente:

void client_NotifyReceived(object sender, ChatServiceProxy.NotifyReceivedEventArgs e) 
{ 
    this.Messages.Text += string.Format("{0}\n\n", e.Error != null ? e.Error.ToString() : e.message); 
} 

private void MyMessage_KeyDown(object sender, KeyEventArgs e) 
{ 
    if (e.Key == Key.Enter) 
    { 
     this.client.PublishAsync(this.MyMessage.Text); 
     this.MyMessage.Text = ""; 
    } 
} 

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    this.client = new ChatServiceProxy.ChatServiceClient(new PollingDuplexHttpBinding { DuplexMode = PollingDuplexMode.MultipleMessagesPerPoll }, new EndpointAddress("../ChatService.svc")); 

    // listen for server events 
    this.client.NotifyReceived += new EventHandler<ChatServiceProxy.NotifyReceivedEventArgs>(client_NotifyReceived); 

    this.client.SubscribedReceived += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_SubscribedReceived); 

    // subscribe for the server events 
    this.client.SubscribeAsync(); 

} 

void client_SubscribedReceived(object sender, System.ComponentModel.AsyncCompletedEventArgs e) 
{ 
    try 
    { 
     Messages.Text += "Connected!\n\n"; 
     gsConnect.Color = Colors.Green; 
    } 
    catch 
    { 
     Messages.Text += "Failed to Connect!\n\n"; 

    } 
} 

Y el web.config es el siguiente:

<system.serviceModel> 
    <extensions> 
     <bindingExtensions> 
     <add name="pollingDuplex" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement, System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 
     </bindingExtensions> 
    </extensions> 
    <behaviors> 
     <serviceBehaviors> 
     <behavior name=""> 
      <serviceMetadata httpGetEnabled="true"/> 
      <serviceDebug includeExceptionDetailInFaults="false"/> 
     </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <bindings> 
     <pollingDuplex>   
     <binding name="myPollingDuplex" duplexMode="MultipleMessagesPerPoll"/> 
     </pollingDuplex> 
    </bindings> 
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/> 
    <services> 
     <service name="ChatDemo.Web.ChatService"> 
     <endpoint address="" binding="pollingDuplex" bindingConfiguration="myPollingDuplex" contract="ChatDemo.Web.ChatService"/> 
     <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> 
     </service> 
    </services> 
    </system.serviceModel> 
+0

Me trataron de solucionar este problema haciendo que las múltiples notificaciones Enhebrado Entonces, en lugar de notificar a los clientes en el ciclo, creo un hilo por cliente y dejo que los hilos notifiquen a los clientes, por lo que un cliente no tiene que esperar al otro. Parece resolver ese problema, pero todavía hay un problema extraño. Cuando el cliente se está ejecutando en IE, si no hay actividad durante 10 segundos, la conexión está muerta. Y la próxima vez que intente enviar un mensaje recibe una excepción que indica que la conexión está en estado de error. Esto solo sucede en IE ... ¿alguien tiene alguna idea? –

Respuesta

2

OK, finalmente encontró una solución. Es un parche sucio, pero funciona y es estable, así que eso es lo que usaré.

Primero, quiero aclarar la situación. Pensé que esto era un punto muerto, pero no fue así. En realidad, fue una combinación de 2 problemas diferentes que me hicieron pensar que todos los clientes están esperando mientras el servidor está atascado en algo. El servidor no estaba atascado, solo estaba en el medio de un proceso muy largo. El caso es que el cliente de IE tenía un problema en sí mismo, lo que parecía que estaba esperando por siempre.

Eventualmente logré aislar los 2 problemas y luego le di a cada problema su propia solución.

Problema número 1: el servidor se bloquea durante un largo tiempo al intentar enviar una notificación a un cliente que se desconectó.

Dado que esto fue hecho en un bucle, otros clientes tuvieron que esperar así:

foreach (IChatNotification channel in this.clients) 
      { 
       try 
       { 
        channel.Notify(message); // if this channel is dead, the next iteration will be delayed 
       } 
       catch 
       { 
        toRemove.Add(channel); 
       } 
      } 

Por lo tanto, para resolver este problema, hice el bucle se inicia un hilo distinto para cada cliente, por lo que las notificaciones para que los clientes se vuelvan independientesAquí está el código final:

[OperationContract(IsOneWay = true)] 
public void Publish(string message) 
{ 
    lock (this.clients) 
    { 
     foreach (IChatNotification channel in this.clients) 
     { 
      Thread t = new Thread(new ParameterizedThreadStart(this.notifyClient)); 
      t.Start(new Notification{ Client = channel, Message = message }); 
     } 
    } 

} 

public void notifyClient(Object n) 
{ 
    Notification notif = (Notification)n; 
    try 
    { 
     notif.Client.Notify(notif.Message); 
    } 
    catch 
    { 
     lock (this.clients) 
     { 
      this.clients.Remove(notif.Client); 
     } 
    } 
} 

en cuenta que hay un hilo para manejar cada notificación del cliente. El hilo también descarta al cliente, si no pudo enviar la notificación.

Número de problema 2: el cliente cancela la conexión después de 10 segundos inactivos.

Este problema, sorprendentemente, solo sucedió en el explorador ... Realmente no puedo explicarlo, pero después de investigar un poco en Google descubrí que no era el único que lo notaba, pero no podía encontrar solución limpia, excepto lo obvio: "solo haga ping al servidor cada 9 segundos". Que es exactamente lo que hice.

Así que extendieron la interfaz contrato para incluir un método de ping servidor, que se activa instantáneamente método de Pong de un cliente:

[OperationContract(IsOneWay = true)] 
public void Ping() 
{ 
    IChatNotification cli = OperationContext.Current.GetCallbackChannel<IChatNotification>(); 
    cli.Pong(); 
} 

controlador de eventos de Pong del cliente crea un hilo que tiene capacidad para 9 segundos y luego se llama el ping método de nuevo:

void client_PongReceived(object sender, System.ComponentModel.AsyncCompletedEventArgs e) 
{ 
    // create a thread that will send ping in 9 seconds 
    Thread t = new Thread(new ThreadStart(this.sendPing)); 
    t.Start(); 
} 

void sendPing() 
{ 
    Thread.Sleep(9000); 
    this.client.PingAsync(); 
} 

Y eso fue todo. Lo probé con varios clientes, eliminé algunos clientes al cerrar sus navegadores, luego los relancé, todo funcionó. Y los clientes perdidos finalmente fueron limpiados por el servidor.

Una nota más - Desde la conexión del cliente resultó ser poco fiable, lo rodeó con un intento - a excepción de captura para que pueda responder a los casos en que la conexión muere de forma espontánea:

 try 
     { 
      this.client.PublishAsync(this.MyMessage.Text); 
      this.MyMessage.Text = ""; 
     } 
     catch 
     { 
      this.Messages.Text += "Was disconnected!"; 
      this.client = null; 
     } 

Esto, por supuesto, no ayuda, ya que el "PublishAsync" regresa al instante, y con éxito, mientras que el código que se generó automáticamente (en Reference.cs) hace el trabajo real de enviar el mensaje al servidor, en otro hilo. La única forma en que podría pensar para detectar esta excepción es actualizando el proxy generado automáticamente ... lo cual es una muy mala idea ... pero no pude encontrar otra forma. (Las ideas serán apreciadas).

Eso es todo. Si alguien conoce una forma más fácil de solucionar este problema, estaré más que feliz de saberlo.

Cheers,

Kobi

+0

Cuando usted está llamando de nuevo al cliente, en lugar de manejar la creación de hilos a sí mismo, es posible que desee depender el ThreadPool. Es un código más simple, y no tiene que preocuparse por sobrecargar accidentalmente el sistema con demasiados hilos. También puede usar el modelo de devolución de llamada Async, pero el código es mucho más complejo: no es realmente recomendable. Con respecto a la conexión de IE que se rescata después de 10 segundos de inactividad, ¿ha intentado utilizar WebRequest.RegisterPrefix() para decirle a Silverlight que desea usar su pila HTTP interna en lugar de la del navegador? –

+1

Hola Ken, gracias por tu comentario. ¿podría explicarnos mejor estos dos temas: el grupo de hilos y la pila HTTP interna V del navegador? Me encantaría saber más sobre eso. –

2

intenta establecer tiempo de inactividad. Tuve el mismo problema antes. Funcionó para mí pollingDuplex InactivityTimeout = "02:00:00" serverPollTimeout = "00:05:00" maxPendingMessagesPerSession = "2147483647" maxPendingSessions = "2147483647" duplexMode = "SingleMessagePerPoll"

+1

Thnx por la respuesta, pero no entiendo muy bien. ¿Dónde configuro estos valores? En el cliente o en el servidor? –

+0

Esto es lo que tengo: msqsf

1

Una mejor manera de resolver el problema # 1 es la creación de la devolución de llamada utilizando el patrón asincrónico:

[OperationContract(IsOneWay = true, AsyncPattern = true)] 
    IAsyncResult BeginNotification(string message, AsyncCallback callback, object state); 
    void EndNotification(IAsyncResult result); 

Cuando el servidor notifica a los clientes restantes que emite la primera mitad:

channel.BeginNotification(message, NotificationCompletedAsyncCallback, channel); 

De esta manera los clientes restantes obtienen notifi ed sin tener que esperar el tiempo de espera en el cliente que ha caído.

Ahora ajuste el método completado estática como

private static void NotificationCompleted(IAsyncResult result) 

En este método completado llamar la mitad restante de la llamada de esta manera:

IChatNotification channel = (IChatNotification)(result.AsyncState); 
    channel.EndNotification(result); 
Cuestiones relacionadas