2012-04-19 94 views
4

我有点困在这里...获取流媒体在IIS托管WCF服务工作

我的目标很简单:我要揭露一个IIS托管(然后Windows Azure中)WCF服务,通过它我可以上传文件,使用流媒体,以及添加一些关于我想要上传的文件的META数据(文件名,MD5-hash所有常用的东西......),并能够显示有关上传的准确进度信息。

首先我创建了一个派生类StreamWithProgress的FileStream,其中我已经覆盖了方法引发一个事件与每个读通过我通过进度信息继承。

其次我创建使用MessageContracthttp://msdn.microsoft.com/en-us/library/ms730255.aspx)来包装元数据的WCF服务和流对象到一个单一的SOAP信封。这项服务非常简单,仅提供一种上传方法。

我已经将所有的缓冲区大小,以接受大量的数据,按:

和的httpRuntime设置为每:

的IIS \ ASP兼容性设置按:

和禁用配料按:

我创建了一个自托管服务通过它上传成功。 然后我'升级'到IIS托管服务(在我的本地计算机上),它工作。 然后我创建了一个本地托管的Windows Azure服务,其中有一个工作的WCF卷轴,

尽管如此,没有一个实例发生实际流式传输......所有这些实例都在发送数据之前对其进行了缓冲。

我遇到了这个问题,当我看到我的客户端正在报告进度,但服务器在整个文件被缓冲之后才开始写入文件。

我的实际代码如下。

任何想法\帮助? 任何将不胜感激...

谢谢!

服务器的web.config:

<?xml version="1.0"?> 
<configuration> 
    <system.serviceModel> 

     <bindings> 
      <basicHttpBinding> 
       <binding name="uploadBasicHttpBinding" 
       maxReceivedMessageSize="2147483647" 
       transferMode="Streamed" 
       messageEncoding="Mtom" 
       maxBufferPoolSize="2147483647" 
       maxBufferSize="2147483647"> 
       <readerQuotas maxArrayLength="2147483647" 
           maxBytesPerRead="2147483647" 
           maxDepth="2147483647" 
           maxNameTableCharCount="2147483647" 
           maxStringContentLength="2147483647"/> 
       </binding> 
      </basicHttpBinding> 
     </bindings> 

      <behaviors> 
       <serviceBehaviors> 
        <behavior name="defaultBehavior"> 
         <serviceMetadata httpGetEnabled="true"/> 
         <serviceDebug includeExceptionDetailInFaults="false"/> 
         <dataContractSerializer maxItemsInObjectGraph="2147483647"/> 
        </behavior> 
       </serviceBehaviors> 
      </behaviors> 

     <!-- Add this for BufferOutput setting --> 
     <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true"/> 

     <services> 
      <service name="WcfService1.Service1" behaviorConfiguration="defaultBehavior">   
       <endpoint binding="basicHttpBinding" contract="WcfService1.IService1" bindingConfiguration="uploadBasicHttpBinding"/> 
       <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> 
      </service> 
     </services> 

    </system.serviceModel> 

    <system.webServer> 
     <modules runAllManagedModulesForAllRequests="true"/> 
    </system.webServer> 

    <system.web> 
     <compilation debug="true"/> 
    <httpRuntime maxRequestLength="2147483647" /> 
    </system.web> 

</configuration> 

服务合同:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Runtime.Serialization; 
using System.ServiceModel; 
using System.ServiceModel.Web; 
using System.Text; 
using System.IO; 

namespace WcfService1 
{ 
    [ServiceContract] 
    public interface IService1 
    { 
     [OperationContract(IsOneWay=true)] 
     void UploadStream(Encapsulator data); 
    } 
} 

实际的服务:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Runtime.Serialization; 
using System.ServiceModel; 
using System.ServiceModel.Web; 
using System.Text; 

using System.IO; 
using System.Web; 
using System.ServiceModel.Activation; 

