WCF 通道模型和绑定(1)
从SOA的实现来看,客户与服务、服务与服务之间的消息传递是关键模块之一。在WCF的模型中,无论通讯的双方处在完全不同的局域网内,还是恰好被寄宿在同一进程内,编程模型都将通信的另一段视为“远端”,都会为传输简历完整的通道和编码器。而绑定和地址,也正是终结点中和通信有关的两个要素。
1.WCF通道模型
绑定的本质是一个配置好的通道栈,为了方便程序员专注于业务逻辑,WCF提供了一系列常用的绑定。但是在学习绑定之前,了解WCF通道模型有助于读者对消息的发送和接收有更直观地理解。本节将介绍WCF通道模型。
1.1WCF通道模型概述
在WCF编程模型中,无论交互的另一方得具体位置在哪里,WCF都会为消息的发送和接收建立一套完整的消息管道,这个消息管道也被称为通道栈(channel stack)。通道栈中的每一个通道组件,都有机会对消息进行处理,而整个通道栈是可编辑并且可插入的,这就确保WCF的通道模型具有相当大的灵活性。另外,WCF通道模型是完全和上层程序隔离的,任何一个服务/客户端都可以轻松地配置到不同的通道模型上去。
通道模型可以被划分为上下两个部分,上面部分的通道被称为协议通道,而下部分的通道被称为传输通道。一个通道栈可以拥有任意个协议通道,但一般只拥有一个传输通道。 传输通道负责把消息进行编码并且发送到远端,编码时传输通道需要使用通道栈的编码器。下图展示了通道模型的结构。
一般而言,协议通道负责维护消息的非业务逻辑功能,这样的功能包括:事务、日志、可靠信息、安全性等。程序员可自定义协议通道并且插入到通道栈中。在一个通道栈中,必须包含至少一个传输通道和编码器,传输通道负责消息的编码和发送。具体地,传输通道会尝试从BindingContext对象中查找编码器。如果没有找到则会使用默认的编码器,在完成消息的编码之后,传输通道负责吧消息发送到远端,这里不同的传输通道将是用不同的传输协议,例如HTTP、TCP、IPC等。
1.2消息交换模式和通道形状
站在消息传输层面,WCF一共支持6种消息交互模式,分别为:数据包模式、请求-相应模式、双工模式、会话数据报模式、会话请求-响应模式和会话双工模式。一个通道可以支持其中一种或者集中交互模式,通道通过通道形状(channel shape)来实现具体的消息交互模式。
所谓的通道形状,指的是定义了发送、接收消息动作的10个借口,它们均集成自IChannel接口。这10个借口是:
IInputChannel,IOutputChannel,IRequestChannel,IReplyChannel,IDuplexChannel,IInputSessionChannel,IOutputSessionChannel,IRequestSessionChannel,IReplySessionChannel,IDuplexSessionChannel
下面来介绍消息交互模式,以及和具体模式有关的通道形状。
1.3数据报模式
数据报模式指的是发送端负责把消息发送给对方并且受到确认消息之后,就完成消息交互的方式。在这种模式下,发送方唯一能够确定的就是消息发送成功,而对于消息是否最终到达服务的终结点、是否被成功处理、返回结果如何都一无所知。下图展示了数据报模式的传输机制。
采用数据报模式的客户端通信实现IOutputChannel接口,而采用数据报模式的服务端通道则实现IInputChannel接口。在实际项目中,程序员直接使用数据报消息模式的机会并不多。
1.4 请求-响应模式
在请求-响应模式中,客户端发送一个消息并且接收一个返回消息来完成一次交互。请求-响应模式可以看作是一种特殊的双工模式。在该模式中,消息的发起端必然是客户端,并且从服务端返回的只有一条消息。客户端在发送出消息后会阻止当前线程并且等待服务端返回消息。这样的模式很适合用于HTTP协议。下图,展示了请求-响应模式的传输机制。
1.5.双工模式
在双工模式中,客户端和服务端都可以任意地向对方发送消息,而对方也可以以任意的次序来接收消息。在这种模式下,发送端和接收端的概念变得不再使用,取而代之的通信的两个端点。下图展示了双工模式的传输机制。
为了实现双工模式,通信段需要实现IDuplexChannel接口。IDuplexChannel接口实际同时实现了IInputChannel接口和IOutputChannel接口,并且使用IInputChannel和IOutputChannel来实现接收和发送消息。所以读者可以将其理解为一对IInputChannel接口和IOutputChannel接口的组合。其中,IInputChannel负责接收信息,而IOutputChannel负责发送消息。
1.6会话数据包模式、会话请求-响应模式和会话双工模式
WCF提供了这3中基本传输模式的带会话模式,分别为带会话的数据报模式、带会话的请求-响应模式和带会话的双工模式。从通信层面来说,会话的概念类似于网络协议中的连接概念。带会话的通信模式类似于面向连接的网络协议,而不带回话的通信模型带相当于无连接的网络协议。一个典型的例子就是TCP协议和UDP协议。
说明:在带会话的3个通信模式中使用的通道形状是:
IInputSessionChannel,IOutputSessionChannel,IRequestSessionChannel,IReplySessionChannel,IDuplexSessionChannel
在通道目标模型中,每个逻辑会话都表现为一个会话通道的实例。因此,由客户端创建冰灾服务端接受的每个新会话都与每一段的一个新会话通道响应。下图展示了带会话的传统模式和不带会话的传统模式区别。
无会话通信模式:
会话通信模式:
1.7通道形状的改变
在使用CustomerBinding时,读者可能已经发现,通信模式和通信协议是密切相关的。例如在使用HTTP协议进行消息传输时,收到协议的限制,数据报模式和双工模式并不能被使用。这个问题的解决方法是进行通道形状改变。通道形状的改变,指的是在传输通道上层添加特定的协议通道,来强制使用某种传输通道不支持的传输模式。
BindingElement[] bindingElements = new BindingElement[3];
bindingElements[0] = new TextMessageEncodingBindingElement();
//OneWayBindingElement可以使得传输通道支持数据报模式
bindingElements[1] = new OneWayBindingElement();
bindingElements[2] = new HttpTransportBindingElement();
这里添加了OneWayBindingElement协议通道来进行通道形状改变。在WCF中,一共有两种协议通道类型用以通道形状改变,分别为:OneWayBindingElement和CompositeDuplexBindingElement。前者把通道形状改变为数据报模式,而后者则把通道形状改变为双工模式。
1.8通道形状和上层服务协议
通道栈会使用通道形状来实现不同的消息交互模式。举例来说,基于UDP的传输通道会实现IInputChannel和IOutputChannel。这是因为其天生的数据报交互模式,而有些通道(比如基于TCP协议的通道)则会实现多种通道形状。WCF会根据上层服务协议来自动选取需要的通道形状。下面列举了不同服务协议的设置下,WCF会使用的通道形状。
单程 | 请求-应答 | 会话 | 回调 | 通道形状 |
Any | Any | NO | Yes | IDuplexChannel |
Any | Any | No | Yes | IDuplexSessionChannel |
Any | Any | Yes | Yes | IDuplexSessionChannel |
Yes | Yes | No | No | IDuplexChannel |
Yes | Yes | No | No | IRequestChannel |
Yes | Yes | NO | No | IDuplexSessionChannel |
Yes | Yes | Yes | No | IDuplexSessionChannel |
Yes | Yes | Yes | No | IRequestSessionChannel |
Yes | No | No | No | IOutputChannel |
Yes | No | No | No | IDuplexChannel |
Yes | No | No | No | IDuplexSessionChannel |
Yes | No | No | No | IRequestChannel |
Yes | No | Yes | No | IOutputSessionChannel |
Yes | No | Yes | No | IDuplexSessionChannel |
Yes | No | Yes | No | IRequestSessionChannel |
No | Yes | No | No | IRequestChannel |
No | Yes | No | No | IDuplexChannel |
No | Yes | No | No | IDuplexSessionChannel |
No | Yes | Yes | No | IRequestSessionChannel |
No | Yes | Yes | No | IDuplexSessionChannel |
说明:不是所通道都会实现所有通道形状的,有时候WCF会被迫使用其他的通道形状。举例来说,如果通道没有实现IInputChannel和IOutputChannel,WCF会尝试使用IDuplexChannel或者IRequestChannel/IReplyChannel来代替。
1.9 通道管理器
在WCF中,实现了两类通道管理器,分别实现IChannelListener<T>和IChannelFactory<T>接口。
IChannelListener<T>负责接收端的消息交互控制工作。这些通道管理器负责侦听消息、建立通道栈,并且提供通道栈顶层通道的引用。大多数情况下,程序员并不需要直接使用IChannelListener<T>接口,而是直接使用ServiceHost类型,而在ServiceHost类型内部,仍然使用了IChannelListener<T>来实现通道的管理。
//建立ChannelListner
IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(new Uri("http://localhost:9090/RequestReplyService"), new BindingParameterCollection());
listener.Open();
//创建IReplyChannel
IReplyChannel replyChannel = listener.AcceptChannel();
replyChannel.Open(); //打开 IReplyChannel
与IChannelListener<T>对应的是IChannelFactory<T>接口,这个管理器负责在发送端控制消息的发送。和IChannelListener<T>一样,IChannelFactory<T>负责创建并管理通道栈。大多数管理员会使用ClientBase<T>类型而代替直接使用IChannelFactory<T>接口。
//创建ChannelFactory
IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>(new BindingParameterCollection());
factory.Open();
//这里创建IRequestChannel
IRequestChannel requestChannel = factory.CreateChannel(new EndpointAddress("http://localhost:9090/RequestReplyService"));
requestChannel.Open();
两个通道管理器有一个明显的区别,那就是IChannelFactory<T>负责关闭通道栈,而IChannelListener<T>却不需要。也就是说,IChannelListener<T>可以独立于它的通道栈而关闭。
1.10 ICommunicationObject接口和状态改变
一般来说,和通信有关的机制都会附带状态机,其状态转换与分配网络资源、生成或接受连接、关闭连接以及终止通信有关。在WCF中,ICommunctionObject接口定义状态机制的统一模型。基本所有的通信组件,包括通道和通道管理器都实现了IcommunicationObject接口。ICommunicationObject接口的定义如下所示:

