EndpointOverview
WCF实际上是构建了一个框架,这个框架实现了在互联系统中各个Application之间如何通信。使得Developers和Architect在构建分布式系统中,无需在考虑如何去实现通信相关的问题,更加关注与系统的业务逻辑本身。而在WCFInfrastructure中,各个Application之间的通信是由Endpoint来实现的。
Endpoint的结构
Endpoint包含以下4个对象:
Address:Address通过一个URI唯一地标识一个Endpoint,并告诉潜在的WCFservice的调用者如何找到这个Endpoint。所以Address解决了WheretolocatetheWCFService?
Binding:Binding实现在Client和Service通信的所有底层细节。比如Client与Service之间传递的Message是如何编码的——text/XML,binary,MTOM;这种Message的传递是采用的哪种Transport——TCP,Http,NamedPipe,MSMQ;以及采用怎样的机制解决SecureMessaging的问题——SSL,MessageLevelSecurity。所以Binding解决的是Howtocommunicatewithservice?
Contract:Contract的主要的作用是暴露某个WCFService所提供的所有有效的Functionality。从MessageExchange的层面上讲,Contract实际上是抱每个Operation转化成为相对应的MessageExchangePattern——MEP(Request/Response;One-way;Duplex)。所以Contract解决的是WhatfunctionalitiesdotheServiceprovide?
Behavior:Behavior的主要作用是定制Endpoint在运行时的一些必要的Behavior。比如Service回调Client的Timeout;Client采用的Credentialtype;以及是否支持Transaction等。
当我们Host一个WCFService的时候,我们必须给他定义一个或多个Endpoint,然后service通过这个定义的Endpoint进行监听来自Client端的请求。当我们的Application需要调用这个Service的时候,因为Client和Service是通过Endpoint的进行通信的,所以我们必须为我们的Application定义Client端的Endpoint。只有当Client的Endpoint和Service端某个Endpoint相互匹配(Service端可以为一个Service定义多个Endpoint),Client端的请求才能被Service端监听到。也就是说,我们只有在Client具有一个与Service端完全匹配的Endpoint,我们才能调用这个Service。而这种匹配是比较严格的,比如从匹配Address方面,Client端和Service端的EndpointAddress不仅仅在URI上要完全匹配Service,他们的Headers也需要相互匹配。对于Binding,一般地,Client需要有一个与Service端完全一样的Binding,他们之间才能通信。
Sample
首先给一个Sample,以便我们对在WCFServiceAplication中如何定义Endpoint有一个感性的认识。整个Solution的结构参照下图,我的上一篇Blog([原创]我的WCF之旅(1):创建一个简单的WCF程序)中有详细的介绍。你也可以通过后面的Link下载相应的SourceCode(http://www.cnblogs.com/files/artech/Artech.WCFService.zip)
1.ServiceContract:Artech..WCfService.Contract/ServiceContract/IGeneralCalculator.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
namespaceArtech.WCFService.Contract
{
[ServiceContract]
publicinterfaceIGeneralCalculator
{
[OperationContract]
doubleAdd(doublex,doubley);
}
}
2.Service:Artech.WCFSerice.Service/GeneralCalculatorService.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingArtech.WCFService.Contract;
namespaceArtech.WCFService.Service
{
publicclassGeneralCalculatorService:IGeneralCalculator
{
IGeneralCalculatorMembers
}
}
3.Hosting:Artech.WCFService.Hosting/Program.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
usingArtech.WCFService.Contract;
usingArtech.WCFService.Service;
usingSystem.ServiceModel.Description;
namespaceArtech.WCFService.Hosting
{
classProgram
{
staticvoidMain(string[]args)
{
//HostCalculatorServiceViaCode();
HostCalculatorSerivceViaConfiguration();
}
///
///Hostingaserviceusingmanagedcodewithoutanyconfiguraitoninformation.
///Pleasenotethattherelatedconfigurationdatashouldberemovedbeforecallingthemethod.
///
staticvoidHostCalculatorServiceViaCode()
{
UrihttpBaseAddress=newUri("http://localhost:8888/generalCalculator");
UritcpBaseAddress=newUri("net.tcp://localhost:9999/generalCalculator");
using(ServiceHostcalculatorSerivceHost=newServiceHost(typeof(GeneralCalculatorService),httpBaseAddress,tcpBaseAddress))
{
BasicHttpBindinghttpBinding=newBasicHttpBinding();
NetTcpBindingtcpBinding=newNetTcpBinding();
calculatorSerivceHost.AddServiceEndpoint(typeof(IGeneralCalculator),httpBinding,string.Empty);
calculatorSerivceHost.AddServiceEndpoint(typeof(IGeneralCalculator),tcpBinding,string.Empty);
ServiceMetadataBehaviorbehavior=calculatorSerivceHost.Description.Behaviors.Find();
{
if(behavior==null)
{
behavior=newServiceMetadataBehavior();
behavior.HttpGetEnabled=true;
calculatorSerivceHost.Description.Behaviors.Add(behavior);
}
else
{
behavior.HttpGetEnabled=true;
}
}
calculatorSerivceHost.Opened+=delegate
{
Console.WriteLine("CalculatorServicehasbeguntolisten");
};
calculatorSerivceHost.Open();
Console.Read();
}
}
staticvoidHostCalculatorSerivceViaConfiguration()
{
using(ServiceHostcalculatorSerivceHost=newServiceHost(typeof(GeneralCalculatorService)))
{
calculatorSerivceHost.Opened+=delegate
{
Console.WriteLine("CalculatorServicehasbeguntolisten");
};
calculatorSerivceHost.Open();
Console.Read();
}
}
}
}
4.Service.svc:http://localhost/WCFService/GeneralCalculatorService.svc
<%@ServiceHostLanguage="C#"Debug="true"Service="Artech.WCFService.Service.GeneralCalculatorService"%>
5.Client:Artech.WCFService.Client/GeneralCalculatorClient.cs&Program.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
usingSystem.ServiceModel.Channels;
usingArtech.WCFService.Contract;
namespaceArtech.WCFService.Client
{
classGeneralCalculatorClient:ClientBase,IGeneralCalculator
{
publicGeneralCalculatorClient()
:base()
{}
publicGeneralCalculatorClient(stringendpointConfigurationName)
:base(endpointConfigurationName)
{}
publicGeneralCalculatorClient(Bindingbinding,EndpointAddressaddress)
:base(binding,address)
{}
IGeneralCalculatorMembers
}
}
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
usingSystem.ServiceModel.Channels;
usingArtech.WCFService.Contract;
namespaceArtech.WCFService.Client
{
classProgram
{
staticvoidMain()
{
try
{
//InvocateCalclatorServiceViaCode();
InvocateCalclatorServiceViaConfiguration();
}
catch(Exceptionex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
staticvoidInvocateCalclatorServiceViaCode()
{
BindinghttpBinding=newBasicHttpBinding();
BindingtcpBinding=newNetTcpBinding();
EndpointAddresshttpAddress=newEndpointAddress("http://localhost:8888/generalCalculator");
EndpointAddresstcpAddress=newEndpointAddress("net.tcp://localhost:9999/generalCalculator");
EndpointAddresshttpAddress_iisHost=newEndpointAddress("http://localhost/wcfservice/GeneralCalculatorService.svc");
Console.WriteLine("Invocateself-hostcalculatorservice");
InvocateSelf-hostservice
Console.WriteLine("\n\nInvocateIIS-hostcalculatorservice");
InvocateIIS-hostservice
}
staticvoidInvocateCalclatorServiceViaConfiguration()
{
Console.WriteLine("Invocateself-hostcalculatorservice");
InvocateSelf-hostservice
Console.WriteLine("\n\nInvocateIIS-hostcalculatorservice");
InvocateIIS-hostservice
}
}
}
6.Self-HostingConfiguration:Artech.WCFService.Hosting/App.config
7.IIS-HostConfiguration:
8.Clientconfiguration:Artech.WCFService.Client/App.config
如何在Application中定义Endpoint
对于Self-Host的Service,绝大部分的Endpoint相关的信息都具有两种定义方式——ManagedCode和Configuration。而对于把ServiceHost到IIS中的情况,Endpoint的信息一般虚拟根目录下的Web.Config中定义。一般的我们我们不推荐使用代码的方式Host和调用Service,这主要是基于以下的理由。首先我们开发的环境往往与部署的环境不尽相同,才用configuration的方式是的我们可以在部署的时候通过修改配置文件以适应新的需要。其次,对于不要出现的新的需要,比如对于一个原来只供Internet内部使用的Service,我们一般会定义一个基于TCP的Endpoint,现在出现来自于Internet的潜在用户,我们只需要通过修改Config文件的方式为这个Service添加一个新的基于Http的Endpoint就可以了。把Endpoint的信息写在config文件中的优势在于,修改config文件的内容是不需要重新编译和重新部署的。相应的定义方式清参照以上的Sample。
下面我们来看看在host一个Service的时候,置于配置文件的信息是如何起作用的。在上面的例子中我们通过下面一段代码Host一个Serivice(对应得ServiceType是GeneralCalculatorService)。
using(ServiceHostcalculatorSerivceHost=newServiceHost(typeof(GeneralCalculatorService)))
{
calculatorSerivceHost.Opened+=delegate
{
Console.WriteLine("CalculatorServicehasbeguntolisten");
};
calculatorSerivceHost.Open();
Console.Read();
}
下面是Service相关的配置信息:
首先我们创建一个ServiceHost对象calculatorSerivceHost,同时指定对用的ServiceType信息(typeof(GeneralCalculatorService))。WCFInfrastructure为在配置文件在ServicesSection寻找是否有相对用的service定义。在这个例子中,他会找到一个name属性为Artech.WCFService.Service.GeneralCalculatorService的Service。然后他会根据定义在Service中的Endpoint定义为calculatorSerivceHost添加相应的Endpoint。如果有对应的EndpointBehavior设置存在于配置文件中,这些Behavior也会设置到改Endpoint中。最后调用Open方法,calculatorSerivceHost开始监听来自Client端的请求。
Address
每一个Endpoint都必须有一个Address,Address定位和唯一标志一个Endpoint。在Managedcode中,Address由System.ServiceModel.EndpointAddress对象来表示。下面是一个Adress的结构:
URI:指定的Endpoint的Location。URI对于Endpoint是必须的。
Identity:当另一个Endpoint与此Endpoint进行消息交互时,可以获取该Identity来Authenticate正在与之进行消息交互的Endpoint是否是它所希望的。Identity对于endpoint是可选的。
Headers:Address可以包含一些可选的Headers,这些header最终会加到相应的SoapMessage的Header中。Header存放的多为Address相关的信息,用于进行AddressingFilter。
Address的主要作用就是同过Uri为Service提供一个监听Address。但在某些特殊的场景中,我们可以借助Address的Headers提供一些扩展的功能。在大多数的情况下Client可以直接访问Service,换句话说,如果我们把Message传递的路径看成是以系列连续的节点(Node)的话,Message直接从Client所在的节点(Node)传递到最终的Service的节点。但在某些情况下,考虑的实现负载平衡,安全验证等因素,我们需要在Client和最终的Service之间加入一些中间节点(Intermediaries),这些中间节点可以在Message到达最终ServiceNode之前作一些工作,比如为了实现负载平衡,它可以把MessageRequest分流到不同的节点——Routing;为了在Message达到最终Node之前,验证Client是否是一个合法的请求,他可以根据Message存储的Credential的信息验证该请求——Authentication。
这些Intermediaries操作的一般不会是MessageBody的内容(大多数情况下他们已经被加密),而是MessageHeader内容。他们可以修改Header的内容,也可以加入一些新的Header。所以为了实现Routing,我们需要在Message加入一些Addressing相关的内容,为了实现Authentication我们需要加入ClientCredential的信息,而这些信息都放在Header中。实际上你可以把很多内容加到Header中。
我们可以通过config文件加入这些Header:
admin
binding="wsHttpBinding"bindingConfiguration=""contract="Artech.WCFService.Contract.ISessionfulCalculator">
admin
Binding
WCF,顾名思义就是实现了分布式系统中各Application之间的Communication的问题。上面我们说过,Client和Service之间的通信完全有他们各自的Endpoint的担当。Address解决了寻址的问题,通过Address,Client知道在哪里可以找到它所希望的Service。但是知道了地址,只是实现的通信的第一步。
对于一个基于SOA的分布式系统来说,各Application之间的通信是通过MessageExchange来实现的。如何实现在各个Application之间进行Message的交互,首先需要考虑的是采用怎样的Transport,是采用Http呢,还是采用TCP或是其他,比如NamedPipe、MSMQ。其次需要考虑的是Message应该采取怎样的编码,是text/XML呢,还是Binary,或是MTOM;此外,对于一个企业级的分布式应用,Security与Robustness是我们必须考虑的问题——我们应该采用TransportLevel的Security(SSL)还是MessageLevel的Security;如何确保我们的Message的传递是可靠的(ReliableMessaging);如何把在各application中执行的操作纳入同一个事物中(Transaction)。而这一些都是Binding需要解决的问题。所以我们可以说Binding实现了Client和Service通信的所有底层细节。
在WCF中,Binding一个BindingElement的集合,每个BindingElement解决Communication的某一个方面。所有的BindingElement大体上可以分为以下3类:
1.TransportBindingElement:实现Communication的Transport选取,每个Binding必须包含一格TransportElement。
2.EncodingBindingElement:解决传递数据的编码的问题,每个Binding必须包含一个EncodingElement,一般由TransportBindingElement来提供。
3.ProtocolBindingElement:解决Security,ReliableMessaging和Transaction的问题。
下边这个表格列出了Binding中的各个层次结构。
Layer
Options
Required
Transactions
TransactionFlowBindingElement
No
Reliability
ReliableSessionBindingElement
No
Security
SecurityBindingElement
No
Encoding
Text,Binary,MTOM,Custom
Yes
Transport
TCP,NamedPipes,HTTP,HTTPS,MSMQ,Custom
Yes
|
|