屋顶上的木帷幕

海鸥之所以追着渔船飞,是因为它们认为会有沙丁鱼抛向大海 - Eric Cantona, 1995
随笔 - 146, 评论 - 3168, 引用 - 56

导航

工具

关于


标签

每月存档

广告



访客

 

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的一部分,是原生支持的。

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

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

 


随贴广告(测试期)
相关文章

打印 | 张贴于 2004-03-08 21:56:00 | Tag:Dot NET

留言反馈

用WSE 2.0在XML Web Services里面实现Callback 编辑
用WSE 2.0在XML Web Services里面实现Callback
2007-04-05 17:35:00 | [匿名用户:chenanlin1981]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
这个也需要加上

public delegate void MessageRecievedHandler(SoapEnvelope envelope);
2007-03-16 09:23:00 | [匿名用户:haozi]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
我对楼主的写法稍微做了一点改动:

楼主的写法没有考虑跨线程调用,我在WSE3.0中调试似乎有些问题。

public class MyReceiver : SoapReceiver
{
public event MessageRecievedHandler MessageRecieved;

protected override void Receive(SoapEnvelope envelope)
{
OnMessageRecieved(envelope);
}

private void OnMessageRecieved(SoapEnvelope envelope)
{
if (MessageRecieved != null)
MessageRecieved(envelope);
}
}

private void Form1_Load(object sender, EventArgs e)
{
int port = (new System.Random()).Next(3000, 7000);

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

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

MyReceiver receiver = new MyReceiver();
receiver.MessageRecieved += new MessageRecievedHandler(receiver_MessageRecieved);

SoapReceivers.Add(myUri, receiver);
}

delegate void Update(TextBox textBox, string message);

public void UpdateText(TextBox textBox, string message)
{
textBox.Text += "\r\nEvent Recieved";
}

void receiver_MessageRecieved(SoapEnvelope envelope)
{
if (this.textBoxOutput.InvokeRequired)
this.textBoxOutput.Invoke(new Update(UpdateText), this.textBoxOutput, "\r\nEvent Recieved");
}
2007-03-16 09:22:00 | [匿名用户:haozi]
re: 用WSE 2.0在XML Web Services里面实现Callback 编辑
收益非浅啊!!!
但楼主有个小问题:
[WebMethod]

public void FireEvent()

{

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

{

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);
i++; // 因为楼主在循环中修改了Listeners。

}

catch(Exception e)

{

this.Listeners.RemoveAt( i );

}

}

}

2006-06-23 11:16:00 | [匿名用户:korn]
re: 用WSE 2.0在XML Web Services里面实现Callback 编辑
to sfsunk:
我认为您的方案还是是轮询的办法好。
2005-12-14 16:00:00 | [匿名用户:沧海月明]
re: 用WSE 2.0在XML Web Services里面实现Callback 编辑
你好,我想问一下,现在我要做一个系统,客户端用smartclient来实现,有一个功能就是服务器主动通知客户端消息。如果用你上面的callback来实现可以么?因为我是基于因特网的,所以可能是用soap.http uri,那客户端的地址可能就不会对服务器有效了?如果用webservice异步调用可以么?或者有什么更好的方案。清高手指点
2005-06-09 17:03:00 | [匿名用户:sfsunk]
re: 用WSE 2.0在XML Web Services里面实现Callback 编辑
SoapReceivers.Add对调用者权限要求得比较高。所以如果我想在WebsErvice中用SoapReceivers.Add来把自己的WebService作为一个接收者,是不是只能把这段代码封装起来放在COM+中呢?
2005-03-16 18:54:00 | [匿名用户:zhengyun]
用WSE 2.0在XML Web Services里面实现Callback 编辑
Ping Back来自:blog.csdn.net
2005-03-12 08:50:00 | [匿名用户:LoveCherry]
re: 用WSE 2.0在XML Web Services里面实现Callback 编辑
上面喜欢 remoting 的个们
能不能告诉我,remoting 怎么取客户端ip?

非要 客户端写代码传递吗?
2005-03-11 23:11:00 | [匿名用户:FlashElf]
re: 用WSE 2.0在XML Web Services里面实现Callback 编辑
我在win2003系统上用上面的例子测试了一下,和timiil 出现的情况一样,不过我也调通了.可是执行FireEvent后,client端就是收不到消息,在同事机子(winxp)上测试就正常,狂晕....
2004-12-24 16:41:00 | [匿名用户:meteor]
用WSE 2.0在XML Web Services里面实现Callback 编辑
Ping Back来自:blog.csdn.net
2004-10-29 09:55:00 | [匿名用户:沐枫]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
谢谢! 问题已经解决了。
1,我水平低,忘了在Application_Start作案。
2,那两句代码改为:
envelope.Context.Addressing.Action = new Action((string)(this.Listeners[i]));
envelope.Context.Addressing.ReplyTo = new ReplyTo(new System.Uri((string)(this.Listeners[i])));
就可以了。。。:)
2004-07-01 17:35:00 | [匿名用户:timiil]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
我用的是WSE 2的technical preview版本的

我估计你用的是WSE 2的正式版本。
-
的确可能有不一样的。毕竟.net 1.0 beta和rtm也有不少的名字区别的
2004-07-01 15:11:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
老大,上边的代码究竟是wse1的还是wse2的啊?
我用wse2临摹了一下,名称空间好像是Microsoft.Web.Services2;
这也不打紧,可要命的是
envelope.Context.Action = new Action((string)(this.Listeners[i]));
envelope.Context.ReplyTo = new ReplyTo(new System.Uri((string)(this.Listeners[i])));
这两句编译错误啊,看wse2的帮助说现在的Context好像不支持这样了,我改成了:
ResponseSoapContext.Current.Addressing.Action = new Action((string)(this.Listeners[i])); ResponseSoapContext.Current.Addressing.ReplyTo = new ReplyTo(new System.Uri((string)(this.Listeners[i])));

