WCF之Message Exchange Patterns

本文介绍了WCF中三种消息交换模式:Request/Reply、One-Way和Duplex。重点阐述了Duplex模式的实现原理及其应用场景,包括双向通信及异步回调功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

消息交换模式(Message Exchange Patterns,MEPS)

在WCF中,服务端与客户端之间消息的交换共有三种模式:Request/Reply,One-Way,Duplex。
1、 Request/Reply
默认的消息交换模式 ,客户端调用服务方法发出请求(Request),服务端收到请求后,进行相应的操作,然后返回一个结果值(Reply)。
如果没有其它特别的设置,一个方法如果标记了OperationContract,则该方法的消息交换模式就是采用的Request/Reply方式,即使它的返回值是void。当然,我们也可以将IsOneWay设置为false,这也是默认的设置。如下的代码所示:
[ServiceContract]
public interface ICalculator
{
 [OperationContract]
 int Add(int a, int b);
 [OperationContract]
 int Subtract(int a, int b);
}
2、One-Way
如果消息交换模式为One-Way,则表明客户端与服务端之间只有请求,没有响应。即使响应信息被发出,该响应信息也会被忽略。 这种方式类似于消息的通知或者广播。当一个服务方法被设置为One-Way时,如果该方法有返回值,会抛出 InvalidOperationException异常。
要将服务方法设置为One-Way非常简单,只需要将OperationContractAttribute的属性IsOneWay设置为true就可以了,如下的代码所示:
public class Radio
{
 [OperationContract(IsOneWay=true)]
 private void BroadCast();
}
3、Duplex
Duplex消息交换模式具有客户端与服务端双向通信的功能 ,同时它的实现还可以使消息交换具有异步回调的作用。要实现消息交换的Duplex,相对比较复杂。它需要定义两个接口, 其中服务接口用于客户端向服务端发送消息,而回调接口则是从服务端返回消息给客户端, 它是通过回调的方式来完成的。
接口定义如下:
服务接口:
[ServiceContract(Namespace = "http://microsoft.servicemodel.samples/ ",
Session = true, 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 equation);
}
注意在接口定义中,每个服务方法的消息转换模式均设置为One- Way 。此外,回调接口是被本地调用,因此不需要定义[ServiceContract]。 在服务接口中,需要设置ServiceContractAttribute的CallbackContract属性,使其指向回调接口的类型type。
对于实现服务的类,实例化模式(InstanceContextMode)究竟是采用PerSession方式,还是PerCall方式,应根据该服务对象是否需要保存状态来决定。如果是PerSession,则服务对象的生命周期是存活于一个会话期间。而PerCall方式下,服务对象是在方法被调用时创建,结束后即被销毁。然而在Duplex模式下,不能使用Single方式,否则会导致异常抛出。本例的实现如下:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorDuplex
{
{
  double result;
  string equation;
  ICalculatorDuplexCallback callback = null;
  public CalculatorService()
  {
     result = 0.0D;
     equation = result.ToString();
     callback = OperationContext.Current.
     GetCallbackChannel();
  }
  public void AddTo(double n)
  {
     result += n;
     equation += " + " + n.ToString();
     callback.Equals(result);
   }
  // Other code not shown.
}
在类CalculatorService中,回调接口对象callback通过OperationContext.Current.GetCallbackChannel<> ()获取。然后在服务方法例如AddTo()中,通过调用该回调对象的方法,完成服务端向客户端返回消息的功能。在使用Duplex 时,Contract使用的Binding应该是系统提供的WSDualHttpBinding ,如果使用BasicHttpBinding,会出现错误。因此Host程序应该如下所示:
   public static void Main(string[] args)
   {
      Uri uri = new Uri("http://localhost:8080/servicemodelsamples ");
     using (ServiceHost host = new ServiceHost(typeof(CalculatorService), uri))
    {
       host.AddServiceEndpoint(typeof(ICalculatorDuplex),new
WSDualHttpBinding(),"service.svc");
       host.Open();
       Console.WriteLine("Press any key to quit service.");
       Console.ReadKey();
    }
 }
如果是使用配置文件,也应作相应的修改,如本例:
<system.serviceModel>
 <client>
   <endpoint name=""
         address="http://localhost:8080/servicemodelsamples/service.svc "
         binding="wsDualHttpBinding"
         bindingConfiguration="DuplexBinding"
         contract="ICalculatorDuplex" />
 </client>
 <bindings>
  <!-- configure a binding that support duplex communication -->
  <wsDualHttpBinding>
    <binding name="DuplexBinding"
          clientBaseAddress="http://localhost:8000/myClient/ ">
    </binding>
  </wsDualHttpBinding>
 </bindings>
</system.serviceModel>
当服务端将信息回送到客户端后,对消息的处理是由回调对象来处理的,所以回调对象的实现应该是在客户端完成,如下所示的代码应该是在客户端中:
public class CallbackHandler : ICalculatorDuplexCallback
{
   public void Equals(double result)
   {
      Console.WriteLine("Equals({0})", result);
   }
   public void Equation(string equation)
   {
      Console.WriteLine("Equation({0})", equation);
   }
}
客户端调用服务对象相应的为:
class Client
{
   static void Main()
   {
      // Construct InstanceContext to handle messages on
// callback interface.
InstanceContext site = new InstanceContext(new CallbackHandler());
// Create a proxy with given client endpoint configuration.
using (CalculatorDuplexProxy proxy =
new CalculatorDuplexProxy(site, "default"))
{
   double value = 100.00D;
   proxy.AddTo(value);
   value = 50.00D;
   proxy.SubtractFrom(value);
   // Other code not shown.
    // Wait for callback messages to complete before
  // closing.
  System.Threading.Thread.Sleep(500);
  // Close the proxy.
  proxy.Close();
}
  }
}
注意在Duplex中,会话创建的时机并不是客户端创建Proxy实例的时候, 而是当服务对象的方法被第一次调用时,会话方才建立,此时服务对象会在方法调用之前被实例化,直至会话结束,服务对象都是存在的。
以上的代码例子 在WinFX的SDK Sample中可以找到。不过该例子并不能直接反映出Duplex功能。通过前面的介绍,我们知道Duplex具有客户端与服务端双向通信的功能,同时它 的实现还可以使消息交换具有异步回调的作用。因此,我分别实现了两个实例来展现Duplex在两方面的作用。
(1)客户端与服务端双向通信功能棗 ChatDuplexWin
实例说明:一个类似于聊天室的小程序。利用Duplex支持客户端与服务端通信的特点,实现了
客户端与服务端 聊天的功能。
服务接口和回调接口的定义如下:
   [ServiceContract(Namespace =
"http://www.brucezhang.com/WCF/Samples/ChatDuplex ", Session = true,
CallbackContract=typeof(IChatDuplexCallback))]
   public interface IChatDuplex
   {
      [OperationContract(IsOneWay=true)]
      void Request(string cltMsg);
          [OperationContract(IsOneWay = true)]
      void Start();
    }
    public interface IChatDuplexCallback
    {
      [OperationContract(IsOneWay=true)]
      void Reply(string srvMsg);
    }
很明显,Request方法的功能为客户端向服务端发送消息,Reply方法则使服务端回送消息给客户端。服务接口IChatDuplex中 的Start()方法,用于显示的建立一个会话,因为在这个方法中,我需要直接获取callback对象,使得服务端不必等待客户端先发送消息,而是可以 利用callback对象主动先向客户端发送消息,从而实现聊天功能。
实现类的代码如下:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class ChatDuplex:IChatDuplex
{
  public ChatDuplex()
  {
         m_callback = OperationContext.Current.GetCallbackChannel();
      }
      private IChatDuplexCallback m_callback = null;
      public void Request(string cltMsg)
      {
         ChatRoomUtil.MainForm.FillListBox(string.Format("Client:{0}",
cltMsg));
      }
      public void Start()
      {
         ChatRoomUtil.MainForm.SetIIMDuplexCallback(m_callback);
      }
   }
因为我要求在服务端界面中,能够将客户端发送来的消息显示在主窗体界面中。所以利用了全局
变量MainForm,用来保存主窗体对 象:
  public static class ChatRoomUtil
  {
      public static ServerForm MainForm = new ServerForm();
  }
而在服务端程序运行时,Application 运行的主窗口也为该全局变量:
Application.Run(ChatRoomUtil.MainForm);要实现聊天功能,最大的障碍是当 服务端收到客户端消息时,不能立即Reply消息,而应等待服务端用户输入回送的消息内容,方可以Reply。也即是说,当客户端调用服务对象的 Request方法时,不能直接调用callback对象。因此我利用Start()方法,将服务对象中获得的callback对象传递到主窗体对象中。 这样,callback对象就可以留待服务端发送消息时调用了:
public partial class ServerForm : Form
{
  private IChatDuplexCallback m_callback;
  private void btnSend_Click(object sender, EventArgs e)
   {
      if (txtMessage.Text != string.Empty)
      {
          lbMessage.Items.Add(string.Format("Server:{0}", txtMessage.Text));
          if (m_callback != null)
          {
              m_callback.Reply(txtMessage.Text);
          }
          txtMessage.Text = string.Empty;
      }
   }
   public void FillListBox(string message)
   {
      lbMessage.Items.Add(message);
   }
    public void SetIIMDuplexCallback(IChatDuplexCallback callback)
   {
      m_callback = callback;
   }
 //Other code not shown;
}
对于客户端 的实现,相对简单,需要注意的是回调接口的实现:
public class ChatDuplexCallbackHandler:IChatDuplexCallback
{
   public ChatDuplexCallbackHandler(ListBox listBox)
   {
      m_listBox = listBox;
   }
   private ListBox m_listBox;
      public void Reply(string srvMsg)
      {
         m_listBox.Items.Add(string.Format("Server:{0}", srvMsg));
      }
   }
由于我自定义了该对象的构造函数,所以在实利化proxy时会有稍微区别:
InstanceContext site = new InstanceContext(new
ChatDuplexCallbackHandler(this.lbMessage));
proxy = new ChatDuplexProxy(site);
proxy.Start();
通过proxy对象的Start()方法,使 得我们在建立proxy对象开始,就创建了会话,从而使得服务对象被实例化,从而得以运行下面的这行代码:
m_callback = OperationContext.Current.GetCallbackChannel();
也就是说,在proxy对象建立之后,服务端就 已经获得callback对象了,这样就可以保证服务
端能够先向客户端发送消息而不会因为callback为null,导致错误的发生。
(2) 消息交换的异步回调功能棗AsyncDuplexWin
实例说明:本实例比较简单,只是为了验证当回调对象被调用时,客户端是否可以被异步运行。 调用服务对象时,服务端会进行一个累加运算。在运算未完成之前,客户端会执行显示累加数字的任务,当服务端运算结束后,只要客户端程序的线程处于 Sleep状态,该回调对象就会被调用,然后根据用户选择是否再继续运行剩下的任务。本例中服务端为控制台应用程序,客户端则为Windows应用程序。
例 子中的接口定义非常简单,不再赘述,而实现类的代码如下:
public class SumDuplex:ISumDuplex
{
  public SumDuplex()
  {
      callback = OperationContext.Current.GetCallbackChannel();
  }
  private ISumDuplexCallback callback = null;
  #region ISumDuplex Members
  public void Sum(int seed)
  {
     int result = 0;
     for (int i = 1; i < = seed; i++)
     {
        Thread.Sleep(10);
        Console.WriteLine("now at {0}",i);
        result += i;
     }
     callback.Equals(result);
  }
  #endregion
}
很显然,当客户端调用该服务对象 时,会在服务端的控制台上打印出迭代值。
由于客户端需要在callback调用时,停止对当前任务的运行,所以需要用到多线程机制:
  public delegate void DoWorkDelegate();
  public partial class ClientForm : Form
  {
    public ClientForm()
    {
        InitializeComponent();
        InstanceContext site = new InstanceContext(new
SumDuplexCallbackHandler(this.lbMessage));
        proxy = new SumDuplexProxy(site);
    }
    private SumDuplexProxy proxy;
      private Thread thread = null;
      private DoWorkDelegate del = null;
      private int counter = 0;
      private void btnStart_Click(object sender, EventArgs e)
      {
         proxy.Sum(100);
         thread = new Thread(new ThreadStart(delegate()
         {
            while (true)
            {
               if (ClientUtil.IsCompleted)
               {
                                 {
                  if (MessageBox.Show("Game over,Exit?", "Notify",
MessageBoxButtons.YesNo,
                      MessageBoxIcon.Question) == DialogResult.Yes)
                  {
                      break;
                  }
              }
    if (counter > 10000)
              {
                  break;
              }
              if (del != null)
               {
    del();
  }
  Thread.Sleep(50);
}
   }
   ));
   del += new DoWorkDelegate(DoWork);
   thread.Start();
}
private void DoWork()
{
   if (lbMessage.InvokeRequired)
   {
       this.Invoke(new DoWorkDelegate(DoWork));
   }
   else
   {
       lbMessage.Items.Add(counter);
       lbMessage.Refresh();
       counter++;
   }
}
  private void ClientForm_FormClosing(object sender, FormClosingEventArgs e)
  {
     if (thread != null)
     {
         thread.Abort();
     }
  }
}
因为需要在多线程中对 ListBox控件的items进行修改,由于该控件不是线程安全的,所以应使用该控件的InvokeRequired属性。此外,在线程启动时的匿名方 法中,利用while(true)控制当前线程的运行,并利用全局变量ClientUtil.IsCompleted判断回调对象是否被调用,如果被调用 了,则会弹出对话框,选择是否退出当前任务。这里所谓的当前任务实际上就是调用DoWork方法,向ListBox控件的items中不断添加累加的 counter值。注意客户端的回调对
象实现如下:
  class SumDuplexCallbackHandler:ISumDuplexCallback
  {
     public SumDuplexCallbackHandler(ListBox listBox)
     {
        m_listBox = listBox;
     }
     private ListBox m_listBox;
     #region ISumDuplexCallback Members
     public void Equals(int result)
     {
      {
 ClientUtil.IsCompleted = true;
      m_listBox.Items.Add(string.Format("The result is:{0}", result));
      m_listBox.Refresh();
   }
   #endregion
}
当客户端点击Start按钮,调用服 务对象的Sum方法后,在服务端会显示迭代值,而客户端也开始执行自己的任务,向ListBox控件中添加累加数。一旦服务端运算完毕,就将运算结果通过 回调对象传送到客户端,全局变量ClientUtil.IsCompleted将被设置为true。如果添加累加值的线程处于sleep状态,系统就会将 结果值添加到ListBox控件中,同时会弹出对话框,决定是否继续剩下的任务。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值