[转载]让网站飞起来01---浏览器缓存技术 - PHP淮北 - 博客园

mikel阅读(1028)

[转载]让网站飞起来01—浏览器缓存技术 – PHP淮北 – 博客园.

前提说明:

缓存技术在在网站应用中非常重要,在减少服务器请求压力和用户浏览速度加块方面有着功不可没的功劳,

昨天在我《网站优化–让你的网页飞起来》里面有人问道关于浏览器缓存原理的问题,萌发了我研究缓存技术的兴趣,本文主要

介绍浏览器的缓存原理。

正文:

缓存技术:

在介绍缓存技术之前,我们先看一张LAMP网站的价格图,通过架构图我们可以很清晰的对网站整体缓存有一定认识

LAMP网站架构方案分析

LAMP网站结构图

 

上面是一种经典的LAMP网站结构图,通过上图我们很清楚的看到一个网站从前台到服务端的整体关系,下面是我根据

上图画的一张LAMP缓存图:

image

LAMP缓存图

从图中我们可以看到网站缓存主要分为五部分

  1. 服务器缓存:主要是基于web反向代理的静态服务器nginx和squid,还有apache2的mod_proxy和mod_cache模块
  2. 浏览器缓存:包括页面html缓存和图片js,css等资源的缓存
  3. PHP缓存:有很多免费的PHP缓冲加速工具,如apc eaccerlertor等
  4. 内存缓存:主要是采用memcache这种分布式缓存机制
  5. 数据库缓存:通过配置数据库缓存,以及数据存储过程,连接池技术等

下面重点介绍浏览器缓存原理:

自己的语言表达能力有限,还是先看图,比较直观也比较简洁:

image

从上图:我们可以知道浏览器缓存重要分为两个部分:

  1. 页面html的缓存
  2. 图片,css,js,flash等缓存
浏览器缓存是基于把页面信息保存到用户本地电脑硬盘里,服务器缓存是基于把用户访问的页面保存到服务器上的硬盘里

 

先介绍页面缓存的原理

页面缓存状态是由http header决定的,一个浏览器请求信息,一个是服务器响应信息。主要包括Pragma: no-cache、Cache-Control、 Expires、 Last-Modified、If-Modified-Since。其中Pragma: no-cache由HTTP/1.0规定,Cache-Control由HTTP/1.1规定。

自己画的工作原理图:

 

image

从图中我们可以看到原理主要分三步:

  1. 第一次请求:浏览器通过http的header报头,附带Expires,Cache-Control,Last-Modified/Etag向服务器请求,此时服务器记录第一次请求的Last-Modified/Etag
  2. 再次请求:当浏览器再次请求的时候,附带Expires,Cache-Control,If-Modified-Since/Etag向服务器请求
  3. 服务器根据第一次记录的Last-Modified/Etag和再次请求的If-Modified-Since/Etag做对比,判断是否需要更新,然后响应请求

相关参数说明;

Cache-Control的主要参数
Cache-Control: private/public Public 响应会被缓存,并且在多用户间共享。 Private 响应只能够作为私有的缓存,不能再用户间共享。
Cache-Control: no-cache:不进行缓存
Cache-Control: max-age=x:缓存时间 以秒为单位
Cache-Control: must-revalidate:如果页面是过期的 则去服务器进行获取。
Expires:显示的设置页面过期时间
Last-Modified:请求对象最后一次的修改时间 用来判断缓存是否过期 通常由文件的时间信息产生
If-Modified-Since :客户端发送请求附带的信息 指浏览器缓存请求对象的最后修改日期 用来和服务器端的Last-Modified做比较

Etag:ETag是一个可以 与Web资源关联的记号(token),和Last-Modified功能才不多,也是一个标识符,一般和Last-Modified一起使用,加强服务器判断的准确度

 

关于图片,css,js,flash的缓存

这个主要通过服务器的配置来实现这个技术,如果使用apache服务器的话,可以使用mod_expires模块来实现:

编译mod_expires模块:

Cd  /root/httpd-2.2.3/modules/metadata

/usr/local/apache/bin/apxs -i -a -c mod_expires.c //编译

编辑httpd.conf配置:添加下面内容

<IfModule mod_expires.c>

ExpiresActive on

ExpiresDefault “access plus 1 month”

ExpiresByType text/html “access plus 1 months”

ExpiresByType text/css “access plus 1 months”

ExpiresByType image/gif “access plus 1 months”

ExpiresByType image/jpeg “access plus 1 months”

ExpiresByType image/jpg “access plus 1 months”

ExpiresByType image/png “access plus 1 months”

EXpiresByType application/x-shockwave-flash “access plus 1 months”

EXpiresByType application/x-JavaScript      “access plus 1 months”

#ExpiresByType video/x-flv “access plus 1 months”

</IfModule>

解释:第一句–开启服务

第二句–默认时间是一个月

在下面是关于各种类型的资源的缓存时间设置

更详细的说明可以参考我的《网站优化–让你的网页飞起来》关于这个模块的使用介绍

参考资料:http://www.infoq.com/cn/articles/etags

                 http://www.lifetyper.com/archives/000087.html
                 http://developer.yahoo.com/performance/rules.html
                 http://www.websiteoptimization.com/speed/tweak/cache/

                  http://www.mnot.net/cache_docs/

[转载]图片存储架构学习:独立的图片服务器,给爱一个独立的空间 - IT青藤屋 - 青藤园

mikel阅读(819)

[转载]图片存储架构学习:独立的图片服务器,给爱一个独立的空间 – IT青藤屋 – 青藤园.

版权声明:非商业自由转载,署名王国峰原文链接

前言

去年我凭着对网站架构的浓厚兴趣陆陆续续给大家分享了不少大型网站架构的经典案例,但是大部分都只是介绍了大概,并没有深入地研究,有兴趣的朋友可以去我博客的网站架构分类下学习讨论。今年我打算继续学习网站架构方面的知识,并对此作更加深入地分析与实践,当然学习成果会及时和大家分享和交流,希望今年自己的能力可以更上一层楼吧。

