数据传输
服务契约
[ServiceContract] public interface IService { [OperationContract] string GetData(int value); [OperationContract] string GetString(string value); [OperationContract] void Upload(Request request); } [MessageContract] public class Request { [MessageHeader(MustUnderstand = true)] public string FileName { get; set; } [MessageBodyMember(Order = 1)] public Stream Content {get;set;} }
服务
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Reentrant)] public class Service : IService { public string GetData(int value) { //Thread.Sleep(120000); return string.Format("You entered: {0}", value); } public string GetString(string value) { //Thread.Sleep(120000); return string.Format("You entered: {0}", value); } public void Upload(Request request) { try { StreamReader sr = new StreamReader(request.Content, Encoding.GetEncoding("GB2312")); StreamWriter sw = new StreamWriter("E:\\" + request.FileName + ".txt", false, Encoding.GetEncoding("GB2312")); while (!sr.EndOfStream) { sw.WriteLine(sr.ReadLine()); //Thread.Sleep(5000); } sr.Close(); sw.Close(); } catch (Exception ex) { } } }
服务配置
<system.serviceModel> <services> <service name="WCF_Find_Error_Lib.Service"> <endpoint address="" binding="basicHttpBinding" contract="WCF_Find_Error_Lib.IService"> <identity> <dns value="localhost" /> </identity> </endpoint> <host> <baseAddresses> <add baseAddress="http://localhost/S" /> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/> <serviceDebug includeExceptionDetailInFaults="False" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
客户端代理
public class ServiceProxy { public string GetData(int value) { string ret = null; ServiceClient client = null; try { client = new ServiceClient(); ret = client.GetData(value); client.Close(); } catch { if (client != null) { client.Abort(); } throw; } return ret; } public string GetString(string value) { string ret = null; ServiceClient client = null; try { client = new ServiceClient(); ret = client.GetString(value); client.Close(); } catch { if (client != null) { client.Abort(); } throw; } return ret; } public void Upload(Request request) { ServiceClient client = null; try { client = new ServiceClient(); client.Upload(request); client.Close(); } catch { if (client != null) { client.Abort(); } throw; } } } [ServiceContractAttribute(ConfigurationName = "IService")] public interface IService { [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IService/GetData", ReplyAction = "http://tempuri.org/IService/GetDataResponse")] string GetData(int value); [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IService/GetString", ReplyAction = "http://tempuri.org/IService/GetStringResponse")] string GetString(string value); [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IService/Upload", ReplyAction = "http://tempuri.org/IService/UploadResponse")] void Upload(Request request); } [MessageContract] public class Request { [MessageHeader(MustUnderstand = true)] public string FileName { get; set; } [MessageBodyMember(Order = 1)] public Stream Content { get; set; } } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Reentrant)] public class ServiceClient : System.ServiceModel.ClientBase<IService>, IService { public ServiceClient() { } public string GetData(int value) { return base.Channel.GetData(value); } public string GetString(string value) { return base.Channel.GetString(value); } public void Upload(Request request) { base.Channel.Upload(request); } }
客户端配置
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" /> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost/S" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService" contract="IService" name="BasicHttpBinding_IService" /> </client> </system.serviceModel>
配置参数:
MaxBufferPoolSize : 从通道接收消息的消息缓冲区管理器分配并供其使用的最大内存量 , BasicHttpBinding.MaxBufferPoolSize 默认值为 524288 个字节。 WSHttpBindingBase.MaxBufferPoolSize 默认值为 65,536 个字节 ,配置它可提高性能。
MaxBufferSize : 从通道接收消息的缓冲区最大大小 , 默认值为 65,536 个字节。
MaxReceivedMessageSize : 此绑定配置的通道上接收的消息 的最大值, 默认值为 65,536 个字节。
TransferMode : 指示是通过缓冲处理还是流处理来发送消息
默认情况下, HTTP 、 TCP/IP 和命名管道传输协议使用缓冲消息传输 。
XmlDictionaryReaderQuotas.MaxStringContentLength :读取器返回最大字符串长度,默认为 8192
1 文件编码引起的错误
问题描述:
客户端向服务端传输数据时,假如是一个文本文件,接口参数是 Stream,那么服务端使用StreamReader或StreamWrite时,不指定编码,服务运行在win7 及更新的操作系统上,默认的编码方式为Unicode,如果文本文件含义中文,那么服务端接收的内容出现乱码。而且服务端接收到的文件大小大于客户端传输的文件大小。
服务端配置
绑定配置如下,其他不变
<bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" maxBufferSize="170000"/> </basicHttpBinding> </bindings>
启动服务,报错,可见单独配置 maxBufferSize是不行的。
修改配置如下,可正常运行:
服务端
<bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" maxBufferSize="170000" maxReceivedMessageSize="170000"/> </basicHttpBinding> </bindings>
客户端配置
<bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" sendTimeout="00:00:10" /> </basicHttpBinding> </bindings>
调用接口
try { ServiceProxy proxy = new ServiceProxy(); //string s = proxy.GetData(1); //Console.WriteLine(s); Request r = new Request { Content = new FileStream("D:\\CSBMTEMP.txt", FileMode.Open), FileName = "CSBMTEMP" }; proxy.Upload(r); Console.Read(); } //catch (CommunicationException ex) //{ //} catch (Exception ex) { }
异常信息:
首先检查文件的大小,发现文件大小没有超过配置的最大值
将服务端配置按如下修改,也就是增大 maxBufferSize 和 maxReceivedMessageSize 的值。
<bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" maxBufferSize="1700000" maxReceivedMessageSize="1700000"> </binding> </basicHttpBinding> </bindings>
运行客户端程序,发现服务端接收到文件为 338K ,远大于客户端上传文件大小, 打开文件,看到乱码。
解决方法
将服务端 Upload 方法修改一下:
StreamReader sr = new StreamReader(request.Content,Encoding.Default);
StreamWriter sw = new StreamWriter("E:\\" + request.FileName + ".txt", false, Encoding.Default);
上面那两行代码添加编码方式为 Encoding.Default ,或者使用 Encoding.GetEncoding("GB2312") 这种编码方式。使用后者更好,因为明确地指出编码方式。
启动服务,运行客户端上传文件,成功且无乱码。文件大小也和客户端上传的相同。
2 maxBufferSize 与 maxReceivedMessageSize 的设置
上面 服务端 maxBufferSize 和 maxReceivedMessageSize 设置为 1700000 ,远大于所传文件大小,那么将其改为 167936 会如何呢?
修改上面两个参数为 167936 ,运行程序。结果报错:
那么,难道上传文件的数据会比这个大吗?是的,这只是消息的一部分内容。从客户端本地 CLR 类型来看,其传递的参数是一个对象: Request ,它包含了两字段, Stream 类型的 Content 和 string 类型的 FileName 。但这只是其中一部分原因。
经测试,若 maxBufferSize 和 maxReceivedMessageSize 设置为大于上传文件的二倍时,上传成功。由于默认采用缓冲处理模式( TransferMode .Buffered ),缓冲处理是将消息全部缓存下来以后才对消息进行处理,猜想,缓存消息所需空间,加上处理消息也需要空间,那么两者的和就是二倍的传入消息大小。
若换成流传输模式 ( 客户端不使用流模式,很奇怪的测试,不过依然通过 )
服务端配置变为:
<basicHttpBinding> <binding name="BasicHttpBinding_IService" transferMode="Streamed"/> </basicHttpBinding>
客户端配置不变,即:
<bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" sendTimeout="00:00:10" /> </basicHttpBinding> </bindings>
客户端调用接口,服务端报错。
客户端配置不变,服务端配置变为:
<basicHttpBinding> <binding name="BasicHttpBinding_IService" maxBufferSize="170000" maxReceivedMessageSize="170000" transferMode="Streamed"> </binding> </basicHttpBinding>
客户端调用接口,正常将文件传输到服务器。
再次将服务端 maxBufferSize 和 maxReceivedMessageSize 修改为小于文件长度: 150000 ,调用接口,可正常传输文件,但是只上传了文件的一半左右。继续将 maxBufferSize 和 maxReceivedMessageSize 调小为 1000000 ,发现只上传了 8K 左右。结论是:当 maxBufferSize 和 maxReceivedMessageSize 设置小于消息大小的时候,程序正常运行,但是服务端无法接收客户端上传的全部数据。然而,如何准确地估计消息大小是个难题。最稳妥的办法是将上面那两个参数配置为消息体的最大值的二倍,另外从客户端设置上传消息的大小很多时候也是必要的。
将绑定换成 netTcpBinding
服务端配置
<bindings> <netTcpBinding> <binding name="NetTcpBinding_IService" maxBufferSize="17936" maxReceivedMessageSize="17936"/> </netTcpBinding> </bindings>
显然 maxBufferSize 和 maxReceivedMessageSize 设置小于客户端发送的消息大小
采用默认的缓冲机制而并非流机制
客户端捕获异常:
从上面的异常中看不出是什么原因造成的,不过看下 _remoteStackTraceString :
Server stack trace:
在 System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
在 System.ServiceModel.Channels.SessionConnectionReader.Receive(TimeSpan timeout)
在 System.ServiceModel.Channels.SynchronizedMessageSource.Receive(TimeSpan timeout)
在 System.ServiceModel.Channels.TransportDuplexSessionChannel.Receive(TimeSpan timeout)
在 System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceive(TimeSpan timeout, Message& message)
在 System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
在 System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
从上面 StreamConnection.Read , SessionConnectionReader.Receive 大概猜测是 maxBufferSize 和 maxReceivedMessageSize 设置引起的,由于套接字连接终止,在服务端无法捕获这类异常。将参数调大就没有这个问题了,数据可正常上传。
那么采用 netTcpBinding 是比较安全的,因为一旦设置的过小,会抛异常而不会出现数据不完整(只上传一部分)的情况。
当数据传输过程中服务关闭或网络中断抛异常:
貌似与 maxBufferSize 和 maxReceivedMessageSize 这两个参数设置不正确所抛异常一样,而且 _remoteStackTraceString 也与其一样,真是让人迷惑啊!!!
绑定配置变换为:
<netTcpBinding> <binding name="NetTcpBinding_IService" maxBufferSize="79360" maxReceivedMessageSize="79360" transferMode="Streamed"/> </netTcpBinding>
客户端配置:
<netTcpBinding> <binding name="NetTcpBinding_IService" sendTimeout="00:00:10"/> </netTcpBinding>
客户端调用报错
修改客户端配置为:
<netTcpBinding> <binding name="NetTcpBinding_IService" sendTimeout="00:00:10" transferMode="Streamed"/> </netTcpBinding>
这次是由于 maxBufferSize 和 maxReceivedMessageSize 这两个参数设置小了的原因。
3 对于字符串传输的限制
服务端配置:
<bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" maxBufferSize="220000" maxReceivedMessageSize="220000" transferMode="Streamed"> </binding> </basicHttpBinding> </bindings> <services> <service name="WCF_Find_Error_Lib.Service"> <endpoint address="" binding="basicHttpBinding" contract="WCF_Find_Error_Lib.IService" bindingConfiguration="BasicHttpBinding_IService"> <identity> <dns value="localhost" /> </identity> </endpoint> <host> <baseAddresses> <add baseAddress="http://localhost/S" /> </baseAddresses> </host> </service> </services>
客户端配置:
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" sendTimeout="00:00:10"/> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost/S" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService" contract="IService" name="BasicHttpBinding_IService" /> </client> </system.serviceModel>
客户端调用:
ServiceProxy proxy = new ServiceProxy(); StreamReader sr = new StreamReader("D:\\CSBMTEMP.txt",Encoding.Default); string str = sr.ReadToEnd(); sr.Close(); proxy.GetString(str);
运行客户端报错:
错误消息为:
格式化程序尝试对消息反序列化时引发异常 : 对操作“ GetString ”的请求消息正文进行反序列化时出现错误。读取 XML 数据时,超出最大字符串内容长度配额 (8192) 。
那么看一下传入的字符串大小为 238367 个字符,因此修改服务端配置文件,而保持客户端配置不变
<basicHttpBinding> <binding name="BasicHttpBinding_IService" maxBufferSize="220000" maxReceivedMessageSize="220000" transferMode="Streamed"> <readerQuotas maxStringContentLength="240000"/> </binding> </basicHttpBinding>
运行客户端程序,依然报错信息如下:
此次是因为服务端 maxBufferSize 和 maxReceivedMessageSize 设置小了,将其设置为 270000 ,客户端保持不变。
客户端抛异常:
服务端参数已经设置的比较大了,但是依然报错,发现客户端没有设置 maxStringContentLength 这个参数,更改客户端配置,服务端保持不变:
<binding name="BasicHttpBinding_IService" sendTimeout="00:00:10" transferMode="Streamed"> <readerQuotas maxStringContentLength="240000"/> </binding>
运行客户端,依然报错
发现服务端 maxReceivedMessageSize 没有设置,更改客户端配置,服务端保持不变, 至此信息被正确接收。
<basicHttpBinding> <binding name="BasicHttpBinding_IService" sendTimeout="00:00:10" transferMode="Streamed" maxReceivedMessageSize="270000"> <readerQuotas maxStringContentLength="240000"/> </binding> </basicHttpBinding>
通过上述测试,发现:
1)当客户端配置不正确或服务端配置不正确时,异常可在客户端捕获,但捕获的异常信息相同,即客户端与服务端因同一类参数配置不正确所引发的异常信息一样,无法通过异常信息分辨出是由于客户端配置不正确还是由于服务端配置不正确引起的。
2)一般地,对于使用basicHttpBinding的服务,当由于配置不正确,抛出异常时,可以通过捕获的异常查看是哪个参数配置不正确,但是使用netTcpBinding的服务则无法通过客户端捕获的异常分辨是哪个参数配置的不正确。
3)客户端与服务端配置不一致时,可正常运行服务,但不一定得到正确的结果。某些资料建议将客户端和服务端配置设置为相同,不失为一种简单的办法,但忽略了配置参数的含义,建议深入理解各个参数的含义,合理配置。
4)多个参数配置不正确时,抛出的异常信息中会选则性地指出某个参数设置不正确,而不是将所有配置不正确的参数都指出来。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。