从零开始学WCF(11)大型数据和流

本文详细介绍了WCF中处理大型数据和流的方法,包括编码数据的文本和二进制形式,重点讨论了二进制内容和大型数据内容的处理。MTOM作为一种优化机制,用于减少大型二进制数据的传输开销。文章还探讨了不同编码器的使用场景,如TextMessageEncodingBindingElement、MtomMessageEncodingBindingElement和BinaryMessageEncodingBindingElement,并提供了启用MTOM的编程模型及示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本考虑事项

WCF是基于XML的通信基础结构。

编码数据:文本和二进制

最常有的顾虑包括:认为与二进制格式相比XML的开销比较大一些(因为其开始标记和结束标记的重复性),数值的编码可能要大得多(因为他们是以文本值来表示的),并且无法有效的表示二进制数据(因为它们必须进行特殊的编码才能嵌入到文本格式中)。

尽管上述以及其他许多类似的问题是存在的,但是XML Web Services环境中的XML文本编码消息与旧样式的远程过程调用(RPC)环境中的二进制编码消息之间的实际差异,通常远远小于最初的考虑中所预想的程度。

虽然XML文本编码消息是透明的并且是"可读的"(也许难以理解),但比较而言,二进制消息则通常相当晦涩,在无工具的情况下很难解码。

要在文本与二进制之间进行选择,不能简单地认为二进制消息总是小于XML文本消息。

XML文本消息的一个明确而无可争议的优点是它们基于标准的,并且提供最为广泛的互操作性选项和平台支持选择范围。


二进制内容

从最终的消息大小这一角度而言,二进制编码优于基于文本编码的一个方面就在于大型二进制数据项,例如,图片、视频、音效剪辑或者必须在服务与其使用者之间交换的任何其他形式的非透明二进制数据。为了使这些类型的数据也适合XML文本,常用的方法就是使用Base64编码对其进行编码。

在Base64编码字符串中,每个字符都表示原始8位数据的6位,这导致Base64的编码开销比率是4:3,且未计算额外的格式字符(回车符/换行符),而按照惯例这些字符通常是会添加的。虽然XML编码与二进制编码之间的差异显著与否通常取决于具体情况,但是当传送500MB负载时大小增加超过33%通常是不受欢迎的。

为避免这种编码开销,消息传输优化机制(MTOM:Message Transmission Optimization Mechanism)标准允许将消息中包含的大型数据元素外部化,并将其作为无任何特殊编码的二进制数据随消息一起传送。利用MTOM,消息将以一种与带附件或嵌入式内容(图片和其他嵌入式内容)的简单邮件传输协议(SMTP)电子邮件类似的方式交换;MTOM消息会打包成多部分/相关MIME(多用途因特网邮件扩展(Multipurpose Internet Mail Extensions))序列,其中根部分是实际的SOAP消息。

但是,与Base64一样,对于MIME格式,MTOM也有一些必要的开销,这样仅在二进制数据元素的大小超过大约1KB时,才能体现出使用MTOM的好处。由于这一开销,如果二进制负载在该阈值之下,则MTOM编码的消息可能会大于对二进制数据使用Base54编码的消息。


大型数据内容

即使不考虑线路需求量,前面提及的500MB负载也对服务和客户端本身提出了很大的考验。默认情况下,WCF会以缓冲模式处理消息。这意味着消息的整个内容在发送前或接收后都存在于内存中。尽管对于大多数情形来说这是个很好的策略,并且是消息传递功能(如数字签名)和可靠传递所必须的,但是大型消息可能很容易最终耗尽系统资源。

处理大型负载的策略是流。尽管消息(尤其是以XML表示的消息)通常会被认为是相对紧凑的数据包,但消息大小也可能达到GB数量级,从而与连续的数据流很相似,而不是易于处理的数据包。当以流模式而不是缓冲模式传输数据时,发送方会议流的形式将消息正文的内容提供给接收方,并且消息基础结果会不断地将就绪的数据从发送发转发给接收方。

传输此类大型数据内容的最常见情形是传输具有以下特点的二进制数据对象:

1) 无法方便地分成消息序列。

2) 必须以及时方式传递。

3) 当开始传输时,还不是已全部就绪。

对于不具有上述限制条件的数据,通常最好在一个会话的范围内发送消息序列,而不是一次性地发送一个大消息。


编码

编码定义了一组规则,规定消息在线路中的存在形式。把我们的消息如何编码,然后在传输。

