回调操作在实际系统中应用的非常广泛,该机制使得服务端可以回调客户端操作,而不需要在客户端实现、寄宿新的服务契约。
回调操作必须使用支持双向通信的绑定如NetTcpBinding、NetNamedPipeBinding、WSDualHttpBinding。
双程操作实例
下面给出一个双程操作的示例,考虑这要的需求,系统的客户端需要每隔一段时间向服务端发送消息,以通知服务端客户端运行正常。一旦这要的消息终端,服务端将立刻采取相应的措施,如通知系统支持人员等。这样的需求在大型系统中非常常见,一般称呼这样的通知为“心跳”,表示该消息反应了客户端的健康状态。
1、定义服务契约
定义服务契约(Service Contract)包括:
回调契约:IheartbeatCallback.cs(心跳频率回调操作)。
服务契约:IheartbeatService.cs(心跳服务契约)。
回调契约属于服务契约的一部分,在WCF中使用ServiceContract的CallbackContract属性可以定义一个服务契约。
1.1 定义回调契约
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCF.Third.Contract
{
/// <summary>
/// 回调契约
/// </summary>
public interface IheartbeatCallback
{
/// <summary>
/// 回调操作,调整心跳频率
/// </summary>
/// <param name="seconds">新的频率</param>
[OperationContract(IsOneWay=true)]
void UpdateInterval(int seconds);
}
}
1.2 定义服务契约
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCF.Third.Contract
{
/// <summary>
/// 心跳服务契约
/// </summary>
[ServiceContract(CallbackContract=typeof(IheartbeatCallback))]
public interface IheartbeatService
{
[OperationContract]
void Heartbeat();
}
}
2、创建服务
创建HeartbeatService.cs类,实现IheartbeatService服务契约接口。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using WCF.Third.Contract;
namespace WCF.Third.Service
{
/// <summary>
/// 心跳服务
/// </summary>
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class HeartbeatService:IheartbeatService
{
private bool _highLoad = false; //是否处于高负荷状态
private bool _changed = false; //状态是否已经改变
//不同负荷下的心跳周期
private int _highloadInterval = 5; //周期是5
private int _lowloadInterval = 2; //周期是2
//设置负荷状态
public bool HighLoad
{
set
{
lock (this) //确定单线程访问状态
{
if (_highLoad != value)
{
//调整状态
_changed = true;
_highLoad = value;
Console.WriteLine("HighLoad已经被设置为:{0}", value);
}
}
}
}
/// <summary>
/// 实现心跳契约
/// </summary>
public void Heartbeat()
{
//如果符合已经更改,则回调客户端,更新心跳频率
if (_changed)
{
IheartbeatCallback callback = OperationContext.Current.GetCallbackChannel<IheartbeatCallback>();
callback.UpdateInterval(_highLoad ? _highloadInterval : _lowloadInterval);
}
Console.WriteLine("{0}收到心跳", DateTime.Now.ToString());
}
}
}
3、实现服务寄宿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using WCF.Third.Service;
using WCF.Third.Contract;
namespace WCF.Third.Host
{
class Program
{
private const string serviceAddressUrl = "net.pipe://localhost/HeartbeatService"; //服务地址
private const string serviceMetadataUrl = "net.pipe://localhost/HeartbeatService/metadata"; //元数据发布的源地址
static void Main(string[] args)
{
try
{
//为了能访问服务对象,这里手动创建服务对象并以该对象创建寄主对象
HeartbeatService instance = new HeartbeatService();
using (ServiceHost host = new ServiceHost(instance))
{
//添加一个终结点
Uri address = new Uri(serviceAddressUrl); //地址
Binding binding = new NetNamedPipeBinding(); //绑定
Type serviceType = typeof(IheartbeatService); //契约
host.AddServiceEndpoint(serviceType, binding, address);
host.Opened += delegate
{
Console.WriteLine("服务已经启动!");
};
host.Open();
while (true)
{
char command = Convert.ToChar(Console.Read());
//通过ServiceHost的SingletonInstance属性来获取寄宿实例
HeartbeatService service = (HeartbeatService)host.SingletonInstance;
switch (command)
{
case 'q': //输入q表示退出程序
return;
case 'h': //输入h表示切换到高负荷
{
if (service != null)
{
service.HighLoad = true;
}
break;
}
case 'l': //输入l表示切换到底负荷
{
if (service != null)
{
service.HighLoad = false;
}
break;
}
default:
break;
}
}
}
}
catch (Exception ex) //捕捉异常
{
Console.WriteLine(ex.ToString());
Console.Read();
}
}
}
}
4、创建客户端调试服务
双程操作的客户端需要公开回调终结点以供服务端进行回调。为了方便双程操作的客户端编程,WCF提供了DuplexClientBase<T>类型。客户端初始化DuplexClientBase<T>代理对象时,需要提供带有回调对象的实例上下文对象。代理会围绕回调上下文创建一个终结点,回调终结点会使用和服务端相同的绑定和传输方式。
4.1 创建客户端类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace WCF.Third.Client
{
//回调契约实现
public class Client:IheartbeatCallback
{
private TimeSpan _interval = TimeSpan.FromSeconds(2); //心跳频率
public TimeSpan Interval{
get{return _interval;}
}
public void UpdateInterval(int seconds)
{
lock(this)
{
_interval = TimeSpan.FromSeconds(seconds);
}
}
}
/// <summary>
/// 回调契约
/// </summary>
public interface IheartbeatCallback
{
/// <summary>
/// 回调操作,调整心跳频率
/// </summary>
/// <param name="seconds">新的频率</param>
[OperationContract(IsOneWay = true)]
void UpdateInterval(int seconds);
}
/// <summary>
/// 心跳服务契约
/// </summary>
[ServiceContract(CallbackContract = typeof(IheartbeatCallback))]
public interface IheartbeatService
{
[OperationContract]
void Heartbeat();
}
//客户端代理的实现
public class HeartbeatProxy : DuplexClientBase<IheartbeatService>, IheartbeatService
{
public HeartbeatProxy(InstanceContext context, Binding binding, EndpointAddress remoteAddress) : base(context, binding, remoteAddress) { }
public void Heartbeat()
{
Channel.Heartbeat();
}
}
}
4.2 运行客户端类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading;
namespace WCF.Third.Client
{
class Program
{
private const string serviceAddressUrl = "net.pipe://localhost/HeartbeatService";
static void Main(string[] args)
{
try
{
Client client = new Client();
using(HeartbeatProxy proxy = new HeartbeatProxy(new InstanceContext(client),new NetNamedPipeBinding(),new EndpointAddress(serviceAddressUrl)))
{
Console.WriteLine("客户端运行");
while (true)
{
proxy.Heartbeat(); //发送心跳
Thread.Sleep(client.Interval); //等待一个周期时间
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally {
Console.Read();
}
}
}
}