IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务

IIS 7.0
使用 WAS 扩展 HTTP 之外的 WCF 服务
Dominick Baier and Christian Weyer and Steve Maine
代码下载位置: WAS2007_09.exe (178 KB)
Browse the Code Online

 

本文以 Windows Server 2008 的预发布版为基础。文中包含的所有信息均有可能变更。
本文讨论:
  • IIS 6.0 和 IIS 7.0 的体系结构和进程模型
  • IIS 6.0 如何承载 Web 服务
  • 用 IIS 7.0 承载可靠的 WCF 服务
  • Windows Process Activation Service (WAS) 工作原理
  • 支持非 HTTP 协议
本文使用了以下技术:
IIS,WAS
过去几年里我们听说的所有关于面向服务的应用程序,已经促使产生了用于设计、构建和部署面向服务的连接系统的真实框架、运行时和其他实用的工具。Windows ® Communication Foundation (WCF) 就是一个很好的例子,它允许您使用托管代码创建服务和服务使用者。
WCF 的一个优势是让您可以在任何 Windows 进程中承载基于 WCF 的服务,这些进程包括控制台应用程序、Windows 窗体应用程序或 Windows Presentation Foundation (WPF) UI 应用程序。您甚至可以在长时间运行的 Windows NT ® 服务(这些在后台运行的服务代表已配置的标识运作)中自承载 WCF 服务。具有基于 HTTP 的终结点的 WCF 服务也可以承载于 IIS 内部,这与由 ASP.NET 和 ASMX 实现的传统 Web 服务非常相似。
如 果您希望通过 IIS 7.0 提供可靠的 WCF 服务,那么必须要了解 IIS 的一项新功能 — Windows Process Activation Service (WAS)。WAS 是 IIS 7.0 的一个基本组件,它使不需安装整个 IIS 软件包就能承载 HTTP 以外的 WCF 服务得以实现。
在我们深入讨论 WAS 并向您展示如何在自己的应用程序中使用它之前,先让我们看一下 IIS 6.0 承载的工作原理,并了解为什么 IIS 7.0 中的新进程更加优秀。

 

IIS 6.0 体系结构和进程模型
要了解 Windows Vista ® 和 Windows Server ® 2008 的新进程激活机制,需要先熟悉 IIS 6.0 体系结构和进程模型。Windows Server 2003 和 IIS 6.0 的体系结构分为两个基本部分:一个侦听器进程和一组工作进程。IIS 6.0 侦听器进程由 w3svc 服务实现,该服务是一个长时间运行的 Windows NT 服务,由 Windows 服务控制管理器 (SCM) 激活。该侦听器进程等待通过 HTTP 到达的消息,再将消息分派至适当的工作进程 (w3wp.exe),该工作进程承载将最终处理请求的应用程序代码。
当 请求到达网络时,由内核模式的 HTTP 堆栈 (http.sys) 处理该请求,之后该请求被传送至侦听器进程。然后,w3svc 进程会查看请求的 URI,并利用它将请求映射到一个位于特定 IIS 应用程序池(该池承载于该工作进程 (w3wp) 的一个实例内)内部的特定 IIS 应用程序,如 图 1 所示。请求 URI 与该应用程序间的映射以存储在 IIS 元数据库中的配置信息为基础,可分别在 metabase.xml 和 mbschema.xml 中找到,这两者都存储在 %windir%\system32\inetsrv 文件夹中。一旦 w3svc 确定了目标应用程序,它就可以在一些内部数据结构中查询,以解析目标工作进程和应用程序池。
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 1  IIS 6.0 基础体系结构 (单击该图像获得较大视图)
根 据目标 IIS 服务器的状态,此时可能会发生以下情形之一。如果由于已为应用程序池接收了上一个请求而使工作进程和应用程序池已在运行,那就不需要激活。所以,该请求仅 被分派到等待中的工作进程。如果没有工作进程处理当前请求,侦听器进程必须在分派请求前创建一个新的 w3wp 实例。
IIS 6.0 工作进程是一种轻型可执行文件。当它开始对一个激活请求做出响应时,首先会加载一个简单的非托管填充程序 DLL (w3wphost.dll) 来支持 w3svc 与工作进程通信。该填充程序还负责加载 aspnet_isapi.dll,该文件会实现 ASP.NET 的托管组件和 IIS 的非托管组件之间的接口。
Aspnet_isapi.dll 负责将公共语言运行时 (CLR) 载入工作进程并创建一个默认的应用程序域,ASP.NET 的托管宿主组件将位于此域中。这些托管宿主组件负责按需创建附加的应用程序域(每个 IIS 应用程序对应一个域),并基于请求的 URL 将请求传送给它们。

 

