第二章 寄宿WCF服务

【摘要】

本章先描述WCF service的工作原理;然后阐述寄宿WCF服务的各种方式;随后通过创建WPF程序和Windows Service来寄宿WCF服务;最后讲述支持WCF内置的各种绑定以及如何对一个WCF服务实现多重绑定。

在第一章中,我们为你介绍了如何创建一个WCF服务,并将其部署到IIS上,然后通过客户端程序访问该服务。在本章中,我们将关注WCF工作的细节;各种寄宿WCF服务的方式;此外,你还将学习到如何创建和配置宿主程序,其负责处理来自客户端的请求并控制WCF服务的状态;你还将进一步了解到绑定的工作原理及WCF运行时如何使用绑定来实现服务的其他特性。

 

【正文】

1 WCF服务的工作原理

从功能方面讲,WCF服务不外乎是一个对外公布了可供客户端调用的一系列操作的对象。当创建一个服务时,通过使用服务契约来描述该服务的操作;并创建一个类来实现该服务契约。为了运行服务,必须为服务对象提供一个运行时环境,该环境使服务能被客户端程序访问。寄宿服务的宿主程序提供运行时环境。从第一章,我们可以得知IIS能够提供这样的运行时环境。当然,你可以自行创建其他种类宿主程序,只要该程序能执行下列任务:

  • 启动和停止WCF服务
  • 侦听客户端的请求并且引导这些请求到服务
  • 服务端向客户端返回响应

为了理解宿主程序是如何工作的,我们先来了解服务端点及WCF运行时如何使用服务端点的绑定元素。下图展示了一个端点典型构成:端点=地址(A)+绑定(B)+契约(C)。

(1)端点。一个应用程序对外提供一个或者多个端点,以供外部应用程序访问。WCF服务的端点包含三个元素

地址(Address)

服务的地址受多个因素影响,包括它所使用的传输协议;因为不同的传输协议使用不同形式的地址空间。

绑定(Binding)

绑定用以描述客户端如何连接到服务端及服务端所需的数据的格式。绑定包含下列信息:

  • 传输协议,传输协议必须与服务的地址保持一致。如果你用是IIS来寄宿服务,那么你就应该执行使用http或者https传输协议;此外,WCF还支持TCP协议,命名管道,消息队列协议。在本章后后续内容中,你将会看到这些协议的具体使用。
  • 消息的编码方式,在大多数情况下,请求和返回消息将以XML格式(普通文本编码格式)进行传输。但是,在某些情况下,你可能需要先将数据进行二进制编码,然后再传输。这种情况适合于传输图像和处理流。在第13章,你将会了解到编码消息的详细内容。
  • 服务的安全性,你可以在消息层和传输层上实现安全性方面的需求;虽然各种的协议都有其自身限制和其他特别的要求。在第四章和第五章你将了解到安全方面的具体内容。
  • 服务的事务性,服务端允许客户端访问一个或者多个资源。客户端程序通过向服务端发送请求,然后更新资源。如果一个客户端一次发送多个更新请求,其结果将会导致服务端资源的多次更新,那么保证所有的更新都可以顺利完成就非常显得重要。如果发生意外,服务端应该自动回滚这些操作。这就是事务的定义。在第九章你可以了解到事务的详细信息。
  • 服务通信的可靠性,客户端通常通过多个网络连接到到服务。但是网络优势并不可靠,而且随时可能会失败,此时保证客户端与服务端可靠得进行对话就显得非常重要。比如,服务端能接受到客户端发送的所有消息,并确保这些信息是按照客户端发送的顺序依次到达服务端。 服务端通过实施可靠的消息协议以保证会话的一致性。在第10章,你将会了解到可靠性的相关知识。

契约

Contract)

WCF服务契约是存储在.NET Framework组件中的一个接口,并且用ServcieContract特性标识。服务契约描述了该服务所有的操作,这些操作通过OperationContract特性标识。任何操作的输入和输出数据都必须进行序列化。服务契约还规定了数据契约,数据契约用来描述复杂数据及如何序列化这些复杂数据。服务对外公布服务契约的描述信息,以使客户端程序能准确地调用相应的操作以及使用正确的消息格式想服务端发送请求消息。

(2)处理客户端请求

一个服务能同时响应多个客户端的请求。为了达到这个目的,宿主程序需处理多个客户端的请求,并将服务端的响应返回到对应的客户端。此外,宿主程序必须确保服务端和客户端之间的消息应满足其绑定所定义的安全性、可靠性和事务性方面的需求。幸运的是,你不必自己亲自写这些代码,因为WCF运行时环境为客户端和服务端提供一系列的通道对象用以实现上述需求。

通道根据服务端配置文件中的绑定信息,执行消息处理流程中的对应任务;比如,传输通道通过指定的传输协议进行通信;事务通道确保会话的一致性。WCF为每个传输协议都内建了相应的通道。WCF还为每种类型的通道提供了不同的数据编码,安全管理,可靠性和事务性支持。WCF运行时将这些通道组合成一个通道堆栈。所有服务端和客户端的消息都进入该堆栈中对应的通道。该堆栈中每一个通道以某种方式传输消息,当完成一个消息传送并将结果输出后,下一个消息进入该通道。通道堆栈双向运行:从接客户端接受到的消息经过该栈后传送至服务端;从服务端的返回消息经过该堆栈后通过网络回传给客户端。如果通道堆栈不能处理消息,它将产生一个错误,并将给错误消息返回至客户端,客户端的消息将不再继续处理。