这样就通过编译了,可是连调用AddListener方法都说错,好像是说那个ArrayList 还是空的啊 ,头晕阿,老大!
2004-07-01 14:54:00 | [匿名用户:timiil]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
不能穿越防火墙实在是太遗憾了,应用WS很大的原因在于这里。
2004-05-19 14:26:00 | [匿名用户:沧海笑一生]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
WS-Addressing应该是可以穿透防火墙或者NAT的,因为WSE2.0强调End-to-End通信。本人也在研究WSE2.0,想用它来做一个项目。非常可惜的是,WSE2.0已经出来快一年了,但是它的帮助文档还几乎是空白的,不知道微软搞什么鬼。
2004-05-09 19:18:00 | [匿名用户:Zou Kok Man]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
有兴趣,等忙完这个项目一定研究研究。。。
问问WSE2。0到具体哪里DOWN?
2004-03-13 02:15:00 | [匿名用户:色盲]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
我的理解是:remoting可以把http以及soap作为一种传输通道/格式。和web services有点接近,不过还是两种技术。我以前满喜欢remoting的,不过现在好像用的人很少了...

这个嘛,本来就是web services的“增强”——enhancements。:D 当然已经不能算纯粹的"web" services啦

WS-Messaging还可以通过smtp来传呢...研究中

btw,现在好像很少人玩星际了....

我以前至少用6个农民才造狗窝的...
2004-03-09 14:31:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
remoting也可以使用soap协议的
我觉得这种用法似乎已经不能算"WEB" Service了,呵呵
2004-03-09 14:26:00 | [匿名用户:5drush]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
@mvm:

原来只是用了张Longhorn的皮(比起XP的桌面,这一张显得很“丰收”啊)~~~ :D

其实现在我对Longhorn(也就是它提供的所谓WinFX类库)的态度很像当年对待.NET Framework,就是只看文档和相关资料,不花过多时间实践(尤其是对未release的产品)。对于有些基础的朋友,这样可能算是一种捷径吧?嘿嘿~~~不过内心中还是很希望Longhorn能够早点RTM,因为像你所说的那些“能很方便的解决一个令人困惑很久的很具体的小问题的新东西”在这个平台中实在太多了!相信它也将给我们带来的更多的激情和机遇。 :)
2004-03-09 13:26:00 | [匿名用户:JGTM'2004 [MVP]]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
如果要叫真,那么可以说效果没有什么本质区别。我不反对这么说。

但这个实现的好处有:
- 代码比较简洁易懂
- 比remoting“轻”
- 对SoapEnvelop等内建支持
- wse是一个语言无关的协议,例如服务器可以用Java来实现;而remoting只能用.net

这些是我的体会。
2004-03-09 11:58:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
我想问一个问题,这个WEB SERVICE跟用Remoting实现CallBack的效果有什么区别?
2004-03-09 11:53:00 | [匿名用户:5drush]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
如果可以为SoapSender和SoapReceiver设置Proxy Server的话,我想还是能穿过防火墙的吧?
2004-03-09 11:43:00 | [匿名用户:sam1111]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
我的同事高惠杰问了一个很好的问题:上面这个例子在防火墙后面还能不能运行?

这点我想到了,但是忘记写上去了。

这个例子肯定是不能在防火墙、HTTP Proxy或者NAT后面运行的。没办法。

好在这在企业内部的业务整合方面肯定是可以用的。
2004-03-09 09:53:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
to sam1111:

WSE 2.0里面好多东西,要有心理准备哦。


不过我的体会,WSE 2.0主要就是两块,一块是和安全、身份等有关的,一块是对soap消息传递增强的(上面的例子就是这一块的)。

2004-03-09 09:04:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
Indigo和Avalon这两个东西听说很久了,但一直没机会玩玩。其实俺平时也不做项目,这种新技术也就是玩玩。主要就是看看能不能解决我以前心里存留的问题,比如web services的callback、比如assembly的合并等等。

要是一个新东西能很方便的解决一个困惑我很久的很具体的小问题,我就马上会被打动了。

有机会还要先看看infopath和smart document。新东西真多啊,光微软的就来不及研究。还有alphaworks和developerwork上的东西,等等。

看看blog是个不错的提高途径。
2004-03-09 09:02:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
to 2004:

我还没碰过longhorn呢。我们那里一个同事装了,结果发现explorer.exe有内存不断迅速增大的现象 :D 于是我决定再观望一下。


我就是在win xp下搞的,满容易的。
2004-03-09 08:58:00 | [匿名用户:mvm]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
看起来很有趣,确实要比我们自己曾经实现过的轮巡的方式要轻很多。
WSE 2.0,有空得好好看看:)
mvm,来参加周五Grace组织的聚会吧
2004-03-08 23:21:00 | [匿名用户:sam1111]
回复: 用WSE 2.0在XML Web Services里面实现Callback 编辑
看样子你是在Longhorn下面研究的(虽然用的是WSE 2.0的类库),Indigo里面好像会比较全面的解决这个问题吧?还没仔细看.NET Show #40里面的演示,感觉似乎有这个意思(因为看到有OneWay=true的写法,那自然应该可以duplex啦?好像是通过IDuplexChannel什么的来实现的……),有机会还请多指教。:)

BTW: “其实,很多技术之间并没有很明显的革命性提高。往往就是变得方便了一些、简洁了一些、容易了一些、快了一些,就已经是很大的提高了。”——Truly agreed!
2004-03-08 22:44:00 | [匿名用户:JGTM'2004 [MVP]]
博客主人设置本博客不允许匿名用户发表言论,请登录后再试

Powered by: Joycode MVC Blogger System