IIS 7.0 和 WAS
如 果您理解 IIS 6.0 进程模型,就能理解 IIS 7.0 和 WAS。IIS 6.0 的所有主要体系结构组件(侦听器、工作进程和应用程序管理器)在 WAS 中也存在。不同的是,IIS 7.0 和 WAS 实现支持非 HTTP 情形,这可能非常适合基于服务的应用程序体系结构。
可管理性、可靠性和可 伸缩性与运行于 Windows Server 2003 的 IIS 6.0 中的 WAS 完全相同。但在 IIS 6.0 中仅对基于 HTTP 的应用程序有效的可解决按需激活、进程运行状况监控、企业级可管理性、快速故障保护等问题的所有 IIS 出色功能,现在对非基于 HTTP 的应用程序和服务同样适用。

 

深入了解 WAS
在 IIS 6.0 中,w3svc 服务实际上承担着双重任务。它在 http.sys 中注册,并且是传入的 HTTP 通信的直接接收者,因此它充当 HTTP 侦听器。同时,它还拥有进程激活组件,负责正确启动 w3wp 的新实例以及分派请求。在 IIS 7.0 中,这两种责任被重构给了不同的 Windows NT 服务。w3svc 进程保留了其作为 HTTP 侦听器的角色,但负责配置和进程激活的组件被作为因子之一计入 WAS,WAS 包括三个部分:配置管理器、进程管理器、非托管侦听器适配器接口。
配置 管理器从 applicationhost.config(IIS 7.0 中针对元数据库的替换文件)中读取应用程序和应用程序池配置。进程管理器将应用程序池映射到现有的工作进程,并负责生成 w3wp 的新实例,以承载新的应用程序池来响应激活请求。非托管侦听器适配器接口定义外部侦听器如何将接收到的激活请求传送给 WAS。
w3svc 服务负责与内核级 http.sys 通信,并通过侦听器适配器接口将 HTTP 激活请求传送给 WAS。
WCF 利用侦听器适配器接口,传送通过支持的非 HTTP 协议(即 TCP、命名管道和 MSMQ)收到的激活请求。通过非 HTTP 协议实际接收请求的 WCF 管道承载于 SMSvcHost.exe 内部,SMSvcHost.exe 承载以下四个长时间运行的 Windows NT 服务:NetTcpPortSharing、NetTcpActivator、NetPipeActivator 和 NetMsmqActivator(参见 图 2)。
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 2  IIS 7.0 和 WAS 基础体系结构 (单击该图像获得较大视图)
NetTcpPortSharing 是 WCF TCP 端口共享服务。它会实现一个集中的 TCP 侦听器,使得多个进程可以在同一 TCP 端口上侦听。即使未安装 IIS 7.0 也可使用该服务。
NetTcpActivator 是 WCF TCP 激活服务。它会将 TCP 激活请求传送到 WAS。
NetPipeActivator 是 WCF 命名的管道激活服务,会将命名的管道激活请求传送到 WAS。
NetMsmqActivator 是 WCF MSMQ 激活服务;它会将 MSMQ 激活请求传送到 WAS。
虽 然这些服务全都位于同一二进制文件中,但它们是不同的 Windows NT 服务,可以单独停止和启动,以减少攻击面和系统开销。它们都是侦听器服务的示例,行为方式也都相似。基于此原因,我们将着重探讨 TCP 激活,它也能够用作命名管道和 MSMQ 的示例。唯一的区别在于两种情况下使用的网络资源的特定类型。

 

配置和多协议寻址
要 使某个应用程序通过 WAS 激活,首先需要对该应用程序进行配置。WAS 中承载的每个应用程序都有一个相应的配置项,位于 %windir%\system32\inetsrv\config\applicationhost.config。此信息包括将承载该应用程序的应用 程序池、可唯一标识该应用程序的 URL 片段等项目。我们稍后会提供相应的示例。与 IIS 6.0 非常相似,每个应用程序都关联到一个应用程序池和一个站点。每个站点都有一个其支持的网络协议的地址绑定集。例如,管理员可以对默认站点进行配置,将其绑 定到 HTTP 端口 80 和 TCP 端口 7777。该站点可能承载位于不同应用程序池的两个应用程序(分别在 /Foo 和 /Bar 上进行侦听)。在此配置中,应用程序 /Foo 将侦听 http://myserver.com/Foo 和 net.tcp://myserver.com:2323/Foo,而 /Bar 将侦听 http://myserver.com/Bar 和 net.tcp://myserver.com:2323/Bar。不管请求的接收方式如何(由于每个应用程序池都获得其各自的工作进程,因此接收方式不 同),所有发送到 /Foo 的请求都将转到工作进程 1,而所有发送到 /Bar 的请求都将转到工作进程 2。一般来说,应用程序站点控制与该应用程序相关联的一组网络地址,而它的应用程序池则控制将承载它的工作进程实例。注意,只有当您不使用 Web 园时,每个应用程序池对应一个工作进程的规则才有效。在 Web 园情况下,每个应用程序池都映射到它们自己的一组工作进程,进程数最多可为应用程序池配置中指定的工作进程的最大数量。

 