当你启动服务时,WCF运行时读取服务配置文件中的端点信息并为配置文件中的每个地址创建一个侦听器。当请求达到该侦听器后,WCF运行时根据配置文件中的某个具体地址对于的绑定信息创建一个通道堆栈,然后引导来自客户端的数据通过该堆栈。如果一个消息通过堆栈中所有的通道,该请求将被传送给一个实例化的服务对象进行处理。

前面已经提到,WCF服务能同时处理来自多个客户端应用程序的请求。为了完成这个目标,WCF运行时创建多个同时存在的服务实例。 WCF运行时创建InstanceContext对象用以控制通道堆栈和服务实例之间的互操作。你可以通过InstatnceContext对象来修改服务实现类的ServcieBehavior特性,以修改WCF运行时创建服务实例的方式。ServieBehavior特性类有一个InstanceContextMode属性,它具有下列值:

描述

InstanceContextMode.PerCall

当客户端调用服务的一个操作时,该服务就创建一个新实例。当调用完成,服务实例回收该实例。

InstanceContextMode.PerSession

如果服务实现了Session;当session启动时创建一个新实例,当Session结束时,回收该实例。一个客户端能在session存活时间内多次调用该服务。但是每个服务实例只能访问一个session. 更多信息,请参考第七章。

InstanceContextMode.Single

只创建一个服务的实例。该实例能被所有客户端和sessions共享。此实例在第一个客户端访问该服务时创建。

默认的InstanceContextMode是PerCall。 你可以按照下列方式修改InstanceContextMode:

ServiceBehavior Attribute

