學會WCF之試錯法——數據傳輸
數據傳輸
服務契約
[ServiceContract] public interface IService { [OperationContract] string GetData(int value); [OperationContract] string GetString(string value); [OperationContract] void Upload(Request request); } [MessageContract] publicclass 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)多個參數配置不正確時,拋出的異常信息中會選則性地指出某個參數設置不正確,而不是將所有配置不正確的參數都指出來。
學會WCF之試錯法——數據傳輸