编码器实现此类编码,并负责将Message内存中消息转变为可以通过网络发送的字节流或字节缓冲区(对于发送方而言)。在接收方,编码器会将一系列字节转变为内存中的消息。

WCF包括三个编码器:

1) TextMessageEncodingBindingElement: 

文本消息编码器是所有基于HTTP的绑定的默认编码器,并且是最关注互操作性的所有自定义绑定的正确选择。此编码器读取和编码标准SOAP 1.1/SOAP 1.2文本消息,而不会对二进制数据进行任何特殊处理。如果消息的MessageVersion设置为None,则SOAP信封包装会从输出中省略,只有消息正文内容会进行序列化。

2) MtomMessageEncodingBindingElement:

MTOM消息编码器是一个文本编码器,实现对二进制数据的特殊处理,默认情况下在任何的标准绑定中都不会使用,因为它是一个严格按具体情况进行优化的实用工具。只有当二进制数据的量不超过某个阀值时,MTOM编码才具有优势,如果消息包含的二进制数据超过了这个阀值,则这些数据会外部化到消息信封之后的MIME部分。

3) BinaryMessageEncodingBindingElement:

二进制消息编码器是Net*(以.NET打头的,例如.NET.TCP,.NET.TCP,NET.Pipe)绑定的默认编码器,当通信双方都基于WCF时,此编码器始终是正确的选择。二进制消息编码器使用.NET 二进制XML格式,此格式是XML信息集(Information Sets,Infosets)的Microsoft特定二进制表示法,与等效的XML 1.0表示法相比产生的需求量通常较小,并将二进制数据编码为字节流。

每个标准绑定都包括一个预配置编码器,因此默认情况下带Net*前缀的绑定使用二进制编码器(通过包括BinaryMessageEncodingBindingElement类),而BasicHttpBinding和WSHttpBinding类则使用文本消息编码器(通过TextMessageEncodingBindingElement类)。


通常,文本消息编码是要求互操作性的任意通信路径的最佳选择,而二进制消息编码则是其他任意通信路径的最佳选择。通常,对于单个消息而言,二进制消息编码生成的消息大小要小于文本编码,并且在通信会话期间消息大小会逐渐变得更小。与文本编码不同的是,二进制编码不需要对二进制数据使用特殊处理(例如,使用Base64),但会将字节表示为字节。


启用MTOM

当要求互操作性,并且必须发送大型二进制数据时,MTOM消息编码是一个备选的编码策略,可以在标准BasicHttpBinding或WSHttpBinding绑定上启用它,方法是:将该绑定的MessageEncoding属性设置为Mtom,或者将MtomMessageEncodingBindingElement编写为CustomBinding。


因为MTOM是在绑定级别启用的,所以启用MTOM会影响给定终结点上的所有操作,通常情况下,只有在终结点交换超过1KB二进制数据的消息时,才应该对它启用MTOM。


编程模型

无论在应用程序中使用三个内置编码器中的哪一个,在传输二进制数据方面的编程体验都是相同的。区别在于WCF如何基于数据类型来处理数据。


当使用MTOM时,将根据以下规则序列化上面的数据协定:

1) 如果binaryBuffer不是null,并且有个别包含足够的数据,使得需要Base64编码所没有的MTOM外部化开销(MIME头等等),则这些数据将外部化并作为二进制MIME部分随消息一起传送。如果未超过阀值,则数据会编码为Base64.

2) 字符串(和其他所有非二进制的类型)无论多大,始终表示为消息正文内的字符


MTOM_DEMO

1) 新建一个WCF Application Service,然后添加“UploadService”WCF服务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.IO;

namespace Video11.Demo1.Mtom
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IUploadService" in both code and config file together.
    [ServiceContract(Namespace = "http://Video11.Demo1.Mtom")]
    public interface IUploadService
    {
        [OperationContract]
        int Upload(Stream data);
    }
}

2) 实现该服务:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace Video11.Demo1.Mtom
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "UploadService" in code, svc and config file together.
    public class UploadService : IUploadService
    {
        public int Upload(System.IO.Stream data)
        {
            int size = 0;
            int bytesRead = 0;
            byte[] buffer = new byte[1024];

            //读取所有流中的数据
            do
            {
                bytesRead = data.Read(buffer, 0, buffer.Length);
                size += bytesRead;
            }
            while (bytesRead > 0);

            //返回流的大小
            return size;
        }
    }
}

