http://www.sufeinet.com/plugin.php?id=keke_group

苏飞论坛

 找回密码
 马上注册

QQ登录

只需一步,快速开始

分布式系统框架(V2.0) 轻松承载百亿数据,千万流量!讨论专区 - 源码下载 - 官方教程

HttpHelper爬虫框架(V2.7-含.netcore) HttpHelper官方出品,爬虫框架讨论区 - 源码下载 - 在线测试和代码生成

HttpHelper爬虫类(V2.0) 开源的爬虫类,支持多种模式和属性 源码 - 代码生成器 - 讨论区 - 教程- 例子

查看: 7298|回复: 4

[综合] HttpHelper异步版本的实现

[复制链接]
发表于 2014-5-26 18:35:47 | 显示全部楼层 |阅读模式
           好了,这将是一篇比较长的贴子。本人也是第一次发文。           Sufei是个好人,本人写爬虫用到过Sufei的类,大赞一个。
           之前有一篇帖子http://www.sufeinet.com/thread-8352-1-1.html  讨论异步的实现,好了,这个讨论完全不着边际。既然曾经收益于HttpHelper,特来回报下站长Sufei。
我们都知道,如果同步请求Internet数据,会造成线程阻塞,实战中,如果开大量线程,会造成CPU线程上下文切换频繁,影响执行效率,同时,越多的线程,意味着GC回收时挂起的线程就越多,效率就越低。于是,一个异步的IO请求就成了追求完美的码农的解决方案。
          .net中的异步实现有两种方式,一种为APM,一种为EAP,如果不明白的请自己Google好了,这里不详细介绍。我这里给出APM的实现。
           好了 直接说下实现过程,这个不复杂,MSDN上有,稍微改造一下就行http://msdn.microsoft.com/zh-cn/library/86wf6409(v=vs.110).aspx
再借鉴SuFei的类,一个异步实现的HttpHelper就完成了
           献丑了,贴上改造HttpHelper后的代码(大家发现没有的方法,在HttpHelper都有,发帖长度被限制,只能干掉了)
           