侦听器如何分辨侦听
侦 听器需要接收消息。为此,它需要打开一个套接字(或一个管道句柄,或启动一个 MSMQ 读取等)。然而,为了接收正确的消息,它需要从 WAS 取得必要的寻址信息。该过程在侦听器启动时完成。协议的侦听器适配器在 WAS 侦听器适配器接口调用一个函数,并实质上说明:“我现在正在 net.tcp 协议进行侦听,请使用我传给您的这组回调函数,告诉我需要知道些什么。”作为响应,WAS 将利用它所拥有的任何配置,为设置为通过相关协议接受消息的应用程序进行回调。对于上面的例子,TCP 侦听器将被告知有两个应用程序(*:7777/Foo 和 *:7777/Bar)被配置为使用 TCP。WAS 还会为每个应用程序分配一个唯一的侦听器通道 ID,用来将请求关联到它们的目标应用程序。
侦 听器进程使用 WAS 提供的配置信息建立路由表,再使用该路由表,在侦听器通道 ID 到达时,将传入请求映射到侦听器通道 ID。该映射的机制是侦听器支持的底层协议的实现细节。重要的是每个侦听器服务都必须能够看到传入消息(比如“啊,这个发送给侦听器通道 x”)并相应地将请求分派到 WAS。碰巧 WCF 会使用 URI 指明通过 TCP/MSMQ/命名管道到达的消息的目的地,但其他协议很可能会以任何适合自己的方式实现此映射。
一旦侦听器服务连接到 WAS 并收到配置信息,它就可以打开自己的网络资源并开始侦听消息。对于 TCP,这会导致 NetTcpActivator 触发打开套接字并异步调用 Socket.Accept,此时侦听器实际上会进入休眠状态,直到有消息到达。

 

通过非 HTTP 协议执行基于消息的激活
侦 听器进程到达等待中的套接字时会被唤醒,并开始读取字节。此时的目的只是为了读取足够的信息,以确定即将到达的消息的最终目的地,然后当侦听器回调至 WAS 并生成工作进程时暂停。对于 TCP,目标地址是通过从支持 SOAP 消息的帧协议读取信息来确定的。一旦侦听器有了目标 URI,它就会使用该 URI 作为至其内部路由表的索引,将 URI 解析为相应的侦听器通道 ID,由 WAS 分配到该地址。
接 着,侦听器服务请求 WAS 激活一个工作进程,通过在侦听器适配器接口调用非托管 WebhostOpenListenerChannelInstance 方法来处理挂起的请求。除其他参数外,WebhostOpenListenerChannelInstance 还会接受侦听器通道 ID 以及数据 blob(以字节数组形式表示),该数据 Blob 将用来在激活进程中初始化管道的接收端。该 Blob 对 WAS 没有影响,它仅是方便允许每个协议侦听器实现能初始化新激活的组件。
当 WAS 收到对 WebhostOpenListenerChannelInstance 的调用时,WAS 的进程管理器部分会生成工作进程的新实例。接着,该工作进程需要先对自己进行一些初始化,然后才能处理侦听器收到的消息。在这段时间内,侦听器可能会继续 从网络接收数据;然而它只能在收到工作进程初始化完成的通知后,才能将消息分派至该工作进程。

 

工作进程初始化
工作进程内部有几个处于活动状态的重要组件,如 图 3 所示。工作进程的体系结构如 图 4 所示。
Figure 3 工作进程中的重要组件

