用WSE 2.0在XML Web Services里面实现Callback

 

XML Web Services原先的一个问题是不能实现真正的Callback。比如用ASP.NET实现的时候,每一个[WebMethod]都是一个远程方法调用,但只支持方法效用而不支持事件(Event),不能像本地调用可以传一个Delegate来实现Callback(Callback、函数指针、Listener模式、中断等其实都是一回事,都是一种事件响应)。

Web Services里面不能支持事件是很不方便的,很多应用就受限制,或者因此就放弃了Web Services技术。当然,也有一些Workaround,比如可以轮询——Outlook Web Access就是轮询的,所以能做到有email来就在屏幕右下角出一个小窗口,效果和MSN Messenger一样,这很酷;或者也可以在客户端起一个Remoting的服务器,把Remote Object的URI传给Web Services,等事件来了以后服务器再去Call客户端,这当然也是也可以。

不过这些Workaround要么有些缺点(性能问题),要么不够直接(Remoting太“重”了)。这就好像你可以在JavaScript里面实现全部的OO,但这会非常非常繁琐。这又好像通过Attribute可以在.NET里面实现AOP,但总感觉不直接。

WSE 2.0 (Web Services Enhancements 2.0)提供了一些TCP Messaging的功能,很好地解决了这个问题(在Web Services架构里面实现原生的Callback)。我写了一个简单的例子来演示Web Services如何支持事件回调:

1. 服务器端的主要代码(片断):

using Microsoft.Web.Services;

using Microsoft.Web.Services.Messaging;

using Microsoft.Web.Services.Addressing;

 

private ArrayList Listeners

{

     get

     {

         return (ArrayList)Application["Listeners"];

     }

}

 

[WebMethod]

public void AddListener(string listener)

{

     this.Listeners.Add(listener);   

}

 

[WebMethod]

public void RemoveListener(string listener)

{

     for(int i=0;i<this.Listeners.Count;i++)

     {

         if(((string)this.Listeners[i]).Equals(listener))

         {

              this.Listeners.RemoveAt( i );

              return;

         }

     }

}

 

[WebMethod]

public void FireEvent()

{

     for(int i=0;i<this.Listeners.Count;i++)

     {

         SoapEnvelope envelope = new SoapEnvelope();

         envelope.SetBodyObject("blah blah");

         envelope.Context.Action = new Action((string)(this.Listeners[i]));

         envelope.Context.ReplyTo = new ReplyTo(new System.Uri((string)(this.Listeners[i])));

         SoapSender peerProxy = new SoapSender(new System.Uri((string)(this.Listeners[i])));

         try

         {

              peerProxy.Send(envelope);

         }

         catch(Exception e)

         {

              this.Listeners.RemoveAt( i );

         }

     }

}

 

[WebMethod]

public string GetListeners()

{

     string listeners="";

     foreach(string item in this.Listeners)

     {

         listeners+=item+";";

     }

     return listeners;

}

一些说明:
a) Application["Listeners"]是在Application_Start里面创建的;
b) Microsoft.Web.Services.*就是WSE的Namespace,需要到微软网站下载安装了才会有;
c) AddListener、RemoveListener、FireEvent都是很好懂的代码,典型的Subscriber模式,其中主要就是靠SoapSender.Send()来最终完成回调,call客户端的事件处理代码。

2. 客户端的主要代码(片断):

public class Form1 : System.Windows.Forms.Form

{

     private void buttonAdd_Click(object sender, System.EventArgs e)

     {

         WSEClient.sha_zheng_0a.Service1 server=new sha_zheng_0a.Service1();

         server.AddListener(this.myUri.ToString());    

         this.textBoxOutput.Text+="\r\n\r\n"+server.GetListeners();

     }

 

     private void buttonRemove_Click(object sender, System.EventArgs e)

     {

         WSEClient.sha_zheng_0a.Service1 server=new sha_zheng_0a.Service1();

         server.RemoveListener(this.myUri.ToString());

         this.textBoxOutput.Text+="\r\n\r\n"+server.GetListeners();

     }

 

     private void buttonFire_Click(object sender, System.EventArgs e)

     {

         WSEClient.sha_zheng_0a.Service1 server=new sha_zheng_0a.Service1();

         server.FireEvent();

     }

 

     private void buttonList_Click(object sender, System.EventArgs e)

     {

         WSEClient.sha_zheng_0a.Service1 server=new sha_zheng_0a.Service1();

         this.textBoxOutput.Text+="\r\n\r\n"+server.GetListeners();

     }

 

     private void Form1_Load(object sender, System.EventArgs e)

     {

         int port=(new System.Random()).Next(3000,7000);

         this.Text="Client Port: "+ port;

         MyReceiver.mainform=this;

         myUri = new Uri("soap.tcp://" + System.Net.Dns.GetHostName() + ":"+port+"/MyEventListner");

         SoapReceivers.Add(myUri, typeof(MyReceiver));

     }

 

     System.Uri myUri;

}

 

public class MyReceiver : SoapReceiver

{

     protected override void Receive(SoapEnvelope envelope)

     {

         mainform.textBoxOutput.Text+="\r\n\r\nEvent Fired";

     }

     public static Form1 mainform;

}

一些说明:
a) WSEClient.sha_zheng_0a.Service1就是Web Services客户端的Proxy;
b) MyReceiver类继承了SoapReceiver类,并重载了Receive()函数,这就是客户端的Callback函数了;
c) 在此之前,先要用SoapReceivers.Add()把MyReceiver类型作为一个接收器绑定到一个端口上进行侦听,然后,把完整的URI注册到服务器端(相当于订阅这个事件)。

3. 效果:

同时可以运行三份客户端程序(分别绑定在不同端口上)并注册到服务器。然后,只要服务器端有事件被触发(FireEvent),所有注册的客户端都能做出响应(在输出框中打印“Event Fired”)。

下面是一个截屏:

 

4. 好在哪里:

客户端很“轻”,而且这套事件响应机制已经是Web Services的一部分,是原生支持的。

“轻”,原生支持,这是最好的两点。

其实,很多技术之间并没有很明显的革命性提高。往往就是变得方便了一些、简洁了一些、容易了一些、快了一些,就已经是很大的提高了。

posted on 2004-06-26 18:01  feeling  阅读(939)  评论(3编辑  收藏  举报

导航