zoukankan      html  css  js  c++  java
  • WCF大文件断点下载示例

      之前发过片段的内容,都没给出完整的项目代码,不少人来要,故整理下,做个完整的演示项目出来,花了4天的时间调试,真要命。另外如果是IIS承载的WCF服务,建议直接写一个继承IHttpHandler的类来实现,那样效率更高,控制更精细,用WCF服务来做仅仅是在不使用IIS的情况下的妥协方法。注:必须安装.NET4.0 以上版本

    先简单介绍下该程序实现的功能:

      一个服务端,使用WCF自承载,默认采用了REST模式,直接提供了HTTP下载,也可以开启NET TCP等其它模式。HTTP下载时,IE是不支持断点续传的,下载软件中,只有网络传送带支持断点续传,其它的软件都不支持,主要是我的WCF服务采用了流模式传输,客户端软件获取不到要接收的数据总长度,以为不支持断点续传,故而直接不考虑续传,但是网络传送带就不同,它能够继续尝试续传请求,看服务端如何响应,因此只有网络传送带支持断点续传。为了解决下载工具不支持断点续传的情况,我自己在客户端里增加了一个HTTP下载方式,输入下载地址,即可开始下载,中间暂停后还可以恢复,从断点处恢复。另外提供直接程序下载方式,那个功能比较简单,同时客户端无法中断下载,除非网络异常造成的中断,一旦中断可以重试,继续从断点处续传,但是手动中断不可以,这个是WCF消息机制密封装导致的,我们无法主动打断消息的传输(文件下载就是在接收消息)。

    这是服务端界面 

     

      点击“开启服务”后,按钮变为“正在运行”,此时服务打开,服务监听端口默认为12251,如果不想用这个端口,可以打开源码重新编译,在Form1里面有个baseAddress变量,修改那个即可,也可以设置到配置文件中,演示项目我就不搞那么麻烦了。

      服务开启后,要先点击“文件目录” ,将当前提供文件下载服务的目录设置下,那个当前目录会显示当前设置的位置。然后点击“产生链接”,这是会出现文件列表,只有一层,未做递归处理。只有产生了链接后的文件,才能被下载到,否则都是非法访问,不给与下载,保证了系统安全。点击“复制选中链接”,你可以直接把下载链接复制到剪贴板,可以直接从IE下载试试效果了。

      那个HTTP帮助的链接,点击后可以看到服务的详细调用说明,如果要改变帮助,可以修改config文件。 

    这是客户端界面:

     

    服务地址输入后,点击“获取下载列表 ”,一旦获取成功,服务地址将不可改变。此时直接用“下载选中文件”功能,则通过WCF的接口函数直接下载文件,和底层传输协议无关。如果先“复制选中链接”,然后“打开HTTP下载窗口”,则通过HTTP地址下载文件,此时是通过WCF提供的RESF服务进行的下载。

    整个演示项目涉及到的知识点很多,故而做了很长时间,下面简单说明下项目中涉及到的几个要点。

    一、消息的流封装。

    以前曾经写过一篇随笔,提到自定义文件流,现在考虑得更加成熟了,使用自定义读取流,可以对任何流进行封装,比如内存流,因为可能我们要传输的内容是要进行预处理的,一边处理,一边传输,这样就必须对内存流进行封装传输。  
    自定义读取流(只读)
        /// <summary>
        
    /// 自定义读取流(只读)
        
    /// </summary>
        internal class CusStreamReader : Stream
        {
            long _endPosition;//结束位置
            Stream innerStream;
            /// <summary>
            
    /// 参数为当前流的断点
            
    /// </summary>
            public event Action<long> Reading;

            /// <summary>
            
    /// 直接使用原始流。
            
    /// </summary>
            
    /// <param name="stream">原始流</param>
            public CusStreamReader(Stream stream)
            {
                this.innerStream = stream;
                _endPosition = stream.Length;
            }
            /// <summary>
            
    /// 使用流当前位置,指定长度初始化自定义流
            
    /// </summary>
            
    /// <param name="stream">原始流</param>
            
    /// <param name="count">使用长度</param>
            public CusStreamReader(Stream stream, long count)
            {
                this.innerStream = stream;
                _endPosition = stream.Position + count;
                if (_endPosition > stream.Length)
                    _endPosition = stream.Length;
            }
            /// <summary>
            
    /// 指定初始位置、长度初始化自定义流
            
    /// </summary>
            
    /// <param name="stream">原始流</param>
            
    /// <param name="offset">初始位置</param>
            
    /// <param name="count">使用长度</param>
            public CusStreamReader(Stream stream, long offset, long count)
            {
                stream.Position = offset > stream.Length ? stream.Length : offset;
                this.innerStream = stream;
                _endPosition = offset + count;
                if (_endPosition > stream.Length)
                    _endPosition = stream.Length;
            }
            /// <summary>
            
    /// 从自定义流读取指定长度到array,但是不超过初始化时设定的长度。
            
    /// </summary>
            
    /// <returns>读取的字节数</returns>
            public override int Read(byte[] array, int offset, int count)
            {
                int readcount = 0;
                if (Position + count > this._endPosition)
                    readcount = innerStream.Read(array, offset, (int)(this._endPosition - Position));
                else
                    readcount = innerStream.Read(array, offset, count);
                if (Reading != null)
                    Reading(Position);
                return readcount;
            }
            /// <summary>
            
    /// 从自定义流读取一个字节,但是不超过初始化时设定的长度。
            
    /// </summary>
            
    /// <returns>读取的字节,未找到则返回-1</returns>
            public override int ReadByte()
            {
                if (Position >= this._endPosition)
                    return -1;
                else
                    return base.ReadByte();
            }

            public override bool CanRead
            {
                get { return innerStream.CanRead; }
            }

            public override bool CanSeek
            {
                get { return false; }
            }

            public override bool CanWrite
            {
                get { return false; }
            }

            public override void Flush()
            {
                throw new NotImplementedException();
            }

            /// <summary>
            
    /// 自定义流剩余长度。
            
    /// </summary>
            public override long Length
            {
                get { return _endPosition - innerStream.Position; }
            }

            /// <summary>
            
    /// 自定义流位置,返回原始流的位置
            
    /// </summary>
            public override long Position
            {
                get
                {
                    return innerStream.Position;
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new NotImplementedException();
            }

            public override void SetLength(long value)
            {
                throw new NotImplementedException();
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                throw new NotImplementedException();
            }
        }
    调用代码
    CusStreamReader fs = new CusStreamReader(new FileStream(file.filepath, FileMode.Open, FileAccess.Read, FileShare.Read), offset, count);
    fs.Reading += (t) =>
    {
        //限速代码,实际使用时可以去掉,或者精确控制
        Thread.Sleep(300);
        Console.WriteLine(t);
    };

    这里要特别说明的是这个Reading事件,非常重要。我们既可以利用它来限速,也可以用它来显示传输的状态,显示当前传了多少字节了。

    二、异常处理 。

    对于非法请求,必须返回错误异常,而WCF默认的错误异常处理和HTTP的不一样,如果用HttpWebRequest来请求下载,错误异常是不能直接捕获到的,查阅了相关资料后发现,要捕获类型为WebException的异常,然后特殊处理。.NET4.0里面,可以直接抛出WebFaultException<T>类型的异常,这样HttpWebRequest请求时可以很方便处理异常信息,但是这又带来一个问题,如果同时支持ServiceModel里访问,这个WebFaultException异常将获取不到内容,只能得到错误代码如(404),显然这样是不行的,反复调试后发现,服务端应该抛出FaultException异常,这样ServiceModel里访问可以直接捕获到异常,HttpWebRequest也可以分析出错误信息,核心代码如下:

    WEB请求时的异常捕获
    try
    {

    }
    catch (WebException ex)
    {
        var errResp = ex.Response as HttpWebResponse;
        using (var stream = errResp.GetResponseStream())
        {
            string message = null;
            using (var sr = new StreamReader(stream))
            {
                message = sr.ReadToEnd();
                var match = Regex.Match(message, "(?is)(?<=<Text[^>]*>).*(?=</Text>)");
                if (match.Success)
                {
                    message = match.Value;
                }
            }
            MessageBox.Show(message);
        }
    }

    其它的就不多说了,自己看代码,虽然只是演示项目,但是涉及到的知识点很多。 

  • 相关阅读:
    Educational Codeforces Round 67 D. Subarray Sorting
    2019 Multi-University Training Contest 5
    Educational Codeforces Round 69 (Rated for Div. 2) E. Culture Code
    Educational Codeforces Round 69 D. Yet Another Subarray Problem
    2019牛客暑期多校训练第六场
    Educational Codeforces Round 68 E. Count The Rectangles
    2019牛客多校第五场题解
    2019 Multi-University Training Contest 3
    2019 Multi-University Training Contest 2
    [模板] 三维偏序
  • 原文地址:https://www.cnblogs.com/qldsrx/p/2437583.html
Copyright © 2011-2022 走看看