我按照 Tomek Janczuk 在 silverlight tv 上的演示创建了一个使用 WCF Duplex Polling Web 服务的聊天程序。客户端订阅服务端,然后服务端向所有连接的客户端发起通知发布事件。
这个想法很简单,在客户端,有一个按钮可以让客户端连接。客户端可以在其中编写消息并发布消息的文本框,以及显示从服务器接收到的所有通知的更大文本框。
我连接了 3 个客户端(在不同的浏览器中 - IE、Firefox 和 Chrome),一切都运行良好。他们发送消息并顺利接收。当我关闭其中一个浏览器时,问题就开始了。一旦一个客户出去,其他客户就会被卡住。他们停止收到通知。
我猜测服务器中通过所有客户端并向它们发送通知的循环卡在现在丢失的客户端上。我尝试捕获异常并将其从客户端列表中删除(请参阅代码),但它仍然没有帮助。
有任何想法吗?
服务器代码如下:
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";
}
}
网络配置如下:
<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>