组件 说明
进程宿主 将 CLR 载入工作进程并初始化默认应用程序域。
应用程序管理器 创建唯一的应用程序域以响应应用程序级别的激活请求,并管理它们的生存期。
进程协议处理程序 实现特定于协议的进程初始化逻辑。
应用程序域协议处理程序 驻留在激活的应用程序域并执行特定于协议的应用程序域初始化。
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 4  工作进程初始化体系结构 (单击该图像获得较大视图)
由 于 IIS 不实现任何托管代码组件,因此将 CLR 载入工作进程的工作落在 ASP.NET 进程宿主上。WAS 通过调用由 aspnet_isapi.dll 公开的 API,在工作进程内部创建进程宿主。WAS 向此函数传递一个回调接口,使托管进程宿主可以用它来与 WAS 进行后续通信。然后,新创建的进程宿主会返回给 WAS,使 WAS 和工作进程之间可以实现双向通信。
WAS 使用它从工作进程中获取的进程宿主的实例来启动特定于协议的初始化例程。进程宿主上的激活系统 StartProcessProtocolListenerChannel 传递给它一个协议关键字(例如“net.tcp”)和一个回调接口,特定于协议的处理程序可以使用它们与 WAS 通信。
当进程宿主收到要在工作进程内部启动新的进程协议侦听器通道的请求时,它做的第一件事就是在配置中查找协议关键字。要求 WAS 用户在启动侦听器之前进行注册。例如,WCF 为 net.tcp 注册了一个进程协议处理程序和一个应用程序域协议处理程序。 图 5 所示的配置数据可以在 %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG 下的 web.config 主文件中找到。
Figure 5 net.tcp 协议处理程序的配置
  1. <system.web>
  2.   <protocols>
  3.     <add name="net.tcp"
  4.      processHandlerType=
  5.       "System.ServiceModel.WasHosting.TcpProcessProtocolHandler,
  6.        System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral,
  7.        PublicKeyToken=b77a5c561934e089"
  8.      appDomainHandlerType=
  9.       "System.ServiceModel.WasHosting.TcpAppDomainProtocolHandler,
  10.        System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral,
  11.        PublicKeyToken=b77a5c561934e089" validate="false" />
  12.   </protocols>
  13. </system.web>
  14.  
一 旦进程宿主解析了配置中的协议关键字,它就会在默认的应用程序域中创建一个已配置进程协议处理程序类型的新实例,并用来调用 StartListenerChannel。此方法需要两个单独的回调接口。一个允许与 WAS 通信(它实际上就是传递给进程宿主的同一个 WAS 回调对象)。另一个允许与进程宿主直接通信(参见 图 6)。
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 6  注册协议处理程序 (单击该图像获得较大视图)
进 程协议处理程序 (PPH) 对于特定协议的承载模型有很大的控制力。例如,PPH 可以在初始化例程期间的某一时刻停下来,并在默认的应用程序域中处理所有后续请求。但是,WCF 选择保留 IIS 6.0 应用程序池模型,在每个应用程序各自的域内进行激活,这样可以单独监视和回收每个应用程序。这样,WCF PPH 需要与 ASP.NET 应用程序管理器合作,此管理器负责管理应用程序以及应用程序域的生存期。
进 程宿主通过它传递给 StartListenerChannel 的 IAdphManager 接口,为 PPH 提供对应用程序管理器功能的访问。为确定激活请求的目标应用程序,PPH 回调到 WAS 并请求侦听器最初调用 WebhostOpenListenerChannelInstance 时提供的数据 Blob。处理返回的字节数组后,WCF PPH 将可以访问唯一标识目标应用程序(例如,/w3svc/1/Foo)的应用程序键;因为按惯例,WCF 侦听器会在它发送给侦听通道的数据 Blob 中包括应用程序键。WAS 对于 Blob 中传递的数据没有任何限制 — 从 WAS 的角度来说,这不过是一个不透明的字节数组。它随即可将应用程序键、协议关键字和侦听器回调接口传递给 IAdphManager.StartAppDomainProtocolListenerChannel,以激活应用程序的应用程序域。
通 常情况下,应用程序管理器会将应用程序键用作所有应用程序域的表索引。当它收到对 StartAppDomainProtocolListener 的调用时,首先会查看此表,以检查该应用程序是否已由以前通过不同协议传送的请求所激活。如果没有,则使用协议关键字查找特定于协议的应用程序域协议处理 程序类型,查找的方式与进程宿主使用此关键字来确定进程协议处理程序类型的方式相同。一旦解析了应用程序域协议处理程序类型,应用程序管理器就在目标应用 程序域中创建它的新实例。最后,应用程序管理器调用 AppDomainProtocolHandler.StartListenerChannel,再次传递 WAS 回调接口,这样,在必要时应用程序域就可以与 WAS 通信。
当应用程序域协议处理程序被实例化之后,工作进程的内部即如 图 7 所示。此时,WAS 认为目标应用程序已完全激活,在应用程序关闭之前不再干预进程。
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 7  应用程序域协议处理程序初始化 (单击该图像获得较大视图)

 

将数据从侦听器传送到工作进程
激 活工作进程和应用程序域是实现把消息实际传送到应用程序这一更大目标的第一步。由于 WAS 是激活服务而不是消息传送服务,因此它不参与这项活动。相反,每个协议实现都能够以适合自己的任意方式实现侦听器和工作进程之间的通信。此体系结构允许多 种特定于协议的优化,允许每个实现利用底层协议的特性。例如,WCF MSMQ 激活服务仅传递数据 Blob 中 MSMQ 队列的名称,允许已激活的应用程序从数据源直接读取,完全绕过侦听器。

 

