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

  • 常规讨论

  • WCF4.0新特性体验(8):自定义绑定实现字节流编码(ByteStream)
    clock        05/18/2012 14:26        |作者        authorfrankxulei |阅读次数:311|

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

    【1】WCF编码器:
    我们知道在WCF4.0以前的版本中已经提供了三种编码器:TextBinaryMTOM 消息编码器。当然我们也可以实现自定义编码器。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)、字节流、自定义
    传输
    TCPHTTPHTTPS、命名管道(也称为 IPC)、对等 (P2P)、消息队列(也称为 MSMQ)、自定义
      这里只有编码和传输层元素是必须的,其它的根据绑定要实现的功能来选择是否添加进来。我们可以查看一下常见绑定的绑定元素(BindingElement)组成。例如BasicHttpBindingNetTcpBinding。它们的绑定元素构造如下:
    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.frankxulei.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.frankxulei.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技术内幕》:绑定

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

        Tags:    , , , , , , ,
    文章类别:    WCF4.0新特性体验 | .NET
    操作: |WCF4.0新特性体验(8):自定义绑定实现字节流编码(ByteStream)
    aa821afb-1b9c-49ab-92b5-627bbd1ef827|2|4.0
    2010年3月28日 14:52
    版主