第十一讲:大消息处理
代码
https://yunpan.cn/cPns5DkGnRGNs 密码:3913
从这一节课开始,不是特殊的Demo,我们不再贴实例Demo的图片了,直接去网盘找相应的项目看
大型消息传送
大消息可能来源于某些场合,比如传输大文件,或者包含了二进制附件作为消息的一部分。消息本身也可能变得很大,例如当大量记录在客户和服务之间传输的时候。大型消息在传送时,会有很多需要考虑的因素,而不仅仅是简单地考虑适应消息的大小要求。当处理大消息时,就应该控制在现有效载荷规模,以减少带宽占用和处理开销,并分流客户和服务的内存消费。
上一节课我们更改了配置,以至于增大接受消息的大小。可这仅仅是接受大小,并没有控制处理开销以及分流
控制有效负载大小
消息大小受你选择的消息编码方式的影响:
BinaryMessageEncoder,TextMessageEncoder, MtomMessageEncoder.
WEB 服务绑定的默认编码方式是Text,但是WEB服务绑定也支持Mtom编码方式。非HTTP绑定对应的默认编码方式是Binary。
用一种紧凑二进制格式来串行化消息,它产生的消息的大小是3种编码方式中最小的体积。Soap1.2消息格式仍然被保留,不过其串行化格式不是可互操作的( 仅限于.NET平台之间通信).
TextMessageEncoder:
用SOAP1.1或SOAP1.2那样的文本格式来串行化消息。这一格式是可互操作的。不过消息大小却要比二进制编码大得多。而且,任何二进制数据,比如,字节数组都是base64编码以便使用XML来表示,这就使得消息规模最大可以膨胀33%
MtomMessageEncoder:
这是一个可互操作的方式(跨平台的编码)来改善大规模二进制数据在SOAP消息的传输性能具有重大的意义。二进制数据的串行化已经是紧凑了,不过,任何时候,当你想要基于HTTP来传输大量的二进制数据时,你就应该考虑激活绑定中的MTOM编码。
我们看这个Demo,这个Demo 与之前的不太一样,前面我们都是以 控制台应用程序以及WinFrom 的方式 寄宿的服务器。
这个Demo将使用IIS方式去寄宿服务器。
创建项目的方法
[ 11-01 ]
项目结构:
[ 11-02 ]
WcfService就是服务端
IService1.cs 为服务器契约
[ 11-03 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.ServiceModel.Web; 7 using System.Text; 8 9 namespace WcfService 10 { 11 12 13 14 // 注意: 如果更改此处的接口名称 "IService1",也必须更新 Web.config 中对 "IService1" 的引用。 15 [ServiceContract] 16 public interface IService1 17 { 18 /// <summary> 19 /// 上传文件 20 /// </summary> 21 /// <param name="path">文件目标路径</param> 22 /// <param name="fileData">文件字节数组</param> 23 [OperationContract] 24 void UploadFile(string path, byte[] fileData); 25 } 26 27 28 }
Service1.svc为服务器契约的实现
[ 11-04 ]
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Runtime.Serialization; 6 using System.ServiceModel; 7 using System.ServiceModel.Web; 8 using System.Text; 9 10 namespace WcfService 11 { 12 // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“Service1”。 13 // 注意: 为了启动 WCF 测试客户端以测试此服务,请在解决方案资源管理器中选择 Service1.svc 或 Service1.svc.cs,然后开始调试。 14 15 16 17 public class Service1 : IService1 18 { 19 /// <summary> 20 /// 上传文件 21 /// </summary> 22 /// <param name="path">文件目标路径</param> 23 /// <param name="fileData">文件字节数组</param> 24 public void UploadFile(string path, byte[] fileData) 25 { 26 FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); 27 fs.Write(fileData, 0, fileData.Length); 28 fs.Flush(); 29 fs.Close(); 30 } 31 } 32 33 }
然后Web.config
[ 11-05 ]
1 <system.serviceModel> 2 <services> 3 <service name="WcfService.Service1" behaviorConfiguration="WcfService.Service1Behavior"> 4 <!-- Service Endpoints --> 5 <!--这里的address不需要配置,因为我们的这个项目是寄宿在IIS中的项目,所以address就默认是IIS指定的地址--> 6 <endpoint address="" binding="wsHttpBinding" contract="WcfService.IService1" bindingConfiguration="MtomBindingConfiguration"> 7 <!-- 8 部署时,应删除或替换下列标识元素,以反映 9 在其下运行部署服务的标识。删除之后,WCF 将 10 自动推导相应标识。 11 --> 12 <identity> 13 <dns value="localhost"/> 14 </identity> 15 </endpoint> 16 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> 17 </service> 18 </services> 19 <behaviors> 20 <serviceBehaviors> 21 <behavior name="WcfService.Service1Behavior"> 22 <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点--> 23 <serviceMetadata httpGetEnabled="true"/> 24 <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息--> 25 <serviceDebug includeExceptionDetailInFaults="false"/> 26 </behavior> 27 </serviceBehaviors> 28 </behaviors> 29 <bindings> 30 <wsHttpBinding> 31 <!--messageEncoding消息编码方式 maxReceivedMessageSize接收最大字节数[ 5000000 就是5M ] receiveTimeout接收超时时间--> 32 <binding name="MtomBindingConfiguration" messageEncoding="Mtom" maxReceivedMessageSize="5000000" receiveTimeout="00:10:00"> 33 <readerQuotas maxArrayLength="5000000"/> 34 </binding> 35 </wsHttpBinding> 36 </bindings> 37 </system.serviceModel>
Web为客户端 首先需要引用上面的服务
然后页面
[ 11-06 ]
代码
[ 11-07 ]
1 /// <summary> 2 /// 上传 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 protected void btnUpload_Click(object sender, EventArgs e) 7 { 8 WCFService.Service1Client proxy = new Web.WCFService.Service1Client(); 9 var length = file.PostedFile.ContentLength; 10 var bytes = new byte[length]; 11 file.PostedFile.InputStream.Read(bytes, 0, length); 12 try 13 { 14 proxy.UploadFile(txtDestination.Text + Path.GetFileName(file.PostedFile.FileName), bytes); 15 Page.ClientScript.RegisterStartupScript(typeof(Page), "js", "alert('上传成功');", true); 16 } 17 catch (Exception ex) 18 { 19 Page.ClientScript.RegisterStartupScript(typeof(Page), "js", "alert('" + ex.ToString() + "');", true); 20 } 21 proxy.Close(); 22 }
客户端的AppConfig文件
[ 11-08 ]
1 <system.serviceModel> 2 <!--<bindings> 3 <wsHttpBinding> 4 <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00" 5 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 6 bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" 7 maxBufferPoolSize="524288" maxReceivedMessageSize="65536" 8 messageEncoding="Mtom" textEncoding="utf-8" useDefaultWebProxy="true" 9 allowCookies="false"> 10 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" 11 maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 12 <reliableSession ordered="true" inactivityTimeout="00:10:00" 13 enabled="false" /> 14 <security mode="Message"> 15 <transport clientCredentialType="Windows" proxyCredentialType="None" 16 realm="" /> 17 <message clientCredentialType="Windows" negotiateServiceCredential="true" 18 algorithmSuite="Default" establishSecurityContext="true" /> 19 </security> 20 </binding> 21 </wsHttpBinding> 22 </bindings>--> 23 <bindings> 24 <wsHttpBinding> 25 <!--sendTimeout 发送的时间 messageEncoding消息编码方式 --> 26 <binding name="WSHttpBinding_IService1" sendTimeout="00:10:00" messageEncoding="Mtom"> 27 <!--readerQuotas发送最大字节数 [ 5000000= 5M ]--> 28 <readerQuotas maxArrayLength="5000000" /> 29 </binding> 30 <binding name="WSHttpBinding_IService11" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 31 bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" 32 messageEncoding="Mtom" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false"> 33 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 34 <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" /> 35 <security mode="Message"> 36 <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" /> 37 <message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="true" /> 38 </security> 39 </binding> 40 </wsHttpBinding> 41 </bindings> 42 <client> 43 <endpoint address="http://localhost:1217/Service1.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService11" contract="WCFService.IService1" 44 name="WSHttpBinding_IService11"> 45 <identity> 46 <dns value="localhost" /> 47 </identity> 48 </endpoint> 49 </client> 50 </system.serviceModel>
然后我们进行 测试,注意 首先需要启动服务端
[ 11-09 ]
[ 11-10 ]
然后再运行Web项目的 Default.aspx
注意:一定要先启用Service1.svc 服务,并且Web端的Web.config 中 endpoint 的 address 一定是 Service1.svc服务的地址
[ 11-11 ]
除了选择编码格式以外,你还可以采用压缩方式,以进一步减少客户和服务之间消息载荷传输的开销。
DEMO :使用MTOM来处理带有二进制数据的大型消息
为大文件传输激活消息流(使用流数据则不用把数据都加载到内存后才传输,减少内存开销)
可以看 云盘上面的 第十一讲 大消息处理 / 代码 / 2.消息流 项目
看完了代码记得 测试一下,传输一个 比较大的 文件,一定要使用消息流的这种方式。
查看在传输的过程中 注意观察 D盘传输的文件 大小变化。
文件不是一次性传入D盘的,也不是将所有文件加载到内存再复制到D盘的,而是 从C盘 传 到D 盘的 文件是经过流,过程中 D盘所传的文件大小是一点一点增加的。
从这个Demo 中我们可以 看出
在默认的情况下,消息在发送和接受的过程中是需要由服务模型来进行缓冲存储的。这样会给服务器内存带来很大的压力,尤其是当多个并发请求同时被处理时。在客户端,当很大的文件被传输和缓存时,这也会带来局部失衡。 减少内存消耗的一个选择,是像消息流操作实例那样,用消息流的方式发送大消息。这样就防止了服务模型将整个消息读进内存,并且允许消息的接收者(客户端)控制消息流的读入方式。对于大的文件来说,这可能意味着直接将消息写入磁盘。
我们说一下 关于 服务端配置 消息大小限额 的 相关节点:
有一些设置影响到服务模型处理消息的方式;有一些特殊设置限制了字符串的长度,数组的长度,对象,XML嵌套和整个消息的大小;其他设置控制了缓冲处理的方式,并作为内存占用的分流和垃圾回收开销。
MaxBufferPoolSize:预先分配缓冲区以处理到达请求,而不是从内存堆中分配.
MaxBufferSize:限制到达消息的缓冲区压力。当对消息被缓存时,其大小必须匹配.
MaxReceivedMessageSize.而对于消息流,缓冲区长度会比较小从而减少缓存消耗.
MaxReceivedMessageSize:限制服务和客户受到的整个串行化消息的大小,这与内存消费无关——反串行化消息时或许需要额外的内存.
下面的属性是阅读器限额---是ReaderQuotas绑定属性一部分,影响XML阅读器处理消息的方式:
MaxArrayLength:限制字符串和其他数组的长度,包括大型二进制传输的字节数组.
MaxBytesPerRead:限制每一个阅读操作的字节数组.
MaxDepth:限制XML元素的嵌套深度.
MaxNameTableCharCount:限制阅读器名称表的大小。名称表包括XML消息流中的前缀和命名空间.
MaxStringContentLength:限制字符串元素的长度.最后一个行为是数据契约串行化限额,这个限额通过一个行为提供,与绑定属性相对:MaxItemsInObjectGraph
限制串行化期间对象图中可供展现的对象数目。如果你传送带有很多嵌套引用类型的对象,那么,你就超出了这个限额.
它们都是局部设置,意思是,它们不包含在服务描述中,因此不能够为客户所知晓,单客户可以分流限额以适应自身的需要,事实上,客户代理依据客户限额来验证消息,使其不会发送超过限额的消息来麻烦服务.
消息流对大文件传输很有用。消息流在发送和接收时不必缓存消息,这使得发送方提供消息流对象而接收方增量式读入它成为可能。这样做减少了内存消耗,尤其是,如果采用文件流方式,就使得服务器端的文件直接以流方式到达客户一端的文件中成为可能。对于
服务器来说,这样做有很多好处,因为它将放置服务器将文件的整个实例装入内存用于并发请求,而原来这样做会很快导致内存异常,从而导致其他任何后续请求不能够得到服务。对于客户来说,当文件很大时,这样做也很有用,因为他们有可能没有足够的内存来缓存整个文件.
为了完全支持消息流,需要满足一些基本要求:
1:操作接收或返回一个单一Stream类型
2:服务契约的绑定协议必须支持消息流
3:必须在绑定中激活消息流.
激活消息流:
你可以在如下的绑定中激活消息流:NetNamedPipeBinding,NetTcpBinding(用于企业级局域网)和BasicHttpBinding(用于互联网).
这是通过设置绑定的TransferMode属性来实现的,该属性的选项如下所示:
Buffered:这是一个默认值,没有消息流.
Streamed:在绑定的请求和回复上激活消息流
StreamedRequest:只在请求方向上激活消息流
StreamedResponse:只在回复方向上激活消息流.
1:优先考虑WSHttpBinding用于WEB服务端点。WSHttpBinding的配置将会随着协议支持会话、可靠通信、事务和安全性的不同变化。(将BasicHttpBinding用于向后兼容,只兼容老的WEB服务,如果需要传输大文件的话也是 有可能 需要考虑使用BasicHttpBinding绑定方式,因为它可以使用Streame 消息流)
2:将NetNamedPipeBinding用于同一台机器上的通信过程。使用NetTcpBinding来支持跨机器边界的调用.
3:如果需要支持基于HTTP的回叫(HTTP方式的双工通信),就用WSDualHttpBinding。
4:留心一下期望的消息有效负载大小,并相应地设置绑定配置。如果你在使用文件传输时,将MTOM协议用于基于HTTP和消息流的大的二进制负载,可以减少内存消耗.