WCF服务器如何通知WCF客户端有关更改的信息?(比简单轮询更好的解决方案,例如Comet或长轮询)

33

参见“WCF push to client through firewall

我需要一个连接到WCF服务器的WCF客户端,当服务器上的一些数据发生更改时,客户端需要更新其显示。

由于客户端和服务器之间可能存在防火墙,因此:

  • 所有通信必须通过HTTP进行。
  • 服务器不能对客户端进行(物理)外呼。

由于我正在编写客户端和服务器,因此不需要将解决方案限制为仅使用SOAP等。


我正在寻找内置的支持 "长轮询" / "Comet" 等的方法。


感谢Drew Marsh提供如何在WCF中实现长轮询的最具信息性的答案。然而,我认为WCF的主要“卖点”是您只需在配置文件中配置要使用的通道即可完成此类操作。例如,我想要一个逻辑上双向但物理上只能入站的通道。


服务器是否可以进行任何外部连接? - Mark Coleman
@Mark,服务器可以在机房内调用其他服务器。 - Ian Ringrose
5个回答

49

我觉得你已经知道答案了:使用长轮询。 :) 所以我想唯一需要解释的是如何使用 WCF 并以最有效的方式实现这一点。


基础知识:

  1. 首先,决定你想要每个"长轮询"的时间长度。为了方便起见,我将选择5分钟的超时。
  2. 在客户端绑定中,更改sendTimeout="00:05:00"
  3. 就像使用XmlHttpRequest(XHR)进行长轮询一样,当超时确实发生时,您需要检测它并重新发出下一个轮询请求。在WCF中非常容易做到这一点,因为有一个特定的异常TimeoutException,您可以捕获以轻松检测这是问题还是其他异常。
  4. 根据您如何托管WCF服务,您需要确保自己能够处理长达5分钟的内容。从纯WCF角度来看,您需要确保设置receiveTimeout="00:05:00"。但是,如果您在ASP.NET中托管,则还需要配置ASP.NET运行时具有更高的超时时间,这可以使用<httpRuntime executionTimeout="300" />来完成(注意:此属性的测量单位为秒)。

在客户端高效处理

如果您只是设置客户端同步调用服务,并且客户端在等待响应时阻塞了5分钟,那么这不是系统资源的有效使用。您可以将这些调用放在后台线程中,但这仍然会在调用未完成时消耗线程资源。处理此问题的最有效方法是使用异步操作。

如果你手动创建服务契约,我建议查看MSDN上关于OperationContractAttribute.AsyncPattern的这一部分以获取有关如何为每个调用添加BeginXXX/EndXXX异步方法对的详细信息。但是,如果你使用svcutil为你生成操作契约,你只需要在命令行上传递/async选项即可生成异步方法。有关此主题的更多详细信息,请查看MSDN上的同步和异步主题
现在,您已经定义了异步操作,这种模式非常类似于使用XHR。您调用BeginXXX方法,并传递一个AsyncCallback委托BeginXXX方法将返回IAsyncResult,如果需要等待操作(在更高级的场景中),则可以将其保留,或者忽略,然后WCF基础结构将在后台异步发送请求到服务器并等待响应。当收到响应发生异常时,将调用传递给BeginXXX方法的回调。在此回调方法内,您需要调用相应的EndXXX方法,将交给您的IAsyncResult传递进去。在调用EndXXX方法期间,您需要使用异常处理来处理可能发生的任何逻辑故障,但这也是您现在能够捕获我们之前提到的TimeoutException的地方。假设您得到了一个良好的响应,则数据将从EndXXX调用中返回,您可以以任何合理的方式对该数据做出反应。

注意: 关于这个模式,需要记住的一件事是线程的性质。来自WCF的异步回调将在托管线程池中的一个线程上接收。如果你计划在诸如WPF或WinForms等技术中更新UI,则需要确保使用InvokeBeginInvoke方法将调用传回UI线程。

在服务器上提高效率

如果我们在客户端担心效率问题,那么在服务器方面就应该加倍关注。显然,这种方法会对服务器端产生更多的需求,因为连接必须保持打开状态并挂起,直到有理由向客户端发送通知为止。挑战在于,您只想将WCF运行时与实际发送事件的客户端的处理绑定在一起。其他所有内容都应该处于睡眠状态,等待事件发生。幸运的是,我们刚刚在客户端使用的相同异步模式也适用于服务器端。但是,现在有一个主要的区别:现在您必须从BeginXXX方法中返回IAsyncResult(因此是WaitHandle),WCF运行时将在调用EndXXX方法之前等待被触发。
在MSDN内部,除了我之前提供的链接外,您不会找到太多文档,不幸的是,他们编写异步服务的示例不太有用。话虽如此,Wenlong Dong曾经撰写了一篇关于使用异步模型扩展WCF服务的文章,我强烈建议您查看。
在此之外,关于如何在服务器端最佳实现异步模型,我无法提供太多建议,因为这完全取决于首先事件的数据源是什么类型。文件I/O?消息队列?数据库?其他一些具有自己的消息服务的专有软件,您正在尝试提供其外观?我不知道,但它们都应该提供自己的异步模型,您可以借助它们来使您的服务尽可能高效。

