双工服务

双工服务协定是一种消息交换模式,其中双方终结点都可独立向对方发送消息。 因此,双工服务可以将消息发送回客户端终结点,从而提供类似事件的行为。 当客户端连接到服务并为服务提供可用来将消息发送回客户端的通道时,就会发生双工通信。 请注意,双工服务的类似事件的行为仅在会话中起作用。

若要创建双工协定,需要创建一对接口。 第一个接口是描述客户端可调用的操作的服务协定接口。 该服务协定必须在 ServiceContractAttribute.CallbackContract 属性中指定一个回调协定。 回调协定是定义服务可在客户端终结点上调用的操作的接口。 虽然系统提供的双工绑定利用了会话,但是双工协定不需要会话。

下面是双工协定的示例。

[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required,
                 CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
{
    [OperationContract(IsOneWay = true)]
    void Clear();
    [OperationContract(IsOneWay = true)]
    void AddTo(double n);
    [OperationContract(IsOneWay = true)]
    void SubtractFrom(double n);
    [OperationContract(IsOneWay = true)]
    void MultiplyBy(double n);
    [OperationContract(IsOneWay = true)]
    void DivideBy(double n);
}

public interface ICalculatorDuplexCallback
{
    [OperationContract(IsOneWay = true)]
    void Equals(double result);
    [OperationContract(IsOneWay = true)]
    void Equation(string eqn);
}
<ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples", SessionMode:=SessionMode.Required, CallbackContract:=GetType(ICalculatorDuplexCallback))> _
Public Interface ICalculatorDuplex
    <OperationContract(IsOneWay:=True)> _
    Sub Clear()
    <OperationContract(IsOneWay:=True)> _
    Sub AddTo(ByVal n As Double)
    <OperationContract(IsOneWay:=True)> _
    Sub SubtractFrom(ByVal n As Double)
    <OperationContract(IsOneWay:=True)> _
    Sub MultiplyBy(ByVal n As Double)
    <OperationContract(IsOneWay:=True)> _
    Sub DivideBy(ByVal n As Double)
End Interface


Public Interface ICalculatorDuplexCallback
    <OperationContract(IsOneWay:=True)> _
    Sub Equals(ByVal result As Double)
    <OperationContract(IsOneWay:=True)> _
    Sub Equation(ByVal eqn As String)
End Interface

CalculatorService 类实现 ICalculatorDuplex 主接口。 服务使用 PerSession 实例模式来维护每个会话的结果。 名为 Callback 的私有属性访问指向客户端的回调通道。 服务使用该回调通过回调接口将消息返送给客户端,如下面的示例代码所示。


[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorDuplex
{
    double result = 0.0D;
    string equation;

    public CalculatorService()
    {
        equation = result.ToString();
    }

    public void Clear()
    {
        Callback.Equation(equation + " = " + result.ToString());
        equation = result.ToString();
    }

    public void AddTo(double n)
    {
        result += n;
        equation += " + " + n.ToString();
        Callback.Equals(result);
    }

    public void SubtractFrom(double n)
    {
        result -= n;
        equation += " - " + n.ToString();
        Callback.Equals(result);
    }

    public void MultiplyBy(double n)
    {
        result *= n;
        equation += " * " + n.ToString();
        Callback.Equals(result);
    }

    public void DivideBy(double n)
    {
        result /= n;
        equation += " / " + n.ToString();
        Callback.Equals(result);
    }

    ICalculatorDuplexCallback Callback
    {
        get
        {
            return OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
        }
    }
}

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)> _
Public Class CalculatorService
    Implements ICalculatorDuplex
    Private result As Double = 0.0R
    Private equation As String

    Public Sub New()
        equation = result.ToString()
    End Sub

    Public Sub Clear() Implements ICalculatorDuplex.Clear
        Callback.Equation(equation & " = " & result.ToString())
        equation = result.ToString()
    End Sub

    Public Sub AddTo(ByVal n As Double) Implements ICalculatorDuplex.AddTo
        result += n
        equation &= " + " & n.ToString()
        CType(Callback, Object).Equals(result)
    End Sub

    Public Sub SubtractFrom(ByVal n As Double) Implements ICalculatorDuplex.SubtractFrom
        result -= n
        equation &= " - " & n.ToString()
        CType(Callback, Object).Equals(result)
    End Sub

    Public Sub MultiplyBy(ByVal n As Double) Implements ICalculatorDuplex.MultiplyBy
        result *= n
        equation &= " * " & n.ToString()
        CType(Callback, Object).Equals(result)
    End Sub

    Public Sub DivideBy(ByVal n As Double) Implements ICalculatorDuplex.DivideBy
        result /= n
        equation &= " / " & n.ToString()
        CType(Callback, Object).Equals(result)
    End Sub

    Private ReadOnly Property Callback() As ICalculatorDuplexCallback
        Get
            Return OperationContext.Current.GetCallbackChannel(Of ICalculatorDuplexCallback)()
        End Get
    End Property
End Class

客户端必须提供一个实现双工协定回调接口的类,以便从服务接收消息。 下面的示例代码演示了一个实现 CallbackHandler 接口的 ICalculatorDuplexCallback 类。

public class CallbackHandler : ICalculatorDuplexCallback
{
    public void Equals(double result)
    {
        Console.WriteLine("Equals({0})", result);
    }

    public void Equation(string eqn)
    {
        Console.WriteLine("Equation({0})", eqn);
    }
}
Public Class CallbackHandler
    Implements ICalculatorDuplexCallback
    Public Overridable Shadows Sub Equals(ByVal result As Double) Implements ICalculatorDuplexCallback.Equals
        Console.WriteLine("Equals({0})", result)
    End Sub

    Public Sub Equation(ByVal eqn As String) Implements ICalculatorDuplexCallback.Equation
        Console.WriteLine("Equation({0})", eqn)
    End Sub
End Class

针对双工协定生成的 WCF 客户端需要在构造时提供一个 InstanceContext 类。 此 InstanceContext 类用作实现回调接口并处理从服务发送回的消息的对象所在的位置。 InstanceContext 类是用 CallbackHandler 类的实例构造的。 此对象处理通过回调接口从服务发送到客户端的消息。

// Construct InstanceContext to handle messages on callback interface
InstanceContext instanceContext = new InstanceContext(new CallbackHandler());

// Create a client
CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);
' Construct InstanceContext to handle messages on callback interface
Dim instanceContext As New InstanceContext(New CallbackHandler())

