5

私は、SilverlightTVでのTomekJanczukのデモンストレーションに従って、WCF DuplexPollingWebサービスを使用するチャットプログラムを作成しました。クライアントはサーバーにサブスクライブし、サーバーは接続されているすべてのクライアントに通知を開始してイベントを公開します。

アイデアはシンプルです。クライアントには、クライアントが接続できるようにするボタンがあります。クライアントがメッセージを書き込んで公開できるテキストボックスと、サーバーから受信したすべての通知を表示する大きなテキストボックス。

3つのクライアント(IE、Firefox、Chromeのさまざまなブラウザー)を接続しましたが、すべてうまく機能しています。メッセージを送受信します。問題は、ブラウザの1つを閉じると始まります。1つのクライアントが停止するとすぐに、他のクライアントはスタックします。彼らは通知を受け取らなくなります。

すべてのクライアントを通過して通知を送信するサーバーのループが、現在欠落しているクライアントでスタックしていると推測しています。例外をキャッチしてクライアントリストから削除しようとしましたが(コードを参照)、それでも役に立ちません。

何か案は?

サーバーコードは次のとおりです。

    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);
            }
        }
    }
}

クライアントコードは次のとおりです。

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";

    }
}

また、Web構成は次のとおりです。

  <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>
4

3 に答える 3

2

inactivityTimeoutを設定してみてください。以前に同じ問題がありました。私のために働いた。pollingDuplex inactivityTimeout = "02:00:00" serverPollTimeout = "00:05:00" maxPendingMessagesPerSession = "2147483647" maxPendingSessions = "2147483647"duplexMode = "SingleMessagePerPoll"

于 2011-01-10T16:45:37.277 に答える
2

OK、ついに解決策を見つけました。汚いパッチのようなものですが、動作し、安定しているので、それを使用します。

まず、状況そのものを明らかにしたいと思います。これはデッドロックだと思っていましたが、そうではありませんでした。実際には、サーバーが何かに固執している間、クライアントがすべて待機していると思わせたのは、2 つの異なる問題の組み合わせでした。サーバーはスタックしていませんでした。非常に長いプロセスの途中でした。問題は、IE クライアント自体に問題があり、永遠に待っているように見えたということです。

私は最終的に 2 つの問題を切り分けることができ、それぞれの問題に独自の解決策を与えました。

問題番号 1: 切断されたクライアントに通知を送信しようとすると、サーバーが長時間ハングします。

これはループで行われたため、他のクライアントも同様に待機する必要がありました。

 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);
                }
            }

そこで、この問題を解決するために、クライアントごとに別個のスレッドでループを開始するようにして、クライアントへの通知が独立するようにしました。最終的なコードは次のとおりです。

[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);
        }
    }
}

各クライアント通知を処理するスレッドが 1 つあることに注意してください。また、通知の送信に失敗した場合、スレッドはクライアントを破棄します。

問題番号 2: クライアントは 10 秒のアイドル状態の後に接続を切断します。

この問題は、驚くべきことに、エクスプローラーでのみ発生しました...実際には説明できませんが、Google でいくつかの調査を行った後、それに気付いたのは私だけではなかったことがわかりました。 - 「サーバーに 9 秒ごとに ping を実行するだけ」。これはまさに私がしたことです。

そこで、クライアントの Pong メソッドを即座に呼び出すサーバーの Ping メソッドを含めるようにコントラクト インターフェイスを拡張しました。

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

クライアントの Pong イベント ハンドラーは、9 秒間スリープしてから再度 ping メソッドを呼び出すスレッドを作成します。

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();
}

そしてそれだけでした。複数のクライアントでテストし、ブラウザを閉じて一部のクライアントを削除してから再起動したところ、すべて機能しました。そして、失われたクライアントは最終的にサーバーによってクリーンアップされました.

もう 1 つ注意 - クライアント接続が信頼できないことが判明したため、try - catch 例外で囲み、接続が自然に切断された場合に対応できるようにしました。

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

もちろん、これは役に立ちません。"PublishAsync" は即座に正常に返されますが、(Reference.cs で) 自動的に生成されたコードは、サーバーにメッセージを送信する実際の作業を別のスレッドで実行します。この例外をキャッチするために考えられる唯一の方法は、自動生成されたプロキシを更新することです...これは非常に悪い考えです...しかし、他の方法を見つけることができませんでした. (アイデアは大歓迎です)。

それで全部です。この問題を回避する簡単な方法を誰かが知っている場合は、喜んでお知らせします。

乾杯、

コビ

于 2011-01-10T16:48:33.460 に答える