在IIS上发布基于Windows Azure Service Bus的WCF服务

在IIS上发布基于Windows Azure Service Bus的WCF服务

概要

随着Windows Azure在中国的落地, 相信越来越多的人会用到Windows Azure。Windows Azure提供了丰富的基于云的各种服务,其中包括Service Bus(服务总线),通过Service Bus, 我们可以将传统的WCF Service注册到Window Azure Service Bus上。本文以IIS8, WCF4.0为例,详细介绍如何将部署在IIS里面的WCF服务如何主动注册到Windows Azure Service Bus。

在IIS上发布基于Windows Azure Service Bus的WCF服务_第1张图片

注册到Windows Azure Service Bus上的WCF服务, 其在运行时候的架构如下,

在IIS上发布基于Windows Azure Service Bus的WCF服务_第2张图片

传统的WCF服务有多种方式来进行HOST,比如self-hosted 或者IIS。如果通过self-hosted的方式的话, 只需要在开启HOST的时候主动注册到Windows Azure Service Bus即可。
如果WCF通过IIS的方式来Host,WAS会依据进来的请求来激活w3wp.exe进程。如果没有没有请求进入到web server的话,WCF不会主动注册到Windows Azure Service Bus的。 这样就存在一个问题:如果客户端第一次去调用该Windows Azure Service Bus Endpoint的时候, 但是由于服务端WCF服务根本没有注册到Windows Azure Service Bus, 那么就会收到“No service is hosted at the specified address.”的异常. 因此将部署在IIS里面的WCF服务注册到Windows Azure Service Bus上需要经过一些特殊的处理。

前提条件

1. 开发工具: Visual Studio 2012 + Windows Azure SDK2.0
2. 一个注册好的Windows Azure 账号
3. Windows Server 2012或者Windows 8
4. IIS8

实施步骤

1.首先我们需要在Windows Azure 里面创建一个Service Bus的namespace. 比如, 我创建了一个叫做IISSBWCF的namespace, 其会自动生成一个相应令牌:

在IIS上发布基于Windows Azure Service Bus的WCF服务_第3张图片

2. 准备好Service Bus的访问令牌之后, 下面我们就需要创建一个WCF服务, 并将该WCF服务注册到该Windows Azure Service Bus上。

    1) 打开VS2012, 创建一个WCF service application, 在该工程中, 我们需要引用ServiceBus相关Assembly。 我们可以通过点击”References” ,右击 选择”Manage NuGet Packages…”, ,然后在线搜索”Windows Azure Service Bus”, 找到后安装即可, 如下所示:

在IIS上发布基于Windows Azure Service Bus的WCF服务_第4张图片

    2) WCF Contract的定义和服务的实现和传统WCF服务没有任何区别。接下来是要把该WCF服务注册到Windows Azure Service Bus上了。 下面的配置演示了通过配置的方式注册到Windows Azure Service Bus:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <appSettings>
    <add key="EnableAutoStart" value="true"/>
    <add key="ActivatedURL" value="/iis-hosted-sb-wcf/Service1.svc"/>
  </appSettings>
  <system.serviceModel>
    <services>
      <service name="IIS_Hosted_SB.Service1">
        <endpoint address="https://iissbwcf.servicebus.windows.net" binding="basicHttpRelayBinding" contract="IIS_Hosted_SB.IService1"  behaviorConfiguration="sbTokenProvider"/>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="sbTokenProvider">
          <transportClientEndpointBehavior>
            <tokenProvider>
              <sharedSecret issuerName="owner" issuerSecret="这里是我的Access Token,做替换处理” /> </tokenProvider> </transportClientEndpointBehavior> </behavior>  
      </endpointBehaviors>
      <serviceBehaviors>
       
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="false" />
    <extensions>
      <!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. -->
      <behaviorExtensions>
        <add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </bindingElementExtensions>
      <bindingExtensions>
        <add name="basicHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </bindingExtensions>
    </extensions>
  </system.serviceModel> 
</configuration>
复制代码

    3) 当我们通过IIS第一次browse这个svc文件的时候, 那么其会自动注册到Windows Azure Service Bus上。注册成功后, 我们在Windows Azure 服务总线的管理台里面,也可以看到起处理活动的状态,如下:

在IIS上发布基于Windows Azure Service Bus的WCF服务_第5张图片

3. 现在问题是: 如果我们没有主动去访问svc文件来激活该WCF服务的话,那么客户端在通过Windows Azure Service Bus去调用这个WCF服务的时候, 就会遇到如下异常,

