今天继续学习WCF4.0新特性体验(8):自定义绑定实现字节流编码,也就是简化字节流编码的新特性,Simple byte stream encoding )。WCF4.0之前的版本中已经提供了三种编码器:Text、Binary 和 MTOM 消息编码器。但是有些时候我们想传递最原始的二进制数据,不进行任何的包装,怎么才能实现呢?WCF4.0中给出了一种新的编码器。今天我们就来学习如何使用这个新的编码器。本节内容会对WCF消息编码器、自定义绑定也会做简要介绍。
【1】WCF编码器:
我们知道在WCF4.0以前的版本中已经提供了三种编码器:Text、Binary 和 MTOM 消息编码器。当然我们也可以实现自定义编码器。Text消息编码器同时支持纯 XML 编码和 SOAP 编码。Text消息编码器的纯 XML 编码模式称为 POX(“Plain Old XML”)编码器,以便与基于文本的 SOAP 编码进行区分。关于三种编码器类型如下:
编码器类型
|
描述
|
文本消息编码器,同时支持纯 POX 编码和 SOAP 编码。使用文本消息编码器可以与非 WCF 终结点交互操作。
|
|
二进制消息编码器,使用精简二进制格式优化通信, WCF 提供的所有编码器中性能最佳的编码器。
|
|
绑定元素,指定使用 MTOM 编码的消息的字符编码和消息版本。MTOM 编码以文本形式传输大多数 XML,但是按原样传输较大的二进制数据块。就效率而言, MTOM 介于在文本编码器(最慢)和二进制编码器(最快)之间。
|
这些是目前我们使用的主要的三种消息编码器类型。但无论什么消息编码器,它们都是MessageEncodingBindingElement 类的子类,必须实现WriteMessage和ReadMessage方法。这两个方法是消息编码的核心实现。《WCF技术内幕》绑定有比较深入的分析。它们的作用如下:
- WriteMessage,此方法把Message 对象数据写入到Stream 对象。
- ReadMessage,此方法采从Stream 对象读取数据,并构建一个新的Message 对象。
【2】字节流编码:
WCF4.0提供了一个新的编码器类型:ByteStreamMessageEncodingBindingElement 。它也是MessageEncodingBindingElement的子类型,
public sealed class ByteStreamMessageEncodingBindingElement : MessageEncodingBindingElement
{......}
{......}
但是,没有定义相对应的绑定支持这个编码,因此,如果在WCF4.0中,我们想使用字节流编码机制,必须自己实现自定义绑定。也就是Creating Custom Binding。
【3】自定义绑定:
这里我们先要了解自定义绑定的概念,然后再来介绍如何通过自定义绑定来使用字节流编码机制。虽然系统提供了许多的绑定,但是WCF框架允许用户自己定义特定的绑定类型。
我们知道绑定由许多不同的绑定元素(BindingElement)组成,这些具有不同功能的绑定元素(BindingElement)累加起来,组成了一个具有完整功能的绑定(Binding)。
Binding按照功能划分,可以看到有如下几个主要层次:事务、可靠性、安全性、编码和传输。
层
|
绑定元素
|
必需
|
事务
|
TransactionFlowBindingElement
|
否
|
可靠性
|
ReliableSessionBindingElement
|
否
|
安全性
|
SecurityBindingElement
|
否
|
编码
|
文本、二进制、消息传输优化机制 (MTOM)、字节流、自定义
|
是
|
传输
|
TCP、HTTP、HTTPS、命名管道(也称为 IPC)、对等 (P2P)、消息队列(也称为 MSMQ)、自定义
|
是
|
这里只有编码和传输层元素是必须的,其它的根据绑定要实现的功能来选择是否添加进来。我们可以查看一下常见绑定的绑定元素(BindingElement)组成。例如BasicHttpBinding和NetTcpBinding。它们的绑定元素构造如下:
BasicHttpBinding的元素列表:
1) TextMessageEncodingBindingElement
2) HttpTransportBindingElement
NetTcpBinding的元素列表:
1) TransactionFlowBindingElement
2) ReliableSessionBindingElement
3) SymmetricSecurityBindingElement
4) BinaryMessageEncodingBindingElement
5) TcpTransportBindingElement
我们能看到为什么BasicHttpBinding的功能如此简单,并且支持Text编码的。而NetTcpBinding相比起来功能更加复杂。比如支持事务、可靠性会话、安全等特性的。这个在《WCF技术内幕》绑定一章里做过详细的介绍。
【4】自定义绑定实现简化字节流编码:
下面我们来讲解一下代码的实现过程,这个实现的过程有点复杂。因为要通过自定义绑定来使用ByteStream编码,还要注意一些限制,必须只能使用http传输。另外还要使用消息契约MessageContract。过程就有些复杂,可能在实际的项目里,使用的时候,大家都会考虑这个问题。只能等WCF在下一个版本里给出正式的支持在使用,也许会简单很多,也许WCF5.0里会出现一个新的ByteStreamHttpBinding。这样开发的时候就会简单很多。目前我们只能通过自己的代码实现。下面我们来分布介绍实现的过程。
【4.1】服务契约:
服务契约里我们定义了一个操作为UploadImage。代码如下:
//1.服务契约
[ServiceContract]
public interface IWCFService
{
//操作契约
[OperationContract(Action = "*", ReplyAction = "*")]
Message UploadImage(Message request);
}
[ServiceContract]
public interface IWCFService
{
//操作契约
[OperationContract(Action = "*", ReplyAction = "*")]
Message UploadImage(Message request);
}
服务实现的这个操作,它会再把原始的图片返回给客户端。通过Message来完成。接受客户端消息,并返回消息给客户端,消息体里就是图片的数据。也就是放在<Binary>图片数据</Binary>节点里的。代码如下:
//2.服务类,继承接口。实现服务契约定义的操作
public class WCFService : IWCFService
{
//实现接口定义的方法
public Message ProcessRequest(Message request)
{
Console.WriteLine("接受图片数据");
//设置返回消息的属性
HttpResponseMessageProperty httpResponseProperty = new HttpResponseMessageProperty();
httpResponseProperty.Headers.Add("Content-Type", "application/octet-stream");
request.Properties.Add(HttpResponseMessageProperty.Name, httpResponseProperty);
//打印时间
Console.WriteLine("返回图片数据 {0}", DateTime.Now.ToLongTimeString());
return request;
}
}
public class WCFService : IWCFService
{
//实现接口定义的方法
public Message ProcessRequest(Message request)
{
Console.WriteLine("接受图片数据");
//设置返回消息的属性
HttpResponseMessageProperty httpResponseProperty = new HttpResponseMessageProperty();
httpResponseProperty.Headers.Add("Content-Type", "application/octet-stream");
request.Properties.Add(HttpResponseMessageProperty.Name, httpResponseProperty);
//打印时间
Console.WriteLine("返回图片数据 {0}", DateTime.Now.ToLongTimeString());
return request;
}
}
【4.2】自定义绑定:
这里我们就假定这个绑定的名字为ByteStreamHttpBinding。这个绑定只有两个绑定元素,一个是负责编码的ByteStreamMessageEncodingBindingElement ,另外一个就是负责传输的HttpTransportBindingElement。但是要设置一些属性。比如最大传输消息大小等等。代码如下:
// Create a custom binding containing two binding elements
//创建自定义绑定,只能使用HttpTransportBindingElement,并且MessageVersion为None
ByteStreamMessageEncodingBindingElement byteStreamBindingElement = new ByteStreamMessageEncodingBindingElement();
byteStreamBindingElement.ReaderQuotas.MaxArrayLength = 900000;
byteStreamBindingElement.ReaderQuotas.MaxBytesPerRead = 4096;
byteStreamBindingElement.ReaderQuotas.MaxDepth = 64;
byteStreamBindingElement.ReaderQuotas.MaxStringContentLength = 900000;
byteStreamBindingElement.ReaderQuotas.MaxNameTableCharCount = 900000;
HttpTransportBindingElement transport = new HttpTransportBindingElement();
transport.TransferMode = TransferMode.Streamed;
transport.MaxReceivedMessageSize = 900000;
transport.MaxBufferSize = 900000;
CustomBinding binding = new CustomBinding(byteStreamBindingElement, transport);
//创建自定义绑定,只能使用HttpTransportBindingElement,并且MessageVersion为None
ByteStreamMessageEncodingBindingElement byteStreamBindingElement = new ByteStreamMessageEncodingBindingElement();
byteStreamBindingElement.ReaderQuotas.MaxArrayLength = 900000;
byteStreamBindingElement.ReaderQuotas.MaxBytesPerRead = 4096;
byteStreamBindingElement.ReaderQuotas.MaxDepth = 64;
byteStreamBindingElement.ReaderQuotas.MaxStringContentLength = 900000;
byteStreamBindingElement.ReaderQuotas.MaxNameTableCharCount = 900000;
HttpTransportBindingElement transport = new HttpTransportBindingElement();
transport.TransferMode = TransferMode.Streamed;
transport.MaxReceivedMessageSize = 900000;
transport.MaxBufferSize = 900000;
CustomBinding binding = new CustomBinding(byteStreamBindingElement, transport);
这个自定义绑定的实现,也可以通过配置文件来完成。另外也可以封装在一个单独的绑定类型ByteStreamHttpBinding。以便重复使用这些代码。
【4.3】宿主:
宿主使用自定义的绑定来托管WCF服务。代码如下:
CustomBinding binding = new CustomBinding(byteStreamBindingElement, transport);
// // Add an endpoint using that binding
////使用自定义绑定增加一个终结点
host.AddServiceEndpoint(typeof(WCFService.IWCFService), binding, "ByteStreamWCFService");
//判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State !=CommunicationState.Opening)
host.Open();
//显示运行状态
// // Add an endpoint using that binding
////使用自定义绑定增加一个终结点
host.AddServiceEndpoint(typeof(WCFService.IWCFService), binding, "ByteStreamWCFService");
//判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State !=CommunicationState.Opening)
host.Open();
//显示运行状态
【4.4】客户端:
客户端这里需要通过Message类型来设置消息。首先我们会从一个犀利哥的图片中读取数据,onst string TestFileName = "./http://www.cnblogs.com/xilige.jpg";这里处理消息体元素生成工作的就是ByteStreamBodyWriter类型,他继承自抽象类BodyWriter,重写了void OnWriteBodyContents(XmlDictionaryWriter writer)方法。这个机制是消息序列化的一个重要步骤。writer会控制每个消息体元素的生成工作。ByteStreamBodyWriter的实现代码如下:
public class ByteStreamBodyWriter : BodyWriter
{
string testFileName;
public ByteStreamBodyWriter(string testFileName)
: base(false)
{
this.testFileName = testFileName;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
//生成初始节点<Binary>
writer.WriteStartElement("Binary");
//写数据
FileStream fs = new FileStream(this.testFileName, FileMode.Open);
byte[] tmp = new byte[fs.Length];
fs.Read(tmp, 0, tmp.Length);
writer.WriteBase64(tmp, 0, (int)tmp.Length);
//生成结束标签</Binary>
writer.WriteEndElement();
fs.Close();
}
}
{
string testFileName;
public ByteStreamBodyWriter(string testFileName)
: base(false)
{
this.testFileName = testFileName;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
//生成初始节点<Binary>
writer.WriteStartElement("Binary");
//写数据
FileStream fs = new FileStream(this.testFileName, FileMode.Open);
byte[] tmp = new byte[fs.Length];
fs.Read(tmp, 0, tmp.Length);
writer.WriteBase64(tmp, 0, (int)tmp.Length);
//生成结束标签</Binary>
writer.WriteEndElement();
fs.Close();
}
}
这里客户端代码就是生成一个消息,消息体里读取的是犀利哥的图片,然后使用自定义绑定建立通道发送消息。代码如下:
const string TestFileName = "./http://www.cnblogs.com/xilige.jpg";
//ByteStreamBodyWriter writer = new ByteStreamBodyWriter(TestFileName);
Message message = Message.CreateMessage(MessageVersion.None, "*", new ByteStreamBodyWriter(TestFileName));
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add("Content-Type", "application/octet-stream");
message.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
//// Create a channel using that binding
////创建通道
EndpointAddress address = new EndpointAddress("http://localhost:8000/ByteStreamWCFService");
ChannelFactory<IWCFService> channelFactory = new ChannelFactory<IWCFService>(binding, address);
IWCFService channel = channelFactory.CreateChannel();
Console.WriteLine("Client calling service");
Message result = channel.UploadImage(message);
//ByteStreamBodyWriter writer = new ByteStreamBodyWriter(TestFileName);
Message message = Message.CreateMessage(MessageVersion.None, "*", new ByteStreamBodyWriter(TestFileName));
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add("Content-Type", "application/octet-stream");
message.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
//// Create a channel using that binding
////创建通道
EndpointAddress address = new EndpointAddress("http://localhost:8000/ByteStreamWCFService");
ChannelFactory<IWCFService> channelFactory = new ChannelFactory<IWCFService>(binding, address);
IWCFService channel = channelFactory.CreateChannel();
Console.WriteLine("Client calling service");
Message result = channel.UploadImage(message);
基本就是这个过程。
【4.5】运行结果:
我们可以启动宿主,然后单步执行客户端代码,看看运行的结果,截图如下:
我们看到客户端上传了一个图片,服务端收到以后又返回给客户端,犀利哥的这个照片大小为41.474K。并且数据是放在消息体体的节点<Binary>里。
【5】总结:
这个新的特性,被称为字节流编码的特性,有点让人无奈,功能是好功能,但是只提供了一个绑定元素,如果我们要使用的话必须自己实现自定义绑定。这个对于大多数开发者来说,可能有点复杂。这里还有几点需要值得注意,就是:
(1)字节流编码ByteStreamMessageEncodingBindingElement目前WCF4.0里只能通过自定义绑定实现。
(2)ByteStreamMessageEncodingBindingElement 只能和Http传输结合,不支持其他的传输。
(3)与流传输一样,字节流不支持可靠性会话,也不支持消息安全。因为数据交大。加密解密处理会耗费较多的资源。
(4)使用Message类型来控制消息的生成过程里,必须把数据放置在节点<Binary></Binary>里。 否则会出现WCF分布式开发常见错误(30):Start element 'Binary' expected(期望的初始元素是'Binary' )错误。
(5)Message类型设置的一个属性"application/octet-stream" ,是是与MIME附件规范,这个表示消息内容是二进制文件。
(6)它与二进制编码器不同的是,二进制编码器是对消息数据做精简二进制编码,而字节流编码则不同,可以传递最原始的二进制数据。
最后给出本文的例子代码给大家学习的时候做个参考。/Files/frank_xl/9.ByteStreamEncoder.rar欢迎留言交流。
参考资料:
5.《WCF技术内幕》:绑定
本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/320272,如需转载请自行联系原作者