1. 和以往一样,先来一张图,然后看图说话:)
说明:界面上V0.3与V0.2差不多,剔除了图片类型复选框,如果你需要下载指定类型的图片的话,可以从 配置->设置正则表达式图片链接分析 里面直接修改匹配图片的正则表达式,最末端就是图片的文件类型了,如:(jpg|jpeg|png|ico|bmp|gif);状态栏增加了一个实际下载图片数量,可以实时的显示当前下载图片的张数;多了一个[重命名]和[下载页面包含CSS文件内的图片],后者就不用说明了,但是前者需要说明一下:这个地方是费了我不少时间了,我原先的重命名方案是DateTime.Now.ToString("yyyyMMddHHmmssfff"),后来又加上了new Random().Next(100)和lock,都不对,显示下载图片的数量和文件夹里面的图片数量不符合,并且选择重命名和不选择重命名文件夹实际图片数相差较大,几张到几十张不等,最后改用了System.Guid.NewGuid()至此基本正确符合,所有猜想,DateTime.Now和Random在遇到异步多线程应该会出现脏读吧?!
栏目里面的配置,开始想的时候觉得有那么点画蛇添足的味道,但是觉得有促进和各位朋友正则表达式交流的作用而保留下来了,所以期待牛人给予友情提示:)
2. 接下来贴部分关键代码和讲解,这里只讲异步的代码,源码注释也比较多:
/// <summary> /// 开始异步分析下载 /// </summary> /// <param name="url"></param> /// <param name="savePath"></param> private void AsyncAnalyzeAndDownload( string url, string savePath) { this.uriString = url; this.savePath = savePath; 初始化全局变量 分析计时开始 using (WebClient wClient = new WebClient()) { AutoResetEvent waiter = new AutoResetEvent(false); //为异步结果返回传参 wClient.QueryString.Add("url", uriString); wClient.QueryString.Add("IsInclueCssImages", _IsInclueCssImages.ToString()); wClient.Credentials = CredentialCache.DefaultCredentials; wClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler(AsyncURIAnalyze); wClient.DownloadDataAsync(new Uri(uriString), waiter); //waiter.WaitOne(); //阻止当前线程,直到收到信号 } } 说明:这里是异步分析和下载的入口,传入网址、保存路径、是否分析在CSS文件内的图片参数。
----------------------------------------------------------------------------------------------------------
/// <summary> /// 异步分析指定网址返回的数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void AsyncURIAnalyze(Object sender, DownloadDataCompletedEventArgs e) { AutoResetEvent waiter = (AutoResetEvent)e.UserState; WebClient nWC = sender as WebClient; bool IsMatchCss = true ; bool IsInclueCssImages = Convert.ToBoolean(nWC.QueryString[ " IsInclueCssImages " ]); try { if ( ! e.Cancelled && e.Error == null ) { string dnDir = string .Empty; string domainName = string .Empty; string uri = nWC.QueryString[ " url " ]; if ( ! uri.StartsWith( " http:// " ) && ! uri.StartsWith( " https:// " )) uri = string .Concat( " http:// " , uri); // 获得域名 http://www.sina.com domainName = GetDomain(uri); // 获得域名最深层目录 http://www.sina.com/mail/ dnDir = GetFullPath(domainName, uri); // 获取数据 string pageData = Encoding.UTF8.GetString(e.Result); // 匹配全路径 AnalyzeContent(Regex.Matches(pageData, ImagePattern), domainName, dnDir); // 是否下载页面包含CSS文件内的图片 if (IsInclueCssImages) { // 匹配CSS文件 // [\w=/]*((\.css){1}) MatchCollection mc = Regex.Matches(pageData, CssPattern, RegexOptions.IgnoreCase); for ( int i = 0 , j = mc.Count; i < j; i ++ ) { string item = mc[i].Value; // 短路径处理 if ( ! item.StartsWith( " http:// " ) && ! item.StartsWith( " https:// " )) item = (item[ 0 ] == ' / ' ? domainName : dnDir) + item; using (WebClient wClient = new WebClient()) { AutoResetEvent are = new AutoResetEvent( false ); wClient.QueryString.Add( " url " , item); wClient.QueryString.Add( " IsOver " , i == j - 1 ? " 1 " : " 0 " ); wClient.QueryString.Add( " IsInclueCssImages " , " false " ); wClient.Credentials = CredentialCache.DefaultCredentials; wClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler(AsyncURIAnalyze); wClient.DownloadDataAsync( new Uri(item), are); } } if (mc.Count == 0 ) IsMatchCss = false ; } } } finally { // waiter.Set(); if ((IsInclueCssImages && ! IsMatchCss) || ( ! IsInclueCssImages && string .IsNullOrEmpty(nWC.QueryString[ " IsOver " ])) || ( ! string .IsNullOrEmpty(nWC.QueryString[ " IsOver " ]) && " 1 " == nWC.QueryString[ " IsOver " ])) { lock (slock) { #region 分析计时结束 QueryPerformanceCounter( ref count1); count = count1 - count; toolStripStatusLabel1.Text = " 分析完毕! " ; toolStripStatusLabel2.Text = string .Format( " | 分析耗时:{0:#.####}秒 " , ( double )(count) / ( double )freq); #endregion // 分析完毕 isAnalyzeComplete = true ; } Application.DoEvents(); } } } 说明:这部分代码是程序的主体部分之一,如果需要分析CSS文件内的图片,则采用递归调用本方法,AnalyzeContent方法为分析图片链接核心代码,这里发现比较有意思的是可以为下次接受的数据传参,即自己传给自己,这样对于判断是否分析完毕有很大便利性,代码如:wClient.QueryString.Add("url", item);
------------------------------------------------------------------------------------------------------
/// <summary> /// 分析链接 /// </summary> /// <param name="mc"></param> /// <param name="domainName"></param> /// <param name="dnDir"></param> private void AnalyzeContent(MatchCollection mc, string domainName, string dnDir) { List < string > urlList = new List < string > (); foreach (Match mt in mc) { string item = mt.Value; /* 处理图片正则表达式的缺陷,即图片必须带域名,如: * 当前正则表达式匹配http://www.icoxxx.com * 匹配结果为http://www.ico */ if (item.Length > 8 && item.IndexOf( ' / ' , 9 ) == - 1 ) continue ; // 短路径处理 if ( ! item.StartsWith( " http:// " ) && ! item.StartsWith( " https:// " )) item = (item[ 0 ] == ' / ' ? domainName : dnDir) + item; // 处理../ if (item.IndexOf( " ../ " ) != - 1 ) { List < string > urls = new List < string > (); urls.AddRange(item.Split( ' / ' )); for ( int i = 0 ; i < urls.Count; i ++ ) if ( " .. " .Equals(urls[i])) { urls.RemoveRange(i - 1 , 2 ); i -= 2 ; } item = Join( " / " , urls); } if ( ! urlList.Contains(item)) { urlList.Add(item); lock (slock) { imgUrlList.Add(item); } // 实时显示分析结果 AddlbShowItem(item); // 边分析边下载 HttpWebRequest hwr = WebRequest.Create(item) as HttpWebRequest; hwr.AllowWriteStreamBuffering = false ; // hwr.ReadWriteTimeout = 5 * 1000; // 默认超时30秒 IAsyncResult res = hwr.BeginGetResponse( new AsyncCallback(AsyncDownLoad), hwr); // 加入线程池控制 ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, new WaitOrTimerCallback(timeoutCallback), hwr, Timeout, true ); } } } 说明:这块代码是分析图片链接的代码,由于正则表达式缺陷,加入了一些处理;加入了线程池控制,说到这里,关于异步线程池的控制几乎没找到中文资料,就在Google Code Seach里面搜索到了以下代码片段:
IAsyncResult res = hwr.BeginGetResponse( new AsyncCallback(AsyncDownLoad), hwr); // 加入线程池控制 ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, new WaitOrTimerCallback(timeoutCallback), hwr, Timeout, true ); 具体如何测试是否加入连接池也没有深入的研究了,所以前面提到新加功能时后面打了一个问号,望资深人士帮忙分析下:)
-------------------------------------------------------------------------------------------------------------
/// <summary> /// 异步接受数据 /// </summary> /// <param name="asyncResult"></param> public void AsyncDownLoad(IAsyncResult asyncResult) { #region 下载计时开始 lock (slock) { if (cfreq == 0L ) { ccount = 0L ; QueryPerformanceFrequency( ref cfreq); QueryPerformanceCounter( ref ccount); } } #endregion WebRequest request = (WebRequest)asyncResult.AsyncState; string url = request.RequestUri.ToString(); int indexItem = this .lbShow.Items.IndexOf(url); // 从未下载的列表中删除已经下载的图片 lock (slock) { imgUrlList.Remove(url); } try { WebResponse response = request.EndGetResponse(asyncResult); long cLength = response.ContentLength; if (cLength > 0 && cLength <= FileSize) { using (Stream stream = response.GetResponseStream()) { Image img = Image.FromStream(stream); img.Save( string .Concat(savePath, " / " , GetFileName(url))); img.Dispose(); stream.Close(); } // allDone.Set(); // 成功下载 if (indexItem >= 0 && indexItem <= this .lbShow.Items.Count) SetlbShowItem(indexItem, " √ " ); } else if (cLength == 0L && indexItem >= 0 ) SetlbShowItem(indexItem, " × " ); else if (indexItem >= 0 ) SetlbShowItem(indexItem, " ! " ); // 表示图片过大或过小 } catch (Exception) { if (indexItem >= 0 ) SetlbShowItem(indexItem, " × " ); } } 说明:这部分代码是异步下载的代码,加入了人性化了多符号标示文件的状态。
代码注释后可讲解的就不多了,比较难的就是异步中同步数据控制,得控制好了并且尽量少,否则对性能能较大影响,大伙有兴趣的话看代码吧:)
结束
感谢能阅读到此处的网友,希望能多多交流关于异步、同步、正则表达式方面的经验,不吝赐教!在下载中可能出现其他意外情况,导致图片已经下载完毕但是下载图片的按钮不可用,状态栏显示正在下载...,持续时间超过1分钟,那么可能是哪里出了BUG,呵呵!可以强制关闭然后重新启动程序下载,或者确认已经下载完毕,状态没法恢复可以从工具->初始化来重置一下。
本文转自博客园农民伯伯的博客,原文链接:,如需转载请自行联系原博主。