在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。

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

传统的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, 其会自动生成一个相应令牌:

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

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

    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 服务总线的管理台里面,也可以看到起处理活动的状态,如下:

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

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,我们都可以得到正确的响应, 如下所示:

参考文档

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
   }

  

 
 
 
原文地址:https://www.cnblogs.com/Leo_wl/p/3095028.html