使用 WCF 上传大文件时遇到困难
SO 上还有很多其他类似的问题。不幸的是,许多人在某些方面似乎互相欺骗。我希望这能够帮助其他人并解决其他问题。
我的项目要求是通过 IIS 将 250MB 文件上传到 IIS 中托管的后端 WCF 服务。我为 IIS 中托管的后端 WCF 服务创建了一些单元测试。它们是:
1) Upload 1MB File
2) Upload 5MB File
3) Upload 10MB file
4) Upload 20MB File
5) Upload 200MB File
马上,很明显我们需要使用某种流或分块文件传输。 我使用了此示例。
该示例描述了使用 .NET Stream 对象的方法。使用流对象的一个副作用是您必须使用消息契约。将 Stream 放入函数的参数列表中是不够的。所以我们就这么做了。
默认情况下,此 WCF 服务的 web.config 非常精简。但没有任何作用:
System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.
经过大量搜索和实验,很明显 BasicHttpBinding 与 Stream 对象和 MessageContract 的这种组合不兼容。 我们必须切换到 WSHttpBinding。
为此,服务器的 web.config 在以下部分中变得稍微复杂一些:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="true"/>
</behavior>
<behavior name="FileServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="500" maxConcurrentSessions="500" maxConcurrentInstances="500"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<wsHttpBinding>
<binding name="FileServiceBinding" closeTimeout="10:01:00"
maxBufferPoolSize="104857600"
maxReceivedMessageSize="104857600" openTimeout="10:01:00"
receiveTimeout="10:10:00" sendTimeout="10:01:00"
messageEncoding="Mtom">
<readerQuotas maxDepth="104857600" maxStringContentLength="104857600"
maxArrayLength="104857600" maxBytesPerRead="104857600"
maxNameTableCharCount="104857600" />
</binding>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="FileServiceBehavior" name="OMS.Service.FileService">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="FileServiceBinding" contract="OMS.Service.IFileService"></endpoint>
</service>
</services>
</system.serviceModel>
无需再做任何工作,1MB 文件现在就可以顺利通过。
为了将大于 4MB 的文件传输到通过,您必须调整 IIS 中 web.config 中的设置(WCF 服务的服务器端)此文章Microsoft 解释了该设置是什么。 例如,如果您将其设置为 8192,那么您将能够上传 5MB 的文件,但不能上传任何更大的文件。
<httpRuntime maxRequestLength="8192" />
我将我的设置为一些淫秽的测试 - 2147483647。前 4 个文件通过此门。
200MB 没有机会进入此门,原因如下:
System.InsufficientMemoryException: Failed to allocate a managed memory buffer of 279620368 bytes. The amount of available memory may be low. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
此问题的解释描述得很好,此海报。
这样想吧。 200MB 的文件从未从客户端发出。它必须由客户端完全加载、加密然后传输到服务器。
当您使用 Visual Studio 2010 生成服务的代理类时,它会将一些内容放入您的 app.config 中。对我来说,它看起来像这样:
<binding
name="Binding_IFileService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Mtom"
textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true" />
</security>
</binding>
关键是安全模式。默认设置为“消息”。该值由服务器上设置的任何内容获取。默认情况下,您的服务器使用消息级安全性。
如果您尝试在服务器上强制它像这样:
<security mode="None">
您会收到此错误:(
System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:8080/oms/FileService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
我确实记得更新客户端代理)
所以,这就是它代表我的地方......帮助!
There are a number of other similar questions on SO about this. Unfortunately, many seem to be dupes of one another in some respect. I hope that this one will help others and put to rest other questions.
My project requirement is to upload 250MB files through IIS into a backend WCF service hosted in IIS. I created some unit tests for the backend WCF service hosted in IIS. They are:
1) Upload 1MB File
2) Upload 5MB File
3) Upload 10MB file
4) Upload 20MB File
5) Upload 200MB File
Right off the bat, it's probably clear that we need to be using some kind of streaming or chunking file transfer. I used this sample.
The sample describes a method that uses the .NET Stream object. A side effect of using the stream object, is that you must use Message contracts. It's not enough to put the Stream in your function's parameter list. So we do that.
By default, the web.config for this WCF service is pretty lean. And nothing works:
System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.
After much searching and experimenting, it's clear that BasicHttpBinding is incompatible with this combination of the Stream object and the MessageContract. We must switch to WSHttpBinding.
To do this, the server's web.config gets slightly more complex under the section:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="true"/>
</behavior>
<behavior name="FileServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="500" maxConcurrentSessions="500" maxConcurrentInstances="500"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<wsHttpBinding>
<binding name="FileServiceBinding" closeTimeout="10:01:00"
maxBufferPoolSize="104857600"
maxReceivedMessageSize="104857600" openTimeout="10:01:00"
receiveTimeout="10:10:00" sendTimeout="10:01:00"
messageEncoding="Mtom">
<readerQuotas maxDepth="104857600" maxStringContentLength="104857600"
maxArrayLength="104857600" maxBytesPerRead="104857600"
maxNameTableCharCount="104857600" />
</binding>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="FileServiceBehavior" name="OMS.Service.FileService">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="FileServiceBinding" contract="OMS.Service.IFileService"></endpoint>
</service>
</services>
</system.serviceModel>
Without any more work to speak of, 1MB file are now passing with no problem.
In order to get files larger than 4MB to pass, you have to adjust a setting in the web.config in IIS (server side of your WCF service) This article from Microsoft explains what that setting is. For instance, if you set it to 8192, then you'll be able to upload the 5MB file, but not anything larger.
<httpRuntime maxRequestLength="8192" />
I set mine to something obscene for testing - 2147483647. The first 4 files pass this gate.
The 200MB didn't get a chance to make it to this gate for the next reason:
System.InsufficientMemoryException: Failed to allocate a managed memory buffer of 279620368 bytes. The amount of available memory may be low. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
The explanation for this problem is described very well, by this poster.
Think of it like this. The 200MB file never made it out of the client. It has to be fully loaded up by the client, encrypted and then transmitted to the server.
When you use Visual Studio 2010 to generate the proxy classes for the service, it puts some stuff into your app.config. For me, it looks like this:
<binding
name="Binding_IFileService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Mtom"
textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true" />
</security>
</binding>
The key is the security mode. It's set to "message" by default. That value is picked up by whatever is set on the server. By default, your server is using Message level security.
If you try to force it on the server to be like this:
<security mode="None">
You get this error:
System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:8080/oms/FileService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
(I did remember to update the client proxy)
And so, that's where it stands for me.... Help!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
WCF 通常不能很好地处理大文件传输,除非您实现流式传输,这实际上可以使用 BasicHttpBindings 来完成。
对于我的项目,我有一个创建服务主机的自定义主机工厂:
您将希望在您的情况下使用 StreamedRequest。
StreamingService 的实现:
我没有在服务本身中指定最大缓冲区大小,但在我的例子中,客户端应用程序将下载限制为每秒 5mb 左右。在您的情况下,服务本身需要设置限制行为。这并不能解决如何告诉服务文件中有多少字节以便正确传输文件的问题,但它应该给您一个开始。
您还应该注意主机配置中 MTOM 的使用。 MTOM 旨在帮助传输大文件(不太适合小文件传输)。
我没有客户端行为的示例,但您的服务应该从上传流的缓冲区中读取字节数并将它们保存到文件中,直到没有任何内容可供流式传输。虽然内存很便宜,但我不建议在内存中存储文件的完整副本,尤其是 200mb 的文件。
您还应该意识到,根据您的网络托管平台(IIS、Apache 等),您可能还会受到给定时间可传输的数据量的限制。但是,更改配置通常可以解决任何托管问题。
我希望这有帮助。
WCF typically doesn't handle large file transfers well unless you implement streaming, which can actually be accomplished with BasicHttpBindings.
For my project, I have a custom host factory that creates service hosts:
You will want to use StreamedRequest in your case.
And the implementation of the StreamingService:
I did not specify a maximum buffer size in the service itself but the client application in my case throttled the download to something like 5mb a second. In your case, the service itself will need to set the throttle behavior. This doesn't solve the issue of how you'll tell the service how many bytes are in the file in order to stream it properly, but it should give you a start.
You should also note the use of MTOM in the host configuration. MTOM was designed to help with the transfer of large files (not so great for small transfers).
I do not have an example of the client behavior, but your service should read the number of bytes from the upload stream's buffer and save them to file until nothing is left to stream. Although memory is cheap I would not recommend storing a complete in memory copy of the file, especially at 200mb.
You should also be aware that depending on your web hosting platform (IIS, Apache etc) you may also be limited to the amount of data that can be transfered at a given time. However, a change in configuration can usually resolve any hosting issues.
I hope this helps.
很好的详细问题:)
您的最后一个错误消息是 404 文件未找到。通常这是因为文件丢失或站点已关闭。检查这一点只是为了排除这种情况。
根据上次更改,您已关闭基于消息的加密。此更改需要在客户端和服务器上完成。如果一侧正在加密,而另一侧不希望消息被加密,那么就会感到困惑。
Nice detailed question :)
Your last error message was 404 file not found. Usually this is that the file is missing or that the site is down. Check this just to rule this out.
Based on the last change, you have turned off messaged based encryption. This change needs to be done on both the client and server. If one side is encrypting and the other not expecting the message to be encrypted it gets confused.
可以压缩文件吗?我知道这不是一个解决方案,但如果您知道将支持的最大大小,它会有所帮助。
如果不是,唯一的方法就是流式传输文件,或者分成几个部分。
It is possible to compress the files?, i know that is not a solution but it can help if you know the max size that is going to be supported.
If not the only way around is streaming the file, or divided in several pieces.