难以使用WCF上传大文件

本文关键字:文件 WCF | 更新日期: 2023-09-27 18:14:17

在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对象的方法。使用流对象的副作用是必须使用Message契约。将Stream放在函数的参数列表中是不够的。所以我们这样做。

默认为web。这个WCF服务的配置非常精简。

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与流对象和MessageContract的这种组合是不兼容的。我们必须切换到WSHttpBinding

要做到这一点,服务器的web。

部分下的配置稍微复杂一些:
<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的文件通过,你必须调整web中的设置。这篇来自微软的文章解释了这个设置是什么。例如,如果您将其设置为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>

密钥为安全模式。默认设置为"message"。该值由服务器上设置的任何内容获取。默认情况下,您的服务器正在使用消息级安全性。

如果你试图在服务器上强制它像这样:

 <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.

(我确实记得更新客户端代理)

所以,这就是它对我的意义....的帮助!

难以使用WCF上传大文件

WCF通常不能很好地处理大文件传输,除非你实现流,这实际上可以用BasicHttpBindings来完成。

对于我的项目,我有一个自定义主机工厂来创建服务主机:

protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
    ServiceHost host = new ServiceHost(serviceType, baseAddresses);
    ContractDescription contract = ContractDescription.GetContract(serviceType);
    BasicHttpBinding binding = new BasicHttpBinding();
    binding.OpenTimeout = TimeSpan.FromMinutes(1);
    binding.ReceiveTimeout = TimeSpan.FromMinutes(1);
    binding.SendTimeout = TimeSpan.FromHours(1);
    binding.TransferMode = TransferMode.StreamedResponse;
    binding.MessageEncoding = WSMessageEncoding.Mtom;
    ServiceEndpoint streaming = new ServiceEndpoint(contract, binding, new EndpointAddress(baseAddresses[0] + "/STREAMING"));
    host.AddServiceEndpoint(streaming);
    return host;
}

在你的情况下,你会想使用StreamedRequest。

StreamingService的实现:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single, AddressFilterMode = AddressFilterMode.Any)]
public class FileStreamingService : IFileStreamingV1
{
    Stream IFileStreamingV1.GetFileStream(string downloadFileLocation)
    {
        if (!File.Exists(downloadFileLocation))
        {
            throw new FaultException("The file could not be found");
        }
        FileStream stream = File.OpenRead(downloadFileLocation);
        return stream;
    }
}

我没有在服务本身中指定最大缓冲区大小,但是在我的例子中,客户机应用程序将下载限制为每秒5mb左右。在您的情况下,服务本身需要设置节流行为。这并不能解决如何告诉服务文件中有多少字节才能正确流式传输的问题,但它应该给你一个开始。

您还应该注意在主机配置中使用MTOM。MTOM的设计是为了帮助传输大文件(对于小文件传输来说不是很好)。

我没有一个客户端行为的例子,但是你的服务应该从上传流的缓冲区中读取字节数,并将它们保存到文件中,直到没有任何东西留给流。虽然内存很便宜,但我不建议在内存中存储文件的完整副本,特别是200mb的文件。

你也应该意识到,根据你的网络托管平台(IIS, Apache等),你也可能被限制在一个给定的时间可以传输的数据量。但是,更改配置通常可以解决任何托管问题。

我希望这对你有帮助。

好详细的问题:)

您的最后一个错误消息是404文件未找到。通常情况下,这是由于文件丢失或网站关闭。检查一下以排除这种可能性。

  • 尝试浏览到您的svc文件
  • 测试它在一个较小的文件中仍然有效。

根据上次更改,您已经关闭了基于消息的加密。此更改需要在客户机和服务器上同时进行。如果一方正在加密,而另一方不希望消息被加密,则会产生混淆。

是否可以压缩文件?,我知道这不是一个解决方案,但它可以帮助,如果你知道最大尺寸的支持。

如果没有,唯一的方法就是流式传输文件,或者将文件分成几个部分。