[转]数据下载(十六)

为了解决那些个把下载过程中的byte数组,要拿去自行处理的变态们,俺特地做了下面的更改。

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;

namespace prjDownLoad
{
    class Program
    {
        //准备了一个用来保存的文件流。
        static FileStream fs;

        static void Main(string[] args)
        {
            fs = new FileStream("c:\\ee.txt", FileMode.Create, FileAccess.Write);
            //在构造时可以不填写url和fileName
            //在下载的时候再填写。
            DownloadUtil du = new DownloadUtil();
            //订阅事件
            du.ReadCompleted += new ReadCompletedHandler(du_ReadCompleted);
            du.ReadProgress += new ReadProgressHandler(du_ReadProgress);
            //在下载的时候临时填写要下载的url
            byte[] bs = du.DownloadData("http://blog.sina.com.cn/dalishuishou");
            Console.WriteLine(bs.Length);
        }

        /// <summary>
        /// 为嘛,读取过程事件只能返回一个字节个数呢?!
        /// 能不能返回所读取的字节数组呢?
        /// 当然可以。
        /// </summary>
        /// <param name="bs"></param>
        static void du_ReadProgress(byte[] bs)
        {
            //既然都拿到了byte数组,呵呵
            //你想咋处理,你咋处理。
            //管俺屁事。
            fs.Write(bs, 0, bs.Length);
            fs.Flush();
            Console.WriteLine("下载了{0}字节",bs.Length);
        }

        static void du_ReadCompleted()
        {
            //如果你要保存成文件,
            //在完成后,应该关文件流了。
            fs.Close();
            Console.WriteLine("下载完成了。");
        }
    }
}

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

namespace prjDownLoad
{
    //读取完成委托
    public delegate void ReadCompletedHandler();
}
using System;
using System.Collections.Generic;
using System.Text;