这几天我一直在关注大型网站中图片存储方面的相关问题,通过了解和实践,体会颇深,我想我可以针对图片存储这个话题写一个系列文章,以便对这次学习的总结。

第一篇,让我们从独立图片服务器开始说起,真爱,不是须要让自己更加独立的么?come on!

正文

一、部署独立图片服务器的必要性

我们知道,无论对于Apache还是IIS,图片始终是最消耗系统资源的,如果将图片服务和应用服务放在同一个服务器的话,应用服务器很容易会因为 图片的高I/O负载而崩溃,因此对于有些大型网站项目,我们有必要将图片服务器和应用服务器分离。部署独立的图片服务器(甚至是服务器集群)是大型网站图 片存储解决方案中最基础的,因为有了独立的图片服务器后,我们才能对图片服务器做更有针对性的性能优化,比如从硬件角度说,图片服务器可以配置高端的硬 盘,7200转的换成15000转的,而CPU却只要一般就可以了;从软件角度说,可以为图片服务器配置特殊的文件系统来满足对图片的I/O请求,如淘宝 的TFS,就很好地解决了大规模小图片文件带来的I/O噩梦,同时,我们也可以采用nginx、squid来代理图片请求等等。

二、采用独立域名

注意,这里是指独立域名,不是子域哦,比如yahoo.com图片服务器用了yimg.com的域名,而不是用二级域名img.yahoo.com,这是为什么呢?个人觉得原因主要有以下几点:

1、同一域名下浏览器的并发连接数有限制,一般在2 – 6之间,下图列举了各个浏览器的并发连接数(来自网络,未经我亲自考证,供参考)

这样,我们如果给图片服务器配置独立的域名,那么在一个页面中加载图片时,就可以突破浏览器连接数的限制,理论上,增加一个独立域名,并发连接数加倍。

2、由于cookie的原因,对缓存不利

比如有一张图片http://www.test.com/img/xx.gif,那么当我们向它发起请求的时候,会带上www.test.com域 名下的cookie,由于大部分web cache都只缓存不带cookie的请求,这样就导致每次的图片请求都不能命中cache,而仍旧要去原始服务器获取图片,导致图片缓存意义不大。所 以,还是给单独搞一个图片独立域名吧,当然,不只是图片,css和js文件也可以参照这个思路来搞。

3、方便CDN同步

这个我不太清楚是怎么回事,我个人猜测和第二点cookie有点关系,还望资深人士留言分享,谢谢。

三、图片服务器分离后,如何进行图片上传和图片同步

当然任何事物都具有两面性,图片服务器分离固然提升了图片访问的效率,大大缓解了服务器因图片造成的I/O瓶颈,但是分离以后图片的上传和同步就成了一个大问题了。下面就我个人的想法谈谈几种解决方案。

1、NFS共享方式

如果你不想在每台图片服务器同步所有图片,那NFS共享是最简单也最实用的方式。NFS是个分布式的客户机/服务器文件系统,NFS的实质在于用户间计算机的共享,用户可以联结到共享计算机并象访问本地硬盘一样访问共享计算机上的文件。

具体实现思路是:web服务器通过nfs挂载多台图片服务器export出来的目录,用户先将图片上传到web服务器,然后将上传的图片通过程序拷 贝到这个mount目录中去,这样那几台图片服务器就也能访问到刚上传的图片了(注意,只是共享了,并没有真正拷贝到图片服务器)。再给那几台图片服务器 绑定独立域名,于是浏览器端就可以用单独的域名来访问图片了。这种方式基本不会有因同步造成的延时,但需要依赖nfs,nfs挂掉会影响web服务器。为 了更直观的表达,我还是上一幅图吧,画得比较粗糙,大家将就着看看。

至于如何配置nfs,大家google一下,或者看一下这篇文章,是在Linux下配置NFS的http://blog.csdn.net/lixinso/article/details/6639643

2、利用FTP同步

和上面nfs不一样的是,用户上传完图片后是利用ftp同步到各个图片服务器的,php、java、ASP.NET基本上都能操作ftp。这样的话 每个图片服务器就都保存一份图片的副本,也起到了备份的作用。但是缺点是将图片ftp到服务器比较耗时,如果异步去同步的话又会有延时,不过一般的小图片 文件也还好了。

当然除了上面两种方法,还有诸如安装同步软件、webservice等方法,但我个人觉得上面2种比较靠谱一点,所以其他的就暂时不介绍了,如果各位朋友有更好地建议,请留言分享。

好了,对于独立图片服务器的介绍就到这里了,欢迎大家补充,咱们下回见。

[转载]图片延迟加载 优化jquery.lazyload.js - 李豫川 - 博客园

mikel阅读(832)

[转载]图片延迟加载 优化jquery.lazyload.js – 李豫川 – 博客园.

由于用户访问页面需要加载很多的图片,延迟加载技术在电子商务网站领域越来越普及,淘宝商城,京东商城,凡客等访问量巨大的电子商务站点为了增加用户用户 体验,访问速度以及减少对自身服务器的压力,纷纷研发或使用延迟加载技术。当然如果没有前端开发的深入研究很难写出自己的js特效包。

JQuery.lazyload.js就是一个基于JQuery框架库的特效应用。它能让用户访问页面的时候,只加载当前屏幕所见内容的图片。

JQuery.lazyload.js使用方法:首先页面需导入jQuery.js。这个是先决条件,毕竟jQuery.lazyload.js特效包是 基于jQuery框架库的!然后需到如Jquery.Lazyload.js。还需在本地服务器上传一张用来占位的图片grey.gif(可以是张很小的 图片或者loading.gif之类表示正在加载的图片)。

<script type="text/JavaScript" src="http://www.wumeiwang.com/js/lazyload/jquery.js"></script>
<script type="text/JavaScript" src="http://www.wumeiwang.com/js

/jquery.lazyload.js”></script>

复制代码
<script type=text/JavaScript src=http://pic.imtimmy.com/wp-content/js/lazyload/jquery.lazyload.js></script>
<script type=text/javascript>
jQuery(document).ready(
function($){
$(
img).lazyload({
placeholder : 
http://www.wumeiwang.com/images/common/grey.gif,
effect      : 
fadeIn
});
});
</script>
复制代码

 

