WCF Data Transfer buffered VS streamed

在Data Transfer中,我们会经常听到开发提到buffer mode和stream mode。对于不了解Data Transfer逻辑的同学来说,很难通过字面意思理解这两种传输方式分别是什么以及他们有什么区别。今天这篇文章将尽量为大家解释这两种传输方式的原理和区别。

  • Buffered vs Streamed概述
  • Buffered vs Streamed效率探究
  • Summary
  • 关于目前Data Transfer的改进建议

Buffered vs Streamed概述

Buffered和Streamed是WCF中的两种传输数据的模式。我们可以直接引用微软对两种模式的解释:

  • Buffered transfers hold the entire message in a memory buffer until
    the transfer is complete. A buffered message must be completely
    delivered before a receiver can read it.

  • Streamed transfers expose the message as a stream. The receiver
    starts processing the message before it is completely delivered.

Buffered Mode只有将想要发送的对象完整传输到接收端后,接收端才能操作该对象。而Streamed Mode可以在对象传输过程中操作传输对象。显然,当需要传输的数据较大且不需要得到完整对象就能继续操作的情况下,Streamed Mode是更好的选择。
WCF应用streamed mode的方式很简单,只需要在代码里给binding的TransferMode赋值,或者通过修改App.config/web.config,修改binding的attribute。

注意:目前Streamed只支持NetTcpBinding,BasicHttpBinding,WSHttpBinding

下面用一个简单的例子来说明上述解释:

Client端Code:

