18

我创建了一个简单的 WCF 服务来原型文件上传。服务:

[ServiceContract]
public class Service1
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "/Upload")]
    public void Upload(Stream stream)
    {
        using (FileStream targetStream = new FileStream(@"C:\Test\output.txt", FileMode.Create, FileAccess.Write))
        {
            stream.CopyTo(targetStream);
        }
    }
}

它使用webHttpBinding设置transferMode为“流式传输”和maxReceivedMessageSize,maxBufferPoolSize并且maxBufferSize全部设置为 2GB。httpRuntimemaxRequestLength设置为 10MB。

客户端通过以下方式发出 HTTP 请求:

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(@"http://.../Service1.svc/Upload");

request.Method = "POST";
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.ContentType = MediaTypeNames.Application.Octet;

using (FileStream inputStream = new FileStream(@"C:\input.txt", FileMode.Open, FileAccess.Read))
{
    using (Stream outputStream = request.GetRequestStream())
    {
        inputStream.CopyTo(outputStream);
    }
}

现在,最后,出了什么问题:

上传 100MB 大的文件时,服务器返回 HTTP 400(错误请求)。我尝试启用 WCF 跟踪,但没有显示错误。当我增加httpRuntime. maxRequestLength到 1GB,文件上传没有问题。MSDN 说“指定输入流缓冲阈值的限制,以 KB 为单位maxRequestLength” 。

这使我相信整个文件(全部 100MB)首先存储在“输入流缓冲区”中,然后才可用于我Upload在服务器上的方法。我实际上可以看到服务器上文件的大小并没有逐渐增加(正如我所期望的那样),相反,在创建它的那一刻它已经是 100MB 大。

问题:我怎样才能让它工作,以便“输​​入流缓冲区”相当小(比如,1MB),当它溢出时,我的Upload方法被调用?换句话说,我希望上传能够真正流式传输,而不必在任何地方缓冲整个文件。

编辑: 我现在发现httpRuntime包含与此处相关的另一个设置 - requestLengthDiskThreshold。似乎当输入缓冲区增长超过此阈值时,它不再存储在内存中,而是存储在文件系统中。所以至少整个 100MB 的大文件没有保存在内存中(这是我最害怕的),但是,我仍然想知道是否有一些方法可以完全避免这个缓冲区

4

2 回答 2

9

如果您使用 .NET 4 并在 IIS7+ 中托管您的服务,您可能会受到以下博客文章中描述的 ASP.NET 错误的影响:

http://blogs.microsoft.co.il/blogs/idof/archive/2012/01/17/what-s-new-in-wcf-4-5-improved-streaming-in-iis-hosting.aspx

基本上,对于流式请求,IIS 中的 ASP.NET 处理程序将在将控制权交给 WCF 之前缓冲整个请求。这个处理程序遵守maxRequestLength限制。

据我所知,该错误没有解决方法,您有以下选择:

  • 升级到 .NET 4.5
  • 自托管您的服务,而不是使用 IIS
  • 使用不基于 HTTP 的绑定,从而不涉及 ASP.NET 处理程序
于 2013-01-24T09:58:13.480 回答
7

这可能是流实现中的错误。我发现一篇 MSDN 文章建议完全按照您在http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/fb9efac5-8b57-417e-9f71-35d48d421eb4/上的描述进行操作。不幸的是,建议修复的微软员工在实施中发现了一个错误,并没有跟进修复的细节。

也就是说,看起来实现被破坏了,您可以通过使用内存分析器分析您的代码并验证整个文件是否存储在内存中来进行测试。如果整个文件都存储在内存中,您将无法解决此问题,除非有人发现您的代码存在配置问题。

也就是说,虽然使用requestLengthDiskThreshold在技术上可以工作,但它会大大增加您的写入时间,因为每个文件必须首先作为临时数据写入,从临时数据中读取,再次作为最终数据写入,最后删除临时数据。正如您已经说过的,您正在处理非常大的文件,所以我怀疑这样的解决方案是否可以接受。

您最好的选择是使用分块框架并手动重建文件。我在http://aspilham.blogspot.com/2011/03/file-uploading-in-chunks-using.html找到了有关如何编写此类逻辑的说明,但没有时间检查其准确性。

很抱歉,我无法告诉您为什么您的代码没有按文档说明工作,但类似于第二个示例的内容应该能够在不增加内存占用的情况下工作。

于 2013-01-23T06:10:20.680 回答