但是如果使用httpwatch等网页测试工具检测页面加载你会发现,图片其实是全部加载完了。因为执行js是在页面的page_load之后。所 有,jquery.lazyload.js加载出来的只是图片的缓存文件。这样不就2次访问服务器加载图片了?即使jquery.lazyload.js 加载的只是缓存图片…

解决办法:在jquery.lazyload.js的62行将 $(self).attr(“original”, $(self).attr(“img”));  修改成 $(self).attr(“original”, $(self).attr(“original”));   然后读图图片的时候将img=  替换成    original=即可。凡客,淘宝商城都是这样的作法。这样才实现真正意义上的延迟加载。

[转载]javascript--识别判断浏览器 - CouverVin - 博客园

mikel阅读(830)

[转载]javascript–识别判断浏览器 – CouverVin – 博客园.

各浏览器useragent的内容如下:

IE 
  而IE各个版本典型的userAgent如下: 
  Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)
 
  其中,版本号是MSIE之后的数字。 

Firefox 
  Firefox几个版本的userAgent大致如下: 
  Mozilla/5.0 (Windows; U; Windows NT 5.2) Gecko/2008070208 Firefox/3.0.1
Mozilla/5.0 (Windows; U; Windows NT 5.1) Gecko/20070309 Firefox/2.0.0.3
Mozilla/5.0 (Windows; U; Windows NT 5.1) Gecko/20070803 Firefox/1.5.0.12
  其中,版本号是Firefox之后的数字。 

Opera 
  Opera典型的userAgent如下: 
  Opera/9.27 (Windows NT 5.2; U; zh-cn)
Opera/8.0 (Macintosh; PPC Mac OS X; U; en)
Mozilla/5.0 (Macintosh; PPC Mac OS X; U; en) Opera 8.0
  
  其中,版本号是靠近Opera的数字。 

Safari 
  Safari典型的userAgent如下: 
  Mozilla/5.0 (Windows; U; Windows NT 5.2) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13
Mozilla/5.0 (iPhone; U; CPU like Mac OS X) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A93 Safari/419.3 

  其版本号是Version之后的数字。 

Chrome 
  目前,Chrome的userAgent是: 
  Mozilla/5.0 (Windows; U; Windows NT 5.2) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13  
  其中,版本号在Chrome之后的数字。 

Navigator 
  目前,Navigator的userAgent是: 
  Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080219 Firefox/2.0.0.12 Navigator/9.0.0.6 
  其中,版本号在Navigator之后的数字。

 

那么,我们就可以用正则来匹配判断浏览器及其版本,代码如下

复制代码
<script type="text/javascript">
    var Sys = {};
    var ua = navigator.userAgent.toLowerCase();
    var s;
    (s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] :
    (s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] :
    (s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] :
    (s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] :
    (s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0;

    //使用
    if (Sys.ie) document.write('IE: ' + Sys.ie);
    if (Sys.firefox) document.write('Firefox: ' + Sys.firefox);
    if (Sys.chrome) document.write('Chrome: ' + Sys.chrome);
    if (Sys.opera) document.write('Opera: ' + Sys.opera);
    if (Sys.safari) document.write('Safari: ' + Sys.safari);
</script>
复制代码

 

[转载]使用 App Studio 快速定制一个你自己的专属应用 - 王博_Nick - 博客园

mikel阅读(875)

[转载]使用 App Studio 快速定制一个你自己的专属应用 – 王博_Nick – 博客园.

如果已有做一个手机应用的想法,只需要一些简单的图片,视频,或者RSS源,就可以通过 App Studio制作出你心仪的应用! App Studio 是一个拥有多种模板样式,操作灵活的 Windows Phone应用的生成工具。如果有一定开发基础你也可以在生成应用的基础上进行更深一步的定制修改使你的应用与众不同。(仅需要4步!

首先注册一个App Studio账户登陆: http://apps.windowsstore.com/default.htm

image

由于目前 App Studio 是Beta 版所以在这里需要发个邮件问 studio@microsoft.com 索取一个邀请代码。 这里是系统自动回复的非常快我就收到代码了。

image

输入你的邀请码就算注册成功了!

image

登陆进来以后这里有多种模板可以供用户选择

image

这里我就随意选择了一个 Official store 的项目模板为大家进行讲解。

 

首先 第一步 App Studio 会要求我我们输入应用的名称和描述信息另外我们也可以自定义一个应用图标在这里上传。

image

第二步 设置应用内容

使用Item中的编辑按钮我们可以对Panorama每个PanoramaItem中ListBox的数据模板进行编辑

image

编辑模板样式排列布局等信息

image

在这里我们可以对绑定的数据源进行编辑,注 这里动态数据源后台生成代码的时候我会为大家介绍数据绑定对象的生成位置

image

设置页面中我们可以使用“加号”按钮来添加主页中的其他Item 当然Item的数据元可以自定义

image

这里我添加一个新的PanoramaItem

image

并且我将她的数据源设置成 RSS 这回方便很多用户动态设置数据源信息。

image

 

第三步 配置你的应用样式

这里提供了多种的配色方案 可以让大家尽情发挥。

image

第四步 也就是最后一步 生成我们应用

点击 Generate 等待我们的应用生成。

image

生成后我们就可以快速部署在手机上了

image

 

安装步骤, 其实这是一个简单的企业级应用分发部署流程。

第一步 安装证书, 证书刚刚通过邮件的形式发给我们刚才的注册邮箱中了。

点击链接 – 添加证书即可

image

 

第二步 部署应用

可以点击邮件中的连接或者通过扫描二维码的形式来下载安装应用。

image

 

另外我们可以通过 代码下载链接获取到源码。

image

打开源码跟大家一起分析一下这个项目

首先我这个项目采用MVVM Pattern 相信有些开发经验的同学上手并不难。 这里的View 和 ViewModel命名都很规范。

image

我在Entities项目中找到页面的数据源 并且都是实现了 INotifyPropertyChanged 接口修改非常方便。

image

另外在 Repositories 项目中找到相关RSS的数据源代码 只是通过 LoadRemote(URL)获取数据源进行处理。

image

这里在给大家介绍下 关于Windows Phone的新策略:

1. 非开发者账号可以解锁1部手机,部署2个应用。 开发者账号依旧是3台设备,部署10个应用。

2. 部署应用是要 WP 的SDK的。

3. 在2013年8月26号之前注册,仍然可以享受 19刀的 WP 开发者账号的优惠!

4. App Studio 生成的应用开发者经过少许修改,也是可以上传应用商店的!

童鞋们还在等什么心动不如行动啊!!

 

参考资料:

Windows Phone Developer Blog

Everyone can build an app – introducing Windows Phone App Studio beta
Making it easier to get started with Windows Phone App Studio beta, simplified phone registration, support options & more payout markets

希望上的总结可以帮助到大家, 同时欢迎大家在这里和我沟通交流或者在新浪微博上 @王博_Nick

[转载]c#实现QQ群成员列表导出及邮件群发之邮件群发 - 狼性法则 - 博客园

mikel阅读(1083)

[转载]c#实现QQ群成员列表导出及邮件群发之邮件群发 – 狼性法则 – 博客园.

前言

  经过前3篇的代码编写我们已经可以通过抓包分析,http协议模拟登陆QQ,并顺利拿到cookie实现获 取群成员等操作,其中重要的算法,如密码算法,gtk算法前文已经毫无保留的给出,通过大家的评论及留言已经看出大家已经能顺利取到群成员,此篇邮件群发 与前几篇关系不大,也是本系列文章的最后一篇,算是补个全。本文将讲述登陆C#利用smtp服务多账号,多协议群发邮件的代码。

软件截图:

 

本文主要完成添加多个发信服务器账号,单线程及多线程邮件群发功能。

功能分解:

1、设置发信SMTP服务器

首先做个窗体,这个不用说很简单,我们可以将常用的smtp,做成下拉列表,本人只为演示所以只写了一个qq的,前台是得开通smtp服务。

发信邮箱设置代码:

复制代码

复制代码
public string sendUser;
        private void btnEnter_Click(object sender, EventArgs e)
        {
            Regex myreg = new Regex(@"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"); //email验证            
            if (myreg.IsMatch(txtEmail.Text) && txtEmail.Text.Trim() != "" && txtEmaipwd.Text.Trim() != "")
            {
                sendUser = txtSmtp.Text + "," + txtEmail.Text.Trim() + "," + txtEmaipwd.Text.Trim();
                this.DialogResult = DialogResult.OK;
            }
            else
            {
                txtSmtp.Text = "";
                txtEmail.Text = "";
                txtEmaipwd.Text= "";
                msg.Text = "添加格式不正确,请重新输入!";
                this.DialogResult = DialogResult.None;
            }
        }

        private void txtEmaipwd_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter) btnEnter_Click(null, null);
        }

        private void btnCanncle_Click(object sender, EventArgs e)
        {
            this.Dispose();
        }
