选择传输方式
传输层是通道堆栈的最低层。WCF中使用的主要传输有HTTP、TCP和命名管道。
WCF编程模型将终结点操作(如服务协定中所表示)与连接两个终结点的传输机制分隔开,这样决定如何向网络公开服务时,就具有一定的灵活性。
在WCF中,可以通过使用“绑定”(有一系列的绑定元素组成)来确定如何在终结点之间通过网络传输数据。
传输由传输绑定元素(绑定的一部分)表示。绑定包括可选的协议绑定元素(如安全)、必需的消息编码器绑定元素和必需的传输绑定元素。
传输会将消息的序列化形式发送到另一个应用程序,或者从另一个应用程序接收消息的序列化形式。
在选择传输方式后,必须选择一个使用此传输方式的绑定。
适合使用HTTP传输的情况:
HTTP是客户端与服务器之间的一个请求/相应协议。
最常见的应用程序包括与WEB服务器进行通信的WEB浏览器客户端。该客户端想服务器发送一个请求,服务器侦听客户端请求消息。当服务器接收到一个请求时,会返回相应,其中包含请求的状态。如果成功,将返回可选数据,如网页、错误消息或其他信息。
HTTP协议不是基于连接的,一旦发送了相应,就不会在维护任何状态。要处理多页事务,引用程序必须持续保持任何必要的状态。
在WCF中,HTTP传输绑定针对与旧式非WCF系统的互操作性进行了优化。如果所有通信方都使用WCF,则使用基于TCP或基于命名管道的绑定会更快。
适合使用TCP传输的情况:
TCP是一个基于连接、面向流的传递服务,具有端对端错误检测和更正功能。“基于连接”表示在交换数据前建立主机之间的一个通信会话。
TCP提供可靠的数据传递,并且易于使用。具体地说,TCP会通知发送方开始传递包,保证按发送时同样的顺序传递这些包,重新传递丢失的包,并确定数据包不重复。
WCF TCP传输已针对通信的两端均使用WCF的情形进行了优化。对于涉及不同计算机之间的通信的情形,此绑定是最快的WCF绑定。消息交换使用BinaryMessageEncodingBindingElement(二进制消息编码)来实现优化的消息传输。
TCP提供双工通信,因此可以用于实现双工协定,即使客户端位于网络地址转换(NAT)的后端也没有关系。
适合使用命名管道传输的情况:
命名管道是Windows操作系统内核中的一个对象,例如,进程可用于通信的一个共享内存区段。命名管道具有一个名称,可用于单一计算机上的进程之间的单向或双工通信。
当一台计算机上的不同WCF应用程序之间要求通信,并且希望阻止来自另一台计算机上的任何通信时,请使用命名管道传输。另一个限制是:从Windows远程桌面运行的进程可能只能用于同一Windows远程桌面会话,除非他们具有提升的特权。
选择消息编码器
在WCF中,可以通过绑定(有一系列绑定元素组成)来确定如何在终结点之间通过网络传输数据。
消息编码器由绑定堆栈中的消息编码绑定元素表示。绑定包括多个可选协议绑定元素(如安全绑定元素或可靠消息绑定元素)、一个必需的消息编码绑定元素以及一个必需的传输绑定元素。
消息编码绑定元素位于可选协议绑定元素之下和必须的传输绑定元素之上。在传出端,消息编码器序列化传出Message并将其传递到传输层。在传入端,消息编码器从传输层接收已序列化的Message并将其传递到更高的协议层(如果存在),如果不存在此协议层,则传递到应用程序。消息编码器是介于传输与应用程序之间的一种转换的动作。
当连接到预先存在的客户端或服务器时,因为需要将消息以另一种预期的方式来解码,所以不能选择使用特定消息编码。但是,如果正在编写一个WCF服务,可以通过多个终结点来公开服务,每个终结点使用不同的消息编码。这样客户端可以选择最佳的编码以通过最合适的终结点与特定的服务通话,还使客户端可以灵活地选择最适合的编码。
系统提供的编码器
1) TextMessageEncodingBindingElement,文本消息编码器,同时支持纯XML编码和SOAP编码。文本消息编码器的纯XML编码模式成为“纯旧式XML”(POX),以便于基于文本的SOAP编码进行区分。若要启用POX,请将MessageVersion属性设置为None。使用文本消息编码器可以与非WCF终结点交互操作。
2) BinaryMessageEncodingBindingElement,二进制消息编码器,使用精简的二进制格式并为WCF到WCF通信进行了优化,因此不可互操作。它也是WCF提供的所有编码器中性能最佳的编码。
3) MTOMMessageEncodingBindingElement,(消息传输优化机制)绑定元素,指定使用MTOM编码的消息的字符编码和消息版本。MTOM是一种用于在WCF消息中传输二进制数据的有效技术。MTOM编码器会尝试在效率和互操作性之间建立平衡。MTOM编码以文本形式传输大多数XML,但是按原样传输较大的二进制数据块,而不是将其转换为文本,以对其进行优化。就效率而言,在WCF提供的编码器中,MTOM结余在文本编码器(最慢)和二进制编码器(最快)之间。
传输配额
WCF传输的默认配额值基于资源的保守分配。这些默认值适合于开发环境和小型安装方案。如果某个安装耗尽了资源或是连接受到限制,则无论是否还有其他资源可用,服务管理员都应检查传输配额各个配额值。
传输配额具有三种类型的配额:
1) “超时”可减少通过长时间占用资源来实施的拒绝服务攻击。
2) “内存分配限制”可防止单个连接耗尽系统内存并拒绝为其他连接提供服务。
3) “集合大小限制”可限制对间接分配内存或限量供应的资源的消耗。
DEMO
1) 我们这里使用一个自承载服务来测试,先创建一个控制台程序,然后添加WCF服务接口:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Video12.Demo1.NetTcp
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "ICalculatorService" in both code and config file together.
[ServiceContract]
public interface ICalculatorService
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
}
2) 实现该WCF服务:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Video12.Demo1.NetTcp
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "CalculatorService" in both code and config file together.
public class CalculatorService : ICalculatorService
{
public double Add(double n1, double n2)
{
double result = n1 + n2;
Console.WriteLine("Received Add({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Subtract(double n1, double n2)
{
double result = n1 - n2;
Console.WriteLine("Received Subtract({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Multiply(double n1, double n2)
{
double result = n1 * n2;
Console.WriteLine("Received Multiply({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Divide(double n1, double n2)
{
double result = n1 / n2;
Console.WriteLine("Received Divide({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
}
}
3) 在Main方法中自承载该服务:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Video12.Demo1.NetTcp
{
class Program
{
static void Main(string[] args)
{
//使用Windows控制台程序来自承载服务
using(ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))
{
//打开承载服务,并且创建监听消息
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
}
}
}
}
4) 配置配置信息,这里使用的传输协议为net.tcp的传输协议。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Video12.Demo1.NetTcp.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
<!--设定自承载的访问地址,也就是SVCUTIL时要使用的地址 http://localhost:8732/Design_Time_Addresses/Video12.Demo1.NetTcp/CalculatorService/-->
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/Video12.Demo1.NetTcp/service"/>
</baseAddresses>
</host>
<!--添加一个支持net.tcp传输协定的终结点-->
<endpoint address="net.tcp://localhost:9000/Video12.Demo1.NetTcp/service" binding="netTcpBinding" bindingConfiguration="Binding1" contract="Video12.Demo1.NetTcp.ICalculatorService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<!--
Following is the expanded configuration section for a NetTcpBinding.
Each property is configured with the default values.
See the Message Security, and Transport Security samples in the WS binding samples
to learn how to configure these features.
-->
<!--设置netTcp绑定参数的属性-->
<bindings>
<netTcpBinding>
<binding name="Binding1" 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"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
5) 运行该服务后,再使用SVCUTIL来生成客户端代理服务类和配置文件。注意CMD要用管理员权限来运行。
1. 找到如下地址“C:\Windows\System32\cmd.exe”命令行工具,右键以管理员身份运行(WIN7系统下)。
2. 输入如下命令:“cd C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin”进入到svcuitl.exe工具所在的文件夹下。然后再输入一下代码生成代理类:
svcutil.exe /out:c:/ClientCode.cs /config:c:/app.config http://localhost/ServiceModelSamples/Service.svc
3.按回车进行生成客户端代理类
6) 创建一个客户端程序Client,然后把生成的两个文件拷贝到该项目里后,在Main方法里调用测试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Video12.Demo1.NetTcp
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "CalculatorService" in both code and config file together.
public class CalculatorService : ICalculatorService
{
public double Add(double n1, double n2)
{
double result = n1 + n2;
Console.WriteLine("Received Add({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Subtract(double n1, double n2)
{
double result = n1 - n2;
Console.WriteLine("Received Subtract({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Multiply(double n1, double n2)
{
double result = n1 * n2;
Console.WriteLine("Received Multiply({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Divide(double n1, double n2)
{
double result = n1 / n2;
Console.WriteLine("Received Divide({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
}
}
7) 设置成这两个项目同时运行,然后测试结果如下,这是用NET.TCP传输协议,然后用9000端口号来侦听。这里要注意使用NET.TCP传输协议的话要打开NoneHTTP激活的处理才可以运行。在控制面板——》程序——》打开或关闭Windows功能:
源代码: http://download.youkuaiyun.com/detail/eric_k1m/6451289
Net.TCP端口共享
DEMO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Video12.Demo2.PortSharing
{
//定义WCF服务接口:
[ServiceContract(Namespace = "http://Video12.Demo2.PortSharing")]
public interface ICalculator
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
//实现WCF服务
public class CalculatorService : ICalculator
{
public double Add(double n1, double n2) { return n1 + n2; }
public double Subtract(double n1, double n2) { return n1 - n2; }
public double Multiply(double n1, double n2) { return n1 * n2; }
public double Divide(double n1, double n2) { return n1 / n2; }
}
class Program
{
static void Main(string[] args)
{
//自承载WCF服务
//由于要使用NET.TPC Port Sharing Service 所以必须要使用NET.TCP绑定才可以。
NetTcpBinding binding = new NetTcpBinding();
binding.PortSharingEnabled = true;
//启动一个随机端口的服务
ServiceHost host = new ServiceHost(typeof(CalculatorService));
ushort salt = (ushort)(new Random()).Next();
string address = String.Format("net.tcp://localhost:9000/calculator/{0}", salt);
host.AddServiceEndpoint(typeof(ICalculator), binding, address);
host.Open();
Console.WriteLine("Service #{0} listening on {1}.", salt, address);
Console.WriteLine("Press <ENTER> to terminate service.");
Console.ReadLine();
host.Close();
}
}
}
2) 然后编译该程序,进入到bin目录里使用管理员身份打开“Video12.Demo2.PortSharing.exe”这个程序来运行该WCF服务。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Video12.Demo2.PortSharing
{
//在客户端也定义一个与服务端相同的服务协定,这样就不用再客户端应用客户端代理类和配置文件
[ServiceContract(Namespace = "http://Video12.Demo2.PortSharing")]
public interface ICalculator
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
class Program
{
static void Main(string[] args)
{
Console.Write("输入端口号测试:");
ushort salt = ushort.Parse(Console.ReadLine());
//定义访问WCF地址
string address = String.Format("net.tcp://localhost:9000/calculator/{0}", salt);
//实例化通道工厂 一个创建不同类型通道的工厂,客户端使用这些通道将消息发送到不同配置的服务终结点。
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(new NetTcpBinding());
//使用通道工厂创建通道
ICalculator proxy = factory.CreateChannel(new EndpointAddress(address));
// Call the Add service operation.
double value1 = 100.00D; double value2 = 15.99D;
double result = proxy.Add(value1, value2);
Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
// Call the Subtract service operation.
value1 = 145.00D;
value2 = 76.54D;
result = proxy.Subtract(value1, value2);
Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);
// Call the Multiply service operation.
value1 = 9.00D;
value2 = 81.25D;
result = proxy.Multiply(value1, value2);
Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);
// Call the Divide service operation.
value1 = 22.00D;
value2 = 7.00D;
result = proxy.Divide(value1, value2);
Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
factory.Close();
}
}
}
4) 使用管理员身份打开两个WCF服务程序后,在运行client客户端程序,进行测试,可以看到打开2个WCF服务程序,然后再打开2个客户端程序,然后再客户端程序里输入相应的WCF服务端口号后,都可以正常的调用该WCF服务。