3) 配置配置信息,注意这里指定了Endpoint的bindingConfiguration属性为Mtom类型的编码。

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>

    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_IUpload" messageEncoding="Mtom"/>
      </wsHttpBinding>
    </bindings>

    <services>
      <service name="Video11.Demo1.Mtom.UploadService" behaviorConfiguration="UploadServiceBehavior">
        <endpoint address="" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IUpload" contract="Video11.Demo1.Mtom.IUploadService"/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    
    <behaviors>
      <serviceBehaviors>
        <behavior name="UploadServiceBehavior">
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  
</configuration>

4) 部署该服务到IIS上,然后创建一个Client客户端程序。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ServiceModel.Channels;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] binaryData = new byte[1000];
            MemoryStream stream = new MemoryStream(binaryData);

            //上传1000字节的stream内存流
            ServiceReference1.UploadServiceClient client = new ServiceReference1.UploadServiceClient();
            Console.WriteLine(client.Upload(stream)); //返回的也是1000个字节
            Console.WriteLine();
            stream.Close();

            CompareMessageSize(100);
            CompareMessageSize(1000);
            CompareMessageSize(10000);
            CompareMessageSize(100000);
            CompareMessageSize(1000000);

            Console.ReadKey();
        }

        private static void CompareMessageSize(int dataSize)
        {
            // Create and buffer a message with a binary payload
            byte[] binaryData = new byte[dataSize];
            Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "action", binaryData);
            MessageBuffer buffer = message.CreateBufferedCopy(int.MaxValue);

            // Print the size of a text encoded copy
            int size = SizeOfTextMessage(buffer.CreateMessage());
            Console.WriteLine("编码之前的消息大小{0}byte,使用Text Encoding编码后大小是{1}", binaryData.Length, size);

            //Print the size of an MTOM encoded copy
            size = SizeOfMtomMessage(buffer.CreateMessage());
            Console.WriteLine("编码之前的消息大小{0}byte,使用MTOM Encoding编码后大小是{1}", binaryData.Length, size);

            Console.WriteLine();
            message.Close();
        }

        private static int SizeOfMtomMessage(Message message)
        {
            //创建MTOM编码对象
            MessageEncodingBindingElement element = new MtomMessageEncodingBindingElement();
            MessageEncoderFactory factory = element.CreateMessageEncoderFactory();
            MessageEncoder encoder = factory.Encoder;

            //写入message并返回其长度
            MemoryStream stream = new MemoryStream();
            encoder.WriteMessage(message, stream);
            int size = (int)stream.Length;

            stream.Close();
            message.Close();
            return size;
        }

        private static int SizeOfTextMessage(Message message)
        {
            //创建一个文本编码
            MessageEncodingBindingElement element = new TextMessageEncodingBindingElement();
            MessageEncoderFactory factory = element.CreateMessageEncoderFactory();
            MessageEncoder encoder = factory.Encoder;

            //写入message并返回它的长度
            MemoryStream stream = new MemoryStream();
            encoder.WriteMessage(message, stream);
            int size = (int)stream.Length;

            message.Close();
            stream.Close();
            return size;
        }
    }
}

5) 运行结果:由此可见MTOM消息编码对比较大的消息的编码上才有优势。



源代码:  http://download.youkuaiyun.com/detail/eric_k1m/6437523

数据的流模式

当有大量的数据要传输时,WCF中的流传输模式是整体缓冲和处理内存中消息的默认行为的一个可行的替代方法。
如果数据无法分段、消息必须以及时的方式传递或者当传输启动时数据尚未完成就绪,则应该考虑启用流模式,且只能对大型消息(带文本或二进制内容)启用流模式。

当启用了流模式时,很多WCF功能都不能使用:
1) 无法执行消息正文的数字签名,因为他们需要对整个消息内容进行哈希算法。采用流模式的情况下,当构造和发送消息头时,内容尚未完全就绪,因此无法计算数字签名。
2) 加密依赖于数字签名来验证是否已正确地重新构造数据。
3) 如果消息在传输过程中丢失,可靠的会话必须在客户端上缓冲已发送的消息以便可以重新传递,并且在将消息传递给服务实现之前必须在服务上保留消息以保存消息顺序,以备在未按照顺序接收消息时可以按照正确的顺序重新排列消息。
由于上述功能约束,只能对流模式使用传输级安全选项,并且无法打开可靠会话。因此,流模式仅在下列系统定义的绑定(endpoint里的binding属性)中可用:
1) BasicHttpBinding
2) NetTcpBinding
3) NetNamedPipeBinding

