【问题标题】:File download through WCF slower than through IIS通过 WCF 下载文件比通过 IIS 慢
【发布时间】:2023-04-04 04:33:01
【问题描述】:

下面的方法(我希望在这篇文章中大大简化它的同时没有犯任何错误)工作正常,并通过 net.tcp 协议使用流传输模式进行设置。问题是性能比通过 IIS 通过 http 下载相同的文件要差得多。为什么会这样?我可以更改哪些内容来提高性能?

Stream WebSiteStreamedServiceContract.DownloadFile( string filePath ) {
    return File.OpenRead( filePath );
}

最后,WCF 是否负责正确处理我的流,它在这方面做得很好吗?如果没有,我应该怎么做?

谢谢。

【问题讨论】:

  • 嗨,格雷格!我正在努力通过 netTcp 设置流式传输通道。根据您的问题,您似乎已经成功地做到了这一点。您能否分享有关服务器和客户端配置的信息?非常感谢!
  • @Nayan 我认为您应该提出一个新问题并指出我的问题。答案可能最终对于 cmets 来说太大了。

标签: wcf streaming


【解决方案1】:

在解决此问题 8 个月后,其中 3 个月与 Microsoft 合作,这是解决方案。简短的回答是服务器端(发送大文件的端)需要使用以下内容进行绑定:

 <customBinding>
  <binding name="custom_tcp">
   <binaryMessageEncoding />
   <tcpTransport connectionBufferSize="256192" maxOutputDelay="00:00:30" transferMode="Streamed">
   </tcpTransport>
  </binding>
 </customBinding>

这里的关键是 connectionBufferSize 属性。可能需要设置其他几个属性(maxReceivedMessageSize 等),但 connectionBufferSize 是罪魁祸首。

无需在服务器端更改任何代码。

无需在客户端更改任何代码。

无需在客户端更改任何配置。

这里是长答案:

我一直怀疑 net.tcp over WCF 速度慢的原因是因为它非常频繁地发送小块信息而不是不经常发送大块信息,这使得它在高延迟网络(互联网)。事实证明这是真的,但要到达那里还有很长的路要走。

netTcpBinding 上有几个属性听起来很有希望:maxBufferSize 是最明显的,maxBytesPerRead 和其他听起来也很有希望。除此之外,可以在客户端和服务器端创建比原始问题中的流更复杂的流 - 您也可以在那里指定缓冲区大小。问题是这些都没有任何影响。一旦你使用了 netTcpBinding,你就完蛋了。

这样做的原因是调整 netTcpBinding 上的 maxBufferSize 会调整协议层上的缓冲区。但是您对 netTcpBinding 所做的任何事情都不会调整底层传输层。这就是为什么我们这么长时间都未能取得进展。

自定义绑定解决了这个问题,因为增加传输层上的 connectionBufferSize 会增加一次发送的信息量,因此传输对延迟的影响要小得多。

在解决这个问题时,我确实注意到 maxBufferSize 和 maxBytesPerRead 确实对低延迟网络(和本地)产生了性能影响。 Microsoft 告诉我 maxBufferSize 和 connectionBufferSize 是独立的,并且它们的值的所有组合(彼此相等,maxBufferSize 大于 connectionBufferSize,maxBufferSize 小于 connectionBufferSize)都是有效的。我们取得了 65536 字节的 maxBufferSize 和 maxBytesPerRead 的成功。不过,这对高延迟网络性能(最初的问题)影响很小。

如果您想知道 maxOutputDelay 的用途,它是在框架引发 IO 异常之前分配给填充连接缓冲区的时间量。因为我们增加了缓冲区大小,所以我们也增加了分配给填充缓冲区的时间。

使用这个解决方案,我们的性能提高了大约 400%,现在比 IIS 略好。还有其他几个因素会影响 IIS over HTTP 和 WCF over net.tcp(以及 WCF over http)的相对和绝对性能,但这是我们的经验。

【讨论】:

  • Greg,你说不需要在客户端更改配置 - 怎么回事?我目前在服务器和客户端上都使用 basicHttpBinding,而 Mtom 需要在客户端和服务上进行配置,因此我看不到客户端如何神奇地识别新的 customBinding 节点?
  • @joe,我们之前使用的是 NetTcpBinding(我们创建一个绑定,然后将其传递给 ChannelFactory - 我们不使用自动生成的代理方式做事),我们将继续使用 NetTcpBinding。我很确定根本没有任何改变,但我不会承诺并说出我的真正意思是,客户端的参数值(MaxBytesPerRead 等)都不是导致原来的问题。
【解决方案2】:

我不知道您的第一个问题的答案(我认为您需要提供更多代码来显示您为两个测试所做的工作),但您的第二个问题是关于流的处置,答案是你需要自己做。

Here is an excellent blog entry 有一些很棒的代码用于此目的。