在 WAS 中承载 WCF 服务
现在,让我们动手实际将 WCF 服务承载于 WAS。首先,我们将设置一个基于 HTTP 的服务,这与我们在 IIS 6.0 中的操作没有太大区别。然后,我们将添加对 TCP、命名管道和 MSMQ 的支持,并详细说明可用配置选项(参见 图 8 获取该简单服务的源代码)。
Figure 8 示例 WCF 服务
  1. [ServiceContract(Name = "WasDemoServiceContract"
  2.                 Namespace = "urn:msdnmag")]
  3. public interface IDemoService
  4. {
  5.   [OperationContract(Name="Ping", Action="Ping"
  6.                      ReplyAction="PingReply")]
  7.   string Ping(string data);
  8. }
  9.  
  10. [ServiceBehavior(
  11.   Name="WasDemoService", Namespace="urn:msdnmag",
  12.   InstanceContextMode=InstanceContextMode.PerCall,
  13.   ConcurrencyMode=ConcurrencyMode.Single)]
  14. public class DemoService : IDemoService
  15. {
  16.   public string Ping(string data)
  17.   {
  18.     return "pinged with: " + data;
  19.   }
  20. }
  21.  
第一步是在 WAS 中创建新应用程序 — 您可以使用 IIS 7.0 管理工具,或使用 Visual Studio ® 来创建。虚拟目录必须包含三项:服务(在 /bin 目录中编译的或如 /App_Code 文件夹中的源代码)、.svc 服务终结点和配置文件。
.svc 文件会在 URI 和服务实现(与 .asmx 文件十分相似)之间建立一个连接。.svc 文件内的 @ServiceHost 指令会指示 WCF 管道为 Service 属性中指定的类型创建一个 ServiceHost 实例:
  1. <% @ServiceHost Service="DemoService" %>
WAS 的配置信息存储在 web.config 文件中。整个 WCF 配置在 system.serviceModel 节发生。这里要说明的是,当承载于 WAS 时不能指定基址,因为该地址由网站和虚拟目录系统决定(参见 图 9 以了解基本配置文件)。已配置的服务行为会启用元数据发布,您可以将 svcutil.exe 或 Visual Studio 中的“Add Service Reference”(添加服务参考)对话框等工具直接指向 .svc 文件,以生成客户端代理。
Figure 9 示例服务的基本配置
  1. <configuration>
  2.   <system.serviceModel>
  3.     <services>
  4.       <service name="DemoService" behaviorConfiguration="Behavior">
  5.         <endpoint address="" binding="wsHttpBinding"
  6.          bindingNamespace="urn:msdnmag" contract="IDemoService" />
  7.       </service>
  8.     </services>
  9.     ...
  10.     <behaviors>
  11.       <serviceBehaviors>
  12.         <behavior name="Behavior">
  13.           <serviceMetadata httpGetEnabled="true" />
  14.         </behavior>
  15.       </serviceBehaviors>
  16.     </behaviors>
  17.   </system.serviceModel>
  18. </configuration>
  19.  
使 用 HTTP 传输的 WCF 终结点会遍历公用 IIS 处理管道。如果您过去使用过 ASP.NET Web 服务,则可能看上去会非常熟悉,但其内在工作原理却有略有不同。对于基于 HTTP 的终结点,实际上有两种操作模式:ASP.NET 兼容性和无兼容性。ASP.NET 兼容性在默认情况下关闭,这就是说 HttpContext.Current 为空,终结点的实际激活并不在 HttpHandler 中发生,而是在 HttpModule 中发生,它将请求传递到 ASP.NET PostAuthenticateRequest 管道事件的 ServiceHost。此处有一些含义;首先,如果您习惯用 ASP.NET 可扩展性模型,您将只接收到管道通知,直至 PostAuthenticateRequest。从此时起,处理将直接跳到 EndRequest 事件。此外,WCF 服务将无法访问任何 ASP.NET 宿主功能,比如会话状态。这种设置是有意为之的。由于 WCF 服务宿主拥有与所有这些功能(会话、身份验证等)对应的传输独立的等效项,您不应尝试将您的服务绑定到一个特定承载环境中。
如果您的确需要 ASP.NET 兼容性(例如,为了在服务和承载于同一应用程序域中的 ASP.NET 应用程序间共享会话状态),您可以使用一个配置开关启用它:
  1. <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
此外,您必须专门为 ASP.NET 兼容性启用您的服务:
  1. [AspNetCompatibilityRequirements(
  2.     RequirementsMode=AspNetCompatibilityRequirementsMode.Required)]
  3. class DemoService : IDemoService { ... }
