用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
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的一部分,是原生支持的。
“轻”,原生支持,这是最好的两点。
其实,很多技术之间并没有很明显的革命性提高。往往就是变得方便了一些、简洁了一些、容易了一些、快了一些,就已经是很大的提高了。