在IIS上发布基于Windows Azure Service Bus的WCF服务_第6张图片

4. 从IIS8开始(IIS7.5通过extension的方式来实现),我们提供了预热功能,即使没有请求进来, 我们也可以采用preload的方式完成初始化的工作,激活相关服务。而我们的WCF服务正好需要利用这一点。

实施步骤如下:
1) 首先需要将WCF服务所运行的Application pool设置为AlwaysRunning.
2) 然后实现IProcessHostPreloadClient接口的Preload方法:在里面实现对svc服务的激活。示例代码如下:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using System.ServiceModel.Activation;
using System.ServiceModel;

namespace IIS_Hosted_SB
{
    public class AutoStartService:System.Web.Hosting.IProcessHostPreloadClient
    {

        public void Preload(string[] parameters)
        {
                        
            bool isServceActivated = false;
            int attempts = 0;
            while (!isServceActivated && (attempts < 10))
            {
                Thread.Sleep(1 * 1000);
                try
                {
                    string ActivatedURL = System.Configuration.ConfigurationManager.AppSettings["ActivatedURL"].ToString();
                    ServiceHostingEnvironment.EnsureServiceAvailable(ActivatedURL);

                   WriteTrace(“Windows Azure Service Bus Endpoint is activated”);

                    isServceActivated = true;
                }
                catch (Exception exception)
                {
                    attempts++;
                    //continue on these exceptions, otherwise fail fast
                    if (exception is EndpointNotFoundException
                        || exception is ServiceActivationException
                        || exception is ArgumentException)
                    {
                        //log
                    }
                    else
                    {
                        throw;
                    }
                }
            }

        }
    }
}
复制代码

3) 修改IIS配置文件(%windir%\system32\inetsrv\config\applicationhost.config), 将刚才的自动激活服务Provider应用到该WCF服务的VD上。修改后的配置文件如下:

复制代码
<sites>
            <site name="Default Web Site" id="1">
                <application path="/">
                    <virtualDirectory path="/" physicalPath="%SystemDrive%\inetpub\wwwroot" />
                </application>
                <application path="/iis-hosted-sb-wcf" applicationPool="DefaultAppPool" serviceAutoStartEnabled="true" serviceAutoStartProvider="AutoStartProvider">
                    <virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot\iis-hosted-sb-wcf" />
                </application>
                <bindings>
                    <binding protocol="http" bindingInformation="*:80:" />
                    <binding protocol="net.tcp" bindingInformation="808:*" />
                    <binding protocol="net.msmq" bindingInformation="localhost" />
                    <binding protocol="msmq.formatname" bindingInformation="localhost" />
                    <binding protocol="net.pipe" bindingInformation="*" />
                </bindings>
                <traceFailedRequestsLogging enabled="true" />
            </site><serviceAutoStartProviders>
 <add name="AutoStartProvider" type="IIS_Hosted_SB.AutoStartService,IIS_Hosted_SB" />
</serviceAutoStartProviders>
    </system.applicationHost>
复制代码

4) 经过以上这些配置后, 我们可以发现只要一旦该application pool被回收之后,马上会有一个新的w3wp.exe被自动激活,同时其会自动注册到Windows Azure Service Bus上。 任何时候客户端通过Windows Azure Service Bus访问该WCF service,我们都可以得到正确的响应, 如下所示:

在IIS上发布基于Windows Azure Service Bus的WCF服务_第7张图片

参考文档

How to Use the Service Bus Relay Service
http://www.windowsazure.com/en-us/develop/net/how-to-guides/service-bus-relay/

IIS hosting of Wcf Services with Servicebus Endpoints
http://archive.msdn.microsoft.com/ServiceBusDublinIIS/Release/ProjectReleases.aspx?ReleaseId=4336

Application Initialization Part 2
http://blogs.iis.net/wadeh/archive/2012/05/01/application-initialization-part-2.aspx

 

希望以上内容对您有所帮助

Winston He

基于WCF回调(WCF Callback)的GPS报警推送(带源码)

2013-05-23 14:15 by GPS产品经理, 247 阅读, 0 评论, 收藏编辑

基于WCF回调(WCF Callback)的GPS报警推送