这 两个组合开关改变了服务请求的内在处理的工作方式。使用这两个开关后,请求就会通过 HttpHandler 被分派到 WCF 运行时,整个的处理管道行为与 ASP.NET Web 服务类似。此外,当前的 HttpContext 仍然可用,可以像往常一样使用它。在 ASP.NET 兼容性模式下运行的 WCF 服务只支持 HTTP 终结点。
现 在您已经了解如何在 WAS 中承载基于 HTTP 的服务,接下来让我们将非 HTTP 终结点添加到该服务。如前所述,协议侦听器负责打开实际传输,并将连接分派至运行服务的应用程序域。这些协议侦听器都是 Windows NT 服务。打开 services.msc 时,您将发现一个 Net.tcp 侦听器适配器,以及用于命名管道与 MSMQ 的侦听器。这些适配器与侦听器连同为 TCP 共享服务的端口,必须启用并运行才能实现非 HTTP 的成功激活。
侦听器适配器的宿主名称和端口配置可以在 WAS 配置文件 applicationHost.config 中找到。您可以使用 appcmd.exe 命令行工具手动编辑这些设置,也可以使用 IIS 7.0 配置 GUI。这些称作绑定的设置在站点级别完成(参见 图 10)。 (注意,Windows Vista 和 Windows Server 2008 附带的 WAS 版本有所不同。在 Windows Vista 中,绑定不是预配置的,而且当前没有 GUI 以图形方式添加它们。之后您将看到必需的 appcmd.exe 命令。在 Windows Server 2008 中,绑定是默认配置的。)
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 10  IIS 6.0 基础体系结构 (单击该图像获得较大视图)
IIS7.0 使用 WAS 扩展 HTTP 之外的 WCF 服务
图 10  IIS 6.0 基础体系结构 
  1. <bindings>
  2.   <binding protocol="https" bindingInformation="*:443:" />
  3.   <binding protocol="http" bindingInformation="*:80:" />
  4.   <binding protocol="net.tcp" bindingInformation="808:*" />
  5.   <binding protocol="net.pipe" bindingInformation="*" />
  6.   <binding protocol="net.msmq" bindingInformation="localhost" />
  7.   <binding protocol="msmq.formatname" bindingInformation="localhost" />
  8. </bindings>
在 bindingInformation 属性中,每个协议都与特定于协议的配置设置相关联。例如,TCP 绑定的 bindingInformation 指定端口和侦听器接受连接所在的宿主名称(* 表示所有宿主名称均有效)。
最 后一步是在承载服务的应用程序中启用选择的协议。截止 Windows Server 2008 Beta 3,都没有适用的 GUI;您必须手动编辑 applicationHost.config。稍后我们将探讨如何使用工具或以编程方式执行这些操作。搜索配置文件中的应用程序元素,然后添加一个 enabledProtocols 属性,如下所示:
  1. <application path="/WasDemo" 
  2.     enabledProtocols="http,net.tcp,net.pipe,net.msmq">
  3.   <virtualDirectory path="/" physicalPath="C:\MSDN\WasDemo\web" />
接着,您只需要为自己的 WCF 服务配置添加额外终结点,使它们可以通过以上协议被访问:
  1. <endpoint address="" binding="netTcpBinding"
  2.   bindingNamespace="urn:msdnmag" contract="IDemoService" />
  3. <endpoint address="" binding="netNamedPipeBinding"
  4.   bindingNamespace="urn:msdnmag" contract="IDemoService" />
当根据元数据生成客户端代理时,配置文件将为每个终结点包含一个客户端元素。在客户端代码中,您可以通过将终结点名称传递至代理构造函数来指定使用哪个传输,如下所示:
  1. WasDemoServiceContractClient proxyTcp = 
  2.   new WasDemoServiceContractClient(
  3.     "NetTcpBinding_WasDemoServiceContract");
  4. Console.WriteLine(proxyTcp.Ping("Hello"));
向 服务添加 MSMQ 终结点还需要一些其他步骤。首先,您必须使用 Message Queuing MMC 管理单元(或通过使用任何可用的编程方法)创建一个队列。另外,您必须在此队列中设置访问控制列表 (ACL),以允许工作进程(默认是 NETWORK SERVICE)从队列读取和速查。
值得注意的是,MSMQ 终结点激活仅能在队列与 .svc 文件(去掉机器名称)同名的情况下才能正确运作。这就是说,如果服务终结点是 /server/app/service.svc,队列名称就必须是 app/service.svc。
在服务配置中,必须指定服务应侦听的队列的名称:
  1. <endpoint address="net.msmq://localhost/private/wasdemo/service.svc"
  2.   binding="netMsmqBinding" bindingNamespace="urn:msdnmag"
  3.   contract="IServiceMsmq" />
