双向通讯是这样的一种机制,就是服务端与客户端的身份是可换的,服务示例可以回调客户端的操作,当一个服务契约被定义在服务端的时候,有时候我们的业务逻辑要求我门进行相应的回调操作。标准的服务契约定义了能够被客户端调用的服务操作。回调契约定义了能够被服务端调用的客户端操作。因此,客户端必须具有实现回调契约的义务和宿主回调对象的能力。每当客户端调用具有回调操作的服务端示例操作的时候,客户端必须提供足够的信息以使服务端能够寻址到客户端并执行相应的回调操作。[引用]
双向通讯是这样的一种机制,就是服务端与客户端的身份是可换的,服务示例可以回调客户端的操作,当一个服务契约被定义在服务端的时候,有时候我们的业务逻辑要求我门进行相应的回调操作。标准的服务契约定义了能够被客户端调用的服务操作。回调契约定义了能够被服务端调用的客户端操作。因此,客户端必须具有实现回调契约的义务和宿主回调对象的能力。每当客户端调用具有回调操作的服务端示例操作的时候,客户端必须提供足够的信息以使服务端能够寻址到客户端并执行相应的回调操作。[引用]
接下来我们就来做这样的一个场景,这个场景也是从网上看的。WCF服务端提供一个加法的服务,客户端调用服务端的服务之后,服务端回调客户端的显示方法最后将结果显示在客户端界面。下面是运行的效果,源码在此下载
在WCF的默认的绑定中WSDualHttpBinding是一个安全且可互操作的绑定。适用于双工服务协定或通过 SOAP 媒介进行的通信。本示例就是基于此绑定来实现的。为在编程中多应用些WCF编程的技术,服务端采用代码来创建服务端承载。
第一步 创建标准契约和回调契约
回调契约,用于在客户端显示结果信息
[ServiceContract(Namespace="http://www,cbcye.com/wcf/Callback/)]
public interface ICalculatorCallback
{
[OperationContract]
void ShowResult(double x, double y, double result);
}
标准契约,声明了回调契约的类型
[ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
public interface IDuplexCalculator
{
[OperationContract]
void Add(double x, double y);
}
第二步 在服务端实现标准契约
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]
public class DuplexCalculatorService:IDuplexCalculator
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
IDuplexCalculator Members#region IDuplexCalculator Members
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
public void Add(double x, double y)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
double result = x + y;
//调用 GetCallbackChannel<(Of <(T>)>) 属性以获取通道,使用该通道可以调用调用服务的客户端实例的操作。
ICalculatorCallback callBack = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
callBack.ShowResult(x, y, result);
}
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
#endregion
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
第三步 创建服务端承载
class Program
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
static void Main(string[] args)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
HostCalculatorService();
}
static void HostCalculatorService()
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Uri dualUri = new Uri("http://localhost:7788/cbcye/Calculator%22);
//向HOST中添加BaseAddress
using (ServiceHost calculatorServiceHost = new ServiceHost(typeof(DuplexCalculatorService), dualUri))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
//WSDualHttpBinding适用于双工服务协定或通过 SOAP 媒介进行的通信。
WSDualHttpBinding wsDualHttpBinding = new WSDualHttpBinding();
//添加服务绑定和服务契约
calculatorServiceHost.AddServiceEndpoint(typeof(IDuplexCalculator), wsDualHttpBinding, string.Empty);
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
//绑定服务行为
ServiceMetadataBehavior behavior = calculatorServiceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (behavior == null)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
behavior = new ServiceMetadataBehavior();
behavior.HttpGetEnabled = true;
calculatorServiceHost.Description.Behaviors.Add(behavior);
}
else
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
behavior.HttpGetEnabled = true;
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
//启动事件
calculatorServiceHost.Opened += delegate
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine("Calculator Service has begun to listen
on "+ dualUri.ToString());
};
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
calculatorServiceHost.Open();
Console.Read();
}
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
第四步 在客户端实现回调契约
class CalculatorCallbackHandler:ICalculatorCallback
{
#region ICalculatorCallback Members
public void ShowResult(double x, double y, double result)
{
Console.WriteLine("x + y = {2} (x = {0} , y = {1})", x, y, result);
}
#endregion
}
第五步 实现客户端代理
class ClientProxy:ClientBase<IDuplexCalculator>,IDuplexCalculator
{
public ClientProxy(InstanceContext callbackInstance)
: base(callbackInstance)
{ }
#region IDuplexCalculator Members
public void Add(double x, double y)
{
this.Channel.Add(x, y);
}
#endregion
}
第六步 客户端调用
程序代码
class Program
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
static void Main(string[] args)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
try
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
InvocateDuplexCalculator();
}
catch (Exception exp)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine("[Error] " + exp.Message);
}
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Console.Read();
}
static void InvocateDuplexCalculator()
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
CalculatorCallbackHandler callbackHandler = new CalculatorCallbackHandler();
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
using (ClientProxy calculator = new ClientProxy(new InstanceContext(callbackHandler)))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine("Begin to invocate duplex calculator
");
calculator.Add(85, 86);
calculator.Close();
}
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
配置文件
由于使用WSDualHttpBinding绑定执行回调时,需要开通两个HTTP通道,一个用于服务,一个用于回调。因此需要配置两个HTTP地址。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<!--bindingConfiguration - 指定一个binding的配置名称,跟<bindings>下面同类<binding>的name匹配-->
<endpoint name="SubscriptionService"
address="http://localhost:7788/cbcye/Calculator%22
binding="wsDualHttpBinding"
bindingConfiguration="wsDualHttpBinding_DuplexCalculator"
contract="Cbcye.Contract.IDuplexCalculator"
/>
</client>
<bindings>
<!-- 指定一个或多个系统预定义的binding,比如<basicHttpBinding>,当然也可以指定自定义的customBinding,
然后在某个指定的binding下建立一个或多个配置,以便被Endpoint来使用这些配置 -->
<wsDualHttpBinding>
<binding
name="wsDualHttpBinding_DuplexCalculator"
clientBaseAddress="http://localhost:7799/cbcye/Calculator%22
/>
</wsDualHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)