【讨论】:

    【解决方案3】:

    我接受了@Greg-Smalter 的建议,并使用反射在NetTcpBinding 上更改了ConnectionBufferSize,这解决了我的问题。流式传输大型文档现在正在快速尖叫。这是代码。

            var transport = binding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
                ?.GetValue(binding);
    
            transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);
    

    【讨论】:

    • 我们有一个 WCF 服务,无法轻易更改(在部署中,使用实时用户)托管在正在使用它的 IIS 中。这变成了反映这一点的问题(或根据其他答案交换为 CustomBinding );事实证明,你可以在神奇的 Configure() 函数周围做一些小技巧。将此添加到服务 .svc 工作:
    • (请参阅下面的回复 - 注释中的空间不足,无法粘贴代码。)
    【解决方案4】:

    这是另一种无需处理反射的方法。只需将NetTcpBinding 包装成CustomBinding。

    var binding = new CustomBinding(new NetTcpBinding
    {
         MaxReceivedMessageSize = 2147483647,
         MaxBufferSize = 2147483647,
         MaxBufferPoolSize = 2147483647,
         ReceiveTimeout = new TimeSpan(4, 1, 0),
         OpenTimeout = new TimeSpan(4, 1, 0),
         SendTimeout = new TimeSpan(4, 1, 0),
         CloseTimeout = new TimeSpan(4, 1, 0),
         ReaderQuotas = XmlDictionaryReaderQuotas.Max,
         Security =
                {
                    Mode = SecurityMode.None,
                    Transport = {ClientCredentialType = TcpClientCredentialType.None}
                },
                TransferMode = TransferMode.Streamed,
                HostNameComparisonMode = HostNameComparisonMode.StrongWildcard
            });
    
    binding.Elements.Find<TcpTransportBindingElement>().ConnectionBufferSize = 665600;
    

    【讨论】:

      【解决方案5】:

      上述基于反射的答案确实对我们有用。如果您需要通过托管的 IIS/WCF 服务执行此操作;您可以使用神奇的配置函数声明来访问绑定来执行此操作:

      public static void Configure(ServiceConfiguration config)  
      {  
          NetTcpBinding tcpBinding = new NetTcpBinding { /* Configure here */ };
      
          var transport = tcpBinding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
                  ?.GetValue(tcpBinding);
          transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);
      
          ServiceEndpoint se = new ServiceEndpoint(ContractDescription.GetContract(typeof(IService)), tcpBinding , new EndpointAddress("net.tcp://uri.foo/bar.svc"))
          {
              ListenUri = new Uri("net.tcp://uri.foo/bar.svc")
          };
      
          config.AddServiceEndpoint(se);
      
          config.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });  
          config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });  
      }  
      

      【讨论】:

      • 我建议避免使用反射并改用CustomBinding。我认为这样更干净。
      • 可能是;但这是一项生产服务,以这种方式更改绑定会使我的蜘蛛感觉对现有部署的客户端感到刺痛;以上已确认不会破坏与它们的兼容性,CustomBinding 没有(尽管我即将对其进行测试。)
      【解决方案6】:

      感谢您在这篇文章中提供的信息。关于这个问题的信息并不多。我想添加一些可能对其他人有帮助的额外细节。

      这里的大多数响应似乎表明人们正在使用单个 NetTcp 端点,或者他们没有在 IIS 中托管 WCF。

      如果您在同一个 wcf 服务中使用多个 netTcp 端点并且它托管在 IIS 中,或者使用使用 WAS 的容器,您可能会遇到这些问题。

      1. 如果您更改一个 NetTcp 端点的 ConnectionBufferSize,则必须更改所有端点,并且它们必须是相同的值。显然,这是在 WAS 中托管时的要求(这是 IIS 使用的)。 (https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-host-a-wcf-service-in-was) 如果您是自托管网络服务,显然这不是问题,但我还没有验证这一点。因此,如果您为一个 NetTcp 添加了 customBinding,但您收到有关缺少 TransportManager 的激活异常,这就是您的问题。
      2. 我无法使用神奇的配置方法来实现这一点。即使基本的 NetTcp 绑定没有太大变化,我也得到了一个空响应对象。我确信有办法解决这个问题,但我没有时间继续深入研究这个问题。
      3. 对我有用的方法是使用 customBinding。如果您使用带有 Windows 身份验证的传输安全性,您只需添加 &lt;windowsStreamSecurity /&gt; 所以绑定最终看起来像这样:
      <binding name="CustomTcpBinding_ServerModelStreamed">
           <windowsStreamSecurity />
           <binaryMessageEncoding />
           <tcpTransport connectionBufferSize="5242880" maxReceivedMessageSize="2147483647" maxBufferSize ="2147483647" transferMode="Streamed" />
      </binding>
      

      您不必更改客户端 NetTcp 配置,当然,前提是您不使用此处未反映的任何其他功能。对于 customBinding,部分的顺序很重要。 https://docs.microsoft.com/en-us/dotnet/framework/wcf/extending/custom-bindings

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-13
        • 2016-07-08
        相关资源
        最近更新 更多