非 HTTP 终结点不传递 IIS 处理管道,将直接路由至 WCF 运行时。这意味着您不能使用 HttpModule 对请求进行预处理或后处理。此外,HttpApplication 类 (global.asax) 的 Application_Start 和 Application_End 不会触发。所以,如果您希望为此类服务运行启动或清理代码,则必须使用 ServiceHost 类的事件。在下一部分我们将详细介绍其工作方式。

 

WAS 承载的服务的生存期管理
当您自承载一个服务(例如,在 Windows NT 服务中)时,该服务的生存期是确定的。它将在调用 Open 后运行,在调用 Close 后关闭(暂时忽略灾难性故障)。此外,Windows NT 服务通常在引导时启动。这与在 WAS 中承载是不同的。
首 先,WAS 的确要求激活(如您所知,“WAS”中的“A”就代表激活)。这就是说,承载服务的应用程序域仅在请求消息进入时才能创建。历经一段可配置的空闲时间后, 应用程序域再次关闭。有几个原因可解释为何 WAS 或 ASP.NET 运行时可以决定回收应用程序域甚至整个工作进程。
首先,WAS 中的应用程序池设置指定工作进程的定期回收,默认每 29 小时回收一次(请注意这个质数是刻意选择的,以最大程度减少实际回收发生在一天当中的同一时间的可能性)。
应 用程序域可以回收还有其他几个原因。影响该应用程序域的 machine.config、web.config、或 applicationHost.config 的配置设置可能已经更改。/bin 或 /App_Code 目录或其内容也可能已经被修改,重新编译(对于 .aspx、.asmx、.svc 等)的次数超出了 machine.config 或 web.config 中的 <compilation numRecompilesBeforeAppRestart /> 设置所指定的限制(默认值设为 15),或者虚拟目录的物理路径已被修改。最后,代码访问安全 (CAS) 策略可能已被修改,或者应用程序子目录可能已被删除。当发生这些情况时,您会丢失所有内存中状态,包括会话或实例和静态变量。这实质上意味着现成的 WAS 承载并不是真正适合会话服务或单例服务。它更适合无状态单调用服务。
WCF ServiceHost 类型包含 Opening 和 Closing 这两个事件。它们是在服务启动和关闭时执行代码的唯一正确方式。问题是,您必须在创建 ServiceHost 的新实例和在其上调用 Open 之间接通这些事件的处理程序。如上所述,当在 .svc 文件中使用 @ServiceHost 指令时,这无法实现。这种情况下的一个可行方案是承载一个自定义服务宿主工厂。它能给您更多控制,允许您处理上述事件。
为 此,必须从一个名为 ServiceHostFactoryBase 的类派生,并实现 CreateServiceHost 方法。该方法从承载环境接收服务类型名称和基址,返回 ServiceHostBase 的一个实例。现在您要负责创建恰当的 ServiceHost,并按照需要对它进行配置,然后将其返回到 WCF 运行时。完成这些操作后,您就能以编程方式访问 WCF 的可扩展性模型,然后接通事件处理程序(参见 图 11)。
Figure 11 使用 CreateServiceHost
  1. public class HostFactory : ServiceHostFactoryBase
  2. {
  3.   public override ServiceHostBase CreateServiceHost(
  4.     string constructorString, Uri[] baseAddresses)
  5.   {
  6.     Type service = Type.GetType(constructorString);
  7.     ServiceHost host = new ServiceHost(service, baseAddresses);
  8.     // hook up event handlers
  9.     host.Opening += OnOpening;
  10.     host.Closing += OnClosing;
  11.     return host;
  12.   }
  13. }
  14.  
要在业已存在的 .svc 终结点和自定义工厂之间建立连接,必须将 Factory 属性添加到 @ServiceHost 指令:
  1. <%@ServiceHost Service="Service" Factory="HostFactory" %>

 

自动设置 WAS 承载的服务
我们已经讨论过,设置 WAS 承载的 WCF 服务包含几个必需的步骤:
  1. 设置物理目录(如果需要,还可设置 ACL)。
  2. 在 WAS 中设置应用程序和虚拟目录。
  3. 配置启用的协议。
  4. 创建队列并设置 ACL(如果使用 MSMQ)。