复制代码
复制代码

添加完成并验证同过以后将smtp服务器,邮箱,密码组合成字符串添加的下面的listbox中(添加发信邮箱按钮):

复制代码

复制代码
private void button1_Click(object sender, EventArgs e)
        {
            AddFrm addSender = new AddFrm();
            if (addSender.ShowDialog() == DialogResult.OK && !lstSender.Items.Contains(addSender.sendUser))
            {
                lstSender.Items.Add(addSender.sendUser);
            }
        }
复制代码
复制代码

这样可以添加多个smtp服务器并设置多个发信账号,从反垃圾邮件的问题来说,还是多添加几个为好。

两种群发短信代码,采用system.net中自带的发信类库中提供的方法:

发送统计类:

复制代码

复制代码
class All
    {
        public static int success = 0;//发送成功的个数
        public static int fail = 0;//发送失败的个数
        public static int runing = 0;//运行的线程数
    }
复制代码
复制代码

发信单元:

复制代码

复制代码
class MainUnit
    {
        public string smtp;
        public string from;
        public string pwd;
        public string to;
        public string subject;
        public string body;
        public ArrayList paths;
        public MainUnit(string Psmtp, string Pfrom, string Ppwd, string Pto, string Psubject, string Pbody, ArrayList Ppaths)
        {
            smtp = Psmtp;
            from = Pfrom;
            pwd = Ppwd;
            to = Pto;
            subject = Psubject;
            body = Pbody;
            paths = Ppaths;
        }
        /*发邮件*/
        public bool SendMail()
        {
            //创建smtpclient对象
            System.Net.Mail.SmtpClient client = new SmtpClient();
            client.Host = smtp;
            client.UseDefaultCredentials = false;
            client.Credentials = new System.Net.NetworkCredential(from, pwd);
            client.DeliveryMethod = SmtpDeliveryMethod.Network;

            //创建mailMessage对象 
            System.Net.Mail.MailMessage message = new MailMessage(from, to);
            message.Subject = subject;
            //正文默认格式为html
            message.Body = body;
            message.IsBodyHtml = true;
            message.BodyEncoding = System.Text.Encoding.UTF8;

            //添加附件
            if (paths.Count != 0)
            {
                foreach (string path in paths)
                {
                    Attachment data = new Attachment(path, System.Net.Mime.MediaTypeNames.Application.Octet);
                    message.Attachments.Add(data);
                }
            }

            try
            {
                client.Send(message);
                //MessageBox.Show("Email successfully sent.");
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Send Email Failed." + ex.ToString());
                return false;
            }
        }

        /*发邮件:线程中使用*/
        public void SendMail2()
        {
            All.runing++;
            //创建smtpclient对象
            System.Net.Mail.SmtpClient client = new SmtpClient();
            client.Host = smtp;
            client.UseDefaultCredentials = false;
            client.Credentials = new System.Net.NetworkCredential(from, pwd);
            client.DeliveryMethod = SmtpDeliveryMethod.Network;

            //创建mailMessage对象 
            System.Net.Mail.MailMessage message = new MailMessage(from, to);
            message.Subject = subject;
            //正文默认格式为html
            message.Body = body;
            message.IsBodyHtml = true;
            message.BodyEncoding = System.Text.Encoding.UTF8;

            //添加附件
            if (paths.Count != 0)
            {
                foreach (string path in paths)
                {
                    Attachment data = new Attachment(path, System.Net.Mime.MediaTypeNames.Application.Octet);
                    message.Attachments.Add(data);
                }
            }

            try
            {
                client.Send(message);
                All.success++;
                All.runing--;
            }
            catch (Exception ex)
            {
                All.fail++;
                All.runing--;
            }
        }
    }
