Pregunta

He seguido la demostración de Tomek Janczuk en la televisión Silverlight para crear un programa de chat que utiliza WCF Duplex servicio de sondeo web. El cliente se suscribe al servidor, y después de los iniciados servidor notificaciones a todos los clientes conectados a publicar eventos.

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

Me conectada 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 uno cerca de los navegadores. Tan pronto como un cliente está fuera, los otros clientes se atascan. Se detienen recibir notificaciones.

Supongo que el bucle en el servidor que va a través de todos los clientes y los envía las notificaciones se ha quedado atascado en el cliente que ahora se encuentra. Intenté agarrar la excepción y quitarlo de la lista 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>
¿Fue útil?

Solución 2

OK, finalmente encontró una solución. Su tipo de un parche sucio, pero funciona y su estabilidad, por lo que eso es lo que vamos a usar.

En primer lugar, quiero aclarar la situación misma. Me pareció que era un callejón sin salida, pero no fue así. En realidad, fue una combinación de 2 problemas diferentes que me hicieron pensar que los clientes están a la espera mientras el servidor se ha quedado atascado en algo. El servidor no estaba atrapado, que era sólo en el medio de un proceso muy largo. Lo que pasa es que el cliente IE tenía un problema en sí mismo, lo que hizo que pareciera que estaba esperando para siempre.

I finalmente logró aislar los problemas 2 y luego le dio a cada problema de su propia solución.

Problema número 1:. Los bloqueos de servidor para un largo tiempo al intentar enviar una notificación a un cliente que fue desconectada

Dado que esto se hizo 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 a los clientes a ser independiente. Aquí 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);
        }
    }
}

Tenga en cuenta que hay un hilo para manejar cada notificación del cliente. El hilo también los descartes del cliente, si no logra enviar la notificación.

Problema número 2:. El cliente mata la conexión después de 10 segundos de inactividad

Este problema, sorprendentemente, sólo ocurrió en el explorador ... Realmente no puedo explicarlo, pero después de hacer algunas investigaciones en Google me encontré con que yo no era el único en notar, pero no pude encontrar ninguna solución limpia excepto lo obvio - "simplemente ping al servidor cada 9 segundos". Que es exactamente lo que hice.

Así que extendió 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 llama al método de ping 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 he comprobado con varios clientes, quitado algunos clientes mediante el cierre de sus navegadores, entonces Reordenar puesto en marcha, 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 los retornos "PublishAsync" al instante, y con éxito, mientras que el código que se genera automáticamente (en Reference.cs) hace el trabajo real de enviar el mensaje al servidor, en otro hilo. La única manera de que pudiera pensar para detectar esta excepción es mediante la actualización del proxy generado automáticamente ... que es una muy mala idea ... pero no pude encontrar ninguna otra manera. (Ideas será apreciado).

Eso es todo. Si alguien sabe de una manera más fácil de evitar este problema, estaré más que feliz de escuchar.

Saludos,

Kobi

Otros consejos

Trate de establecer InactivityTimeout. Tenía el mismo problema antes. Trabajado para mí. pollingDuplex InactivityTimeout = "02:00:00" serverPollTimeout = "00:05:00" maxPendingMessagesPerSession = "2147483647" maxPendingSessions = "2147483647" duplexMode = "SingleMessagePerPoll"

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 al resto de clientes que emite la primera mitad:

    channel.BeginNotification(message, NotificationCompletedAsyncCallback, channel);

De esta manera los clientes restantes recibir notificaciones sin tener que esperar a que el tiempo de espera en el cliente que se ha desconectado.

Ahora configurar el método estático completado como

    private static void NotificationCompleted(IAsyncResult result)

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

    IChatNotification channel = (IChatNotification)(result.AsyncState);
    channel.EndNotification(result);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top