更新处方

由于这似乎是一个受欢迎的答案,我认为我应该回来并提供一个更新,考虑到最近的变化。目前有一个名为SignalR的.NET库,提供了这种精确功能,绝对是我推荐实现与服务器通信的方式。


+1 @Drew: 关于服务器实现,你能解释一下为什么返回一个IAsyncResult比在服务器上循环睡眠直到服务有东西返回更好吗? - Sylvain
2
当你在循环或阻塞中睡眠时,或使用任何其他机制阻塞WCF线程时,实际上是消耗了WCF运行时的一个线程。 当你这样做时,它无法使用该线程处理任何其他传入请求,最终可能导致整个运行时(和线程池)饥饿,从而导致服务性能严重下降。请记住,默认情况下,WCF服务只允许16个并发调用。在这种模式下,很容易耗尽这些调用。虽然你可以通过maxConcurrentCalls设置增加这个数字,但这只会推迟不可避免的结果。 :) - Drew Marsh
同样的模式在ASP.NET中也存在。他们也有一种异步编程模型,通过IHttpAsyncHandler接口公开。它也极少被使用。关键是你不能做CPU绑定的工作。这不是将工作分配给另一个后台线程,因为最终它会竞争相同的CPU资源。在这些情况下,您可能需要保持同步。它涉及到对其他资源的调用,这些资源本身可能会阻塞并花费时间:任何网络调用、文件系统、从队列中读取/写入等。 - Drew Marsh
2
@Drew Marsh,感谢您提供的有关如何使用WCF实现长轮询的最详细答案。 您是否知道是否有一个内部实现此功能的WCF通道?例如,我想要一个在逻辑上是双向的通道,但在物理上只接收传入消息的通道。 - Ian Ringrose
1
你能得到的最接近的是Silverlight附带的PollingDuplexHttpBinding,但他们没有将其纳入WCF中...即使在4.0版本中也是如此。 :( - Drew Marsh
SignalR在没有网络连接的情况下能否正常工作?因为我正在开发WCF和Xamarin Android应用程序,我需要让Xamarin应用程序通知客户端某些信息。 - Jamshaid K.

2
如果服务器可以对服务总线进行出站连接,你就可以实现一种回调类型。这样,客户端/服务器就不需要彼此了解,只需要了解依赖的服务总线即可。请参见 .NET 服务总线 你需要研究一下 WSDualHttpBinding WSDualHttpBinding为Web服务协议提供了与WSHttpBinding相同的支持,但用于双工契约。WSDualHttpBinding仅支持SOAP安全性并且需要可靠的消息传递。此绑定要求客户端具有提供服务的回调端点的公共URI。这由ClientBaseAddress提供。双重绑定将客户端的IP地址暴露给服务。客户端应使用安全性来确保它仅连接到信任的服务。

1
双向绑定将客户端的IP地址暴露给服务。但是我说有防火墙阻止服务器与客户端建立连接,那么这该怎么办? - Ian Ringrose
抱歉,我错过了关于服务器无法与客户端建立物理连接的那部分内容。 - Mark Coleman

2
虽然不是WCF,但你可以尝试使用XMPP来实现这个功能。InfoQ 上有一篇文章介绍了此方法和其他系统。虽然文章指出XMPP不能在HTTP上使用,但使用BOSH时可以。
有一些.NET库可供使用,例如agsXMPP
我工作的公司正在开始使用它向应用程序推送更新通知,以便刷新用户界面的某些部分。

1

谷歌搜索“WCF双工”(Programming相关)。 我已经在不同大陆成功地使用了netTcpBinding来实现这个,但我不确定basicHttpBinding是否适用。

不过,这确实需要服务器回调客户端。如果服务器不允许这样做,则轮询可能是您唯一的选择...


0

如果服务器不能调用客户端(通常情况下不应该),您应该像您指定的那样让客户端轮询服务器。 由于您已经有了 WCF 基础,只需添加一个相应的操作即可。


WCF默认超时时间为60秒,但可以更改。它是为SOA设计的,我不确定是否内置了像彗星一样的行为,尽管您可以“强制”它(从未尝试过,但您可以将超时设置为1小时,然后在服务器上等待操作……)我认为这不明智。 - Dani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接