由于NetTcpBinding和NetNamedPipeBinding的基础传输具有内在的可靠传递和基于连接的会话支持,因为与HTTP不同,这两个绑定在实践中受上述约束的影响非常小。
流模式在消息队列(MSMQ)传输中不可用,因此不能与NetMsmqBinding或MsmqIntegrationBinding类一起使用。消息队列传输仅支持限定消息大小的缓冲数据传输,而大多数情形下其他所有传输都不具有任何实际的消息大小限制。
当使用对等通信传输时,流模式也不可用,因此流模式在NetPeerTcpBinding中不可用。

启用流模式

可以通过以下方式启用流模式:
1) 以流模式发送和接受请求,以缓冲模式接收和返回相应(StreamRequest)。
2) 以缓冲模式发送和接受请求,以流模式接受和返回相应(StreamResponse)。
3) 在两个方向均使用流模式发送/接收请求/相应(Streamed)。
可以通过将传输模式设置为Buffered来禁用流模式,该设置是所有绑定的默认设置。


流式传输的编程模型

流式传输的编程模型非常简单。要接收流数据,请指定具有单个Stream类型化输入参数的操作协定。为了返回数据流,应返回一个Stream引用。

上面实例中的操作Echo接收并返回一个流,因此应当用在具有Streamed的绑定上。对于操作RequestInfo,StreamResponse最适合,因为它仅仅返回Stream。单项操作最适合于StreamedRequest。
请注意,向后面的Echo或ProvideInfo操作添加第二个参数会导致模式恢复为缓冲策略,并使用流的运行时序列化表示。 只有具有单个输入流参数的操作才与端对端请求流兼容

类似的,此规则也适用于消息协定。如下面的消息协定所示,在流模式的消息协定中,只能有一个正文成员。如果希望使用流传送更多信息,必须在消息头中携带这一信息。消息正文是专为流内容保留的。


DEMO

1) 新建一个Windows控制台项目,然后添加WCF服务——StreamingService,定义WCF服务接口IStreamingService
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.IO;

namespace Video11.Demo2.Stream
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IStreamingService" in both code and config file together.
    [ServiceContract(Namespace = "http://Video11.Demo2.Stream")]
    public interface IStreamingService
    {
        [OperationContract]
        System.IO.Stream GetStream(string data);

        [OperationContract]
        bool UploadStream(System.IO.Stream stream);

        [OperationContract]
        System.IO.Stream EchoStream(System.IO.Stream stream);

        [OperationContract]
        System.IO.Stream GetReverseStream();
    }
}

2) 实现该服务接口:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.IO;