namespace WcfService1 
{ 
    [MessageContract] 
    public class Encapsulator 
    { 
     [MessageHeader(MustUnderstand = true)] 
     public string fileName; 
     [MessageBodyMember(Order = 1)] 
     public Stream requestStream; 
    } 

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 
    public class Service1 : IService1 
    { 
     public Service1() 
     { 
      HttpContext context = HttpContext.Current; 

      if (context != null) 
      { 
       context.Response.BufferOutput = false; 
      } 
     } 

     public void UploadStream(Encapsulator data) 
     { 
      const int BUFFER_SIZE = 1024; 

      int bytesRead = 0; 

      byte[] dataRead = new byte[BUFFER_SIZE]; 

      string filePath = Path.Combine(@"C:\MiscTestFolder", data.fileName); 

      string logPath = Path.Combine(@"C:\MiscTestFolder", string.Concat(data.fileName, ".log")); 

      bytesRead = data.requestStream.Read(dataRead, 0, BUFFER_SIZE); 

      StreamWriter logStreamWriter = new StreamWriter(logPath); 

      using (System.IO.FileStream fileStream = new System.IO.FileStream(filePath, FileMode.Create)) 
      { 
       while (bytesRead > 0) 
       { 
        fileStream.Write(dataRead, 0, bytesRead); 
        fileStream.Flush(); 

        logStreamWriter.WriteLine("Flushed {0} bytes", bytesRead.ToString()); 
        logStreamWriter.Flush(); 

        bytesRead = data.requestStream.Read(dataRead, 0, BUFFER_SIZE); 
       } 

       fileStream.Close(); 
      } 

      logStreamWriter.Close(); 
     } 
    } 
} 

客户端的app.config:

<?xml version="1.0"?> 
<configuration> 
    <system.serviceModel> 

     <bindings> 
      <basicHttpBinding> 
       <binding name="BasicHttpBinding_IService1" closeTimeout="00:01:00" 
        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 
        allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" 
        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" 
        messageEncoding="Mtom" textEncoding="utf-8" transferMode="Streamed" 
        useDefaultWebProxy="true"> 
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" 
         maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 
        <security mode="None"> 
         <transport clientCredentialType="None" proxyCredentialType="None" 
          realm="" /> 
         <message clientCredentialType="UserName" algorithmSuite="Default" /> 
        </security> 
       </binding> 
      </basicHttpBinding> 
     </bindings> 

     <client> 
      <endpoint address="http://localhost/WcfService1/Service1.svc" 
       binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" 
       contract="UploadService.IService1" name="BasicHttpBinding_IService1" /> 
     </client> 

    </system.serviceModel> 

    <startup> 
     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> 
    </startup> 
</configuration> 

客户端主代码:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

using CustomFileUploaderTester.UploadService; 
using System.ServiceModel; 
using System.IO; 

namespace CustomFileUploaderTester 
{ 
    class Program 
    { 
     private static long bytesRead = 0; 

     static void Main(string[] args) 
     { 
      Service1Client client = new Service1Client(); 

      using (StreamWithProgress fstream = new StreamWithProgress(@"C:\BladieBla\someFile.wmv", FileMode.Open)) 
      { 
       client.InnerChannel.AllowOutputBatching = false; 

       fstream.ProgressChange += new EventHandler<StreamReadProgress>(fstream_ProgressChange); 

       client.UploadStream("someFile.wmv", fstream); 

       fstream.Close(); 
      } 

      Console.ReadKey(); 
     } 

     static void fstream_ProgressChange(object sender, StreamReadProgress e) 
     { 
      bytesRead += e.BytesRead; 

      Console.WriteLine(bytesRead.ToString()); 
     } 
    } 
} 

