wcf-- part three

http://www.cnblogs.com/wayfarer/archive/2006/04/17/377016.html

示例代码下载:DuplexSample.rar

四、Service Contract编程模型
在Part Two中,我以“Hello World”为例讲解了如何定义一个Service。其核心就是为接口或类施加ServiceContractAttribute,为方法施加OperationContractAttribute。在Service的方法中,可以接受多个参数,也可以有返回类型,只要这些数据类型能够被序列化。这样一种方式通常被称为本地对象,远程过程调用(local-object, Remoting-Procedure-Call)方式,它非常利于开发人员快速地进行Service的开发。

在Service Contract编程模型中,还有一种方式是基于Message Contract的。服务的方法最多只能有一个参数,以及一个返回值,且它们的数据类型是通过Message Contract自定义的消息类型。在自定义消息中,可以为消息定义详细的Header和Body,使得对消息的交换更加灵活,也更利于对消息的控制。

一个有趣的话题是当我们定义一个Service时,如果一个private方法被施加了OperationContractAttribute,那么对于客户端而言,这个方法是可以被调用的。这似乎与private对于对象封装的意义有矛盾。但是这样的规定是有其现实意义的,因为对于一个服务而言,服务端和客户端的需求往往会不一致。在服务端,该服务对象即使允许被远程调用,但本地调用却可能会因情况而异。如下面的服务定义:
[ServiceContract]
public class BookTicket
{
 [OperationContract]
 public bool Check(Ticket ticket)
 {
  bool flag;
  //logic to check whether the ticket is none;
  return flag;
 }
 [OperationContract]
 private bool Book(Ticket ticket)
 {
  //logic to book the ticket
 }
}
在服务类BookTicket中,方法Check和Book都是服务方法,但后者被定义成为private方法。为什么呢?因为对于客户而言,首先会检查是否还有电影票,然而再预定该电影票。也就是说这两项功能都是面向客户的服务,会被远程调用。对于Check方法,除了远程客户会调用该方法之外,还有可能被查询电影票、预定电影票、出售电影票等业务逻辑所调用。而Book方法,则只针对远程客户,只可能被远程调用。为了保证该方法的安全,将其设置为private,使得本地对象不至于调用它。

因此在WCF中,一个方法是否应该被设置为服务方法,以及应该设置为public还是private,都需要根据具体的业务逻辑来判断。如果涉及到私有的服务方法较多,一种好的方法是利用设计模式的Façade模式,将这些方法组合起来。而这些方法的真实逻辑,可能会散放到各自的本地对象中,对于这些本地对象,也可以给与一定的访问限制,如下面的代码所示:
internal class BusinessObjA
{
 internal void FooA(){}
}
internal class BusinessObjB
{
 internal void FooB(){}
}
internal class BusinessObjC
{
 internal void FooC(){}
}
[ServiceContract]
internal class Façade
{
 private BusinessObjA objA = new BusinessObjA();
 private BusinessObjB objB = new BusinessObjB();
 private BusinessObjC objC = new BusinessObjC();
 [OperationContract]
 private void SvcA()
 {
  objA.FooA();
 }
 [OperationContract]
 private void SvcB()
 {
  objB.FooB();
 }
 [OperationContract]
 private void SvcC()
 {
  objC.FooC();
 }
}
方法FooA,FooB,FooC作为internal方法,拒绝被程序集外的本地对象调用,但SvcA,SvcB和SvcC方法,却可以被远程对象所调用。我们甚至可以将BusinessObjA,BusinessObjB等类定义为Façade类的嵌套类。采用这样的方法,有利于这些特殊的服务方法,被远程客户更方便的调用。

定义一个Service,最常见的还是显式地将接口定义为Service。这样的方式使得服务的定义更加灵活,这一点,我已在Part Two中有过描述。当然,采用这种方式,就不存在前面所述的私有方法成为服务方法的形式了,因为在一个接口定义中,所有方法都是public的。

另外一个话题是有关“服务接口的继承”。一个被标记了[ServiceContract]的接口,在其继承链上,允许具有多个同样标记了[ServiceContract]的接口。对接口内定义的OperationContract方法,则是根据“聚合”的原则,如下的代码所示:
[ServiceContract]
public interface IOne
{
    [OperationContract(IsOneWay=true)]
    void A();
}
[ServiceContract]
public interface ITwo
{
    [OperationContract]
    void B();
}
[ServiceContract]
public interface IOneTwo : IOne, ITwo
{
    [OperationContract]
    void C();
}

