通过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

posted on 2012-12-28 22:42  夏狼哉  阅读(2485)  评论(0编辑  收藏  举报