【问题标题】:Difficulty using WCF for uploading large files难以使用 WCF 上传大文件
【发布时间】:2011-11-13 17:09:38
【问题描述】:

关于这个,还有许多其他类似的问题。不幸的是,许多人似乎在某些方面互相欺骗。我希望这个可以帮助其他人并解决其他问题。

我的项目要求是通过 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

马上,很明显我们需要使用某种流式传输或分块文件传输。 I used this sample.

该示例描述了一种使用 .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(WCF 服务的服务器端)的 web.config 中调整一个设置This article from Microsoft explains what that setting is. 例如,如果您将其设置为 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.

这个问题的解释描述的很好,by this poster

这样想。 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.

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

所以,这就是它代表我的地方......帮助!

【问题讨论】:

  • 没有答案,但流式处理适用于 BasicHttpBinding 和消息协定。在那个阶段没有看到您的 .config,我无法告诉您是什么导致了您的 400...
  • 这不是一个真正的答案,但是当我在绑定和端点配置方面遇到问题时,我会使用 VS2010 中的工具来帮助使 XML 不那么混乱。转到“工具”->“WCF 服务配置编辑器”。您可以使用它来编辑客户端和主机配置。正如答案中已经提到的,两者都需要匹配,否则您将收到端点未找到错误或安全错误。顺便说一句,很好的详细问题!
  • 也没有答案。如何为 NetTcpBinding 做到这一点?我不想提出一个重复的问题......有什么想法吗?

标签: c# wcf iis


【解决方案1】:

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 等),您可能还受限于在给定时间可以传输的数据量。但是,配置更改通常可以解决任何托管问题。

我希望这会有所帮助。

【讨论】:

    【解决方案2】:

    好详细的问题:)

    您的最后一条错误消息是 404 文件未找到。通常这是文件丢失或站点已关闭。检查这个只是为了排除这个。

    • 尝试浏览到您的 svc 文件
    • 测试它是否仍然适用于较小的文件。

    根据上次更改,您已关闭基于消息的加密。此更改需要在客户端和服务器上完成。如果一方正在加密而另一方不希望消息被加密,那么它就会混淆。

    【讨论】:

      【解决方案3】:

      可以压缩文件吗?我知道这不是一个解决方案,但如果您知道要支持的最大大小,它会有所帮助。

      如果不是唯一的方法是流式传输文件,或者分成几部分。

      【讨论】:

      • 并非如此。就我而言,我碰巧知道这些文件的大小大约为 300MB。但是可以说它被压缩为 50MB 的文件。这个还是太大了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多