none
IIS 托管的 WCF 服务中“basicHttpBinding”协议究竟能否以“流”方式进行大文件传输? RRS feed

  • 问题

  • 项目中一个小模块需要向服务器上传文件的功能,考虑性能问题,使用了基于流的传输,同时因要兼顾到仍有 IIS 6 上的部署的可能,传输协议只能选择“basicHttpBinding”。

    基于 WCF,此功能实现起来非常简单,起初为了方便,服务端使用了控制台方式的托管宿主,没有遇到任何问题,测试传输小到几个字节,大到几百M的文件稳定通过,但在将服务端托管到 IIS 后就不行了,无论文件大小,使用“Streamed”传输模式就是出错,只有改为“Buffered”才能成功,查阅 MSDN 并未发现有此限制,所以我有了疑虑,IIS 托管的 WCF 服务中“basicHttpBinding”协议究竟能否以“流”方式进行大文件传输?如果可以,正确的做法是什么呢?肯请大家指点迷津!

    测试环境:
       服务器端:Windows Server 2008 R2 + IIS 7.5 + .Net 4.0
       客户端:  Windows 7 U (管理员权限) + .Net 4.0

    为便于说明问题,精简代码如下:

    服务器端:
    ==========
    消息契约:
        [MessageContract]
        public class FileMessage : IDisposable
        {
            [MessageHeader(MustUnderstand = true)]
            public string FileName { get; set; }
    		
            [MessageHeader(MustUnderstand = true)]
            public long Length { get; set; }
    
            [MessageBodyMember(Order = 1)]
            public System.IO.Stream FileContents { get; set; }
    
            public void Dispose()
            {
                if (FileContents != null)
                {
                    FileContents.Close();
                    FileContents = null;
                }
            }
        }
    服务契约及实现:
        [ServiceContract]
        public interface IFileTransferService
        {
            [OperationContract]
            void UplodaFile(FileMessage fileMessage);
        }
    
        public class FileTransferService : IFileTransferService
        {
    		private string StoragePath{ get { return "R:\\UploadFiles\\";} } 
    		
            public void UplodaFile(FileMessage fileMessage)
            {
                string fileFullName = this.StoragePath + fileMessage.FileName;
    
                const int chunkSize = 2048;
                byte[] buffer = new byte[chunkSize];
    
                using (System.IO.FileStream writeStream = new System.IO.FileStream(fileFullName, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write))
                {
                    do
                    {
                        int bytesRead = fileMessage.FileContents.Read(buffer, 0, chunkSize);
                        if (bytesRead == 0) break;
    
                        writeStream.Write(buffer, 0, bytesRead);
                    } while (true);
                }
            }
        }
    web.config:
    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.0"/>
        <httpRuntime maxRequestLength="2147483647"/>
      </system.web>
      
      <system.serviceModel>
        <bindings>
          <basicHttpBinding>
            <binding name="MyBasicHttpBinding" messageEncoding="Mtom" transferMode="Streamed"
                     maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
              <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647"
                            maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
            </binding>
          </basicHttpBinding>
        </bindings>
        <services>
          <service name="Org.AnnexService.FileTransferService">
            <endpoint address="" binding="basicHttpBinding" bindingConfiguration="MyBasicHttpBinding"
              contract="Org.AnnexService.IFileTransferService" />
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="true" />
              <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <!--<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />-->
      </system.serviceModel>
    </configuration>
    客户端:
    ==========
    public class ServiceClient : IDisposable
    {
    	private AnnexServiceReference.FileTransferServiceClient _client;
    
    	public ServiceClient(string serviceAddress)
    	{
    		// 客户端没有使用配置文件,因为服务地址不是固定的,所以直接用代码创建
    		EndpointAddress address = new EndpointAddress(serviceAddress);
    		BasicHttpBinding binding = new BasicHttpBinding()
    		{
    			TransferMode = System.ServiceModel.TransferMode.Streamed, // 如果这里设置为 Buffered 则一切正常
    			MessageEncoding = WSMessageEncoding.Mtom,
    			SendTimeout = TimeSpan.FromMinutes(10),
    			MaxBufferSize = 2147483647,
    			MaxBufferPoolSize = 2147483647,
    			MaxReceivedMessageSize = 2147483647
    		};
    
    		_client = new AnnexServiceReference.FileTransferServiceClient(binding, address);
    	}
    
    	public void UploadFile(string fileName, string storagePath)
    	{
    		try
    		{
    			using (System.IO.FileStream stream = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
    			{
    				FileMessage fileMsg = new FileMessage()
    				{
    					FileName = System.IO.Path.GetFileName(fileName),
    					FileContents = stream,
    					Length = stream.Length
    				};
    				_client.UplodaFile(fileMsg.FileName, fileMsg.Length, fileMsg.FileContents);
    			}
    		}
    		catch
    		{
    			// ...
    		}
    	}
    }
    
    以上客户端代码调用 “_client.UplodaFile 方法时即会抛出如下异常:

    捕捉到 System.ServiceModel.ProtocolException
      Message=远程服务器返回了意外响应: (400) Bad Request。
      Source=mscorlib
      ...
      InnerException: System.Net.WebException
           Message=远程服务器返回错误: (400) 错误的请求。
           Source=System
           StackTrace:
                在 System.Net.HttpWebRequest.GetResponse()
                在 System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
           InnerException: 




    2012年2月9日 7:05

答案

  • 谢谢 Frank Xu Lei 的回复,我这样试了一下,问题依旧:
    BasicHttpBinding binding = new BasicHttpBinding()
    {
    	TransferMode = System.ServiceModel.TransferMode.Streamed,
    	MessageEncoding = WSMessageEncoding.Mtom,
    	SendTimeout = TimeSpan.FromMinutes(10),
    	MaxBufferSize = 2147483647,
    	MaxBufferPoolSize = 2147483647,
    	MaxReceivedMessageSize = 2147483647,
    	ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
    	{
    		MaxArrayLength = 2147483647,
    		MaxBytesPerRead = 2147483647,
    		MaxDepth = 2147483647,
    		MaxNameTableCharCount = 2147483647,
    		MaxStringContentLength = 2147483647
    	}
    };
    不过有一个新的发现,原来我的所有项目都是在同一个解决方案中进行调试的,刚才我把其中的网站项目单独发布到了 IIS 下,客户端竟然可以上传文件了,奇怪了,我再试试!
    2012年2月9日 9:04

全部回复

  • 400错误,应该是客户端参数问题。

    我建议你参考服务端的

    <binding name="MyBasicHttpBinding" messageEncoding="Mtom" transferMode="Streamed"
                    
    maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
             
    <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647"
                           
    maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
           
    </binding>
    配置,把这里设置的在客户端全部使用代码实现。你现在的客户端只是设置了几个参数,消息编码都没设置。你可以设置以后,重新试试


    Frank Xu Lei--谦卑若愚,好学若饥
    老徐的网站】:http://www.frankxulei.com/

    微软WCF中文技术论坛
    微软WCF英文技术论坛

    Windows Azure中文技术论坛

    2012年2月9日 7:59
    版主
  • 谢谢 Frank Xu Lei 的回复,我这样试了一下,问题依旧:
    BasicHttpBinding binding = new BasicHttpBinding()
    {
    	TransferMode = System.ServiceModel.TransferMode.Streamed,
    	MessageEncoding = WSMessageEncoding.Mtom,
    	SendTimeout = TimeSpan.FromMinutes(10),
    	MaxBufferSize = 2147483647,
    	MaxBufferPoolSize = 2147483647,
    	MaxReceivedMessageSize = 2147483647,
    	ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
    	{
    		MaxArrayLength = 2147483647,
    		MaxBytesPerRead = 2147483647,
    		MaxDepth = 2147483647,
    		MaxNameTableCharCount = 2147483647,
    		MaxStringContentLength = 2147483647
    	}
    };
    不过有一个新的发现,原来我的所有项目都是在同一个解决方案中进行调试的,刚才我把其中的网站项目单独发布到了 IIS 下,客户端竟然可以上传文件了,奇怪了,我再试试!
    2012年2月9日 9:04