static void TestStream(){using (var sm = new FileStream(@"C:\lg.zip", FileMode.Open)){BasicHttpBinding binding = new BasicHttpBinding();binding.SendTimeout = ((BasicHttpBinding)binding).SendTimeout = TimeSpan.FromMinutes(100);binding.TransferMode = TransferMode.Streamed;var factory = new ChannelFactory<ITService>(binding, new EndpointAddress($"http://{host}:14011/TService/Streamed"));mChannel = factory.CreateChannel();Console.WriteLine("Start send Stream. TransferMode is Streamed . Time now is {0}", DateTime.Now.ToLongTimeString());mChannel.OneWayStream(sm);}using (var sm = new FileStream(@"C:\lg.zip", FileMode.Open)){BasicHttpBinding binding = new BasicHttpBinding();binding.SendTimeout = ((BasicHttpBinding)binding).SendTimeout = TimeSpan.FromMinutes(100);//binding.TransferMode = TransferMode.Streamed;var factory = new ChannelFactory<ITService>(binding, new EndpointAddress($"http://{host}:14011/TService/Buffered"));mChannel = factory.CreateChannel();Console.WriteLine("Start send Stream. TransferMode is Buffered . Time now is {0}", DateTime.Now.ToLongTimeString());mChannel.OneWayStream(sm);}Console.ReadKey();}

Server端code:

 public void OneWayStream(Stream stream){Console.WriteLine("Got Stream. Time Now:{0}", DateTime.Now.ToLongTimeString());using (stream){}}

Server端配置文件片段:

<endpoint address="http://10.2.66.62:14011/TService/Streamed" binding="basicHttpBinding" bindingConfiguration="streamedHttpBinding" contract="TransferService.ITService"></endpoint><endpoint address="http://10.2.66.62:14011/TService/Buffered" binding="basicHttpBinding" bindingConfiguration="bufferedHttpBinding" contract="TransferService.ITService"></endpoint><endpoint address="http://10.2.66.62:14011/TService/Mtom" binding="basicHttpBinding" bindingConfiguration="mtomHttpBinding" contract="TransferService.ITService"></endpoint><endpoint address="http://10.2.66.62:14011/TService/Buffered/Mtom" binding="basicHttpBinding" bindingConfiguration="bufferedMtomHttpBinding" contract="TransferService.ITService"></endpoint><endpoint address="net.tcp://10.2.66.62:14012/TService/Streamed" binding="netTcpBinding" bindingConfiguration="streamednetTcpBinding" contract="TransferService.ITService"></endpoint><endpoint address="net.tcp://10.2.66.62:14012/TService/Buffered" binding="netTcpBinding" bindingConfiguration="bufferednetTcpBinding" contract="TransferService.ITService"></endpoint><basicHttpBinding><binding name="streamedHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" transferMode="Streamed" messageEncoding="Text" maxBufferPoolSize="524288" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding><binding name="bufferedHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" maxBufferPoolSize="524288" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding><binding name="mtomHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" transferMode="Streamed" messageEncoding="Mtom" maxBufferPoolSize="524288" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding><basicHttpBinding><binding name="streamedHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" transferMode="Streamed" messageEncoding="Text" maxBufferPoolSize="524288" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding><binding name="bufferedHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" maxBufferPoolSize="524288" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding><binding name="bufferedMtomHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" maxBufferPoolSize="524288" messageEncoding="Mtom" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding><binding name="mtomHttpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647" transferMode="Streamed" messageEncoding="Mtom" maxBufferPoolSize="524288" maxBufferSize="2147483647" allowCookies="true"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="65536" maxNameTableCharCount="65536"/></binding></basicHttpBinding><netTcpBinding><binding name="streamednetTcpBinding" receiveTimeout="10:10:10" transferMode="Streamed" maxReceivedMessageSize="2147483647"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="4096" maxNameTableCharCount="65536"/></binding><binding name="bufferednetTcpBinding" receiveTimeout="10:10:10" maxReceivedMessageSize="2147483647"><readerQuotas maxDepth="320" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="4096" maxNameTableCharCount="65536"/></binding></netTcpBinding>

测试文件大小340MB,测试结果如下:

Client端

Server端

从测试结果可以看出,通过Streamed Mode发送的Stream,接收端在1s之内就可以得到Stream对象并操作。而通过Buffered Mode发送的Stream,接收端在接近20s之后才获得Stream对象,由于client和server机器在同一网段,足以将完整的Stream发送到目的端。

Buffered vs Streamed效率探究

  • Buffered Performance

    通过上面的阐述和测试,我们知道Buffered Mode实际上是一种类似于同步的传输方式。在实际的数据传输过程中,我们不可能将Stream本身完整发送到目的端,所以buffered mode的Data Transfer实现类似如下。为了防止磁盘读写对测试结果造成影响,本文所有Send操作会将本地FileStream读取到MemoryStream中,再向接收端发送,接收端只读取接收到的Buffer或者Stream,不会写入磁盘中。
    Client端Code:

innerStream = new FileStream(@"C:\test.zip", FileMode.Open);int read = 0;int offset = 0;var buffer = new byte[64 * 1024];while ((read = innerStream.Read(buffer, 0, 64 * 1024)) != 0){memory.Write(buffer, 0, read);offset += read;}
var buffer = new byte[64 * 1024];int read = 0;Stopwatch watch = new Stopwatch();memory.Position = 0;watch.Start();while ((read = memory.Read(buffer, 0, 64 * 1024)) != 0){mStream.Write(buffer, 0, read);}mStream.Flush();//mStream.Position = 0;watch.Stop();Console.WriteLine("Send time:{0}", watch.ElapsedMilliseconds);

Server端Code:

public void PutBuffer(byte[] buffer, int totalSize){            Console.WriteLine("Time now is :{0}. Receive buffer:{1}, total size:{2}",DateTime.Now.ToLongTimeString(), buffer.Length, totalSize);}

仍然以340MB大小文件为例,测试不同网络条件下buffered mode的传输表现,server端WCF配置文件参考第一节。

  • 内网无延迟

    • BasicHttpBinding
      发送端数据

    • BasicHttpBinding with Mtom

Mtom是为HttpBinding提供的传输优化机制

  • NetTcpBinding

    发送端数据

在理想的网络条件下,buffered mode在NetTcpBinding和BasicHttpBinding中的表现区别不大,NetTcpBinding比BasicHttpBinding略快一些,如果BasicHttpBinding应用了Mtom的编码模式,与NetTcpBinding几乎没有效率上的区别。

  • 内网延迟100ms

    • BasicHttpBinding

    发送数据端

    • BasicHttpBinding with Mtom

  • NetTcpBinding

    发送端数据

    我们看到在高延迟下buffered mode的表现是灾难性的,从理想网络条件下的接近9M/s的速度骤减到只有200~400kb/s。由于每次传输buffer都是同步传输,都会受到网络延迟的影响,显然这个影响的程度是我们不能接受的。

疑问:我们看到NetTcpBinding下每次调用OperationContract方法所花费的时间是BasicHttpBinding下花费时间的一半左右,原因尚待研究。

对象完整发送完全之前接收端无法操作对象,是buffered mode的特性之一。对于大数据,如果我们一次将大数据发送到目的端,很容易导致内存溢出。如果分为多次发送,网络延迟会施加在每次传输中,造成的效率影响十分巨大。综上,buffered mode的传输方式不适合应用在较大数据的传输方案中。

现在我们需要寻找一种调用传输(OperationContract)次数少(最好是一次),且不会在单次传输中出现内存溢出的传输方式。让我们来看看Streamed Mode是否是那个救世主。

  • Streamed Performance

    Streamed Mode的特性决定了我们可以先将Stream对象发送到接收端,然后在发送端将数据写入Stream中,接收端从Stream中直接读取数据。这种情况下,我们可以使用WCF提供的异步方式来发送。

Client端Code:

var doc = new Document() { Stream = mStream, Length = innerStream.Length };Stopwatch watch = new Stopwatch();watch.Start();                 var result = mChannel.BeginTransferDocument(doc, OnTransferCompleted, mChannel);Thread start = new Thread(SendThread);start.IsBackground = true;start.Start();result.AsyncWaitHandle.WaitOne();static void SendThread(){var buffer = new byte[64 * 1024];int read = 0;Stopwatch watch = new Stopwatch();watch.Start();while ((read = memory.Read(buffer, 0, 64 * 1024)) != 0){mStream.Write(buffer, 0, read);}mStream.Flush();watch.Stop();Console.WriteLine("Send time:{0}",watch.ElapsedMilliseconds);doc.Stream.Position = 0;}

Contract:

[OperationContract]void TransferDocument(Document document);[OperationContract(AsyncPattern = true)]
IAsyncResult BeginTransferDocument(Document document,
AsyncCallback callback, object asyncState);void EndTransferDocument(IAsyncResult result);[MessageContract]public class Document{Stream stream;long length = 0L;[MessageBodyMember]public Stream Stream{get { return stream; }set { stream = value; }}[MessageHeader(MustUnderstand = true)]public long Length{get { return length; }set { length = value; }}}

Server端Code:

public void TransferDocument(Document document){Console.WriteLine("Begin TransferDocument");var buffer = new byte[64 * 1024];int offset = 0;int read = 0;int readTimes = 0;while (true){if ((read = document.Stream.Read(buffer, 0, 64 * 1024)) == 0){Thread.Sleep(1000);continue;}break;}Console.WriteLine("Time now :{0} Current read:{1}", DateTime.Now.ToLongTimeString(), read);offset += read;readTimes++;while (offset < document.Length){read = document.Stream.Read(buffer, 0, 64 * 1024);if (read != 0){readTimes++;Console.WriteLine("Time now :{0} Current read:{1}", DateTime.Now.ToLongTimeString(), read);}offset += read;           Console.WriteLine("Total size:{0}. Total read times is {1}", offset.ToString(), readTimes);}}

注意:在Streamed传输中,如果要传输的对象不只是Stream,那么必须给对象添加MessageContract,而不是DataContract,其中Stream属性是Message Body。

从代码中可以看到,我们只调用了一次OperationContract方法,之后都是在操作传递过去的Stream,这符合了我们要减少直接调用WCF OperationContract的目的。

所有测试条件与Buffered Mode相同,我们来看一下Streamed Mode在不同网络条件下的表现。

  • 内网无延迟

    • BasicHttpBinding

    发送端数据

接收端数据

疑问:在BasicHttpBinding且Transfer Mode为Streamed情况下,接收端每次只能读取1536byte,无论maxBytesPerRead设置为多少。目前还未找到原因,这是制约传输效率的重要原因。

  • BasicHttpBinding with Mtom

    发送端数据

    接收端数据

    疑问:在BasicHttpBinding 将编码模式改为Mtom后,变为每次可读取4096byte。

  • NetTcpBinding

    发送端数据

    接收端数据

    疑问:在NetTcpBinding中我们也遇到了有趣的事情,maxBytesPerRead是65536且maxBufferSize是2147483647(2G)情况下,接收端每次最多只能读取65529且下次读取会很少,目前怀疑是TCP的窗口滑动机制在作祟,窗口滑动是TCP的流量控制机制。

虽然存在只读取几个字节的情况,但是由于NetTcpBinding每两次可以读取64k数据,所以在理想网络情况下,NetTcpBinding+Streamed似乎是我们的最优解。

  • 内网100ms延迟

    • BasicHttpBinding
      发送端数据

      接收端数据

      带宽占用峰值

    • BasicHttpBinding with Mtom

    发送端数据

    接收端数据

    带宽占用峰值

    • NetTcpBinding

    发送端数据

    接收端数据

带宽占用峰值

出乎我们的意料,在延迟较高的情况下,nettcpbinding的表现令人大跌眼镜,平均速度只有400kb/s,带宽占用也一直稳定在4Mbps左右。反而httpbinding的速度达到甚至超过了直接在server间Copy的速度。

我们知道Http是基于tcp的协议,也就是说http协议进行的操作理论上比net.tcp只多不少,为什么会快于net.tcp呢。目前找到的一个比较合理的解释是,在WCF中,tcp transport是connection-based,发送数据包之前需要和对方确认状态,这个过程无疑放大了network delay带来的影响。而http transport则不是connection-based,发送任何相应之前不需要确认状态。

我们会继续研究哪些因素或设定会导致http会快于net.tcp

Choose a transport

连接文中提到,如果发送端和接收端都是WCF程序,NetTcpBinding的效率是要优于HttpBinding的,而我们在内网无延迟的测试中也证明了这个情况。但是如果我们想建立一个可以在恶劣网络条件下仍然能保持较高传输效率的Large Data Transfer体系,目前来看NetTcpBinding恐怕无法满足我们的要求。

在研究的最后,我将发送端和接收端机器之前的带宽限制为5500kbps,延迟保持100ms不变。

  • BasicHttpBinding
  • BasicHttpBinding with Mtom

这两种情况都几乎可以将带宽占满,BasicHttpBinding with Mtom的表现要相对好一些。

Summary

Binding Buffered内网无延迟 Buffered内网100ms延迟 Streamed内网无延迟 Streamed内网100ms延迟 100ms延迟+5500kbps
NetTcpBinding 8761kb/s 437kb\s 14571kb/s 449kb/s
BasicHttpBinding 7419kb/s 234kb/s 3778kb/s 1002kb/s 宽度满占用
BasicHttpBinding With Mtom 9514kb/s 194kb/s 8570bk/s 1315kb\s 宽带满占用

为了更明确network delay对于NetTcpBinding的影响,另外测试了50ms延迟下的情况

Binding Streamed内网50ms延迟 Buffered内网50ms延迟
NetTcpBinding 671kb/s 602kb/s
BasicHttpBinding 1139kb/s 315kb/s
BasicHttpBinding With Mtom 1401kb/s 334kb/s

从目前的测试结果来看,考虑的适应大多数网络情况,我们应该采取基于HttpBinding with Mtom+Stream的解决方案。但是由于NetTcpBinding Transport是connection-based的传输方式,相比HttpBinding,稳定性会更有保证。

关于目前Data Transfer的改进建议

目前Data Transfer普遍还在使用buffered mode的传输方式,且实现方法和本文例子中的方式基本一致,在存在延迟的情况下效率会大打折扣。建议放弃这种传输方式。

目前Http Stream Mode采取了将数据先缓存在本地,之后将读取本地文件的stream异步发送到目的端。并且创建了很多数据单元来进行上述操作。目前这套逻辑遇到过连接异常断开的问题,且未找到原因。

WCF Data transfer buffered VS streamed相关推荐

  1. 如何消费WCF Data Services定义的服务操作

    Service Operations (WCF Data Services)描述了如何 自定义WCF Data Service的服务.客户端如何消费可以参考文章Service Operations a ...

  2. WCF Data Service 的.NET Client 的不支持原生类型服务操作的解决方法

    WCF Data Service  的.NET Client 的不支持返回值为原生类型(string,int)的服务操作调用,例如我们用如下服务操作: [WebGet] public ObjectQu ...

  3. WCF Data Service文章列表

    张善友blogs,有不少文章 http://www.cnblogs.com/shanyou/category/240225.html WCF Data Service安全分析和说明 http://ww ...

  4. 5G NR RLC:Data Transfer ARQ

    其他相关内容 RLC PDU and Parameters RLC架构和RLC entity 一 RLC entity handling RLC entity有建立.重建和释放的过程(establis ...

  5. 【转】WCF Data Service 使用小结(二) —— 使用WCF Data Service 创建OData服务

    在 上一章 中,介绍了如何通过 OData 协议来访问 OData 服务提供的资源.下面来介绍如何创建一个 OData 服务.在这篇文章中,主要说明在.NET的环境下,如何使用 WCF Data Se ...

  6. 【转】WCF Data Service 使用小结 (一)—— 了解OData协议

    最近做了一个小项目,其中用到了 WCF Data Service,之前是叫 ADO.NET Data Service 的.关于WCF Data Service,博客园里的介绍并不多,但它确实是个很好的 ...

  7. WCF Data Services 基础

    把最近使用的WCF Data Service和WCF RIA Service的使用例子发布在站点http://dskit.codeplex.com , 本系列文章就把WCF Data Service和 ...

  8. WCF Data Services服务端处理汇总

    和可以在客户端直接使用的查询对应,在服务端也有很多可以增强的功能 Service Operations 自己发布一些业务逻辑的处理 Service operations enable you to e ...

  9. HTTP Basic Authentication验证WCF Data Service

    WCF Data Service是OData协议,也是RESTFul Service的一种,上篇文章已经介绍了HTTP Basic Authentication for RESTFul Service ...

最新文章

  1. Sql注入和Html注入
  2. org.springframework.security.web.util.TextEscapeUtils
  3. mysql建立数据浏览器_一个简单的MySQL数据浏览器
  4. 九十三、动态规划系列之股票问题(下)
  5. 【转】蓝牙技术及其系统原理
  6. 基于C语言的函数指针应用-消息命令处理框架
  7. Bash数组操作教程
  8. 如何激活 Trend Micro Deep Security Agent
  9. 涂鸦板制作教程——其中的重做和撤消我觉得不错
  10. python语言通过import_python语言的引入(import)机制简述
  11. idea中maven找不到本地仓库jar包_有人说 Maven 很简单,我却被 伤害 过
  12. 《BDD100K: A Diverse Driving Dataset for Heterogeneous Multitask Learning》论文阅读笔记
  13. ionic加载html5,ionic 加载动画
  14. MTK Android 之MT6577驱动笔记
  15. 2022 世界人工智能大会,都讲了些啥?
  16. c语言作业订单号查询,C语言 查询订单系统进不去 还请高手指点
  17. 移动安全规范 — 2 -蓝牙安全规范
  18. mysql benchmark 测试工具_mysql benchmark基准测试
  19. 《机器学习实战》— k-近邻算法
  20. uniapp开发app过程中集成友盟统计

热门文章

  1. Linux常用命令和工具
  2. 我的世界粘土服务器怎么注册a,我的世界粘土服务器怎么进 | 手游网游页游攻略大全...
  3. 开发APP帮用户虚拟定位打卡!创始人二审改判四年
  4. python软件下载免费还是收费-开源等于免费吗?真相在这里
  5. 产品运营的工作内容都有哪些
  6. 计算机二级是笔试还是机考,计算机二级考试是上机考还是笔试?
  7. 爆肝200天!18岁上海高中生自制机器人,250行Python代码「注入灵魂」
  8. Http响应状态Status为canceled
  9. 有效利用Oracle官方的免费学习资源
  10. Excel 2010 VBA 入门 114 设置自定义函数的说明