namespace prjDownLoad
{
    //读取过程委托
    public delegate void ReadProgressHandler(byte[] bs);
}
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace prjDownLoad
{
    /// <summary>
    /// 下载信息实体类
    /// </summary>
    [Serializable]
    public class DownloadInfo
    {
        Stream readSt;
        /// <summary>
        /// 读取数据的流
        /// </summary>
        public Stream ReadSt
        {
            get { return readSt; }
            set { readSt = value; }
        }

        Stream writeSt;
        /// <summary>
        /// 写入数据的流
        /// </summary>
        public Stream WriteSt
        {
            get { return writeSt; }
            set { writeSt = value; }
        }

        byte[] buffer = new byte[1024];
        /// <summary>
        /// 缓冲字节数组,用来存储从流中读取的数据的容器
        /// </summary>
        public byte[] Buffer
        {
            get { return buffer; }
            set { buffer = value; }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net;
using System.IO;

namespace prjDownLoad
{
    public class DownloadUtil
    {
        //添加一个AutoResetEvent
        //这是一个信号机
        AutoResetEvent are=new AutoResetEvent(false);
        //读取完成事件
        public event ReadCompletedHandler ReadCompleted;
        //读取过程事件
        public event ReadProgressHandler ReadProgress;

        //临时保存字节数组的集合。
        List<byte> list = new List<byte>();

        ////只所以不把字段公开的原因是:
        ////告诉客户,你必须通过构造把
        ////地址和文件名传递进来,只有
        ////这一种方式,别想着等实例化
        ////以后再赋值。
        ////省的麻烦。
        //string url;
        //只保留fileName
        string fileName;
        ///// <summary>
        ///// 通过构造方法将要下载的Url和要保存的文件名传进来
        ///// </summary>
        ///// <param name="url"></param>
        ///// <param name="fileName"></param>
        //public DownloadUtil(string url,string fileName)
        //{
        //    this.url = url;
        //    this.fileName = fileName;
        //}

       
        /// <summary>
        /// 返回为byte数组,意味着用户
        /// 可以不用提供文件名来下载。
        /// 用户可以用得到的byte数组
        /// 自行构造文件。
        /// 这样一来,从外面传过来url
        /// 和fileName的地方可以由构造
        /// 转移到DownloadData中了。
        /// </summary>
        /// <returns></returns>
        public byte[] DownloadData(string url,string fileName)
        {
            //不光WebResponse的获得使用异步方式
            //连读取数据都使用异步方式。
            //创建WebRequest对象
            //WebRequest wr = WebRequest.Create("http://blog.sina.com.cn/dalishuishou");
            this.fileName = fileName;
            WebRequest wr = WebRequest.Create(url);
            //开始异步地获取回应对象
            //如果有了回应对象,那么把过程交给GetResponseCallBack去处理
            wr.BeginGetResponse(new AsyncCallback(GetResponseCallBack), wr);
            are.WaitOne();
            return list.ToArray();
        }

        /// <summary>
        /// 万一某个变态说:
        /// 俺不要下载了就保存
        /// 俺就要byte数组。
        /// 哼哼,老子等着你呢。
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public byte[] DownloadData(string url)
        {
            return DownloadData(url, "");
        }

        /// <summary>
        /// 当有回应对象时调用这个方法来处理。
        /// </summary>
        /// <param name="result"></param>
        void GetResponseCallBack(IAsyncResult result)
        {
            //拿到那个请求对象
            WebRequest wr = result.AsyncState as WebRequest;
            //如果回应回完了,那么拿到回应对象
            WebResponse wsp = wr.EndGetResponse(result);
            //从回应中拿到流
            Stream st = wsp.GetResponseStream();
           
            //将异步操作过程中用到的信息封装为对象
            DownloadInfo di = new DownloadInfo();
            di.ReadSt = st;
            //用文件名不为空来判断不填文件名的变态。
            if (fileName!="")
            {
                //输出的文件流,此处也可替换为其他的流。
                //FileStream fs = new FileStream("c:\\ee.txt", FileMode.Create, FileAccess.Write);
                FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
                di.WriteSt = fs;
            }
           
            //传入异步读取方法中
            //第一个参数:读取的内容放到啥地方。
            //第二个参数:从流的啥地方开始读取,一般从头开始,选择0。
            //第三个参数:读取多少数据,一般容器多大,就读取多少数据。
            //第四个参数:当读取完成后调用的方法,注意这个方法不是把所有数据都读取完成
            //            而是这一次读取完成后,比如这次只读取了1024字节,那么当读取了
            //            1024字节以后,就会完成一次,就会调用一次ReadCallBack方法。
            //第五个参数:读取完成过程中用到的参数。
            st.BeginRead(di.Buffer, 0, di.Buffer.Length, new AsyncCallback(ReadCallBack), di);
        }

        /// <summary>
        /// 一次读取完成后调用的方法
        /// </summary>
        /// <param name="result"></param>
        void ReadCallBack(IAsyncResult result)
        {
            //先把实体类对象从AsyncState中拿出来。
            DownloadInfo di = result.AsyncState as DownloadInfo;
            //看看这次读取完成后读取了多少玩意。
            //具体的内容在di的Buffer中放着呢。

            int x = di.ReadSt.EndRead(result);
            //如果x大于零,那么还有的读。
            if (x > 0)
            {
                byte[] bs=SubBytes(di.Buffer, x);
                list.AddRange(bs);
                //激发读取过程事件
                if (ReadProgress != null)
                {
                    ReadProgress(bs);
                }
                //如果填了文件名,那就往文件流中写
                if (fileName!="")
                {
                    //把读取到的数据,写入写入流中
                    di.WriteSt.Write(di.Buffer, 0, x);
                    //清输出缓冲区,把数据压过去。
                    di.WriteSt.Flush();
                }
                //到这儿应该还有的读,所以再读一回。
                //此处是递归了,再次调用这个方法自身。
                di.ReadSt.BeginRead(di.Buffer, 0, di.Buffer.Length, new AsyncCallback(ReadCallBack), di);
            }
            else//否则没的读了。
            {
                //关读取流
                di.ReadSt.Close();
                //填了文件名才给关写入流
                //否则俺关个屁啊。
                if (fileName!="")
                {
                    //关写入流
                    di.WriteSt.Close();
                }
                //激发读取完成事件
                if (ReadCompleted != null)
                {
                    ReadCompleted();
                }
                //清除fileName中的内容
                //防止用户多次调用DownloadData时出现问题
                fileName = "";
                //读取完成后,再通知其他线程
                //可以做其他的工作了。
                are.Set();
            }
        }
        /// <summary>
        /// 截取byte数组中的内容
        /// 原因在于:即使数据没读取完成
        /// byte数组中的内容很可能没填满
        /// 所以不能把byte数组中所有的内容
        /// 都添加进临时集合中。
        /// </summary>
        /// <param name="bs"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        byte[] SubBytes(byte[] bs, int length)
        {
            byte[] temp=new byte[length];
            Array.Copy(bs, 0, temp, 0, length);
            return temp;
        }
    }
}

转摘自:http://blog.sina.com.cn/s/blog_49458c270100iovv.html

posted @ 2011-03-02 00:03  愤怒的熊猫  阅读(127)  评论(0编辑  收藏  举报