[转]我们已经知道,WCF的客户端通过Endpoint来访问WCF服务端的服务,也就是说,WCF Service Provider将WCF service通过Endpoint暴露出来供Service consumer调用。
而每个Endpoint包含3个主要要素:Address, binding, contract。其中,Address提供了每个Endpoint的唯一地址;Contract具体指定了这个服务提供什么功能,Client和Server交互的输入输入,消息格式,其它约定等。而真正实现了通信功能的则是Binding。
WCF中实现通信功能的binding很简单,就两步:
- 根据需要,选择/创建合适的binding类型,例如WSHttpBinding,WSDualHttpBinding或NetTcpBinding;或者创建您自定义的binding;(WCF预定义了9种类型供我们选择)
- 创建实现binding的Endpoint,可以通过代码实现,或配置文件配置。如果Endpoint是代码实现的,则Endpoint必须要加到ServiceHost实例。
Binding真正实现了连接到Endpoint的通信细节。具体到通信的处理内部,可能很简单,也可能很复杂:
- Transport (传输协议)
- Encoding (消息编码)
- Protocol (安全性,可靠消息传递和事务)
Binding类型的选择
WCF中实现通信功能的binding很简单的原因之一是WCF已经预定义了9种常用的通信类型。那我们实际项目中,如何选择正确的类型?
- BasicHttpBinding: 默认关闭security,不支持WS-*,不支持SOAP安全和事务。类似以前的ASPX Web Service,使用HTTP,使用Txt/XML作消息编码。
- WSHttpBinding:支持WS-*,支持SOAP安全和事务。支持HTTP和HTTPS。
- WSDualHttpBinding:相比WSHttpBinding,支持duplex services - which provides the ability for a service to communicate back to the client via a callback。支持reliable sessions和communication via SOAP intermediaries。
- WSFederationHttpBinding:支持WS-Federation protocol,提供良好的可信任的Authentication和授权。
- NetTcpBinding:提供安全、可靠的.net到.net的跨主机的TCP通信。支持SOAP安全、事务和可靠性。使用二进制编码。
- NetNamedPipeBinding:提供安全、可靠的同主机跨进程的命名管道通信。支持SOAP安全、事务和可靠性。使用二进制编码。
- NetMsmqBinding:提供安全、可靠的.net到.net的跨主机的MSMQ通信。使用二进制编码。(可支持disconnected operation不连接的操作)
- NetPeerTcpBinding:提供安全的点到点的通信。支持SOAP安全、事务和可靠性。使用二进制编码。
- MsmqIntegrationBinding:用来集成legacy的MSMQ和WCF。使用二进制编码。
其中,已Net开头的类型不具备互操作性,只限于.net到.net平台通信。而其他的则是WebService绑定,具有互操作性。
下图是选择的建议:
Binding如何实现通信细节 - Channel layer - Channel stacks
Channel - A channel, is the medium through which messages are exchanged.
例如,通常的消息收发过程:
- 客户端建立一个channel到服务端
- 服务端accept客户端的请求,打开一个channel
- 客户端通过这个channel发生request消息
- 服务端通过这个channel reply应答到客户端
Channel Stacks - 一连串的channel来处理消息,分别处理消息的安全性、互操作性、消息pattern、消息传输等任务。不管Channel具体完成怎样的功能,他们都可以看成是一个个Message处理器,这包括为了某种需求添加、修改Soap header;压缩整个Message、或者Message body; 对Message进行签名或者加密等等。例如,最底层的transport channel负责消息收发,上面的protocol channels提供通信功能和消息操作等。
Channel stacks是使用工厂模式创建的。消息发送端创建一个ChannelFactory,在binding的接收端创建一个IChannelListener来侦听incoming message。ChannelFactory创建一个channel stack,用它application就可以用来发消息。IChannelListener收到消息后交给侦听的application,创建channel stack。
Client Channel
例如一个WCF客户端,使用channel级编程实现步骤:
- 创建一个binding
- 创建/build一个channel factory
- 创建一个channel
- 发送请求
- 读取应答
- 关闭channel对象
CustomBinding cb = new CustomBinding ();
cb.Elements.Add(new TcpTransportBindingElement());
IChannelFactory<IRequestChannel> cf =
cb.BuildChannelFactory<IRequestChannel>(new BindingParameterCollection());
cf.Open();
IRequestChannel chnl = cf.CreateChannel(new
EndpointAddress(“net.tcp//localhost:8000/CoolApp”));
Message reqmess =
Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
“http://wrox.com/requestaction”, “Message body data”);
Message repmess = chnl.Request(reqmess);
textbox1.text = “Sending message...”;
string MessageData = repmess.GetBody<string>();
textbox2.text = MessageData;
reqmess.Close();
repmess.Close();
chnl.Close();
cf.Close();
Service Channel
例如一个WCF服务端,使用channel级编程实现步骤:
- 创建一个binding
- 创建一个channel listener
- 打开channel listener
- 读取请求,发送应答
- 关闭channel对象
CustomBinding cb = new CustomBinding();
cb.Elements.Add(new TcpTransportBindingElement());
IChannelListener<IReplyChannel> lis =
cb.BuildChannelListener<IReplyChannel>(new
Uri(“net.tcp//localhost:8000/CoolApp”),
new BindingParameterCollection());
lis.Open();
IReplyChannel repchnl = lis.AcceptChannel();
repchnl.Open();
RequestContext rc = repchnl.ReceiveRequest();
Message reqmes = rc.RequestMessage;
Message repmes = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,“”,“”);
rc.Reply(repmes);
reqmes.Close();
rc.Close();
repchnl.Close();
lis.Close();
扩展channel,Extend channel,创建自定义的channel
有以下几个步骤来创建自定义的channel
- 选择合适的MEP (Message Exchange Pattern)
- 创建channel factory和listener。Channel listeners是在Server端创建channels用来侦听和收消息,channel factory是在发送端/客户端创建channels用来发消息。
- 加上binding element
- 处理异常
选择合适的MEP (Message Exchange Pattern),有3种MEP供选择:
- Datagram: 客户端发一个消息不期望应答。客户端并且也不能保证接受方收到了消息。
- Request-Response:客户端发一个消息,并收到一个应答。传统的Request-Reply 消息交换模式。
- Duplex:客户端可以发任意多个消息,并按任意次序收到应答。双向Duplex消息交换模式。
Chanel listeners (收)
Channel listener在服务端创建Channel,侦听消息,从下层接收消息,把消息放到队列,channel各自从队列拿消息,处理消息,最后通过channel把消息交给上层。
Chanel factories (发)
Channel factory在发送端/客户端创建channels, 用来发消息。Channels从上层拿到消息,处理消息,然后交给下层。Channel factory也要负责关闭自己创建的channels。
加上binding element,创建IChannelFactory和IChannelListener对象
一个Binding由BindingElement collection组成, 构成BindingElement collection的元素是一个个的BindingElement。BindingElement的最重要的功能就是创建IChannelFactory和IChannelListener对象。
//the following code uses the BuildChannelFactory of type IRequestChannel with a TcpTransportBindingElement:
CustomBinding cb = new CustomBinding();
TcpTransportBindingElement el = new TcpTransportBindingElement();
BindingParameterCollection bpc = new BindingParameterCollection();
BindingContext bc = new BindingContext(cb, bpc);
//Create channel factory
IChannelFactory<IRequestChannel> factory =
el.BuildChannelFactory<IRequestChannel>(bc);
factory.Open();
EndpointAddress ea = new
EndpointAddress(“net.tcp://localhost:8000/CoolChannelApp”);
IRequestChannel reqchan = factory.CreateChannel(ea);
reqchan.Open();
Message request =
Message.CreateMessage(MessageVersion.Default, “this stuff rocks!”);
Message response = reqchan.Request(request);
textBox1.Text = response.Headers.Action.ToString;
reqchan.Close();
factory.Close();
//the following code uses the BuildChannelListener of type IReplyChannel for accepting channels.
The return value is the IChannelListener of type IChannel from the context:
CustomBinding cb = new CustomBinding();
TcpTransportBindingElement el = new TcpTransportBindingElement();
BindingParameterCollection bpc = new BindingParameterCollection();
Uri ba = new Uri(“net.tcp://localhost:8000/CoolChannelApp”);
String relativeAddress = “net.tcp://localhost:8000/CoolChannelApp/WCFService”;
BindingContext bc = new BindingContext(cb, bpc, ba, relativeAddress,ListenUriMode.Explicit);
//Create channel listener
IChannelListenr<IReplyChannel> listen =
el.BuildChannelListener<IReplyChannel>(bc);
listen.Open();
IReplyChannel repchan = listen.AcceptChannel();
repchan.Open();
RequestContext rc = repchan.ReceiveRequest();
Message message = rc.RequestMessage;
if (message.Headers.Action == “this stuff rocks!”)
{
Message replymessage = Message.CreateMessage(MessageVersion.Default, “I KNOW!”);
rc.Reply(replymessage);
}
message.Close();
repchan.Close();
listen.Close();