namespace Video11.Demo2.Stream
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "StreamingService" in both code and config file together.
    public class StreamingService : IStreamingService
    {        
        public System.IO.Stream GetStream(string data)
        {
            //图像文件放在服务端的bin文件夹里
            //Path.Combine(System.Environment.CurrentDirectory, ".\\image.jpg")获得的地址是当前项目bin文件夹里的image.jpg文件
            string filePath = Path.Combine(System.Environment.CurrentDirectory, ".\\image.jpg");
            //把指定路径的文件打开并读取到FileStream流文件里,然后返回这个文件流对象
            //如果该文件夹里没有这个文件,则捕获异常
            try
            {
                FileStream imageFile = File.OpenRead(filePath);
                return imageFile;
            }
            catch (IOException ex)
            {
                Console.WriteLine(
                    String.Format("An exception was thrown while trying to open file {0}", filePath));
                Console.WriteLine("Exception is: ");
                Console.WriteLine(ex.ToString());
                throw ex;
            }
        }

        //从客户端上传一个流文件,然后服务端接收后返回一个bool值
        public bool UploadStream(System.IO.Stream stream)
        {
            //Path.Combine()将多个字符串组合成一个路径。
            //Environment.CurrentDirectory 属性:获取或设置当前工作目录的完全限定路径。也就bin\debug文件夹
            string filePath = Path.Combine(System.Environment.CurrentDirectory, "uploadedfile.jpg");
            try
            {
                Console.WriteLine("保存到文件 {0}", filePath);
                //File.Open 方法 (String要打开的文件路径, FileMode指定文件不存在时候是否创建该文件, FileAccess可以对文件执行的操作):
                //在由受信任的应用程序调用时,用指定模式和访问权限打开指定路径上的 FileStream。
                FileStream outstream = File.Open(filePath, FileMode.Create, FileAccess.Write);
                //限定流大小为4K
                const int bufferLen = 4096;
                byte[] buffer = new byte[bufferLen];
                int count = 0;
                //Stream.Read(buffer字节数组此方法返回时该缓冲区包含指定的字符数组该数组的offset和offset+count-1之间的值由当前源中读取的字节替换,
                //offset buffer中的从零开始的字节偏移量,从此处开始存储当前流中读取的数据。
                //count 要从当前流中最多读取的字节流
                //)
                //所以这的意思是:读取stream流的数据,从位置0开始一直读取到bufferLen位置也就是4K大小,
                //然后再把读取到的流存储到了buffer中。返回值count为读入缓冲区buffer中的总字节数。
                while ((count = stream.Read(buffer, 0, bufferLen)) > 0)
                {
                    Console.WriteLine(".");
                    //把从0到count大小字节流的buffer字节数组复制到outstream流中去。
                    outstream.Write(buffer, 0, count);
                }
                //关闭流
                outstream.Close();
                stream.Close();

                Console.WriteLine();
                Console.WriteLine("文件 {0} 已经保存", filePath);

                return true;
            }
            catch (IOException ex)
            {
                Console.WriteLine(
                    String.Format("An exception was thrown while opening or writing to file {0}", filePath));
                Console.WriteLine("Exception is: ");
                Console.WriteLine(ex.ToString());
                throw ex;
            }
        }

        //客户端发给服务端一个流文件,然后再有服务端返回一个流文件
        public System.IO.Stream EchoStream(System.IO.Stream stream)
        {
            string filePath = Path.Combine(System.Environment.CurrentDirectory, "echofile.jpg");
            try
            {
                FileStream outstream = File.Open(filePath, FileMode.Create, FileAccess.Write);
                //把传进来的stream复制一份给outstream
                this.CopyStream(stream, outstream);
                outstream.Close();
                stream.Close();

                FileStream echoFile = File.OpenRead(filePath);
                return echoFile;
            }
            catch (IOException ex)
            {
                Console.WriteLine(
                    String.Format("An exception was thrown while opening or writing to file {0}", filePath));
                Console.WriteLine("Exception is: ");
                Console.WriteLine(ex.ToString());
                throw ex;
            }
        }

        private void CopyStream(System.IO.Stream instream, FileStream outstream)
        {
            //固定每次读取的字节流大小为4K
            const int bufferLen = 4096;
            byte[] buffer = new byte[bufferLen];
            int count = 0;
            while ((count = instream.Read(buffer, 0, bufferLen)) > 0)
            {
                outstream.Write(buffer, 0, count);
            }
        }

        public System.IO.Stream GetReverseStream()
        {
            string filePath = Path.Combine(System.Environment.CurrentDirectory, "\\image.jpg");
            ReverseStream stream = new ReverseStream(filePath);
            return stream;
        }
    }
}


3) 配置配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>

      <services>
        <service name="Video11.Demo2.Stream.StreamingService" behaviorConfiguration="StreamingServiceBehavior">
          <host>
            <baseAddresses>
              <!--这里要打开8000端口才行,否则报错,请参照http://blog.youkuaiyun.com/eric_k1m/article/details/12949169-->
              <add baseAddress="http://localhost:8000/Design_Time_Addresses/Video11.Demo2.Stream/StreamingService/"/>
            </baseAddresses>
          </host>
          <endpoint address="ep1" binding="basicHttpBinding" 
                    bindingConfiguration="HttpStreaming" 
                    contract="Video11.Demo2.Stream.IStreamingService"/>
          <endpoint address="http://localhost:9000/Design_Time_Addresses/Video11.Demo2.Stream/StreamingService/ep2"
                    binding="customBinding"
                    bindingConfiguration="Soap12"
                    contract="Video11.Demo2.Stream.IStreamingService"/>
          <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
      </services>

      <bindings>
        <!--使用basicHttpBinding使用流模式-->
        <basicHttpBinding>
          <binding name="HttpStreaming" maxReceivedMessageSize="67108864" transferMode="Streamed"/>
        </basicHttpBinding>
        <!--使用customBinding使用HTTP和流模式-->
        <customBinding>
          <binding name="Soap12">
            <textMessageEncoding messageVersion="Soap12WSAddressing10"/>
            <httpTransport transferMode="Streamed" maxReceivedMessageSize="67108864"/>
          </binding>
        </customBinding>
      </bindings>
      
        <behaviors>
            <serviceBehaviors>
                <behavior name="StreamingServiceBehavior">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>


