积极答复者
IIS 托管的 WCF 服务中“basicHttpBinding”协议究竟能否以“流”方式进行大文件传输?

问题
-
项目中一个小模块需要向服务器上传文件的功能,考虑性能问题,使用了基于流的传输,同时因要兼顾到仍有 IIS 6 上的部署的可能,传输协议只能选择“basicHttpBinding”。
基于 WCF,此功能实现起来非常简单,起初为了方便,服务端使用了控制台方式的托管宿主,没有遇到任何问题,测试传输小到几个字节,大到几百M的文件稳定通过,但在将服务端托管到 IIS 后就不行了,无论文件大小,使用“Streamed”传输模式就是出错,只有改为“Buffered”才能成功,查阅 MSDN 并未发现有此限制,所以我有了疑虑,IIS 托管的 WCF 服务中“basicHttpBinding”协议究竟能否以“流”方式进行大文件传输?如果可以,正确的做法是什么呢?肯请大家指点迷津!
服务器端:
测试环境:
服务器端:Windows Server 2008 R2 + IIS 7.5 + .Net 4.0
客户端: Windows 7 U (管理员权限) + .Net 4.0
为便于说明问题,精简代码如下:
==========
消息契约:
[MessageContract] public class FileMessage : IDisposable { [MessageHeader(MustUnderstand = true)] public string FileName { get; set; } [MessageHeader(MustUnderstand = true)] public long Length { get; set; } [MessageBodyMember(Order = 1)] public System.IO.Stream FileContents { get; set; } public void Dispose() { if (FileContents != null) { FileContents.Close(); FileContents = null; } } }
服务契约及实现:
[ServiceContract] public interface IFileTransferService { [OperationContract] void UplodaFile(FileMessage fileMessage); } public class FileTransferService : IFileTransferService { private string StoragePath{ get { return "R:\\UploadFiles\\";} } public void UplodaFile(FileMessage fileMessage) { string fileFullName = this.StoragePath + fileMessage.FileName; const int chunkSize = 2048; byte[] buffer = new byte[chunkSize]; using (System.IO.FileStream writeStream = new System.IO.FileStream(fileFullName, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write)) { do { int bytesRead = fileMessage.FileContents.Read(buffer, 0, chunkSize); if (bytesRead == 0) break; writeStream.Write(buffer, 0, bytesRead); } while (true); } } }
web.config:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0"/> <httpRuntime maxRequestLength="2147483647"/> </system.web> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="MyBasicHttpBinding" messageEncoding="Mtom" transferMode="Streamed" maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647"> <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/> </binding> </basicHttpBinding> </bindings> <services> <service name="Org.AnnexService.FileTransferService"> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="MyBasicHttpBinding" contract="Org.AnnexService.IFileTransferService" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name=""> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> <dataContractSerializer maxItemsInObjectGraph="2147483647"/> </behavior> </serviceBehaviors> </behaviors> <!--<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />--> </system.serviceModel> </configuration>
客户端:
==========
public class ServiceClient : IDisposable { private AnnexServiceReference.FileTransferServiceClient _client; public ServiceClient(string serviceAddress) { // 客户端没有使用配置文件,因为服务地址不是固定的,所以直接用代码创建 EndpointAddress address = new EndpointAddress(serviceAddress); BasicHttpBinding binding = new BasicHttpBinding() { TransferMode = System.ServiceModel.TransferMode.Streamed, // 如果这里设置为 Buffered 则一切正常 MessageEncoding = WSMessageEncoding.Mtom, SendTimeout = TimeSpan.FromMinutes(10), MaxBufferSize = 2147483647, MaxBufferPoolSize = 2147483647, MaxReceivedMessageSize = 2147483647 }; _client = new AnnexServiceReference.FileTransferServiceClient(binding, address); } public void UploadFile(string fileName, string storagePath) { try { using (System.IO.FileStream stream = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read)) { FileMessage fileMsg = new FileMessage() { FileName = System.IO.Path.GetFileName(fileName), FileContents = stream, Length = stream.Length }; _client.UplodaFile(fileMsg.FileName, fileMsg.Length, fileMsg.FileContents); } } catch { // ... } } }
以上客户端代码调用 “_client.UplodaFile 方法时即会抛出如下异常:
捕捉到 System.ServiceModel.ProtocolException
Message=远程服务器返回了意外响应: (400) Bad Request。
Source=mscorlib
...
InnerException: System.Net.WebException
Message=远程服务器返回错误: (400) 错误的请求。
Source=System
StackTrace:
在 System.Net.HttpWebRequest.GetResponse()
在 System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
InnerException:
答案
-
谢谢 Frank Xu Lei 的回复,我这样试了一下,问题依旧:
BasicHttpBinding binding = new BasicHttpBinding() { TransferMode = System.ServiceModel.TransferMode.Streamed, MessageEncoding = WSMessageEncoding.Mtom, SendTimeout = TimeSpan.FromMinutes(10), MaxBufferSize = 2147483647, MaxBufferPoolSize = 2147483647, MaxReceivedMessageSize = 2147483647, ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas() { MaxArrayLength = 2147483647, MaxBytesPerRead = 2147483647, MaxDepth = 2147483647, MaxNameTableCharCount = 2147483647, MaxStringContentLength = 2147483647 } };
不过有一个新的发现,原来我的所有项目都是在同一个解决方案中进行调试的,刚才我把其中的网站项目单独发布到了 IIS 下,客户端竟然可以上传文件了,奇怪了,我再试试!- 已标记为答案 Peter pi - MSFTModerator 2012年2月16日 6:04
全部回复
-
400错误,应该是客户端参数问题。
我建议你参考服务端的
<binding name="MyBasicHttpBinding" messageEncoding="Mtom" transferMode="Streamed"
maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647"
maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
</binding>
配置,把这里设置的在客户端全部使用代码实现。你现在的客户端只是设置了几个参数,消息编码都没设置。你可以设置以后,重新试试Frank Xu Lei--谦卑若愚,好学若饥
【老徐的网站】:http://www.frankxulei.com/ -
谢谢 Frank Xu Lei 的回复,我这样试了一下,问题依旧:
BasicHttpBinding binding = new BasicHttpBinding() { TransferMode = System.ServiceModel.TransferMode.Streamed, MessageEncoding = WSMessageEncoding.Mtom, SendTimeout = TimeSpan.FromMinutes(10), MaxBufferSize = 2147483647, MaxBufferPoolSize = 2147483647, MaxReceivedMessageSize = 2147483647, ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas() { MaxArrayLength = 2147483647, MaxBytesPerRead = 2147483647, MaxDepth = 2147483647, MaxNameTableCharCount = 2147483647, MaxStringContentLength = 2147483647 } };
不过有一个新的发现,原来我的所有项目都是在同一个解决方案中进行调试的,刚才我把其中的网站项目单独发布到了 IIS 下,客户端竟然可以上传文件了,奇怪了,我再试试!- 已标记为答案 Peter pi - MSFTModerator 2012年2月16日 6:04