// 摘要:
// 为系统中所有面向通信的对象(包括通道、通道管理器、工厂、侦听器以及调度程序和服务主机)定义基本状态机的协定。
public interface ICommunicationObject
{
// 摘要:
// 获取面向通信的对象的当前状态。
//
// 返回结果:
// 对象的 System.ServiceModel.CommunicationState 的值。
CommunicationState State { get; }
// 摘要:
// 当通信对象完成从正在关闭状态转换到已关闭状态时发生。
event EventHandler Closed;
//
// 摘要:
// 当通信对象首次进入正在关闭状态时发生。
event EventHandler Closing;
//
// 摘要:
// 当通信对象首次进入出错状态时发生。
event EventHandler Faulted;
//
// 摘要:
// 当通信对象完成从正在打开状态转换到已打开状态时发生。
event EventHandler Opened;
//
// 摘要:
// 当通信对象首次进入正在打开状态时发生。
event EventHandler Opening;
// 摘要:
// 使通信对象立即从其当前状态转换到关闭状态。
void Abort();
IAsyncResult BeginClose(AsyncCallback callback, object state);
IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state);
IAsyncResult BeginOpen(AsyncCallback callback, object state);
IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state);
void Close();
void Close(TimeSpan timeout);
void EndClose(IAsyncResult result);
void EndOpen(IAsyncResult result);
void Open();
void Open(TimeSpan timeout);
}
从上面代码中可以看出,ICommunicationObject的目的集中在状态机的管理上,包括改变状态的方法、状态改变触发的时间一级当前状态的获取。在该状态机中,一共有6种可用状态,这些状态定义在CommunicationState枚举类型中。其定义如下所示。
public enum CommunicationState
{
Created = 0, //已创建
Opening = 1, //打开中
Opened = 2, //已打开
Closing = 3, //关闭中
Closed = 4, //已关闭
Faulted = 5, //错误
}
// 摘要:
// 定义 System.ServiceModel.ICommunicationObject 可存在的状态。
public enum CommunicationState
{
// 摘要:
// 指示通信对象已实例化且可配置,但尚未打开或无法使用。
Created = 0,
//
// 摘要:
// 指示通信对象正从 System.ServiceModel.CommunicationState.Created 状态转换到 System.ServiceModel.CommunicationState.Opened
// 状态。
Opening = 1,
//
// 摘要:
// 指示通信对象目前已打开,且随时可供使用。
Opened = 2,
//
// 摘要:
// 指示通信对象正转换到 System.ServiceModel.CommunicationState.Closed 状态。
Closing = 3,
//
// 摘要:
// 指示通信对象已关闭,且不再可用。
Closed = 4,
//
// 摘要:
// 指示通信对象发生错误,无法恢复且不再可用。
Faulted = 5,
}
在初始状态下,所有的状态机都维持在Created的状态下,随着系统的运行,ICommunicationObject对象中的方法将被调用改变状态机的状态。
注意:状态机的改变都是前向的。以下图而言,状态的改变都是从上至下的,这就意味着WCF的状态机不可逆。一旦某个状态机到达了Closeed或者Faulted的状态,就无法再回到Created状态,这意味着程序需要重新创建新的状态机。
ICommunicationObject提供了5个状态改变事件,分别为Opening、Opened、Faulted、Closing和Closed。灵活应用这些事件可以在状态改变时执行特定的状态。ClientBase<T>就是一个实现了ICommunicationObject的类型。以该类型为例,下面演示状态改变事件的使用。代码,如下所示:
服务器端:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace HelloWorldServiceHost
{
//服务的宿主
class ServiceHostMainClass
{
/// <summary>
/// 入口方法
/// </summary>
static void Main(string[] args)
{
//由于MyHostService实现了IDisposable接口,所以使用using
using (MySerivceHost host = new MySerivceHost())
{
host.Open();
//等待客户端访问
Console.Read();
}
}
}
/// <summary>
/// 封装了ServiceHost和其构造过程
/// </summary>
public class MySerivceHost:IDisposable
{
//ServiceHost对象
private ServiceHost _myHost;
//基地址
public const String BaseAddress = "net.pipe://localhost";
//可选地址
public const String HelloWorldServiceAddress = "HelloWorld";
public const String GoodByeAddress = "GoodBye";
//服务契约定义类型
public static readonly Type ContractType = typeof(HelloWorldService.IService);
//服务契约实现类型
public static readonly Type ServiceType = typeof(HelloWorldService.Service);
//服务只定义了一个绑定
public static readonly Binding HelloWorldBinding = new NetNamedPipeBinding();
/// <summary>
/// 构造ServiceHost对象
/// </summary>
protected void ConstructServiceHost()
{
//初始化ServiceHost对象
_myHost = new ServiceHost(ServiceType, new Uri[] { new Uri(BaseAddress) });
//添加一个终结点
_myHost.AddServiceEndpoint(ContractType, HelloWorldBinding, HelloWorldServiceAddress);
//_myHost.AddServiceEndpoint(ContractType, HelloWorldBinding, GoodByeAddress);
}
/// <summary>
/// ServiceHost只读属性
/// </summary>
public ServiceHost Host
{
get
{
return _myHost;
}
}
/// <summary>
/// 打开服务
/// </summary>
public void Open()
{
Console.WriteLine("开始启动服务。。。");
_myHost.Open();
Console.WriteLine("服务已经启动。。。");
}
/// <summary>
/// 构造方法
/// </summary>
public MySerivceHost()
{
ConstructServiceHost();
}
/// <summary>
/// ServiceHost实现了IDisposable接口,这里调用它的Dispose方法
/// </summary>
public void Dispose()
{
//ServiceHost显示实现了IDisposable的Dispose方法,所以这里要先强制转换成IDisposable类型
if (_myHost != null)
(_myHost as IDisposable).Dispose();
}
}
}
服务契约定义类型和实现:

using System;
using System.ServiceModel;
namespace HelloWorldService
{
//HelloWorld的服务契约
[ServiceContract]
public interface IService
{
//服务操作
[OperationContract]
String HelloWorld(String name);
//服务操作
[OperationContract]
String GoodBye(String name);
}
}
using System;
using System.ServiceModel;
namespace HelloWorldService
{
//服务的实现
public class Service : IService
{
//服务操作实现
public String HelloWorld(String name)
{
//拼装字符串并返回
return name + " 说:HelloWorld!";
}
/// <summary>
/// 服务操作实现
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public string GoodBye(string name)
{
return name + " 说:GoodBye!";
}
}
}
客户端调用代码:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace HelloWorldClient
{
class Client
{
/// <summary>
/// 入口方法
/// </summary>
static void Main(string[] args)
{
using (HelloWorldProxy proxy = new HelloWorldProxy())
{
//获得状态机对象,并添加处理事件
ICommunicationObject communicationObject = proxy as ICommunicationObject;
communicationObject.Opening += new EventHandler(communicationObject_Opening);
communicationObject.Closed += new EventHandler(communicationObject_Closed);
//利用代理调用服务
Console.WriteLine(proxy.HelloWorld("WCF"));
Console.WriteLine(proxy.GoodBye("WCF"));
}
Console.Read();
}
static void communicationObject_Opening(object sender, EventArgs e)
{
Console.WriteLine("状态机开启!");
}
static void communicationObject_Closed(object sender, EventArgs e)
{
Console.WriteLine("状态机关闭!");
}
}
//硬编码定义服务契约
[ServiceContract]
interface IService
{
//服务操作
[OperationContract]
String HelloWorld(String name);
//服务操作
[OperationContract]
String GoodBye(String name);
}
/// <summary>
/// 客户端代理类型
/// </summary>
class HelloWorldProxy : ClientBase<IService>, IService
{
//硬编码定义绑定
public static readonly Binding HelloWorldBinding = new NetNamedPipeBinding();
//硬编码定义地址
public static readonly EndpointAddress HelloWorldAddress = new EndpointAddress(new Uri("net.pipe://localhost/HelloWorld"));
/// <summary>
/// 构造方法
/// </summary>
public HelloWorldProxy() : base(HelloWorldBinding, HelloWorldAddress) { }
public String HelloWorld(String name)
{
//使用Channel属性对服务进行调用
return Channel.HelloWorld(name);
}
public String GoodBye(string name)
{
//使用Channel属性对服务进行调用
return Channel.GoodBye(name);
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步