报警推送数据在很多软件中都有需求,比如任务提醒、消息广播、实时的监控报警等等。凡是对实时性要求越高的场景,越是需要服务器及时、准确地向客户端推送数据。一般的推送,我们可以选择使用socket,因为socket是双工通信的最佳模式。但是直接使用socket来开发,对于复杂的报警逻辑、权限判断、报警注册、数据库调用和更新处理来说,使用Socket处理,代码比较难以维护。

考虑到目前的基于部标808的GPS平台,我们决定使用WCF来作为平台的基础服务架构,而WCF的回调模式可以满足GPS报警复杂的业务模式:

GpsAlarmService 源码下载

1.注册
用户注册后,需要加载自己分配的功能权限和数据权限,功能权限决定了是否能看报警。
数据权限,决定了能看到那些报警,那些车辆的报警。

2.GPS报警发布
通常我们将808GPS服务器作为报警发布者,当接收到车辆GPS终端发送上来的报警后,发布给报警服务模块,由报警服务模块再根据逻辑转发给订阅者.

3.报警订阅
部标808协议规定了32路的报警再加上其他扩展的平台报警,可多达几十种报警,客户端需要通过订阅功能来接收自己感兴趣的报警。

4.报警过滤
报警最大的问题,不是如何实时的推送到客户端,而是如何避免误报。需要有一套算法设定来过滤掉无效的报警。频繁的误报,会对客户造成困扰,也会造成狼来了的效果,多次误报后,用户就失去了对报警的信任。如在工厂围墙的红外监控报警,报警设定的过于敏感,一有风吹草动就报警,保安就不得休息,时间长了就不看它,当有人非法翻越围墙的时候,反而没有看到。简单的过滤,就是时间过滤法,如当报警超过10秒后推送到客户端。

5.报警显示与处理
报警如何显示,如何避免重复显示,累积的未处理报警如何处理等等,这个也是个比较麻烦的用户体验的问题,很少有人去问问用户是否反感不断弹屏的功能设计。

基于WCF回调的双工通信,可以很好的完成报警推送。WCF中NetTcpBinding支持回调,因为从本质上讲TCP和IPC协议支持双向通信.

实现步骤:

1)首先定义报警服务接口(契约),提供订阅、注销、发布的外部接口功能。

?
namespace  GpsNET
{
     /**
      * Gps报警推送服务
      */
     [ServiceContract(SessionMode=SessionMode.Required,
         CallbackContract= typeof (IGpsServiceCallback))]
     interface  IGpsEventService
     {
         /**
          * 订阅
          * UserId 注册用户ID
          * Alarms 要订阅的报警类型ID
          * 注意IsOneWay = true,避免回调时发生死锁
          */
         [OperationContract(IsOneWay = true )]
         void  Subscribe( int  UserId, List< int > Alarms);
 
         //注销
         [OperationContract(IsOneWay = true )]
         void  Unsubscribe( int  UserId);
     }
}

  

2)定义GPS事件回调函数,当发生报警时,客户端会自动触发事件,关于IsOneWay = true这里就不多说了。

?
namespace  GpsNET
{
     /**
      * 报警回调
      */
     public  interface  IGpsServiceCallback
     {
         /**
          * msgItems 接收到的报警事件集合
          */
         [OperationContract(IsOneWay = true )]
         void  OnMessageReceived(List<AlarmItem> msgItems);
     }
}

  

3)报警服务实现

