随笔 - 830  文章 - 8 评论 - 944 trackbacks - 19

当有大量的数据要传输(例如文件的上传和下载)时,WCF的流模式是比较好的选择,因为流模式不是全部传输完后才响应,而是一边读取一边传输消息,改善了系统的吞吐量和响应效率。

但是,WCF对于Stream操作有一些限制:

1. 绑定的限制。由于低层协议特性限制,WCF的流模式只支持如下四种:

  • BasicHttpBinding
  • NetTcpBinding
  • NetNamedPipeBinding
  • WebHttpBinding

2. OperationContract接口的限制。若要对数据进行流处理,服务的 OperationContract 必须满足两个要求:

  • 最多只能有一个参数。
  • 参数和返回值的类型中至少有一个必须是 Stream, Message 或 IXmlSerializable。

3. 会话模式限制

  当InstanceContextMode设置为Single或PerSession的时,多个服务请求可能会使用同一数据通道。此时就无法实现并发下载,只能依次顺序执行,而这往往不是我们所期望的结果。因此,对于使用流模式的服务,需要将InstanceContextMode设置为PerCall。

如下是几个有效的示例:

    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public interface IstreamingSample
    {
        [OperationContract]
        Stream GetStream(string data);
        [OperationContract]
        bool UploadStream(Stream stream);
        [OperationContract]
        Stream EchoStream(Stream stream);
        [OperationContract]
        Stream GetReversedStream();
    }

如果有许多信息需要作为参数传递,可以使用MessageContract构造一个复杂点的消息。

    [MessageContract]
    public class UploadStreamMessage
    {
        [MessageHeader]
        public string id;
        [MessageBodyMember]
        public Stream data;
    }

这个参数的限制并不难理解:

  1. 一次请求-响应之间只发送一个数据包,本质上来说只能携带一个参数。
  2. 在缓冲模式下,系统可以把多个参数全部加载到内存中后,然后通过序列化的方式将其合并为一个数据包再发送,这样看起来可以发送多个参数了。
  3. 流模式并不能使用这种方案,因此顶多只能发一个参数。要发多个参数,必须自己定义数据包格式(需要满足MessageContract),将多个参数整合到一个参数中发送。

下面我就以一个文件上传为例,简单的演示一下如何实现流模式数据传输。

一. 服务器端修改配置

  1. 设置TransferMode。它支持四种模式(Buffered、Streamed、StreamedRequest、StreamedResponse),请根据具体情况设置成三种Stream模式之一。
  2. 修改MaxReceivedMessageSize。该值默认大小为64k,因此,当传输数据大于64k时,则抛出CommunicationException异常。(可以直接设置为int.max)
  3. 修改receiveTimeout sendTimeout。大数据传送时间较长,需要修改这两个值,以免传输超时。

    <basicHttpBinding>
        <binding name="BasicBinding" receiveTimeout="00:30:00" sendTimeout="00:30:00" maxReceivedMessageSize="104857600" transferMode="Streamed" />
    </basicHttpBinding>

二. 定义契约,并实现服务

这个接口很简单,就是实现上传一个文件:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Task UploadFile(Stream stream);
    }

由于接口中除了Stream没有其它的有限参数,我这里的实现也很简单,只是直接把它保存为1.jpg。

    public class Service1 : IService1
    {
        public async Task UploadFile(Stream stream)
        {
            using (stream)
            using (var file = File.Create(@"R:\server\1.jpg"))
            {
                await stream.CopyToAsync(file);
            }
        }
    }

三. 访问服务

客户端的实现一如既往的简单,为了示例简单,这里客户端是以同步的方式访问的。

    static void Main(string[] args)
    {
        var client = new WcfClient.Service.Service1Client();
        using (var file = File.OpenRead(@"R:\client\1.jpg"))
        {
            client.UploadFile(file);
        }
        Console.WriteLine("finished");
    }

四. 扩展功能

前面的例子作为文件上传还是不够的,主要存在如下两个缺点:

  1. 不能指定文件名
  2. 不能获取上传进度

首先来解决文件名问题,不能指定文件名的主要原因就是消息体中对参数个数有限制,因此,必须把文件名和stream放在一起作为参数传入。这个只需要用MessageContract定义一个消息即可:

    [MessageContract]
    public class UploadStreamMessage
    {
        [MessageHeader]
        public string Name { get; set; }
        [MessageBodyMember]
        public Stream Stream { get; set; }
    }

这样,把UploadStreamMessage作为参数,就可以携带文件名信息了。

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Task UploadFile(UploadStreamMessage msg);
    }

    public class Service1 : IService1
    {
        public async Task UploadFile(UploadStreamMessage msg)
        {
            using (msg.Stream)
            using (var file = File.Create(@"R:\server\" + msg.Name))
            {
                await msg.Stream.CopyToAsync(file);
            }
        }
    }

重新启动服务后,更新客户端,此时就会发现生成的代码中都把参数给分离出来了,非常贴心。

  

    static void Main(string[] args)
    {
        var client = new WcfClient.Service.Service1Client();
        using (var file = File.OpenRead(@"R:\client\1.jpg"))
        {
            client.UploadFile("test.jpg", file);
        }
        Console.WriteLine("finished");
    }

至于当前上传了多少数据,直接取一下FileStream的Position就可以了,就不用远程服务器提供接口了。

PS:关于大数据传输,流模式并非唯一选择,我这里也只是对流模式进行了蜻蜓点水般的介绍,更多信息可以参看MSDN的相关文章:1. 大型数据和流,2. 如何启用流处理。CodeProject上的文章WCF Streaming: Upload/Download Files Over HTTP介绍得非常详细,强烈推荐一下。

 

 

posted on 2012-12-30 11:28  天方  阅读(...)  评论(...编辑  收藏