复制代码
复制代码

下面看下发信界面的设置:

添加附件代码,我们可以将附件地址放在一个ArrayList中动态添加,同时将附件的名称显示在文本框中:

复制代码

复制代码
private void addfujian_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            openFujiandialog.Filter = "所有文件(*.*)|*.*";
            if (openFujiandialog.ShowDialog() == DialogResult.OK)
            {
                string[] files = openFujiandialog.FileNames;
                foreach (string file in files)
                {
                    paths.Add(files);
                    txtFujian.Text += Path.GetFileName(file) + " ";
                }
            }
        }
复制代码
复制代码

清空附件代码;

复制代码

复制代码
 private void clearfujian_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            txtFujian.Text = "";
            paths.Clear();
        }
复制代码
复制代码

发信标题及内容检测代码:

复制代码

复制代码
public bool CheckEmail()
        {
            string err = "注意:\n\n";
            int errCount = 0;
            if (txtTitle.Text.Trim().Length == 0)
            {
                err += "标题不能为空!\r\n";
                errCount++;
            }
            if (txtBody.Text.Trim().Length == 0)
            {
                err += "正文不能为空!";
                errCount++;
            }
            Regex myreg = new Regex(@"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"); //email验证
            if (errCount == 0)
                return true;
            else
            {
                MessageBox.Show(err, "提示");
                return false;
            }
复制代码
复制代码

一切设置完成后就是群发邮件了,本软件只是给固定群发送邮件,大家可以扩展导入列表群发邮件:

复制代码

复制代码
        private void button2_Click(object sender, EventArgs e)
        {
            if (!CheckEmail()) return;
            //群成员邮箱集合,可扩展比如导入等
            if (toUser.Count < 1) MessageBox.Show("没有群成员!");
            //存放发件人的listbox
            if (lstSender.Items.Count < 1) MessageBox.Show("没有发件人!");
            ArrayList alist = new ArrayList();
            for (int i = 0; i < toUser.Count; i++)
            {
                string mail;
                if (i < lstSender.Items.Count)
                {
                    mail = toUser[i].ToString() + "," + lstSender.Items[i].ToString();
                }
                else
                {
                    mail = toUser[i].ToString() + "," + lstSender.Items[i % lstSender.Items.Count].ToString();
                }
                alist.Add(mail);
            }
            for (int j = 0; j < alist.Count; j++)
            {
                string senderStr = alist[j].ToString();
                string[] str = senderStr.Split(new char[] { ',', ',', ',' });
                string smtpStr = str[1];
                string fromStr=str[2];
                string pwdStr = str[3];
                string toMail = str[0];
                MainUnit mu = new MainUnit(smtpStr, fromStr, pwdStr, toMail, txtTitle.Text, txtBody.Text, paths);
                Thread mythread = new Thread(new ThreadStart(mu.SendMail2));
                mythread.Start();
            }
            int percentValue = 0;
            while (All.runing != 0)
            {
                //设置滚动条
                Application.DoEvents();
                this.proBar.Value = All.runing;
                this.proBar.Maximum = toUser.Count;
                this.proBar.Minimum = 0;
                percentValue = 100 * (this.proBar.Value - this.proBar.Minimum) / (this.proBar.Maximum - this.proBar.Minimum);//将当前进度转化为百分比的形式
                lblProbar.Text = percentValue.ToString() + "%";//在Label中显示百分比的值       
            }
            if (percentValue == 100)
            {
                //发送结果提示
                DialogResult ok = MessageBox.Show("【结果】发送成功:" + All.success.ToString() + "条  发送失败:" + All.fail.ToString() + "");
                if (ok == DialogResult.OK)
                {
                    proBar.Value = 0;
                    lblProbar.Text = "";
                    All.fail = 0;
                    All.success = 0;
                }
            }            
        }
复制代码
复制代码

导出及过滤列表就是些简单的文件读写操作了,应该有比我更好的办法来实现。

结语:

本系列文章的目的是给大家一个思路,想自己没事研究qq群发,空间日志,游戏等外挂操作的可以参考一下前面几篇文章。QQ的加密算法及参数计算方法 相信会随着时间推移而改变,到时候登陆肯定不成功,有一的那个时效性。本文中存在很多问题,如发垃圾邮件如何解决,群发邮件效率不高,发信正确统计成功失 败等问题,有兴趣的朋友可以自己研究一下。

本系列目录:

1、c#实现QQ群成员列表导出及邮件群发开篇

2、c#实现QQ群成员列表导出及邮件群发之模拟QQ登陆

3、c#实现QQ群成员列表导出及邮件群发之群列表及群成员获取

4、c#实现QQ群成员列表导出及邮件群发之邮件群发

[转载]ASP.NET Web API的核心对象:HttpController - Artech - 博客园

mikel阅读(869)

[转载]ASP.NET Web API的核心对象:HttpController – Artech – 博客园.

对于ASP.NET Web API来说,所谓的Web API定义在继承自ApiController的类中,可能ApiController是大部分读者最为熟悉的类型了。但是我们将ASP.NET Web API下的Controller称为HttpController,它是对所有实现了接口IHttpController的所以Controller类型 的统称,而ApiController仅仅视为IHttpController接口的一个实现而已,所以我们会更多地强调HttpController的 概念。[本文已经同步到《How ASP.NET Web API Works?》]

目录
HttpController
HttpControllerContext
HttpControllerDescriptor
ApiController
实例演示:证明针对每次请求创建的HttpController都是“全新的”

HttpController

既然HttpController指的是所有实现了IHttpController接口的类型,我们自然得下来了解一下这个接口的定义。如下面的代码片断所示,在IHttpController接口中仅仅定义了唯一的方法ExecuteAsync方法,该方法以异步的方式执行HttpController。

public interface IHttpController
{
    Task<HttpResponseMessage> ExecuteAsync( HttpControllerContext controllerContext,  CancellationToken cancellationToken);
}

与我们在“消息处理管道” 中介绍的HttpMessageHandler的SendAsync方法一样,该ExecuteAsync方法返回的依然是一个 Task<HttpResponseMessage>对象,代表一个用于处理响应消息的Task。实际上HttpController可以视 为对ASP.NET Web API的消息处理管道的延续。如右图所示,作为消息处理管道最后一个HttpMessageHandler的HttpRoutingDispatcher 利用自身的HttpControllerDispatcher激活并调用ExecuteAsync方法执行目标HttpController。方法调用返 回的Task<HttpResponseMessage>对象正式HttpControllerDispatcher的SendAsync方法的返回值。

与HttpMessageHandler的SendAsync方法不同之处在于:ExecuteAsync方法的参数不再是表示请求的 HttpRequestMessage对象,而是一个HttpControllerContext对象。顾名思 义,HttpControllerContext表示当前基于HttpController的上下文,HttpController可以认为是在该上下文 中执行。

HttpControllerContext

如下面的代码片断所示,通过定义在HttpControllerContext中的属性我们可以得到用于配置消息处理管道的 HttpConfiguration,封装路由数据的HttpRouteData,以及表示当前请求的HttpRequestMessage。这三个属性 可以在构建HttpControllerContext的时候直接通过构造函数的参数指定,我们也可以先创建一个空的 HttpControllerContext对象之后直接对这些属性赋值。

public class HttpControllerContext
{
    public HttpControllerContext();
    public HttpControllerContext(HttpConfiguration configuration,  IHttpRouteData routeData,HttpRequestMessage request);   

    public HttpConfiguration      Configuration { get; set; }
    public IHttpRouteData         RouteData { get; set; }
    public HttpRequestMessage     Request { get; set; }

    public IHttpController              Controller { get; set; }
    public HttpControllerDescriptor     ControllerDescriptor { get; set; }
}

既然HttpControllerContext是针对HttpController的上下文,我们自然可以从中得到这个 HttpController。我们除了可以通过属性Controller获取或者设置对应的HttpController之外,还可以利用另一个属性 ControllerDescriptor获取或者设置用于描述HttpController的 System.Web.Http.Controllers.HttpControllerDescriptor对象。

HttpControllerDescriptor

HttpControllerDescriptor封装了某个HttpController类型的元数据,可以视为对应HttpController类型的描述对象。除此之外,HttpControllerDescriptor 还具有根据元数据创建对应HttpController的能力,实际上ASP.NET Web API的HttpController激活系统就是根据HttpControllerDescriptor来创建目标HttpController的。

如下面的代码片断所示,我们可以通过HttpControllerDescriptor的属性Confiruation、 ControllerName和ControllerType获取当前的HttpConfiguration和被描述HttpController的名称 和类型。这三个属性可以在构建HttpControllerDescriptor时通过构造函数的参数显式指定,我们可以可以先 构建一个空的HttpControllerDescriptor对象,然后手工设置这些属性。

public class HttpControllerDescriptor
{   
    public HttpControllerDescriptor();
    public HttpControllerDescriptor(HttpConfiguration configuration,   string controllerName, Type controllerType);

    public virtual IHttpController CreateController(HttpRequestMessage request);

    public virtual Collection<T> GetCustomAttributes<T>() where T: class;
    public virtual Collection<T> GetCustomAttributes<T>(bool inherit)   where T: class;
    public virtual Collection<IFilter> GetFilters();

    public HttpConfiguration     Configuration { get; set; }
    public string                ControllerName { get; set; }
    public Type                  ControllerType { get; set; }

    public virtual ConcurrentDictionary<object, object> Properties { get; }
}

我们说HttpControllerDescriptor具有根据元数据信息创建目标HttpController的能力主要体现在其 CreateController方法上。实际上本章的核心内容“HttpController的激活”就体现在 HttpControllerDescriptor的这个CreateController方法上,所有后续的内容基本上都是围绕着这个方法展开的。

HttpControllerDescriptor还具有一个字典类型的只读属性Properties,它使我们可以将通过指定一个字符串类型的 Key将任何一个对象附加到某个HttpControllerDescriptor,然后通过这个Key人任何情况下将这个附加的对象提取出来。我们在 HttpRequestMessage类型中已经看到过了类似的设计。

ApiController

我们现在来介绍一下我们创建HttpController类型默认采用的基类ApiController。如下面的代码片断所示,除了实现接口 IHttpController外,HttpController还实现了接口IDisposable。如果在自定义HttpController需要实 现一些资源回收的工作,可以将它们定义在重写的Dispose方法中(受保护的虚方法Dispose)。

public abstract class ApiController : IHttpController, IDisposable
{
    public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);
    protected virtual void Initialize(HttpControllerContext controllerContext);

    public void Dispose();
    protected virtual void Dispose(bool disposing);

    public HttpControllerContext     ControllerContext { get; set; }
    public HttpConfiguration         Configuration { get; set; }
    public HttpRequestMessage        Request { get; set; }
    public IHttpRouteData            RouteData { get; set; }

    public ModelStateDictionary     ModelState { get; }
    public UrlHelper                Url { get; set; }
    public IPrincipal               User { get; }
}

