通过PollingDuplexHttpBinding来实现双工通讯(从WCF服务端推送消息到客户端) 是比较”旧式”的做法. 在SilverLight4以前的版本中, SilverLight并不支持net.tcp通讯, 所以只能通过包装http通讯来实现.
不过, 毕竟http穿透防火墙的能力无人能及, 所以可能还是会有用到PollingDuplexHttpBinding来实现双工的时候. 下面进入正题.
一. 建立演示项目
在Vs中新建一个SilverLight项目, (我用的是silverLight5, .net 4.5) , 命名为SLHttpPollingDuplexSample, 下一步, 然后选择建立一个新的Web application来托管这个SL项目, web application将被自动命名为SLHttpPollingDuplexSample.Web.
二. 创建服务
2.1 在SLHttpPollingDuplexSample.Web项目上右击->Add new item->Wcf service, 由于是演示项目, 不再改名, 就叫service1.svc. 这时项目中会加入两个新文件: IService.cs和Service1.svc.
将IService1.cs改为如下内容:
namespace SLHttpPollingDuplexSample.Web
{
[ServiceContract(CallbackContract = typeof(IClientCallback))]
public interface IService1
{
[OperationContract]
void Register();
}
public interface IClientCallback
{
[OperationContract(IsOneWay = true)]
void PushMessage(string message);
}
}
简单看一下这个接口文件:
IService1是服务的接口, 它包括一个Register方法, 当客户端调用该方法时, 就意味着它要向服务器订阅信息, 服务器在推送的时候, 就会把信息推送到这个客户端. (当然现实中还需要一个退订功能, 不过这里只是演示基本功能)
下面的接口IClientCallbak 是回调接口, 当服务端需要向客户端推送消息时, 就是通过这个接口来推送的.
2.2 然后把Service1.svc.cs改成如下内容:
public class Service1 : IService1
{
public Service1()
{
new System.Threading.Thread(PumpMessage).Start();
}
private static Dictionary<IClientCallback, byte> _clients = new Dictionary<IClientCallback, byte>();
public void Register()
{
var c = OperationContext.Current.GetCallbackChannel<IClientCallback>();
if (!_clients.ContainsKey(c))
{
lock (_clients)
{
_clients.Add(c, 0);
}
}
}
private void PumpMessage()
{
var deadClients = new List<IClientCallback>();
while (true)
{
System.Threading.Thread.Sleep(10000);
if (_clients.Count < 1)
continue;
foreach (var c in _clients.Keys)
{
try
{
c.PushMessage(DateTime.Now.ToString("HH:mm:ss"));
}
catch
{
deadClients.Add(c);
}
}
if (deadClients.Count > 0)
{
lock (_clients)
{
deadClients.ForEach(b => _clients.Remove(b));
}
deadClients.Clear();
}
}
}
}
关于这个类:
PumpMessage是实现了一个简单的自动推送消息泵, 它会每隔十秒向客户端发送一次消息(当前时间). 如果发现推送失败, 就会认为这个客户端已经离线, 将它从客户端列表中删除.
_clients是一个静态的客户端列表, 虽然它是一个字典型, 但是它的value部分没有用处, 这里只是用字典的快速检索key功能, 所以value部分统一放了个0.
Register的实现也非常简单明了, 它直接把当前信道加入到_clients中.
2.3 添加引用及web.config配置
PollingDuplex并不是默认的.net的一部分, 所以必须通过增加引用的方式把这个dll文件加入项目.
在项目上右击->add reference->browse,
找到C:\Program Files\Microsoft SDKs\Silverlight\v5.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll.
注意在Client文件中也有一个同名的dll文件, 这里一定要选择Server文件夹.
引用完成之后, 在web.config的<system.serviceModel>节按如下配置:
<system.serviceModel>
<bindings>
<pollingDuplexHttpBinding>
<binding name="pollingDuplexHttpBinding1" duplexMode="MultipleMessagesPerPoll" maxOutputDelay="00:00:00.2"/>
</pollingDuplexHttpBinding>
</bindings>
<services>
<service name="SLHttpPollingDuplexSample.Web.Service1">
<host>
<baseAddresses>
<add baseAddress="http://localhost/SLHttpPollingDuplexSample.Web/Service1.svc"/>
</baseAddresses>
</host>
<endpoint address="" binding="pollingDuplexHttpBinding" bindingConfiguration="pollingDuplexHttpBinding1" contract="SLHttpPollingDuplexSample.Web.IService1"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<extensions>
<bindingExtensions>
<add name="pollingDuplexHttpBinding" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</bindingExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
2.4 发布及跨域权限设置
将SLHttpPollingDuplexSample.Web发布到随便哪个目录下, 然后在IIS中add->application把这个目录加入iis.
然后还需要在网站的根目录下放置一个cross domain policy(clientaccesspolicy.xml)文件以允许SL访问.
<?xml version="1.0" encoding ="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
<socket-resource port="4502-4506" protocol="tcp" />
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
三. 配置SL端
在SL项目中添加服务引用( 地址:http://localhost/SLHttpPollingDuplexSample.Web/Service1.svc ) , 但是由于vs没有内置对PollingDuplex的支持, 所以生成的ServiceReferences.ClientConfig 是空的. 这里将不使用这个文件, 直接用代码创建endpoint.
本来在SL项目中需要引用System.ServiceModel.PollingDuplex.dll 的客户端版, 但是在我的vs2012中, 添加服务引用以后, 就自动引用了, 我印象似乎vs2010还没有这个功能. 如果在SL项目的Reference中没有看到System.ServiceModel.PollingDuplex, 就需要到上面引用服务端dll的位置, 找到Library\Client文件夹, 引用其下面的System.ServiceModel.PollingDuplex.dll .
在SL的MainPage中放一个文本框txt1和一个按钮btn1, 在按钮btn1的点击事件中:
private void btn1_Click_1(object sender, RoutedEventArgs e)
{
var address = new EndpointAddress("http://localhost/SLHttpPollingDuplexSample.Web/Service1.svc");
var binding = new PollingDuplexHttpBinding(PollingDuplexMode.MultipleMessagesPerPoll);
var proxy = new ServiceReference1.Service1Client(binding, address);
proxy.PushMessageReceived += proxy_PushMessageReceived;
proxy.RegisterAsync();
}
void proxy_PushMessageReceived(object sender, ServiceReference1.PushMessageReceivedEventArgs e)
{
txt1.Text = e.message;
}
把SLHttpPollingDuplexSample.Web项目中自动生成的SLHttpPollingDuplexSampleTestPage.html设为起始项, F5运行吧, 看到SL界面以后, 点击一个按钮btn1, 稍等一会儿, 应该就可以看到文本框中出现了一个时间 , 那就是服务器端推送过来的数据了.
结语: 这种方式只是作为一个备选 , 一般情况下, 如果net.tcp可用, 应该首选net.tcp来进行通讯. 关于通过net.tcp来实现双工, 请参看下一篇文章 SilverLight与WCF服务双工通讯第二篇:Net.Tcp binding