在这个例子中,接口IOneTwo继承了接口IOne和ITwo。此时服务IOneTwo暴露的服务方法应该为方法A、B和C。

然而当我们采用Duplex消息交换模式(文章后面会详细介绍Duplex)时,对于服务接口的回调接口在接口继承上有一定的限制。WCF要求服务接口IB在继承另一个服务接口IA时,IB的回调接口IBCallBack必须同时继承IACallBack,否则会抛出InvalidContractException异常。正确的定义如下所示:
[ServiceContract(CallbackContract = IACallback)]
interface IA {}
interface IACallback {}

[ServiceContract(CallbackContract = IBCallback)]
interface IB : IA {}
interface IBCallback : IACallback {}

五、消息交换模式(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控件中,同时会弹出对话框,决定是否继续剩下的任务。

注:本文示例的代码和实例均在Feb 2006 CTP版本下运行。

< 未完待续>

参考:
1、David Chappell,Introducing Windows Communication Foundation
2、Microsoft Corporation,WinFX SDK

posted on 2006-04-17 12:25 Bruce Zhang 阅读(3807) 评论(9)  编辑  收藏 所属分类: WCF & SOA

 

@idior
我并非没有考虑过你说的情况,但如要在一个小小的例子中真实地去反映你所说的异步的情况,太难了。而我之所以采用了UI,就是希望这个小例子能给人一个比较容易理解的异步调用的效果。因为运行这个例子后,可以非常清晰地看到,当服务端在执行服务逻辑时,客户端的自身逻辑是不受干扰的,一旦服务执行结束,客户端就能收到该回调信息。所以,我的例子是为这样的一个目的所考虑。

之所以使用多线程,是因为如不使用它,那么当服务端返回回调信息后,客户端的Windows应用程序并不能马上执行该回调,即使线程处于Sleep中,只有将回调方法放到多线程中,才可以达到这样的效果。  

  

#2楼 [楼主] 2006-04-17 13:14 Bruce Zhang(wayfarer)

@idior
Message-Oriented可能会在后面介绍,关于异步的调用也会在后面的系列详细介绍。实际上WCF采用的异步,还是最常用的异步模式,和Web Service没有太大区别,利用Begin和End,利用IAsyncResult等。比较特殊的还是这个Duplex。

当然也希望早点看到你的文章。

#3楼  2006-04-17 13:28 idior

简单的例子很容易举啊。 只要在service的方法中使用Thread.Sleep(100000);就可以体现出duplex的应用背景了---Service要长时间运行,使得原来的线程监听方式不合适。而且举例子最好还是用console.

利用Begin和End,利用IAsyncResult 这种方式是假异步, 在很多的应用(尤其是与人有关的service)中Duplex才是正道。

另外我有两点疑惑:
1.
---
在Duplex模式下,不能使用Single方式,否则会导致异常抛出。此外,也不能使用PerCall方式,因为在PerCall方式下,服务对象在方法被调用时创建,结束后即被销毁,从而导致整个方法调用的结果会出现错误
---
很想知道为什么。且不论Single. 不支持PerCall,我觉得是很不正常的, ws的一个重要特性就是stateless. WCF支持stateful也就算了,怎么能取消stateless呢?如果一个服务有状态,那么怎么load-balance?

2.WSDualHttpBinding 我以为Duplex是基于tcp协议的,用http怎么做的?

晕死 我竟然把第一个评论改了。下次写在自己文章中算了。

#4楼 [楼主] 2006-04-17 13:35 Bruce Zhang(wayfarer)

@idior
OK,要这样设置当然可以,问题是就没人愿意去执行它了。明白意思就行。

在Duplex下,不是不能使用PerCall,而是采用这种方式会将服务对象的状态丢掉。这要视情况而定。看来我的描述有错误,需要改正。已修改为:“对于实现服务的类,要求其实例化模式(InstanceContextMode)是PerSession方式,也就是该服务对象的生命周期是存活于一个会话期间。在Duplex模式下,不能使用Single方式,否则会导致异常抛出。此外,如果需要保存服务对象的状态,那么就不应使用PerCall方式,因为在PerCall方式下,服务对象在方法被调用时创建,结束后即被销毁。”

第二个问题要问微软才知道了,呵呵。  

#5楼  2006-04-17 13:40 idior

服务对象的状态丢掉
那是因为它举的例子的特殊性。 其实这个例子很烂(无非想卖弄一下它支持状态),ws本来就应该是无状态的,ms还搞这么一个例子。即便是支持状态wcf的方式也不正规(没有用ws-resource),好像是用session实现的。

#6楼 [楼主] 2006-04-17 13:41 Bruce Zhang(wayfarer)

@idior
哈哈,你的第一个评论,我这里还有,我帮你再贴上吧,哈哈:

文章中的例子举的不太好。

duplex主要的特点就是异步---真正的异步。
我们知道在duplex之前也有ws的异步调用。但是那是假的,client始终维持了一个监听的线程在等待service端的回复。client与service的连接也并没有被中断。
而在支持了soap package在tcp层传输之后(http的请求响应模式无法支持真正的异步调用),我们才能真正实现ws的异步调用(之前的连接关闭,没有线程等待),这就是duplex提供给我们的功能,当然在wse中的SoapSender, SoapReceiver也可以实现,duplex应该是在此之上的封装。而要想让两个不同的连接发送的消息关联则离不开ws-addressing协议。

ws为什么需要异步调用,为什么原来的线程监听的callback方式不适合?
这更ws的业务背景有联系, 一个service可能与人打交道, 那么一个service的完成就不是几分钟,几秒那么快了,可能需要几个小时,甚至几天。在这种情况下,通过后台线程监听的callback方式的效率就很低,也很容易出问题。
比如我向service下了一个订单,可能过了几个小时才能得到回复,难道你要我保持几个小时的连接不中断?

而wayfarer的例子陷入了另一个技术(多线程)的泥潭,甚至和ui打起交道,容易让人迷失本质。

由于ws最重要的特点就是loosely coupled, 而异步也算是loosely coupled的重要体现之一(reduces the dependency between
components by allowing a component to not "wait" for another component. )。所以wayfarer的这篇文章把握到了ws的一个重要特性,不错!

如果我来写ws的应用的话,我觉得有两点是最重要的
1. message-oriented
2 Asynchrony
争取有空介绍一下。

@idior
在我的第一个例子中,就必须设置为PerSession。如果设置为PerCall,那么在服务端主动发送消息给客户端时,callback就有可能为null,而出现错误。

其实这个InstanceContextMode,有点像Remoting中的SingleCall和SingleTon。除此之外,还提供了PerSession和Share。有这个PerSession还是挺好的。至少现在,它既能支持有状态对象,又能支持无状态对象。

实际上我的描述还是不好,应该是服务对象应该设置为PerSession还是PerCall,应根据服务对象的状态保持来决定。   

@idior
最后修改如下:对于实现服务的类,实例化模式(InstanceContextMode)究竟是采用PerSession方式,还是PerCall方式,应根据该服务对象是否需要保存状态来决定。如果是PerSession,则服务对象的生命周期是存活于一个会话期间。而PerCall方式下,服务对象是在方法被调用时创建,结束后即被销毁。然而在Duplex模式下,不能使用Single方式,否则会导致异常抛出。

谢谢你的提示。 
 

#9楼  2006-04-17 15:09 idior

由于load-balance的原因,一般情况下ws是stateless的。一旦服务有了状态,就无法方便的使用对象池来减少对象的数量,从而提高负载。(这个道理和ejb中的session bean类似)

针对这个问题grid组织提出了ws-resource规范(引入服务资源的概念), 当然ws-coordinate(引入context的概念)也能实现某种程度的状态。而ms的有状态服务并不是依赖于这两个规范实现的,它是依赖于asp.net特有的session来实现的,不是标准的解决方法。

  #1楼  [楼主] 2006-04-17 13:12 Bruce Zhang(wayfarer)
CCF大数据与计算智能大赛-面向电信行业存量用户的智能套餐个性化匹配模型联通赛-复赛第二名-【多分类,embedding】.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值