我们可以通过属性ControllerContext从ApiController对象中获取或者为它设置一个 HttpControllerContext对象作为其上下文,另外三个属性Configuration、Request和RouteData与此 HttpControllerContext的同名属性引用这同一个对象。

ApiController的只读属性ModelState返回一个具有字典数据结构的ModelStateDictionary对象,包含其中的 数据会被以“Model绑定”的形式绑定到目标Action方法的某个参数。除此之外,ApiController的ModelState属性还用于保存 参数验证失败后的 错误消息。另一个参数Url返回一个类型为UrlHelper的对象,该对象用于根据注册的HttpRoute和提供的路由变量生成相应的URL。

ApiController的User返回当前线程的Principal。相信读者还会记得在“消息处理管道”中介绍HttpServer时我们谈 到:如果当前线程的Principal为Null,作为消息处理管道“龙头”的HttpServer会在SendAync方法执行过程创建一个空的 GenericPrincipal对象作为当前线程的“匿名”Principal。所以对于匿名请求来说,ApiController的User属性会返 回这个通过HttpServer设置的空GenericPrincipal对象。

从上面给出的代码片断我们还会看到ApiController包含一个受保护的Initialize方法,该方法根据指定的 HttpControllerContext对自身作相应的初始化处理。一旦执行了Initialize方法,当前ApiController对象将处于 初始化状态。在默认情况下,此Initialize会在实现的ExecuteAsync方法中被调用。