?
namespace  GpsNET
{
     /**
      * Gps报警推送服务
      */
     [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
     internal  sealed  class  GpsEventService:IGpsEventService
     {
         protected  static  log4net.ILog logger = log4net.LogManager.GetLogger( typeof (GpsEventService));
         public  delegate  void  CallbackDelegate<T>(T t);
         //客户端的报警消息接收事件
         public  static  CallbackDelegate<List<AlarmItem>> MessageReceived;
         //订阅者
         public  static  List<AlarmSubscriber> Subscribers = new  List<AlarmSubscriber>();
 
         //用户订阅报警,Alarms代表要订阅的报警类型
         public  void  Subscribe( int  UserId, List< int > Alarms)
         {
             IGpsServiceCallback callback = OperationContext.Current.GetCallbackChannel<IGpsServiceCallback>();
             
             User u = GetUser(UserId);
             AlarmSubscriber subscriber =  GetSubscirber(UserId);
             if  (subscriber == null )
             {
                 subscriber = new  AlarmSubscriber();
                 subscriber.User = u;
                 Subscribers.Add(subscriber);
                 logger.Info( "客户端"  + UserId + "注册" );
             }
             subscriber.Alarms = Alarms; //更新订阅
             subscriber.ClientCallback = callback;
             //绑定退出事件,在客户端退出时,注销客户端的订阅
             ICommunicationObject obj = (ICommunicationObject)callback;
             obj.Closed += new  EventHandler(GpsEventService_Closed);
             obj.Closing += new  EventHandler(GpsEventService_Closing);       
         }
 
         private  AlarmSubscriber GetSubscirber( int  UserId)
         {
             foreach (AlarmSubscriber sub in  Subscribers)
             {
                 if  (sub.User.Id == UserId)
                     return  sub;
             }
             return  null ;
         }
 
         private  User GetUser( int  UserId)
         {
             return  new  User(UserId);
         }
 
         void  GpsEventService_Closing( object  sender, EventArgs e)
         {
             logger.Info( "客户端关闭退出..." );
         }
 
         void  GpsEventService_Closed( object  sender, EventArgs e)
         {
             IGpsServiceCallback callback = (IGpsServiceCallback)sender;
             Subscribers.ForEach( delegate (AlarmSubscriber subscriber)
             {
                 if  (subscriber.ClientCallback == callback)
                 {
                     Subscribers.Remove(subscriber);
                     logger.Info( "用户"  + subscriber.User.Id + "Closed Client Removed!" );
                 }
 
             });
             
         }
         //客户端断开
         public  void  Unsubscribe( int  UserId)
         {
             IGpsServiceCallback callback = OperationContext.Current.GetCallbackChannel<IGpsServiceCallback>();
             
             Subscribers.ForEach( delegate (AlarmSubscriber subscriber)
             {
                 if  (subscriber.User.Id == UserId)
                 {
                     Subscribers.Remove(subscriber);
                     logger.Info( "用户"  + subscriber.User.Id + "注销 Client Removed!" );
                 }
 
             });
         }
 
         //向客户端推送报警数据
         public  static  void  SendAlarmMessage(List<AlarmItem> alarmItems)
         {
             //没有要推送的报警数据
             if  (alarmItems.Count == 0)
                 return ;
 
             Subscribers.ForEach( delegate (AlarmSubscriber subscriber)
             {
                 ICommunicationObject callback = (ICommunicationObject)subscriber.ClientCallback;
                 if  (((ICommunicationObject)callback).State == CommunicationState.Opened)
                 {
                     try
                     {
                         //此处需要加上权限判断、订阅判断等
                         subscriber.ClientCallback.OnMessageReceived(alarmItems);
                     }
                     catch  (Exception ex)
                     {
                         Subscribers.Remove(subscriber);
                         logger.Error( "用户"  + subscriber.User.Id + "出错:"  + ex.Message);
                         logger.Error(ex.StackTrace);
                     }
                 }
                 else
                 {
                     Subscribers.Remove(subscriber);
                     logger.Info( "用户"  + subscriber.User.Id + "Closed Client Removed!" );
                 }
             });
         }
        
         //通知用户服务已经停止
         public  static  void  NotifyServiceStop()
         {
             List<AlarmItem> msgItems = new  List<AlarmItem>();
             msgItems.Add( new  AlarmItem(0, "Stop" ));
             SendAlarmMessage(msgItems);
         }
     }
}

  4)客户端调用

?
public  partial  class  Form1 : Form, GpsAlarm.<span style= "color: #ff0000;" ><strong>IGpsEventServiceCallback</strong></span>
    {
        int  UserId = 1;
        public  Form1()
        {
            InitializeComponent();
        }
 
        GpsAlarm.GpsEventServiceClient client;
        private  void  Form1_Load( object  sender, EventArgs e)
        {
            try
            {
                client = new  GpsAlarm.GpsEventServiceClient( new  InstanceContext( this )); //注意Form要实现接口
                //注册并订阅报警类型是1,2,3
                client.Subscribe(UserId, new  int []{1,2,3});
                listBox1.Items.Add( "注册成功,等待消息推送" );
            }
            catch  (Exception ex)
            {
                listBox1.Items.Add(ex.ToString());
            }
        }
 
        #region IEventSystemCallback Members
        /**
         * 监听报警事件
         */
        public  void  OnMessageReceived(AlarmItem[] msgItems)
        {
            foreach  (AlarmItem mi in  msgItems)
            {
                listBox1.Items.Add(mi.Name);
            }
        }
        #endregion
    }

  

 
 
 

你可能感兴趣的:(回调,WCF,GPS报警推送,JT808,WCF推送,JT/T808,808)