4) 先运行该自承载服务,然后再使用SVCUTIL生成客户端代理类和客户端配置文件:


5) 新建一个客户端程序client,然后把生成的客户端代理类和客户端配置文件拷贝到该项目里。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            //获得要存储到客户端的文件地址也是在bin文件加下的clientfile.jpg文件里
            string filePath = Path.Combine(System.Environment.CurrentDirectory, "clientfile.jpg");
            //创建客户端代理类,这里的“BasicHttpBinding_IStreamingService”是endpointConfigurationName也就是终结点配置名,可以在客户端的app.config里查看endpoint信息获得,这是由于服务端有多个endpoint导致的,这里需要指定endpoint名字来确定生成哪种客户端代理类实例。这里就是使用basicHttpBinding绑定的服务端endpoint。
            StreamingServiceClient client1 = new StreamingServiceClient("BasicHttpBinding_IStreamingService");
            Console.WriteLine("———使用HTTP的绑定———");
            Console.WriteLine("Calling GetStream()");
            Stream stream1 = client1.GetStream("some dummy data");
            SaveStreamToFile(stream1, filePath);

            Console.WriteLine("调用UploadStream()");
            FileStream instream1 = File.OpenRead(filePath);
            bool result1 = client1.UploadStream(instream1);

            Console.WriteLine("调用EchoStream()");
            FileStream filestream1 = File.OpenRead(filePath);
            Stream stream2 = client1.EchoStream(filestream1);
            string filePathEcho = Path.Combine(System.Environment.CurrentDirectory, "echobackfile.jpg");
            SaveStreamToFile(stream2, filePathEcho);


            Console.WriteLine("调用GetReveredStream()");
            string filePathReverse = Path.Combine(System.Environment.CurrentDirectory, "Reversefile.jpg");
            stream1 = client1.GetReverseStream();
            SaveStreamToFile(stream1, filePathReverse);

            instream1.Close();
            Console.ReadKey();

            //重复使用TCP
            //repeating using TCP
            StreamingServiceClient client2 = new StreamingServiceClient("CustomBinding_IStreamingService");

            Console.WriteLine("------ Using Custom HTTP ------ ");

            Console.WriteLine("Calling GetStream()");
            Stream stream2 = client2.GetStream("some dummy data");
            SaveStreamToFile(stream2, filePath);

            Console.WriteLine("Calling UploadStream()");
            FileStream instream2 = File.OpenRead(filePath);
            bool result2 = client2.UploadStream(instream2);

            instream2.Close();

            Console.WriteLine("Calling GetReversedStream()");
            stream2 = client2.GetReverseStream();
            SaveStreamToFile(stream2, filePath);
            client2.Close();

            Console.WriteLine();
            Console.WriteLine("Press <ENTER> to terminate client.");
            Console.ReadLine();
        }

        private static void SaveStreamToFile(Stream stream, string filePath)
        {
            Console.WriteLine("Saving to file {0}", filePath);
            //创建一个文件流实例,指定该文件流保存到filepath里。
            FileStream outstream = File.Open(filePath, FileMode.Create, FileAccess.Write);
            //把从服务器端获得的文件流拷贝到上面所创建好的文件流outstream里。
            CopyStream(stream, outstream);
            outstream.Close();
            Console.WriteLine();
            Console.WriteLine("File {0} saved", filePath);
        }

        private static void CopyStream(Stream instream, FileStream outstream)
        {
            //read from the input stream in 4K chunks
            //and save to output stream
            const int bufferLen = 4096;
            byte[] buffer = new byte[bufferLen];
            int count = 0;
            int bytecount = 0;
            //限制只能读最大的文件流大小为4K
            //把从服务器端读取来的文件流instream写入到客户端的outstream里。
            while ((count = instream.Read(buffer, 0, bufferLen)) > 0)
            {
                
                outstream.Write(buffer, 0, count);
                Console.Write(".");
                bytecount += count;
            }

            Console.WriteLine();
            Console.WriteLine("Wrote {0} bytes to stream", bytecount);
        }
    }
}

源代码: http://download.youkuaiyun.com/detail/eric_k1m/6442635
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值