请看下面三张图,它们形象地说明了WCF运行时创建服务实例的三种模式(注:图片来自http://www.codeproject.com/KB/WCF/WCFInstance.aspx)

第二章 寄宿WCF服务_第1张图片

第二章 寄宿WCF服务_第2张图片

第二章 寄宿WCF服务_第3张图片

WCF客户端程序能通过代理类与WCF服务进行通信。你可使用Visual Studio或svcuti.exe来生产代理类。代理类在客户端实现一个通道堆栈。你可以采用与服务端堆栈同样的方式配置客户端通道堆栈。所有服务端的响应将通过客户端通道堆栈。为了成功的通信,客户端和服务端应采用对应的通道堆栈,且绑定设置应相互兼容。

WCF通道堆栈与TCP/IP栈。
第二章 寄宿WCF服务_第4张图片

两者的共同点:栈中的每一层均提供该层下面的一些抽象,并仅向上的一层公开该抽象。而且在这两种情况下,两个堆栈通信时,每个层均与另一个堆栈中的相应层通信,例如,IP与IP层通信,TCP层与TCP层通信,以此类推。

两者的不同点:TCP堆栈旨在提供物理网络的抽样,而通道堆栈不仅提供消息传送方式的抽象,还提供其他功能(比如消息的内容或通信所使用的协议)的抽象。例如:可靠的会话绑定元素是通道堆栈的一部分,但却在不传输本身中。次抽象是通过要求堆栈中的底部通道是基础传输协议适应通道推展体系结构,然后依赖堆栈中更上层的通道通信功能来实现的。

WCF运行时架构

请移步至idior的博客Inside WCF runtime(http://www.cnblogs.com/idior/articles/971252.html)

2 使用WAS寄宿WCF服务

在第一章中,你已经学习到如何将一个WCF服务寄宿在IIS上。IIS提供了为Web服务提供了广泛的、可靠的宿主环境,这意味着,客户端程序通过互联网连接到该服务。Web服务使用HTTP协议进行通信传输。IIS侦听HTTP请求,如果一个请求到达,IIS激活对应的服务去处理该请求。虽然HTTP协议是一个非常适宜于通过互联网连接到Web服务的传输协议。但是,当在企业内部创建一个供内部客户端访问的服务时,应采用其他的协议。

WAS通过移除对HTTP协议的依赖而扩展了IIS的功能。使用WAS,你的寄宿服务可以采用其他的协议,比如TCP,命名管道和MSMQ。WAS能够侦听请求并激活一个采用其他协议并等待在对应地址上服务。到目前为止, 我们所学习到的WCF服务,在多数情况下,都只仅仅关注配置文件中的传输协议和地址的详置信息。因为服务契约,数据契约和服务的实现很大程序上独立于宿主环境和服务采用的传输协议。

在下面的练习中,你配置WAS使ProductService寄宿于其上,该服务通过TCP接受请求。你将更新第一章创建的ProductClient程序,使其能通过TCP协议连接到ProductService。

准备工作:安装和配置WAS。由于WAS在Windows 7上并没有默认安装和配置。你需要用管理员身份执行下列步骤:

Windows—开始菜单—控制面板—程序

添加删除Windows特性

选择Windows process activation service及其子项目;并选择.net framework 3.5及其子项目。然后点击OK

第二章 寄宿WCF服务_第5张图片

当安装完毕之后,请检查IIS对应的ASP.NET的版本。如果不是4.0版本,请在Visual Studio Command Prompt窗口中执行命令:aspnet_regiis -iru

(1)配置宿主环境使WCF服务支持TCP协议

用管理员身份运行IIS管理工具

在iis管理工具中,选择"编辑绑定"

第二章 寄宿WCF服务_第6张图片

站点绑定对话将将出现,如果你成功安装了WAS,那么将显示该网站的默认绑定协议。从下图中可以看到,WAS所采用的net.tcp协议将默认监听808端口。如果你想修改该端口,点击"Edit"按钮。在本章的例子中,我们将使用默认的端口。

第二章 寄宿WCF服务_第7张图片

关闭站点绑定对话框。

展开站点,选择ProductsService,并选择高级设置,对该服务添加tcp.net协议。

第二章 寄宿WCF服务_第8张图片

关闭高级设置对话框。

现在,ProductsService的宿主环境被配置为使用HTTP和TCP协议侦听请求。

(2)配置客户端应用程序,使其能通过TCP协议连接WCF服务

按照下面的文件接口复制第一章的ProductsClient项目

第二章 寄宿WCF服务_第9张图片

由于我们已经在IIS上部署了ProductsService,并且该服务使用HTTP和TCP协议,因为我们需要在ProductsClient项目中更新服务引用的地址
第二章 寄宿WCF服务_第10张图片

然后在弹出的对话框中输入地址:第二章 寄宿WCF服务_第11张图片

然后点击OK,Visual Studio将会自动更新。

更新完毕后,打开app.config文件,你会发现在配置文件中,多了一个endpoint和与之对应的banding信息。

修改Progrom.cs,更新proxy的定义。ProductsServcieClient proxy = new ProductsServcieClient("NetTcpBinding_IProductsServcie");

编译你的项目,然后查看运行结果。你将会发现,与第一章的结果完全一致;区别只不过是采用ProductsClient使用TCP传输协议连接到ProductsService罢了。

3 在应用程序中寄宿WCF服务

除了IIS或WAS,你还可以使用其他的方式寄宿WCF服务:

  • 你可以创建一个Windows程序寄宿WCF服务,用户使用它来启动和停止WCF服务
  • 你可以在Windows服务中寄宿WCF服务,这样只要Windows运行,你的服务也会一直运行
  • 你还可以在WF服务程序中寄宿WCF服务,这其实是Windows程序寄宿WCF服务的扩展,当然这种方式有其独有的定义和实现服务的方式。

在本章的后续内容中,你将会看到如何创建Windows应用程序和Windows服务来寄宿WCF服务。在第八章将学习如何使用WF来寄宿WCF服务。在我们开始练习前,你应该了解ServiceHost类。

(1)使用ServiceHost类

第二章 寄宿WCF服务_第12张图片

到目前为止,我们所创建的WCF宿主程序都能自动执行服务。如果你创建一个自己的程序,而不使用IIS或者WAS,你可以通过使用System.ServiceModel命名空间下的ServiceHost类来完成这样的任务。ServiceHost对象可以从包含服务类的组件中实例化一个服务对象;通过绑定信息来配置服务的端点,绑定信息可以功过配置文件或者代码方式进行设置;设置服务所需的安全需求;为你所指定的每个地址创建侦听对象

你通过指定服务实现类的类型,创建一个ServiceHost对象。你可以按照下列方式指定ServiceHost侦听请求的地址:

 
     
     
     
     
ServiceHost productsServiceHost = new ServiceHost( typeof (ProductsService), new Uri[] { new Uri( " http://localhost:8000/ProductsService/ProductsService.svc " ), new Uri( " tcp.net://localhost:8080/TcpProductsService " ) });

上述例子使用了在第一章中创建的ProductService服务。它使用了两个地址:第一个采用HTTP传输,第二个使用TCP。严格的来讲,你在ServcieHost构造函数中指定的地址是基本地址。基本地址只是地址初始化的一部分。当你在配置文件中设置了更详细的地址信息,那么这个详细的地址信息将和基本地址合并。比如:

 

 

ServiceHost
     
     
     
     
ServiceHost productsServiceHost = new ServiceHost( typeof (ProductsService),

new Uri( " http://localhost:8000/ProductsService/ " ));

在配置文件中,你做了如下配置:



 

 
*.Config
     
     
     
     
< endpoint address ="ProductsService.svc" binding ="basicHttpBinding" name ="ProductsServiceHttpEndpoint" contract ="Products.IProductsService" />
 
 

那么WCF运行时,将会把这两个元素合并而生成一个地址:http://localhost:8000/ProductsService/ ProductsService.svc。 这是一个非常有用的特性,管理员可以在某个站点上使用这个特性引导一个服务使用一个特殊的地址。此特性还可以让开发人员对寄宿服务的站点拥有控制权。

如果你在ServiceHost构造函数中,没有指定地址参数,那么WCF运行时将使用在配置文件中指定的地址信息,并且在所有配置的端点上侦听请求。这将赋予管理员对该服务所使用的地址和传输拥有完整的控制权。为了方便,在本章随后的练习中,我们将尽可能地在配置文件中指定详细的地址信息。但是,当创建企业应用是,你可以通过编程方式为服务端点指定基本地址。

 ServiceHost对象的初始化过程:

ServieHost(Type serviceTpye, Uri[]baseAddresses)

实例化 ServiceHost

--> InitializeDescription(serviceType, new UriSchemekeyedCollection(baseaddressessed))

Initializes a description of the service hosted based on its type and specified base addresses

根据服务的类型和基本地址,实例化Servicehost

---->base.InitializeDescription(baseAddresses);

Creates and initializes the service host with the contract and service descriptions

创建和实例化ServieHost,并指定服务契约和服务描述

 ------>CreateDescription(out dictionary);

创建Servicehost的描述

------>ApplyConfiguration();

从配置文件中加载服务描述信息,并应用到在WCF运行时已经创建的ServiceHost对象上。

当你创建了ServcieHost对象,你可以通过使用open方法开始侦听请求:

 
 

 

    
    
    
    
productsServiceHost.Open();

 启动ServiceHost对象,会让WCF运行时检查服务每个端点的绑定配置,并且在每个端点的地址上开始侦听。启动一个服务需要耗费一定时间。使用一个重载的方法可以通过指定时间间隔来启动服务,如果在指定的时间内服务未能启动,那么将会抛出异常。此外,ServiceHost还支持用异步方式(BeginOpen和EndOpen)启动服务。

   

通过调用ServiceHost的Close方法停止一个服务。Close使WCF运行时停止侦听请求,并且彻底地关闭服务。在服务被完全关闭前,处在运行状态的任务将被执行完。和启动服务一样,你也可以用异步方式(BeginClose和EndClose)关闭服务。

ServiceHost提供了一些事件,以供追踪ServieHost对象的状态。下表列出了这些事件:

事件

描述

Opening

当通信对象转换到正在打开状态时发生

Opened

当通信对象转换到已打开状态时发

Closing

当通信对象转换到正在关闭状态时发

Closed

当通信对象转换到已关闭状态时发

Faulted

在通信对象转换到出错状态时发

UnknownMessageReceived

接收未知消息时发

4 使用WPF寄宿WCF服务

创建WCF服务类库

创建一个WCF服务类库的新项目(项目名:ProductServiceLibrary;方案名:ProductServiceLibrary)

第二章 寄宿WCF服务_第13张图片

删除默认的IService.cs,Servcie.cs和app.config;复制第一章的IProductsServcie和ProductService到该项目中,并且将这两个文件包括到项目中。并且删除两个文件中的using System.ServiceModel.Web;

添加第一章ProductsEntityModel项目生产的DLL引用;添加Service.ServiceModel和System.Data.Entity引用。

此时,你的项目应该是这样的:

第二章 寄宿WCF服务_第14张图片

生成项目,确保没有警告和错误。

创建WPF寄宿WCF

添加一个新的WPF项目(ProductsServiceHost)到解决方案ProductServiceLibrary中.

第二章 寄宿WCF服务_第15张图片

引用System.ServiceModel和ProductsServiceLibrary项目,重命名MainWindow.xaml为HostController.xaml。

打开App.xaml,修改StartupUri为HostController.xaml

添加两个button控件(一个启动WCF服务,一个停止服务),一个label控件,一个textbox控件(显示WCF服务的状态)到HostController.xaml;其xaml文件与下面类似(不用完全一致,你完全可以自己定义控件的名字,位置等)

 

 

HostController.xaml
复制代码
     
     
     
     
< Window x:Class ="ProductsServiceHost.HostController" xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" Title ="Products Servcie Host" Height ="350" Width ="350" > < Grid > < Button Content ="Start" Height ="23" HorizontalAlignment ="Left" Margin ="42,47,0,0" Name ="btnStart" VerticalAlignment ="Top" Width ="75" Click ="btnStart_Click" /> < Button Content ="Stop" IsEnabled ="False" Height ="23" HorizontalAlignment ="Left" Margin ="213,47,0,0" Name ="btnStop" VerticalAlignment ="Top" Width ="75" Click ="btnStop_Click" /> < Label Content ="Service Status:" HorizontalAlignment ="Left" Margin ="50,104,0,179" Name ="label1" /> < TextBox Height ="26" IsReadOnly ="True" HorizontalAlignment ="Left" Margin ="137,106,0,0" Name ="txtStatus" VerticalAlignment ="Top" Width ="120" /> Grid > Window >
复制代码
 

添加启动和停止WCF的逻辑。完成后的.cs文件如下:

 

 

HostController逻辑
复制代码
     
     
     
     
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Data;

using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using Products;

namespace ProductsServiceHost
{
///
/// Interaction logic for HostController.xaml
///

public partial class HostController : Window
{
private ServiceHost productsServiceHost;

public HostController()
{
InitializeComponent();
}

private void handleException(Exception ex)
{
MessageBox.Show(ex.Message,
" Exception " , MessageBoxButton.OK, MessageBoxImage.Error);
}

private void btnStart_Click( object sender, RoutedEventArgs e)
{
try
{
productsServiceHost
= new ServiceHost( typeof (Products.ProductsService));
productsServiceHost.Open();
btnStop.IsEnabled
= true ;
btnStart.IsEnabled
= false ;
txtStatus.Text
= " Service Running " ;


DataTable table
= new DataTable();
table.Columns.Add(
" Address " , typeof ( string ));
table.Columns.Add(
" Binding " , typeof ( string ));
table.Columns.Add(
" Contract " , typeof ( string ));

foreach (ChannelDispatcher cd in productsServiceHost.ChannelDispatchers)
{
table.Rows.Add(cd.Endpoints[
0 ].EndpointAddress.Uri.AbsoluteUri,
cd.BindingName.Substring(cd.BindingName.LastIndexOf(
" : " ) + 1 ),
productsServiceHost.Description.ServiceType.Name);
}

listView1.DataContext
= table;

}
catch (Exception ex)
{
handleException(ex);
}
}

private void btnStop_Click( object sender, RoutedEventArgs e)
{
try
{
productsServiceHost.Close();
btnStop.IsEnabled
= false ;
btnStart.IsEnabled
= true ;
txtStatus.Text
= " Service Stopped " ;
}
catch (Exception ex)
{
handleException(ex);
}
}


}

}
复制代码

 

生成项目,确保没有警告和错误。

配置WPF

添加app.config到你创建的WPF项目中。

启动WCF配置工具配置WCF服务;你可以在app.config上点击右键,选择"编辑WCF配置"

第二章 寄宿WCF服务_第16张图片

或者通过Visual Studio->工具àWCF配置工具;然后打开app.config.

第二章 寄宿WCF服务_第17张图片

点击服务,在右侧的服务面板上点击"创建一个新服务",将出现"新服务向导"。

第二章 寄宿WCF服务_第18张图片

在服务类型页面,点击浏览按钮,打开ProductsServiceLibrary.dll,

第二章 寄宿WCF服务_第19张图片

然后,点击"打开"按钮,流行浏览器窗口将自动发现该程序集内的服务实现类。

第二章 寄宿WCF服务_第20张图片

再次点击"打开"按钮,将回到服务类型对话框;你将发现"Products.ProductsService"已经自动填充到服务类型文本框中。然后,点击"下一步",选择服务契约。

第二章 寄宿WCF服务_第21张图片

点击"下一步"选择通信模式,在本例中,我们将选择TCP通信协议。

第二章 寄宿WCF服务_第22张图片

点击下一步;输入端点的地址"net.tcp://localhost:8080/TcpService"

第二章 寄宿WCF服务_第23张图片

点击"下一步",进入预览界面;如果你觉得不需要修改设置,点击"完成"按钮。然后,你会发现你所创建的服务将会出现在左边"服务"下。

为Products.ProductsService端点设置名称:

第二章 寄宿WCF服务_第24张图片

在"文件"菜单,选择"保存",保存你的服务配置。

打开app.config,你将看到节点已经添加

第二章 寄宿WCF服务_第25张图片

生成项目,确保没有警告和错误。

创建WCF客户端

复制之前创建的的ProductsClient项目到ProductsServiceLibrary。

打开app.config,修该NetTcpBinding_IProductsService端点配置;修改其地址为net.tcp://localhost:8080/TcpService.

生成项目,确保没有警告和错误

测试:

设置ProductsServiceHost为启动项目,按F5启动该项目;

点击"start"按钮,启动WCF服务;

第二章 寄宿WCF服务_第26张图片

然后,通过下列方式启动测试项目ProductsClient:

第二章 寄宿WCF服务_第27张图片

你将会得到如下结果:

第二章 寄宿WCF服务_第28张图片

(1)配置服务以支持多个端点

启动WCF服务编辑工具,打开ProductsServcieHost项目的app.config文件;添加HttpBinding_IProductsServcie 端点。【注意:basicHttpBinding的地址必须是一个具体的地址,不能采用http://host/*,而必须是http://host/*Service/*.svc】

第二章 寄宿WCF服务_第29张图片

修改ProductsClient的app.config;修改basicHttpBinding端点的地址为http://localhost:8000/HttpService/ProductsService.svc

修改program.cs,使其使用basicHttpBinding端点与服务进行通信。

按照前述步骤测试。你将会得到如下结果:

第二章 寄宿WCF服务_第30张图片

第二章 寄宿WCF服务_第31张图片

请注意到客户端和服务端,我添加了部分功能,用以验证WCF的服务配置了多个端点;而且客户端当前是通过哪个协议与WCF服务端进行通信。

5 理解Endpoints和Bindings

到目前为止,你已经了解到端点和绑定是WCF框架中重要的组成部分。通过服务端点,你指定服务契约;提供服务侦听的地址;指定客户端连接到服务端的绑定;并对外公布该服务所包含的功能。在客户端,你也需指定这些信息,用以保证客户端能顺利地连接到服务端。

绑定犹如一头让你充满好奇心的野兽,因为它含有许多相当重要的信息。在前文中我们已经提到,通过绑定我们可以定义策略;这样,连接到该服务的客户端必须遵守此策略。 策略指通信机制(TCP,HTTP,MSMQ等);服务端的安全要求;服务端的事务规范;以及服务端如何提供可靠的通信,诸如此类信息。

一个WCF的绑定,包含一个或者多个绑定元素。一个绑定元素负责处理服务非功能性操作的某一个具体方面。比如:该服务是否支持事务,或者如何实现完全的服务。你通过各种不同的组合方式将各种元素组合在一起创建一个绑定。每一个绑定有一个描述传输协议的元素,一个处理消息编码的元素。你可以添加其他元素来增加服务的特性。绑定元素与通道紧密相关。当你使用宿主元素启动一个服务时,WCF运行时使用配置文件中的绑定信息创建服务端通道堆栈。当客户端通过代理对象连接到服务是,创建客户端通道堆栈。为了保证两者顺利通信,客户端与服务端的绑定配置应相匹配。

(1)WCF内置Bingdings

WCF类库在System.ServcieModel.Channels命名空间下提供了许多类来实现绑定元素。比如BinaryMessageEncodingBinaryElement类用来负责编码和解码XML消息;AsymmetricSecurityBindingELement类负责非对称加密。HttpsTransportBindingElement类负责使用HTTPS传输协议传送消息;ReliableSessionBindingElement类负责实现消息的可靠性。大多数绑定元素都提供了相应的属性,以供你修改修改该元素的工作方式。比如AsymmetricSecurityBindingELement类有一个名为DefaultAlgorithmSuite的属性,通过它你可以指定加密消息的算法。WCF还允许你自定义绑定元素(关于这点,非本书讨论的范围)。

虽然使用各种绑定元素组合绑定带来了很大的灵活性,但是并非所有的绑定元素组合在一起都是有意义的。此外,如果你创建全球性的解决方案,你一定要记住,在分布式环境中,并非所有的客户端和服务都是采用WCF开发。你应该使用和其他技术创建的服务和客户端向兼容的绑定。

WCF设计者在WCF类库的System.ServiceModel命名空间下提供了内置的绑定。在之前的练习中,你已经使用了两种绑定:BasicHttpBinding和NetTcpBinding。所有的内置绑定中,有一部分绑定用在Windows平台上;还有一些兼容WS-*规范,WS-I 1.1与1.2。下表列出了WCF内置的绑定。

绑定

描述

BasicHttpBinding

适用于与符合 WS-Basic Profile 的 Web 服务(例如基于 ASP.NET Web 服务 (ASMX) 的服务)进行的通信。此绑定使用 HTTP/HTTPS 作为传输协议,并使用文本/XML 作为默认的消息编码

BasicHttpContextBinding

BasicHttpBinding的扩展,支持并使用HTTP Cookies来存储和传输上下文消息。

WS2007HttpBinding

一个安全且可互操作的绑定,可为 Security, ReliableSession 的正确版本和 TransactionFlow 绑定元素提供支持。关于细节,将在第七章中介绍。

WSHttpBinding

一个安全且可互操作的绑定,适合于非双工服务约

WSHttpContextBinding

WSHttpBinding的扩展,实现了通过SOAP消息的头部信息来收发上下文消息。

WSDualHttpBinding

一个安全且可互操作的绑定,适用于双工服务协定或通过 SOAP 媒介进行的通信

WebHttpBinding

可用于为通过 HTTP 请求(而不是 SOAP 消息)公开的 WCF Web 服务配置终结点

WS2007FederationHttpBinding

一个安全且可互操作的绑定,它派生自 WS2007HttpBinding 并支持联合安全性。

WSFederationHttpBinding

一个安全且可互操作的绑定,支持 WS 联合协议并使联合中的组织可以高效地对用户进行身份验证和授权。

NetTcpBinding

一个安全且经过优化的绑定,适用于 WCF 应用程序之间跨计算机的通信

NetTcpContextBinding

NetTcpBinding的扩展,实现了通过SOAP消息的头部信息来收发上下文消息。

NetNamePipeBinding

一个安全、可靠且经过优化的绑定,适用于 WCF 应用程序之间计算机上的通信

NetMsmqBinding

一个排队绑定,适用于 WCF 应用程序之间的跨计算机的通信

MsmqIntegrationBinding

适用于 WCF 应用程序和现有消息队列(也称为 MSMQ)应用程序之间跨计算机的通信

下图是内置绑定的类图(WebHttpBinding不包括在内,因为其属于Syswem.ServiceModel.Web命名空)

第二章 寄宿WCF服务_第32张图片

(2)配置Bindings

本章后续内容将介绍通过用代码实例化绑定,使用该绑定创建端点;然后使用AddServiceEndpoint方法将该端点添加到ServiceHost类。类似地,在客户端你可以通过代码方式添加绑定到程序(第十一章将有详细的示例)。但是,通常情况下,都是通过配置文件来设置服务端和客户端的绑定信息。我们以ProductsClient程序的配置文件为例:

app.config
复制代码
     
     
     
     
xml version="1.0" encoding="utf-8" ?>
< configuration >
< system.serviceModel >
< bindings >
< basicHttpBinding >
< binding name ="BasicHttpBinding_IProductsServcie" closeTimeout ="00:01:00"
openTimeout
="00:01:00" receiveTimeout ="00:10:00" sendTimeout ="00:01:00"
allowCookies
="false" bypassProxyOnLocal ="false" hostNameComparisonMode ="StrongWildcard"
maxBufferSize
="65536" maxBufferPoolSize ="524288" maxReceivedMessageSize ="65536"
messageEncoding
="Text" textEncoding ="utf-8" transferMode ="Buffered"
useDefaultWebProxy
="true" >
< readerQuotas maxDepth ="32" maxStringContentLength ="8192" maxArrayLength ="16384"
maxBytesPerRead
="4096" maxNameTableCharCount ="16384" />
< security mode ="None" >
< transport clientCredentialType ="None" proxyCredentialType ="None"
realm
="" />
< message clientCredentialType ="UserName" algorithmSuite ="Default" />
security >
binding >
basicHttpBinding >
< netTcpBinding >
< binding name ="NetTcpBinding_IProductsServcie" closeTimeout ="00:01:00"
openTimeout
="00:01:00" receiveTimeout ="00:10:00" sendTimeout ="00:01:00"
transactionFlow
="false" transferMode ="Buffered" transactionProtocol ="OleTransactions"
hostNameComparisonMode
="StrongWildcard" listenBacklog ="10"
maxBufferPoolSize
="524288" maxBufferSize ="65536" maxConnections ="10"
maxReceivedMessageSize
="65536" >
< readerQuotas maxDepth ="32" maxStringContentLength ="8192" maxArrayLength ="16384"
maxBytesPerRead
="4096" maxNameTableCharCount ="16384" />
< reliableSession ordered ="true" inactivityTimeout ="00:10:00"
enabled
="false" />
< security mode ="Transport" >
< transport clientCredentialType ="Windows" protectionLevel ="EncryptAndSign" />
< message clientCredentialType ="Windows" />
security >
binding >
netTcpBinding >
bindings >
< client >
< endpoint address ="http://localhost:8000/HttpService/ProductsService.svc"
binding
="basicHttpBinding" bindingConfiguration ="BasicHttpBinding_IProductsServcie"
contract
="ProductsService.IProductsServcie" name ="BasicHttpBinding_IProductsServcie" />

< endpoint address ="net.tcp://localhost:8080/TcpService"
binding
="netTcpBinding" bindingConfiguration ="NetTcpBinding_IProductsServcie"
contract
="ProductsService.IProductsServcie" name ="NetTcpBinding_IProductsServcie" >
endpoint >
client >
system.serviceModel >
configuration >
复制代码

部分指定客户端的端点信息。每一个端点指明了其使用的绑定。端点Binding属性的值来自WCF内置的绑定;其名字采用骆驼拼写法(camelCase)命名标准。部分设置每个绑定的详细信息—这部分是可选的,因为你可以使用绑定的默认值。上述配置文件中,显示地设置了绑定的某些属性。在后续章节中,你会进一步了解到绑定的一些其他属性。通过WCF的SDK,你可以查询到每一个绑定所有属性信息。

当配置绑定时,有一个特性需要留意;你可以指定默认的绑定配置选项。比如下面的配置文件:

xyz.config
复制代码
     
     
     
     
xml version="1.0" ?>
< configuration >
< system.serviceModel >
< bindings >
< netTcpBinding >
< binding transferMode ="Streamed" >
netTcpBinding >
bindings >
< services >
< service name ="StreamingService" >
< endpoint address ="net.tcp://localhost:8080/StreamedTcpService"
binding
="netTcpBinding"
contract
="ProductsService.IProductsService"
name
="NetTcpBinding_IProductsService" />
service >
services >
system.serviceModel >
configuration >
复制代码

你添加了一个netTcpBinding配置,并设置其使用流传输模式。但是,请注意,该配置是匿名的;绑定并没有指定名字。服务StreamingService配置为使用NetTcpBinding绑定。该服务并未设置bindingConfiguration。因此该服务的绑定将使用系统定义的默认选项。此时,匿名绑定将发挥作用,并且覆盖系统定义的默认选项。更进一步来讲,匿名绑定将作用于对所有未指定bindingConfiguration的NetTcpBinding。

(3)默认的Endpoints

在第一章中创建ProductsSerice服务时,你并未在web.config中设置任何端点和绑定信息。但是,当宿主环境(IIS)启动该服务时,IIS基于HTTP传输协议和虚拟文件的逻辑地址(URL)自动创建了默认的端点。事实上,IIS调用了ServiceHost类中AddDefaultEndpoints方法生产了该端点的详细内容。

AddDefaultEndpoints方法根据服务包含契约的基本地址添加端点。举例来说,假设服务的基本地址是http://localhost/ProductsService,并且该服务的服务契约是Products.IProductsService。 AddDefaultEndpoints将为该服务创建一个BasicHttpBinding绑定,其逻辑地址将为基本地址加svc的全名(http://localhost/ProductsService/*.svc)。如果服务实现类实现了两个服务契约,AddDefaultEndpoints将添加两个端点,每个契约对应一个端点。类似地,如果一个服务设置了两个基本地址而且实现了两个服务契约,AddDefaultEndpoints将添加4个端点。

那么AddDefaultEndpoints是如何准确地选择哪个绑定?比如HTTP默认将使用BasicHttpBinding,AddDefaultEndpoints是如何做到这一点呢?它为什么不选择WSHttpBinding?原因在于,在machine.config文件中保存了对应的关系:【注意:这个对应的关系在我的电脑上没找到】

 

machine.config
复制代码
     
     
     
     
< system.ServiceModel >
...
< protocolMapping >
< clear />
< add scheme ="http" binding ="basicHttpBinding" bindingConfiguration ="" />
< add scheme ="net.tcp" binding ="netTcpBinding" bindingConfiguration ="" />
< add scheme ="net.pipe" binding ="netNamedPipeBinding" bindingConfiguration ="" />
< add scheme ="net.msmq" binding ="netMsmqBinding" bindingConfiguration ="" />
protocolMapping >
...
system.ServiceModel >
复制代码

 如果你想改变默认的设置,你可以添加到web.config或者app.config.其将覆盖machine.config中的配置。比如,你可以将默认的HTTP协议该为使用WSHttpBinding。

复制代码
    
    
    
    
< system.ServiceModel >
...
< protocolMapping >
< add scheme ="http" binding ="wsHttpBinding" bindingConfiguration ="" />
protocolMapping >
...
system.ServiceModel >
复制代码

  

6 在Windows Services中寄宿WCF服务

在结束本章之前,你将了解到另外一寄宿WCF服务的方式,并且还可以学习到如何使用代码方式为服务添加端点。

在程序中寄宿WCF服务依赖用户启动和停止服务,这要求当前登录到Windows系统的用户不能注销。一个较好的解决办法是在Windows服务中寄宿WCF服务。通过这种方式,你可以设置在开机时自动运行一个Windows服务,当然,系统管理员可以在需要的时候停止或重启该服务。

在下面的例子中,你将创建一个Windows服务用以寄宿ProductServcie服务,该服务只允许本机客户端程序访问该服务,并且该服务使用命名管道传输协议侦听固定的地址。

创建Windows服务

创建WindowsProductsService项目和方案

第二章 寄宿WCF服务_第33张图片

创建好项目后,冲命名Service1.cs为ServiceHostController.cs;在弹出的对话框中,点击"是";以更新项目中有关信息。

添加引用:System.ServiceModel, System.Runtime.Serialization和System.Data.Entity.

添加引用:ProductsEntityModel.dll,IProductsService.cs,ProductsService.cs,以及app.config;注意app.config将仅仅保留数据库连接字符串部分。

添加逻辑部分

Server Host逻辑
复制代码
      
      
      
      
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;

using System.ServiceModel;
using System.ServiceModel.Description;
using Products;

namespace WindowsProductsService
{
public partial class ServiceHostController : ServiceBase
{
private ServiceHost productsServiceHost;

public ServiceHostController()
{
InitializeComponent();

this .ServiceName = " ProductsService " ;
this .CanStop = true ;
this .AutoLog = true ;
}

protected override void OnStart( string [] args)
{
System.Diagnostics.Debugger.Launch();

productsServiceHost
= new ServiceHost( typeof (ProductsService));

NetNamedPipeBinding binding
= new NetNamedPipeBinding();
productsServiceHost.AddServiceEndpoint(
typeof (IProductsServcie),
binding,
" net.pipe://localhost/ProductsServicePipe "
);

productsServiceHost.Open();

}

protected override void OnStop()
{
productsServiceHost.Close();
}
}
}
复制代码

生成项目,并确认没有警告和错误

创建Windows服务安装程序

创建installer

第二章 寄宿WCF服务_第34张图片

修改组件serviceInstaller1的服务的名字为ProductsService,设置其启动方式为自动。

第二章 寄宿WCF服务_第35张图片

设置serviceProcessInstaller1的账户为LocalService

第二章 寄宿WCF服务_第36张图片

生成项目

安装服务

用管理员身份启动Visual Studio 2010命令行窗口

设置目的项目所在路径C:\****\Learning\WCF\Step.by.Step\Solutions\Chapter2\WindowsProductsService\WindowsProductsService\bin\Debug.

执行安装服务的命令:installutil WindowsProductsService.exe (删除服务:installutil WindowsProductsService.exe /u)

安装完成后,运行services.msc启动Windows服务窗口,双击ProductsService,然后启动该服务。

第二章 寄宿WCF服务_第37张图片

测试Windows服务

复制之前的客户端项目,然后使用WCF服务配置管理工具,创建一个客户端的端点;然后修改program.cs中相应的设置,你将可以得到运行结果;

或者,你可以通过编程的方式来创建客户端的端点,绑定;然后仿佛服务端。具体请看随后的源代码。

生成项目,如果一切OK,你可以得到如下结果:

第二章 寄宿WCF服务_第38张图片

注意:

你不可以通过添加服务引用的方式添加使用NetNamePipeBinding绑定创建的服务;

第二章 寄宿WCF服务_第39张图片

如果你在Windows服务中寄宿WCF时,采用HTTP绑定协议,你还需要做高级设置,详细内容请查询MSDN。

在域环境下,上述测试项目或许有错误。因为根据我的经历,在公司的电脑上执行返回结果为List时的请求是,报错。而家中电脑一切OK。如果有人知道详情,请指点。

7 总结

本章为了展示了如何创建WCF服务的宿主程序。你已经看到了使用各种不同的程序(WAS,WPF,Windows服务)来寄宿WCF服务。你也已经了解了如何指定WCF绑定的传输协议,编码方式,及其他非服务功能方面的特性,比如可靠性、安全性和事务性。我们还为你介绍了WCF内置的各种绑定。你还学习了如何为一个WCF指定多个绑定。最后你还了解到了如何通过配置文件和编写代码的方式设置绑定信息。

本章代码下载。

【附加内容】

有园子里的朋友,让找WCF整个框架的东西。本人不敢妄自菲薄,只能发一张图给大家。我相信,随着本书翻译过程的进行,大家对每不部分都熟悉了之后,再回头来看WCF的框架,必定有全面而深刻的认识。

第二章 寄宿WCF服务_第40张图片

-----其他参考-----

http://msdn.microsoft.com/zh-cn/library/ms731079.aspx 基本术语

http://msdn.microsoft.com/en-us/library/ms733107.aspx 端点

http://msdn.microsoft.com/en-us/library/ms731088.aspx channel

http://msdn.microsoft.com/en-us/library/aa347789.aspx 数据传输架构

http://msdn.microsoft.com/en-us/magazine/cc163412.aspx WCF Address in depth

http://msdn.microsoft.com/en-us/magazine/cc163447.aspx WCF Messaging Foundation

http://msdn.microsoft.com/en-us/magazine/cc163394.aspx WCF binding in depth

你可能感兴趣的:(编程语言,小技巧,WCF系列文章)