WAS 为配置提供了多种不同的接口,包括 Windows Management Instrumentation (WMI)、命令行工具 (appcmd.exe) 和全新的托管 API。WMI 和 appcmd.exe 非常适用于编写脚本和批处理文件,新的托管 API 则让您能轻松访问更复杂的部署方案(如 MSI 包)的配置。请参见 图 12 获得一些 appcmd.exe 示例。
Figure 12 使用 appcmd.exe 设置 WAS 应用程序
  1. REM adding bindings to the default web site
  2. appcmd.exe set site "Default Web Site" –
  3.     +bindings.[protocol='net.tcp',bindingInformation='808:*']
  4. appcmd.exe set site "Default Web Site" –
  5.     +bindings.[protocol='net.pipe',bindingInformation='*']
  6. appcmd.exe set site "Default Web Site" –
  7.     +bindings.[protocol='net.msmq',bindingInformation='localhost']
  8.  
  9. REM add a new application
  10. appcmd add app /site.name:"Default Web Site" /path:/WasDemo 
  11.     /physicalpath:c:\etc\WasDemo
  12.  
  13. REM enabled protocols for application
  14. appcmd.exe set app "Default Web Site/WasDemo" 
  15.     /enabledProtocols:http,net.pipe,net.tcp,net.msmq
  16.  
托 管的配置 API 可在新的 Microsoft.Web.Administration.dll 程序集中找到,以 ServerManager 类为中心。您会在其中找到一个完整的对象模型,涵盖应用程序池、工作进程、站点、应用程序和配置文件。使用该 API 设置应用程序只需要使用几行代码,如 图 13 所示。另请参见 图 14,了解一些可为 NETWORK SERVICE 帐户设置队列和最少 ACL 的帮助器方法。
Figure 14 创建队列并设置 ACL
  1. private static void SetupMsmq(string queuename)
  2. {
  3.   // create queue – in this case a transactional queue is created
  4.   MessageQueue queue = MessageQueue.Create(queuename, true);
  5.   queue.Label = queuename;
  6.  
  7.   // set ACL
  8.   queue.SetPermissions(GetAccountName(
  9.     WellKnownSidType.NetworkServiceSid), 
  10.     MessageQueueAccessRights.ReceiveMessage);
  11. }
  12.  
  13. private static string GetAccountName(WellKnownSidType wellKnownSid)
  14. {
  15.   NTAccount account = (NTAccount) new SecurityIdentifier(wellKnownSid,
  16.     null).Translate(typeof(NTAccount));
  17.   return account.Value;
  18. }
  19.  
Figure 13 使用托管的配置 API
  1. private static void SetupWasApp(string virtualPath, string physicalPath,
  2.   string enabledProtocols)
  3. {
  4.   ServerManager manager = new ServerManager();
  5.  
  6.   // creating the application
  7.   manager.Sites[0].Applications.Add(virtualPath, physicalPath);
  8.   manager.CommitChanges();
  9.  
  10.   // setting up the enabled protocols
  11.   Application wasApp = manager.Sites[0].Applications[virtualPath];
  12.   wasApp.EnabledProtocols = enabledProtocols;
  13.   manager.CommitChanges();
  14. }
  15.  

 

扩展 WAS
WAS 是可扩展的,这意味着您可以编写自己的对自定义 WCF 传输信道的支持。Windows SDK 包含基于用户数据报协议 (UDP) 的 WAS 激活扩展的示例,但是扩展 WAS 以添加您自己的功能绝非易事。这涉及到实现自定义进程和应用程序域协议处理程序,以及创建用于侦听的代码以接收各个目标传输机制的相关消息。要充分说明这 个问题,需要专门深入探讨该主题的文章。
但是,有一点非常重要,必须提 到。WAS 中的最大请求执行限制数根据运行的操作系统的不同而具有不同的值。对于 Windows Server 2008,它是没有限制的,但对于 Windows Vista Ultimate 以及 Business 来说,连接限制数为 10。Windows Vista 的家庭版,即 Windows Vista Home Basic 和 Home Premium 以及 Windows Vista Starter,请求执行限制数都是 3。在规划实现时应考虑到这一点。

 

Dominick Baier是德国 thinktecture (thinktecture.com) 的安全咨询师。此外,他还是 DevelopMentor ( develop.com) 安全和 WCF 课程负责人和开发人员安全 MVP,同时也是《Developing More Secure Microsoft ASP.NET 2.0 Applications》(Microsoft Press, 2006) 一书的作者。您可以在 leastprivilege.com 访问他的博客。

 

Christian Weyer是 thinktecture 的共同创始人之一,也是一位首席架构师,曾用 Java、COM、DCOM、COM+、Web Services、WCF 和其他技术进行分布式应用程序的建模和实现。您可以通过 thinktecture.com/staff/christian 与他联系。

 

Steve Maine是 Microsoft Connected Framework 团队的一位高级项目经理。他目前致力于研究 .NET Framework 3.5 中即将发布的 WCF 的多项专注于 Web 的编程模型功能。他的博客地址: hyperthink.net/blog

你可能感兴趣的:(http)