在默认情况下,ASP.NET Web API的HttpController激活系统总是创建一个新的HttpController来处理每一个请求。对于其类型继承自 ApiController的HttpController来说,如果在执行ExecuteAsync方法的时候发现当前的ApiController已 经处于“初始化”的状态,会直接抛出一个InvalidOperationException异常。

实例演示:证明针对每次请求创建的HttpController都是“全新的”

举个简单的例子,我们定义了如下一个继承自ApiController的类MyApiController,并通过如下的方式将原本为受保护的Initialize方法转换成一个公有方法,以方便我们后续的调用。

public class MyApiController : ApiController
{
    public new void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);
    }
}

然后我们执行如下一段代码,它的特别指出在于:我们在调用MyApiController对象的ExecuteAsync方法之前调用了Initialize方法对其作了初始化处理。

MyApiController controller = new MyApiController();
HttpControllerContext controllerContext = new HttpControllerContext(new HttpConfiguration(), new HttpRouteData(new HttpRoute()), new HttpRequestMessage());
controller.ControllerContext = controllerContext;
controller.Initialize(controllerContext);
controller.ExecuteAsync(controllerContext, new CancellationToken(false));

 

当执行ApiController的ExecuteAsync方法的时候会抛出如图4-2所示的InvalidOperation,并提 示:“Cannot reuse an ‘ApiController’ instance. ‘ApiController’ has to be constructed per incoming message. Check your custom ‘IHttpControllerActivator’ and make sure that it will not manufacture the same instance.”错误消息已经表明了ApiController是不能“重用”的,用于处理每一个请求的ApiController都应该是“全新” 的。

[转载]asp.net mvc Route 使用自定义条件(constraints)禁止某ip登陆 - 狼性法则 - 博客园

mikel阅读(947)

[转载]asp.net mvc Route 使用自定义条件(constraints)禁止某ip登陆 – 狼性法则 – 博客园.

前言

本文的目的是利用Mvc route创建一个自定义约束来控制路由跳转实现禁止ip登陆,当然例子可能不合理,但是文章要表明的意思是当普通的路由约束不能解决我们实际的需求时, 我们可以通过自定义路由限制条件实现,比如当匹配路由有数据库交互,根据url跳转等实际需求时,使用自定义条件此问题便可迎刃而解。

实现

举例普通的路由约束:

复制代码
routes.MapRoute(
    "Product",
    "Product/{productId}",
    new {controller="Product", action="Details"},
    new {productId = @"\d+" }
 );
复制代码
 匹配包含一个的整数productId的URLs。当定义一个route时,我们能够使用一个限制条件来限制URLs,使它匹配这个route。在例子中,这个route包含一个只匹配整数的正则表达式约束。