派生FileStream类(StreamWithProgress

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 

namespace CustomFileUploaderTester 
{ 
    public class StreamReadProgress : EventArgs 
    { 
     #region Public Properties 

     public long BytesRead 
     { 
      get; 
      set; 
     } 

     public long Length 
     { 
      get; 
      set; 
     } 

     #endregion 

     #region Constructor 

     public StreamReadProgress(long bytesRead, long fileLength) 
      : base() 
     { 
      this.BytesRead = bytesRead; 

      this.Length = fileLength; 
     } 

     #endregion 
    } 

    public sealed class StreamWithProgress : FileStream 
    { 
     #region Public Events 

     public event EventHandler<StreamReadProgress> ProgressChange; 

     #endregion 

     #region Constructor 

     public StreamWithProgress(string filePath, FileMode fileMode) 
      : base(filePath, fileMode) 
     { 
     } 

     #endregion 

     #region Overrides 

     public override int Read(byte[] array, int offset, int count) 
     { 
      int bytesRead = base.Read(array, offset, count); 

      this.RaiseProgressChanged(bytesRead); 

      return bytesRead; 
     } 

     #endregion 

     #region Private Worker Methods 

     private void RaiseProgressChanged(long bytesRead) 
     { 
      EventHandler<StreamReadProgress> progressChange = this.ProgressChange; 

      if (progressChange != null) 
      { 
       progressChange(this, new StreamReadProgress(bytesRead, this.Length)); 
      } 
     } 


     #endregion 
    } 
} 

- 更新:2012-04-20

后,我已经安装了环 - 我使用RawCap跟踪通信,并且看到数据实际上是流式传输的,但IIS服务器在调用之前缓存所有数据网络方法!

根据这个帖子:

http://social.msdn.microsoft.com/Forums/is/wcf/thread/cfe625b2-1890-471b-a4bd-94373daedd39

它是WCF继承ASP.Net行为......但是,他们在.NET 4.5谈论修复此:|

如果有人有任何其他建议,它将是伟大的!

谢谢!

+0

如果您降低服务器端MaxBufferSize属性,它会产生什么影响吗? – RichBower 2012-04-19 15:23:51

+0

嗨RichBower,我试过,谢谢...在你的帖子后,我去了所有的ReaderQuota和绑定设置...但无济于事...... – mnemonic 2012-04-20 07:14:51

回答

3

经过一些严格的测试后,我看到数据实际上正在流式传输。我将[MessageContract]属性应用到封装器类 (根据http://msdn.microsoft.com/en-us/library/ms733742.aspx),并使我能够发送一些关于该文件的额外元数据。使用 WireShark和RawCap很清楚,在读取流时通过线路发送数据。

另一个困扰他的问题是,在实际调用上传方法之前,正在流式传输的数据是缓冲的服务器端(使用IIS 7.5)! 这是一个值得关注的问题,但根据这个:http://social.msdn.microsoft.com/Forums/is/wcf/thread/cfe625b2-1890-471b-a4bd-94373daedd39, 修复应该在4.5版本的.Net

3

您正在使用Mtom和流式传输模式。这可能会导致问题。请尝试删除Mtom。其实,无论如何,Mtom是一个非常古老的标准。另外,在流式传输模式下使用SOAP服务时,我们只能有一个类型为Stream的参数。我们不能使用像封装器一样的自定义类型。

构建文件上传/下载服务的推荐解决方案是使用REST。在.NET平台上构建REST服务的一种方法是使用ASP.NET Web API:http://www.asp.net/web-api。使用这个API,我们不需要处理流式传输模式。我们需要处理的是Range标题。这篇博文可能有帮助:http://blogs.msdn.com/b/codefx/archive/2012/02/23/more-about-rest-file-upload-download-service-with-asp-net-web-api-and-windows-phone-background-file-transfer.aspx。但是请注意,此API尚未发布。如果您不想使用预发布产品,则可以使用其他技术,例如,您可以将MVC控制器用作REST服务,或使用WCF REST服务,或者构建自定义http处理程序等。如果您想要要使用Stream,需要一个自定义流。我想建议您检查http://blogs.msdn.com/b/james_osbornes_blog/archive/2011/06/10/streaming-with-wcf-part-1-custom-stream-implementation.aspx的样本。

最好的问候,

Ming Xu。

+0

嗨,感谢您的答复! 不幸的是使用REST不是我们的选择,但感谢参考!我相信它最终会派上用场! :) – mnemonic 2012-04-24 14:05:41