[C#] 纯文本查看 复制代码
#define trace
//#undef trace
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Spider.Tool;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using Spider.DataControl.SpiderTrace;

namespace Spider.DataControl.Tool
{
    sealed class [color=#ff0000]MyAsyncPara[/color]
    {
[color=#ff0000]        public const int DefaultTimeOutSpan = 60 * 1000;[/color]
        public HttpResult result = new HttpResult();
        //HttpWebRequest对象用来发起请求
        public HttpWebRequest request = null;
        //获取影响流的数据对象
        public HttpWebResponse response = null;
        //响应流对象
        public Stream streamResponse;
        public int Length = 10 * 1024;
        public Byte[] buffer = new Byte[10 * 1024];
        public Action<HttpResult> callBack;
        public MemoryStream MemoryStream = new MemoryStream();
        public HttpItem objhttpItem;
        public bool TimeOut = false;

    }
    public class AsyncHttpHelper : IDisposable
    {
        #region 预定义方法或者变更

        public void GetHtmlDataAsync(HttpItem objhttpItem, [color=#ff0000]Action<HttpResult> callBack[/color])
        {
            MyAsyncPara pa = new MyAsyncPara();
#if trace
[color=#ff0000]            TraceDic.AddAsyncUrl(objhttpItem.urlGuid, objhttpItem.URL);[/color]
            pa.result.Guid = objhttpItem.urlGuid;
#endif
            pa.objhttpItem = objhttpItem;
            pa.callBack = callBack;
            try
            {
                SetRequest(pa);
                pa.request.BeginGetResponse(GetResponseCallback, pa);
            }
            catch (Exception ex)
            {
                pa.result.Html = "0";
                pa.result.ErrorMsg = ex.Message;
                callBack(pa.result);
            }

        }

        void GetResponseCallback(IAsyncResult result)
        {
            MyAsyncPara pa = result.AsyncState as MyAsyncPara;
            try
            {
                pa.response = (HttpWebResponse)pa.request.EndGetResponse(result);
                if (pa.response.ContentEncoding != null && pa.response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))
                {
                    //开始读取流并设置编码方式
                    //new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240);
                    //.net4.0以下写法
                    pa.streamResponse = new GZipStream(pa.response.GetResponseStream(), CompressionMode.Decompress);
                }
                else
                {
                    //开始读取流并设置编码方式
                    //response.GetResponseStream().CopyTo(_stream, 10240);
                    //.net4.0以下写法
                    pa.streamResponse = pa.response.GetResponseStream();
                }
                pa.streamResponse.BeginRead(pa.buffer, 0, pa.Length, ReadResponseStreamCallBack, pa);
            }
            catch (Exception ex)
            {
                pa.result.ErrorMsg = ex.Message;
                pa.result.Html = "0";
                pa.callBack(pa.result);
            }
        }

        private void [color=#ff0000]TimeoutCallback(object state, bool timedOut)[/color]
        {
            if (timedOut)
            {
                System.Diagnostics.Trace.WriteLine("timeout");
                MyAsyncPara pa = state as MyAsyncPara;
                pa.TimeOut = true;
                ProcessData(pa);
            }
        }

        void ReadResponseStreamCallBack(IAsyncResult result)
        {
            MyAsyncPara pa = result.AsyncState as MyAsyncPara;
            if (pa.TimeOut)
                return;
            try
            {
                int bytesRead = pa.streamResponse.EndRead(result);
                if (bytesRead > 0)
                {
                    pa.MemoryStream.Write(pa.buffer, 0, bytesRead);
                    System.Threading.Thread.Yield();
                    IAsyncResult ar = pa.streamResponse.BeginRead(pa.buffer, 0, pa.Length, ReadResponseStreamCallBack, pa);
[color=#ff0000]                    System.Threading.ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, TimeoutCallback, pa, MyAsyncPara.DefaultTimeOutSpan, true);[/color]
                }
                else
                {
                    ProcessData(pa);
                }
            }
            catch (Exception ex)
            {
                pa.result.ErrorMsg = ex.Message;
                pa.result.Html = "0";
                pa.callBack(pa.result);
            }
        }

        private void ProcessData(MyAsyncPara pa)
        {
            byte[] RawResponse = pa.MemoryStream.ToArray();
            //..此处省略三百字
            pa.MemoryStream.Close();
            pa.response.Close();
            pa.request.Abort();
            pa.callBack(pa.result);
        }

    }


}


好了,这里面的HttpItem和HttpResult都是站长Sufei的HttpHelper提供的。大家如果没有自行下载。
这个类的用法很简单,如下。

[C#] 纯文本查看 复制代码
   AsyncHttpHelper target = new AsyncHttpHelper();
        HttpItem objhttpItem = new HttpItem();
 objhttpItem.URL = "http://myhb.qq.com/f-1001229421-1.htm";
[color=#ff0000]            objhttpItem.urlGuid = Guid.NewGuid().ToString();[/color]
            target.GetHtmlDataAsync(objhttpItem, [color=#ff0000]CallBack[/color]);


至于CallBacK方法,是个匿名委托,就不多介绍了,用来处理请求的结果。

好了,细心的童鞋可能发现了,我用了一个类,用来追踪异步请求时的Url ,这个类的实现很简单,如下:
[C#] 纯文本查看 复制代码
  public class TraceDic
    {
        private static Dictionary<string, string> urlDic = new Dictionary<string, string>();
        private static object m_lock = new object();

        public static void AddAsyncUrl(string guid, string url)
        {
            lock (m_lock)
            {
                urlDic.Add(guid, DateTime.Now.ToString() + "   " + url);
            }
        }

        public static void RemoveUrl(string guid)
        {
            lock (m_lock)
            {
                urlDic.Remove(guid);
            }
        }

        public static string GetString()
        {
            lock (m_lock)
            {
                StringBuilder sb = new StringBuilder();
                foreach (KeyValuePair<string, string> s in urlDic)
                {
                    if (string.IsNullOrEmpty(sb.ToString()))
                        sb.Append("\r\n");
                    sb.Append(s.Key + "    " + s.Value + "\r\n");
                }
                return sb.ToString();
            }

        }
    }


好了,为什么要干这件事情呢,先埋个

童鞋们会发现,以前需要开N个线程去请求N个链接,或者放入线程池,与前者等价。现在用我的异步类,加上一个队列,一个线程就可以轻松愉快的解决了,完全不会阻塞IO,速度飞快,等请求完了之后,系统会自动调用你的委托方法。由于线程变少了,CPU切换变少了,GC回收效率变高了,整个程序一下子就清爽了很多。

再细心点的童鞋会问,   
  System.Threading.ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, TimeoutCallback, pa, MyAsyncPara.DefaultTimeOutSpan, true);
这段代码是干嘛的?
好了,简单点说,就是设定一个异步超时值,如果超时,那么不再等待,直接在TimeOutCallBack中,处理结果了。
那么,现在这个类看起来没什么问题了。欢迎大家使用。
如果有填坑的,欢迎跟帖。













1. 开通SVIP会员,免费下载本站所有源码,不限次数据,不限时间
2. 加官方QQ群,加官方微信群获取更多资源和帮助
3. 找站长苏飞做网站、商城、CRM、小程序、App、爬虫相关、项目外包等点这里
发表于 2014-5-26 18:45:59 | 显示全部楼层
受教了,学习中……
还请楼主继续完善下,把填起来……
发表于 2014-5-26 21:31:30 | 显示全部楼层
掉抗里了
回复

使用道具 举报

发表于 2014-5-27 08:42:24 | 显示全部楼层
好好写,我支持你,如果需要可以给你开个分类。
发表于 2014-6-4 17:10:16 | 显示全部楼层
比较小白,楼主能给个完整的代码就好了,拜谢
您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

QQ|手机版|小黑屋|手机版|联系我们|关于我们|广告合作|苏飞论坛 ( 豫ICP备18043678号-2)

GMT+8, 2024-5-14 05:14

© 2014-2021

快速回复 返回顶部 返回列表