WCF4.0新特性体验(8):自定义绑定实现字节流编码(ByteStream)

简介:



今天继续学习WCF4.0新特性体验(8):自定义绑定实现字节流编码,也就是简化字节流编码的新特性,Simple byte stream encoding )。WCF4.0之前的版本中已经提供了三种编码器:TextBinary 和 MTOM 消息编码器。但是有些时候我们想传递最原始的二进制数据,不进行任何的包装,怎么才能实现呢?WCF4.0中给出了一种新的编码器。今天我们就来学习如何使用这个新的编码器。本节内容会对WCF消息编码器、自定义绑定也会做简要介绍。

【1】WCF编码器:
 我们知道在WCF4.0以前的版本中已经提供了三种编码器: TextBinary 和  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); 
        }
       服务实现的这个操作, 它会再把原始的图片返回给客户端。通过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; 
                } 
        }
     这里的代码很简单,就是对请求消息做了一下简单地转换,返回给客户单,我们只监控消息的返回时间。
【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);
      这个自定义绑定的实现,也可以通过配置文件来完成。另外也可以封装在一个单独的绑定类型 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(); 
                                //显示运行状态
【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(); 
                } 
        }
 
      这里客户端代码就是生成一个消息,消息体里读取的是犀利哥的图片,然后使用自定义绑定建立通道发送消息。代码如下:
 
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);
       基本就是这个过程。
【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 ,如需转载请自行联系原作者
相关文章