这里匹配一个整数的productId,当不满足这个条件是将交由另一个route处理,或者,如果没有匹配的routes, “The resource could not be found ”错误将被返回,普通的路由约束可以完成一些简单的没有业务逻辑的路由限制,比如限制某IP用户访问等,下面的自定义约束就是来解决这个问题。
自定义条件约束:
下面来看一些System.Web.Route给我们提供的自定义约束的接口IRouteConstraint,定义如下:
复制代码
// 摘要:
    //     定义类必须实现才能检查某 URL 参数值是否对约束有效的协定。
    [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
    public interface IRouteConstraint
    {
        // 摘要:
        //     确定 URL 参数是否包含此约束的有效值。
        //
        // 参数:
        //   httpContext:
        //     一个对象,封装有关 HTTP 请求的信息。
        //
        //   route:
        //     此约束所属的对象。
        //
        //   parameterName:
        //     正在检查的参数的名称。
        //
        //   values:
        //     一个包含 URL 的参数的对象。
        //
        //   routeDirection:
        //     一个对象,指示在处理传入请求或生成 URL 时,是否正在执行约束检查。
        //
        // 返回结果:
        //     如果 URL 参数包含有效值,则为 true;否则为 false。
        bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
    }
复制代码

我们只要写一个自定义约束类来实现此接口的Math方法,它返回一个布尔值。这个布尔值决定该请求是否应该被route对象处理。

下面我们来定义自定义路由约束:

复制代码
public class AbandonConstraint : IRouteConstraint
        {
            public bool Match(HttpContextBase httpContext, Route route, string parameterName,
            RouteValueDictionary values, RouteDirection routeDirection)
            {
                var currentIp = httpContext.Request.UserHostAddress;
                //如果当前访问用户的ip是127.0.0.1,则匹配Abandon路由,如果不是则匹配其他路由
                return currentIp == "127.0.0.1";
            }
        }
复制代码

路由设置:

复制代码
routes.MapRoute(
                name: "Abandon",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Abandon", id = UrlParameter.Optional },
                constraints:new { customConstraint = new AbandonConstraint() }//自定义限制路由
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
复制代码

配置好以后,我们本地运行程序,便能匹配到Abandon路由,若我们修改所要限制的ip地址,则匹配Default路由。如下图本地运行命中abandon默认地址的action

总结:

自定义的路由约束在实际应用中对于复杂的路由匹配条件,比如有数据库交互,提供了便利的使用条件,同时当请求与自定义路由约束匹配时我们还可以动态为其添加,修改路由参数,控制跳转等操作,注意约束条件必须继承IRouteConstraint,并且实现Match方法。

时间仓促,代码及文章比较杂乱,有什么出错的地方欢迎指出。

[转载]Lucene.net 系列四 --- index 下 - idior - 博客园

mikel阅读(1026)

[转载]Lucene.net 系列四 — index 下 – idior – 博客园.

本文将介绍有关索引并发控制的问题,以结束对Lucene.net建立索引问题的讨论.

1. 允许任意多的读操作并发.即可以有任意多的用户在同一时间对同一份索引做查询工作.

2. 允许任意多的读操作在索引被正在被修改的时候进行.即哪怕索引正在被优化,添加删除文档,这时也是允许用户对索引进行查询工作. (it’s so cool.)

3. 同一时间只允许一个对索引修改的操作.即同一时间只允许IndexWriter或IndexReader打开同一份索引.不能允许两个同时打开一份索引.

Lucene提供了几种对索引进行读写的操作.添加文档到索引,从索引中删除文档,优化索引,合并Segments.这些都是对索引进行写操作的方法. 查询的时候就会读取索引的内容.

有关索引并发的问题是一个比较重要的问题,而且是Lucene的初学者容易忽略的问题,当索引被破坏,或者程序突然出现异常的时候初学者往往不知道是自己的误操作造成的.

下面让我们看看Lucene是如何处理索引文件的并发控制的.

首先记住一下三点准则:

1. 允许任意多的读操作并发.即可以有任意多的用户在同一时间对同一份索引做查询工作.

2. 允许任意多的读操作在索引被正在被修改的时候进行.即哪怕索引正在被优化,添加删除文档,这时也是允许用户对索引进行查询工作. (it’s so cool.)

3. 同一时间只允许一个对索引修改的操作.即同一时间只允许IndexWriter或IndexReader打开同一份索引.不能允许两个同时打开一份索引.

第一个准则很容易理解,第二个准则说明Lucene对并发的操作支持还是不错的.第三个准则也很正常,不过需要注意的是第三个准则只是表明 IndexWriter和IndexReader不能并存,而没有反对在多线程中利用同一个IndexWriter对索引进行修改.这个功能可是经常用到 的,所以不要以为它是不允许的.不过这个时候的并发就需要你自己加以控制,以免出现冲突.

(注: 在前面的系列中已说过IndexReader不是对Index进行读操作,而是从索引中删除docuemnt时使用的对象)

有关这三个原则在实际使用Lucene API时候的体现,让我们先看看下面这张表:


表中列出了有关索引的主要读写操作.其中空白处表示X轴的操作和Y轴的操作允许并发.

而X处表明X轴的操作和Y轴的操作不允许同时进行.

比如Add document到索引的时候不允许同时从索引中删除document.

其实以上这张表就是前面三个准则的体现.Add Optimize Merge操作都是由IndexWriter来做的.而Delete则是通过IndexReader完成.所以表中空白处正是第一条和第二条准则的体现,而X(冲突)处正是第三个原则的具体表现.

 

为了在不了解并发控制的情况下对Lucene API的乱用. Lucene提供了基于文件的锁机制以确保索引文件不会被破坏.

当你对index 进行修改的时候, 比如添加删除文档的时候就会产生 ***write.lock文件,而当你从segment进行读取信息或者合并segments的时候就会产生***commit.lock文件.在默认 情况下,这些文件是放在系统临时文件夹下的. 简而言之, write.lock文件存在的时间比较长,也就是对index进行修改的锁时间比较长,而commit.lock存在的时间往往很短.具体情况见下表.

如果索引存在于server, 很多clients想访问的时候,自然希望能看到其他用户的锁文件,这时把锁文件放到系统临时文件夹就不好了.此时可以通过配置文件来改变锁文件存放的位置.

比如在一个ASP.NET的应用下,你就可以象下面这样利用web.config文件来实现你的目的.

<configuration>
<appSettings>
<add key=”Lucene.Net.lockdir” value=”c:yourdir” />
</appSettings>
</configuration>

不仅如此,在某些情况下比如你的索引文件存放在一个CD-ROM中,这时根本就无法对索引进行修改,也就不存在所谓的并发冲突,这种情况下你甚至可以讲锁文件的机制取消掉.同样通过配置文件.

<configuration>
<appSettings>
<add key=”disableLuceneLocks” value=”true” />
</appSettings>
</configuration>

不过请注意不要乱用此功能,不然你的索引文件将不再受到安全的保护.

下面用一个例子说明锁机制的体现.

using System;
using System.IO;
using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.Store;
using NUnit.Framework;
using Directory = Lucene.Net.Store.Directory;

[TestFixture]
public class LockTest
{
private Directory dir;

 [SetUp]
public void Init()
{
String indexDir = “index”;
dir = FSDirectory.GetDirectory(indexDir, true);
}

 [Test]
[ExpectedException(typeof(IOException))]
public void WriteLock()
{
IndexWriter writer1 = null;
IndexWriter writer2 = null;
try
{
writer1 = new IndexWriter(dir, new SimpleAnalyzer(), true);
writer2 = new IndexWriter(dir, new SimpleAnalyzer(), true);

}
catch (IOException e)
{
Console.Out.WriteLine(e.StackTrace);
}
finally
{
writer1.Close();
Assert.IsNull(writer2);
}
}

 [Test]
public void CommitLock()
{
IndexReader reader1 = null;
IndexReader reader2 = null;
try
{
IndexWriter writer = new IndexWriter(dir, new SimpleAnalyzer(),
true);
writer.Close();
reader1 = IndexReader.Open(dir);
reader2 = IndexReader.Open(dir);
}
finally
{
reader1.Close();
reader2.Close();
}
}
}

不过很令人失望的是在Lucene(Java)中应该收到的异常在dotLucene(1.4.3)我却没有捕获到.随后我在dotLucene的论坛上问了一下,至今尚未有解答.这也是开源项目的无奈了吧.