' Create a client
Dim client As New CalculatorDuplexClient(instanceContext)

服务的配置必须设置为提供同时支持会话通信和双工通信的绑定。 wsDualHttpBinding 元素支持会话通信,并通过提供双 HTTP 连接(一个连接对应于一个方向)来允许进行双工通信。

在客户端,必须配置一个服务器可用来连接客户端的地址,如下面的示例配置所示。

注意

未能使用安全会话通过身份验证的非双工客户端通常会引发 MessageSecurityException。 但是,如果使用安全会话的双工客户端未能通过身份验证,客户端则收到 TimeoutException

如果使用 WSHttpBinding 元素创建客户端/服务,并且不包含客户端回调终结点,您将收到以下错误。

HTTP could not register URL
htp://+:80/Temporary_Listen_Addresses/<guid> because TCP port 80 is being used by another application.

下面的示例代码演示了如何以编程方式指定客户端终结点地址。

WSDualHttpBinding binding = new WSDualHttpBinding();
EndpointAddress endptadr = new EndpointAddress("http://localhost:12000/DuplexTestUsingCode/Server");
binding.ClientBaseAddress = new Uri("http://localhost:8000/DuplexTestUsingCode/Client/");
Dim binding As New WSDualHttpBinding()
Dim endptadr As New EndpointAddress("http://localhost:12000/DuplexTestUsingCode/Server")
binding.ClientBaseAddress = New Uri("http://localhost:8000/DuplexTestUsingCode/Client/")

下面的示例代码演示了如何在配置中指定客户端终结点地址。

<client>
    <endpoint name ="ServerEndpoint"
          address="http://localhost:12000/DuplexTestUsingConfig/Server"
          bindingConfiguration="WSDualHttpBinding_IDuplexTest"
            binding="wsDualHttpBinding"
           contract="IDuplexTest" />
</client>
<bindings>
    <wsDualHttpBinding>
        <binding name="WSDualHttpBinding_IDuplexTest"
          clientBaseAddress="http://localhost:8000/myClient/" >
            <security mode="None"/>
         </binding>
    </wsDualHttpBinding>
</bindings>

警告

双工模型不会自动检测服务或客户端何时关闭其通道。 因此,如果客户端意外终止,默认情况下将不会通知服务,或者如果服务意外终止,将不会通知客户端。 如果使用的是已断开连接的服务,则会引发 CommunicationException 异常。 客户端和服务可以实现自己的协议以相互通知对方(如果它们这么选择)。 有关错误处理的详细信息,请参阅 WCF 错误处理

请参阅