[转载]GCC强大背后

mikel阅读(913)

[转载]GCC强大背后 – wwang’s blog – 博客园.

前记: 经常浏览博客园的同学应该会觉得本文有标题党之嫌,这个标题的句式来自于MiloYip大牛的大作《C++强大背后》,在此,向Milo兄致意。

GCC,全称GNU Compiler Collection,是一套GNU开发的编译器环境,它的创始人便是大名鼎鼎的Richard.M.Stallman。 最初GCC刚开始开发时,它还叫做GNU C Compiler,随着开发的深入,GCC很快得到了扩展,不仅可以支持C语言,还可以处理C++,Pascal,Object-C,Java以及Ada 等其他语言。目前,GCC不仅是GNU的官方编译器,也成为编译和创建其他操作系统的编译器,包括BSD家族以及MAC OS X等。另外,GCC也是跨平台交叉编译的首选,它不仅支持Intel的x86系列,同时也支持MIPS,ARM,PowerPC,SPARC等等处理器。 可以这么说,即使GCC不是世界上效率最高的编译器,它也一定是世界上最全面的编译器。

1. GCC简介

我们先回到一个常识性的问题,什么是编译器呢?简单地说,编译器可以看作是一个语言翻译器。就像把中文翻译成英语一样,编译器可以把高级语言翻译成计算机能够执行的机器语言。这样看来,GCC可以算得上是一个精通多国语言的高级翻译官了。
最简单的GCC使用指令如下所示:
gcc  hello.c  -o  hello
GCC接受hello.c作为输入,最后产生目标可执行代码hello。这个简单的流程实际上经历了很多步骤,如下图所示:
虽然我们只用了一条命令就完成了编译,但实际上gcc命令依次呼叫了cpp,gcc自己,gas以及ld来进行完整的编译流程,最后生成最终的可执行文件hello。
下面我们看一下分解动作:
cpp  hello.c  >  hello.i
gcc  -S  hello.i
as  hello.s  -o  hello.o
ld  -dynamic-linker  /lib/ld-linux.so.2  /usr/lib/crt1.o  /usr/lib/crti.o  /usr/lib/gcc/i686-linux-gnu/4.4.5/crtbegin.o -L/usr/lib/gcc/i686-linux-gnu/4.4.5 hello.o  -lgcc  -lgcc_eh  -lc  /usr/lib/gcc/i686-linux-gnu/4.4.5/crtend.o  /usr/lib/crtn.o  -o  hello
看完这些步骤有没有晕头转向的感觉呢?对于普通的用户来说,还是让GCC帮我们做这些事情比较好。
对于如何学习使用GCC,可以参考GCC官方的手册,如果大家觉得官方的手册太罗嗦,我这里推荐一本GCC的入门书籍《An Introduction to GCC》,这本书详尽的介绍了GCC的使用方法,内容浅显易懂,很适合初学者。

2. GCC强大的背后

学过编译原理这门课程的同学对下面这副图应该很熟悉,这是经典的编译流程。

GCC作为经典的编译器,自然也是遵循这个教科书流程(实际GCC的处理更复杂点,但本质上是一样的)。我们先简化一下上面这幅图,以中间代码为分界,前面的词法分析、语法分析、语义分析我们把它称之为前端处理,后面的优化和目标代码生成我们称之为后端处理。

试想一下,是否可以为不同的高级语言单独写一个前端,然后为不同的处理器架构单独写一个后端呢?

GCC基本上也是这么实现的,不过不要误会,并没有一个统一的gcc执行程序能够处理如此多的前端和后端,每个语言的编译器都是一个独立的程序(如 C语言的编译器是gcc,C++的编译器是g++),而不同的后端也要对应不同的可执行程序。你可以下载单独的一份GCC源代码,通过不同的 configure来编译自己需要的编译器。

而且,编译器的实现也比上图要复杂的多,前端的主要功能是产生一个可供后端处理的语法树,而语法树结构实际上很难与处理器架构脱钩,这些都是编译器应用中需要解决的问题。

GCC强大的真正原因是什么?是因为它支持了众多的前端和后端吗?这些都不过是一个表象而已。GCC是一款真正自由的编译器,我们可以随时把代码拿 过来修改以实现自己需要的功能。如果你的硬件平台增加了一些指令,而普通的编译器并不能产生这些指令怎么办?在GCC后端添加这些指令吧。如果你觉得C语 言用的不太顺手,想给它添加一些功能怎么办?修改GCC的前端吧。因为有了GCC,我们才拥有这些自由,以及迅速实现自己想法的能力,而这些才是GCC强 大背后的基础。

2010年1月份的时候,Google的Go语言前端被允许进入GCC编译器家族,GCC更加强大了。

3. GCC的多样性

GCC因为其灵活性被应用到了很多领域和系统,从PC上的开发到嵌入式开发,都可以见到GCC的影子。

3.1   PC开发

我们先看看PC。MAC自从投入Intel的怀抱,是否也可以看作是一种PC呢?

Linux

Linux系统应该是GCC的主战场,但也是最没必要去说的一个系统,除了GCC,难道我们还有更好的选择吗?Linux内核、Apache服务器、MySQL数据库,等等一系列伟大的作品都是通过GCC来构建的,GCC可以说是GNU/Linux系统的基石。

MAC OS X

MAC OS X也是GCC的重度用户,其应用程序开发环境Cocoa就是使用的GCC,所以在MAC OS X下开发也是离不开GCC的。

Windows

现在在Windows下开发C/C++程序一般都是用微软的编译器,当年的Borland已经成为传说。但是如果你不想付钱的话,也可以考虑Windows下的GCC。
在Windows下体验GCC最常用的有两种方式:一是在Cygwin下使用GCC,另外一种是使用MinGW。
Cygwin是一个自由软件的集合,最初由Cygnus Solutions开发,目的是在Windows系统上运行类Unix的软件。通过Cygwin编译的程序可以在Windows上运行,但必须使用cygwin.dll。
MinGW(Minimalist GNU for Windows),是将GNU开发工具移植到Windows平台的产物,包括一系列头文件、库和可执行文件,用MinGW开发的程序不需要额外的第三方 DLL就可以直接在Windows上运行。Nokia的图形开发包QT在Windows下就是调用MinGW来编译的。

DOS

在DOS系统下也是可以用GCC的,国内的DOS开发者可能更熟悉Turbo C或者Open Watcom,GCC的DOS版本DJGPP其 实也是32位DOS程序开发的主流环境之一。最初DJGPP的发起人DJ Delorie曾经询问过Richard Stallman,FSF是否考虑过把GCC移植到MS-DOS下,当时Richard的回答是GCC太庞大,而MS-DOS只不过是个16位的操作系 统,所以官方并没有考虑这件事。DJ Delorie并没有因此而退缩,最终给我们带来了这个优秀的开发平台。DJGPP刚开始开发时叫做djgcc,在引入了C++之后改为现在这个名字 (DJ’s GNU Programming Platform)。

3.2   嵌入式开发

对于嵌入式开发领域来说,因为开发板的能力限制,是无法运行编译环境的,这样就需要在PC上通过交叉编译来生成目标可执行程序,GCC的高度灵活性在嵌入式开发上发挥了极大的作用。

Android

看看当今最火的移动平台Android,就是完全用GCC来构建的,请注意,这里指的不是Android应用程序,Android下的应用程序是运行在Dalvik虚拟机上的Java程序。

iOS

Apple的iOS应用程序开发也是用Cocoa来进行,这怎么会离开GCC呢?

MeeGo/Symbian

在Apple和Google的冲击下,Nokia似乎已经日薄西山了,但对于中国国情来说,真的是这样吗?iPhone高高在上,拥有者只会是 少数,Google退出中国之后,Android Market很难在国内有所作为,再加上Nokia手机一贯皮实的口碑,现在的Symbian以及将来的MeeGo的保有量应该不会低。Nokia已经把 Symbian和MeeGo的开发环境统一到QT上了,这里依然是GCC的一亩三分田。
GCC是强大的,但它并不是一个人在战斗,在它的背后站着GNU工具链,包括make,GCC,Binutils,GDB等一系列工具,这些工具之间是相辅相成的,只有把它们组合起来使用才能发挥其最大的威力。

[转载]Qt Model/View 概论(-)、

mikel阅读(1072)

[转载]Qt Model/View 概论(-) – 博水 – 博客园.

Model-View-Controller(MVC), 是从Smalltalk发展而来的一种设计模式,常被用于构建用户界面。它强制性的使应用程序的输入、处理和输出分开。

在Qt中引入了一个MVC的变体—model/view结构。这个结构依然是把数据存储与数据表示进行了分离,它与MVC都基于同样的思想,但 它更简单一些。这种分离使得在几个不同的view上显示同一个数据成为可能,也可以重新实现新的view,而不必改变底层的数据结构。为了更灵活的对用户 输入进行处理,引入了delegate这个概念。它的好处是,数据项的渲染与编程可以进行定制。其具体工作机制如下图:

从上图可看出,Model直接读取数据,View可以直接显示数据,也可以显示经过Delegate处理后的数据。同时用户可以直接通过 Delegate直接编辑数据通过model存入数据文件中。models,views,delegates之间通过信号,槽机制来进行通讯。

工作机制:

传统中View只用来负责数据显示,因此在view创建时并不需要model只有当其显示信息的时候才会用到model。model通过QAbstractItemMode提供统一的接口,view会调用model的index来获得一个indexmodel,然后再通过indexmodel来获得想要得到的data.

model负责从数据集里面选取合适的数据提供给View因此model可以充当数据的选择和过滤器,另一方面可以接收Delegate发回的信息更新数据集中的数据信息。

Deletegate是由View层通过各种signal或者event事件引发,实现数据的更改并通过Model写入数据集中。

[转载]流行数据库SQL差异分析之“限制结果集行数”

mikel阅读(1053)

[转载][笔记]流行数据库SQL差异分析之“限制结果集行数” – Astar之学习.NET路程 – 博客园.

前言:近期所参与的项目共涉及了三种数据库,SQLServer、Oracle、MySQL。所以就找来这些资料,并记录为笔记供以后参考。

限制结果集行数

在进行数据检索的时候需要只检索结果集中的部分行,比如说“检索成绩排前三名的学生”、“检索工资水平排在第3位到第7位的员工信息”,这种功能被 称为“限制结果集行数”。虽然主流的数据库系统中都提供了限制结果集行数的方法,但是无论是语法还是使用方式都存在着很大的差异。即使是同一个数据库系统 的不同版本(比如MSSQLServer2000和MSSQLServer2005)也存在着一定的差异。

MYSQL

MYSQL中提供了LIMIT关键字用来限制返回的结果集,LIMIT放在SELECT语句的最后位置,语法为“LIMIT首行行号,要返回的结果集的最大数目”,比如下面的SQL语句将返回按照工资降序排列的从第二行开始(行号从0开始)的最多五条记录:

SELECT * FROM T_Employee ORDER BYFSalary DESC LIMIT 2,5      //返回第二行到第七行

SELECT * FROM T_Employee ORDER BYFSalary DESC LIMIT 0,5      //返回前五条

SQLServer2000

SQLServer2000中提供了TOP关键字用来返回结果集中的前N条记录,其语法为”SELECT TOP限制结果集数目字段列表SELEC下语句其余部分”,比如下面的SQL语句用来检索工资水平排在前五位(按照工资从高到低)的员工信息:

select top 5 * from T_Employee orderby FSalary Desc

SQLServer2000没有直接提供返回提供“检索从第5行开始的10条数据”、“检索第五行至第十二行的数据”等这样的取区间范围的功能,不 过可以采用其他方法来变通实现,最常使用的方法就是用子查询,比如要实现检索按照工资从高到低排序检索从第六名开始一共三个人的信息,那么就可以首先将前 五名的主键取出来,在检索的时候检索排除了这五名员工的前三个人,SQL如下:

SELECT top 3 * FROM T_Employee

WHERE FNumber NOT IN

(SELECT TOP 5 FNumber FROM T_EmployeeORDER BY FSalary DESC)

ORDER BY FSalary DESC

SQLServer2005

SQLServer2005兼容几乎所有的SQLServer2000的语法,所以可以使用上面提到的方式来在SQLServer2005中实现限 制结果集行数,不过SQLServer2005提供了新的特性来帮助更好的限制结果集行数的功能。这个新特性就是窗口函数 ROW_NUMBER(),ROW_NUMBER()函数可以计算每一行数据在结果集中的行号(从1开始计数),其使用语法如下:

ROW_NUMBEROVER(排序规则)

比如执行下而的SOL语句:

SELECTROW_NUMBER() OVER(ORDER BY FSalary),FNumber,FName,FSalary,FAge FROM T_Employee //返回排序过的行号

ROW_NUMBER()不能用在WHERE语句中,可以用子查询来返回第3行到第5行的数据:

SELECT * FROM

(

SELECT ROW_NUMBER() OVER(ORDER BY FSalary DESC)AS rownum,

FNumber,FName,FSalary,FAge FROMT_Employee

) AS a

WHERE a.rownum>=3 AND a.rownum<=5

Oracle

Oracle中支持窗口函数ROW_NUMBER(),其用法和SQLServer2005中相同,比如执行下面的SQL语句:

SELECT * FROM

(

SELECT ROW_NUMBER() OVER(ORDER BYFSalary DESC) row_num,

FNumber,FName,FSalary,FAge FROMT_Employee

) a

WHERE a.row_num>=3 AND a.row_num<=5

注意:rownum在Oracle中为保留字,所以这里将SQLServer2005中用到的rownum替换为row_num,Oracle中定 义表别名的时候不能使用AS关键字,所以这里也去掉了AS,Oracle支持标准的函数ROW_NUMBER(),不过Oracle中提供了更方便的特性 用来计算行号,也就在Oracle中可以无需自行计算行号,Oracle为每个结果集都增加了一个默认的表示行号的列,这个列的名称为rownum,比如 执行下面的SQL语句:

SELECT rownum,FNumber,FNas.e,FSalary,FAgeFROM T _Employee

执行完毕我们就能在输出结果中看到下面的执行结果:

使用rownum可以很轻松的取得结果集中前N条的数据行,比如执行下面的SQL语句可以得到按工资从高到底排序的前6名员工的信息:

SELECT * FROM _Employee WHERE rownum<=6 ORDER BY FSalary Desc

下面的SQL就可以非常容易的实现“按照工资从高到低的顺序取出第三个到第五个员工信息”的功能了:

SELECTrownum,FNumber,FName,FSalary,FAge FROM T_Employee

WHERE rownum BETWEEN 3 AND 5

ORDER BY FSalary DESC                     //执行结果为空,结论看下面。

回顾一下rownum的含义:rownum为结果集中每一行的行号(从1开始计数)。对于下面的SQL:

SELECT * FROM_Employee WHERE rownum<=6 ORDER BY FSalary Desc

当进行检索的时候,对于第一条数据,其rownum为1,因为符合“WHERErownum<=6″所以被放到了检索结果中,当检索到第二条数据的时候,其rownum为2,因为符合“WHERE

rownum<=6″所以被放到了检索结中。依次类推,直到第七行。所以这句SQL语句能够实现“按照工资从高到低的顺序取出第三个到第五个员工信息”的功能。

而对于这句SQL语句:

SELECTrownum,FNumber,FName,FSalary,FAge FROM T_Employee

WHERE rownum BETWEEN 3 AND 5

ORDER BY FSalary DESC

当进行检索的时候,对于第一条数据,其rownum为1,因为不符合“WHERErownum BETWEEN 3 AND 5″,所以没有被放到了位索结果中,当检索到第二条数据的时候,因为第一条数据没有放到结果集中,所以第二条数据的rownum仍然为1,而不是我们想像 的2,所以因为不符合‘WHERErownum<=6″,没有被放到了植索结果中,当检索到第三条数据的时候,因为第一、二条数据没有放到结果集 中,所以第三条数据的rownum仍然为1,而不是我们想像的3,所以因为不符合“WHERErownum<=6″,没有被放到了检索结果中。依此 类推,这样所有的数据行都没有被放到结果集中。

因此如果要使用rownum来实现“按照工资从高到低的顺序取出第三个到第五个员工信息”的功能,就必须借助于窗口函数ROW_NUMBER()。

DB2

DB2中支持窗口函数ROW_NUMBER(),其用法和SQLServer2005以及Oracle中相同,比如执行下面的SQL语句:

SELECT * FROM

(

SELECT ROW_NUMBER() OVER(ORDER BYFSalary DESC) row_num,

FNumber,FName,FSalary,FAge FROMT_Employee

) a

WHERE a.row_num>=3 ANDa.row_num<=5

除此之外,DB2还提供了FETCH关键字用来提取结果集的前N行,其语法为“FETCH FIRST 条数 ROWS ONLY”,比如我们执行下面的SQL语句可以得到按工资从高到底排序的前6名员工的信息:

SELECT * FROM T_Employee ORDER BYFSalary Desc FETCH FIRST 6 ROWSONLY

注意:FETCH子句要放到ORDERBY语句的后面。

DB2没有直接提供返回提供“检索从第5行开始的10条数据”、“检索第五行至第十二行的数据”等这样的取区间范围的功能,不过可以采用其他方法来 变通实现,最常使用的方法就是用子查询,比如要实现检索按照工资从高到低排序检索从第六名开始一共三个人的信息,那么就可以首先将前五名的主键取出来,在 检索的时候检索排除了这五名员工的前三个人。

SELECT * FROM T_Employee

WHERE FNumber NOT IN

(

SELECT FNumber FROM T_Employee

ORDER BY FSalary DESC

FETCH FIRST 5 ROWS ONLY

)

ORDER BY FSalary DESC

FETCH FIRST 3 ROWS ONLY

数据库分页

实现数据库分页的核心技术就是“限制结果集行数“,假设每一页显示的数据条数为PageSize,当前页数(从0开始技术)为 Currentlndex,那么我们只要查询从第PageSize*CurrentIndex开始的PageSize条数据得到的结果就是当前页中的数 据,当用户点击【上一页】按钮的时候,将Currentlndex设置为Currentlndex-1,然后重新检索。当用户点击【下一页】按钮的时候, 将Currentlndex设置为Currentlndex+1,然后重新检索。当用户点击【首页】按钮的时候,将Currentlndex设置为0,然 后重新检索。当用户点击【首页】按钮的时候,将Currentlndex设置为“总条数/PageSize”,然后重新检索。

[转载]HttpUtility.UrlEncode

mikel阅读(1035)

[转载]HttpUtility.UrlEncode 在 Encode 的时候, 将空格转换成加号(‘+’), 在 Decode 的时候将加号转为空格, 但是浏览器是不能理解加号为空格的, 所以如果文件名包含了空格, 在浏览器下载得到的文件, 空格就变成了加号 – 爱晚红枫技术部广东分部 – 博客园.

在用 ASP.NET 开发页面的时候, 我们常常通过 System.Web.HttpUtility.UrlEncode 和 UrlDecode 在页面间通过 URL 传递参数. 成对的使用 Encode 和 Decode 是没有问题的.

但是, 我们在编写文件下载的页面的时候, 常常用如下方法来指定下载的文件的名称:
Response.AddHeader(
Content-Disposition,attachment; filename=
+ HttpUtility.UrlEncode(fileName, Encoding.UTF8));
之所以转换成 UTF8 是为了支持中文文件名.

这时候问题就来了, 因为 HttpUtility.UrlEncode 在 Encode 的时候, 将空格转换成加号(
+), 在 Decode 的时候将加号转为空格, 但是浏览器是不能理解加号为空格的, 所以如果文件名包含了空格, 在浏览器下载得到的文件, 空格就变成了加号.

一个解决办法是, 在 HttpUtility 的 UrlEncode 之后, 将
+ 替换成 %20( 如果原来是 + 则被转换成 %2b ) , 如:
fileName
= HttpUtility.UrlEncode(fileName, Encoding.UTF8);
fileName
= fileName.Replace(+, %20);
不明白微软为什么要把空格转换成加号而不是
%20. 记得 JDK 的 UrlEncoder 是将空格转换成 %20的.
经检查, 在 .Net
2.0 也是这样.

HttpUtility.UrlEncode 在 Encode 的时候, 将空格转换成加号(+), 在 Decode 的时候将加号转为空格, 但是浏览器是不能理解加号为空格的, 所以如果文件名包含了空格, 在浏览器下载得到的文件, 空格就变成了加号

<html>
<head>
<meta http-equiv=’Content-Type’ content=’text/html; charset=gb2312′>
<title>URL解码(Decode)/编码(Encode)</title>
</head>
<body>
<center><font color=green size=+2>URL解码(Decode)/编码(Encode)</font><br>
需要解码的字符串:
<TEXTAREA ID=”String1″ ROWS=”10″ COLS=”30″></TEXTAREA> 解码后的字符串:<TEXTAREA ID=”String2″ ROWS=”10″ COLS=”30″></TEXTAREA><br>
需要编码的字符串:
<TEXTAREA ID=”String3″ ROWS=”10″ COLS=”30″></TEXTAREA> 编码后的字符串:<TEXTAREA ID=”String4″ ROWS=”10″ COLS=”30″></TEXTAREA><br>
<INPUT TYPE=”button” ID=”Decode” value=”解码(Decode)” onClick=”JavaScript:String2.value=decodeURI(String1.value);”>
<INPUT TYPE=”button” ID=”Encode” value=”编码(Encode)” onClick=”JavaScript:String4.value=encodeURI(String3.value);”>
</center>
</body>
</html>

url传递中文的解决方案总结

1.设置web.config文件。(我不喜欢设置成这样)
<system.web>
……
<globalization requestEncoding=”gb2312″ responseEncoding=”gb2312″ culture=”zh-CN” fileEncoding=”gb2312″ />
……
</system.web>

或者:
aspx文件中:
<meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>

2.传递中文之前,将要传递的中文参数进行编码,在接收时再进行解码。
>> 进行传递
string Name = “中文参数”;
Response.Redirect(“B.aspx?Name=”+Server.UrlEncode(Name));

>> 进行接收
string Name = Request.QueryString[“Name”];
Response.Write(Server.UrlDecode(Name));
或者:

NavigateURL='<%# “WebForm2.aspx?Singer=” + HttpUtility.UrlEncode(“中国人”, System.Text.Encoding.GetEncoding(“GB2312”)) %>’


3.如果是从 .HTML 文件向 .Aspx 文件进行传递中文参数的话(即不从后台用 Redirect()方法进行 Url 转换)。一样要将传递的中文参数进行编码,在接收时再进行解码。
>> 进行传递
<script language=”JavaScript”>
function GoUrl()
{
var Name = “中文参数”;
location.href = “B.aspx?Name=”+escape(Name);
}
</script>
<body onclick=”GoUrl()”>
>> 进行接收
string Name = Request.QueryString[“Name”];
Response.Write(Server.UrlDecode(Name));

一般来说。设置web.config文件就可以了。但是如果你用 JavaScript 调用 webservice 方法的话(往webservice里面传递中文参数)。设置 web.config 文件好象无效。

————————————————————
在html中实现编解码:

<script language=”javascript”>
function openUrl(src)
{
var strUrl=escape(src);
window.open(strUrl);
}

function change_url(src)
{
document.location.href=escape(src);
}

</script>

在新窗口保存
<a href=’javascript:openUrl(“css/20040603123628交易中心烟叶网上集中交易系统合同.doc”);’ >20040603123628交易中心网上集中交易系统合同</a>

当前位置保存,无闪烁。
<a href=”#” onclick=javascript:change_url(“css/20040603123628交易中心烟叶网上集中交易系统合同.doc”)>20040603123628交易中心网上集中交易系统合同</a>

注意:路径中的斜线是:“/”,而不是“\”,否则也不行啊。

----------------------------------------
前一阵遇到在做.net Web开发时,碰到一个很奇怪的问题,就是Url中,如果将中文字符作为参数值传递时,QueryString得到的值可能会出错。简单的说,比如下面这个Url:
UrlParmTest.aspx?parm1=中国&parm2=中国人
在Request.QueryString时,parm1和parm2得到都是”中国”,显然出现了问题,可是在某些情况下却是正常的。

如果请求不是直接通过URL,而使用Response.Redirect在服务器端操作,没有遇到过类似的问题。

当时我想中文是双字节编码,可能传递的时候就是有不确定性,还是用英文好。

可是为什么在Server端Redirect就是正常的,问题在哪里呢?


如果在.cs文件中设置中文参数,请在中文参数外使用Server.UrlEncode(“中文”)对中文进行Encode
如果在.aspx文件中设置,请使用<%=Server.UrlEncode(“中文”)%>进行Encode
在QueryString时,不用再进行Decode,可以获得正常的中文字符串

下面是给出的一些解释:
UrlEncode把一些多字节字符转换成url里允许的单字节字符,本来浏览器就会自动做的,但是目前确实存在一些问题,所以自己再Encode一下,在接受端会自动对Url进行Decode。

我想Response.Redirect可能可以确保作Encode的工作,所以没有问题。

JavaScript Base64编码和解码,实现URL参数传递

为什么需要对参数进行编码?相信有过开发的经验的广大程序员都知道,在Web中,若是直接在Url地址上传递参数值,若是中文,或者+等什么的就会出现乱码现象,若是数字或者英文的好象没有什么问题,简言之,传递过来的参数是需要进行编码的。
在这里,也许有人会说,为什么不直接用Server.UrlDecode和Server.UrlEncode这两个来进行编码和解码的操作呢?

的确,这两个服务器端对象很好使用,用起来也很方便,但是,若在客户端是HTML的Input,查询的时候页面是HTML或者其他的,反正不是.NET的,那这个对象还可以用吗?


我现在就遇到这样的问题,查询的东东放在页面,而且那个页面我根本不想让他是.aspx结尾的,哈,感觉HTML的挺不错,而且里面的控件也是用HTML对象的。

下面先来看两个函数,UTF16转UTF8和UTF8转Utf16的。
function utf16to8(str)
{
var
out, i, len, c;

out = “”;
len
= str.length;
for(i = 0; i < len; i++) {
c
= str.charCodeAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
out += str.charAt(i);
}
else if (c > 0x07FF) {
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}
else {
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}

}

return out;
}


function utf8to16(str)
{
var
out, i, len, c;
var char2, char3;

out = “”;
len
= str.length;
i
= 0;
while(i < len) {
c
= str.charCodeAt(i++);
switch(c >> 4)
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += str.charAt(i1);
break;
case 12: case 13:
// 110x xxxx   10xx xxxx
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx  10xx xxxx  10xx xxxx
char2 = str.charCodeAt(i++);
char3
= str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2
& 0x3F) << 6) |
((char3
& 0x3F) << 0));
break;
}

}


return out;
}


那么为什么需要进行转化呢?因为在JavaScript中获得的中文字符是用UTF16进行编码的,和我们统一的页面标准格式UTF
8可不一样哦,所以需要先进行转化,上面的函数UTF16到UTF8,然后再进行Base64的编码。

下面是关于Js进行Base64编码和解码的相关操作:

var base64EncodeChars
= ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/;
var base64DecodeChars
= new Array(
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 1, 1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 1, 1, 1, 1, 1, 1,
1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 1, 1, 1, 1, 1,
1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 1, 1, 1, 1, 1);
//客户端Base64编码
function base64encode(str) {
var
out, i, len;
var c1, c2, c3;

len
= str.length;
i
= 0;
out = “”;
while(i < len) {
c1
= str.charCodeAt(i++) & 0xff;
if(i == len)
{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
out += ==;
break;
}

c2
= str.charCodeAt(i++);
if(i == len)
{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
out += =;
break;
}

c3
= str.charCodeAt(i++);
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
out += base64EncodeChars.charAt(c3 & 0x3F);
}

return out;
}

//客户端Base64解码
function base64decode(str) {
var c1, c2, c3, c4;
var i, len,
out;

len
= str.length;
i
= 0;
out = “”;
while(i < len) {
/* c1 */
do {
c1
= base64DecodeChars[str.charCodeAt(i++) & 0xff];
}
while(i < len && c1 == 1);
if(c1 == 1)
break;

/* c2 */
do {
c2
= base64DecodeChars[str.charCodeAt(i++) & 0xff];
}
while(i < len && c2 == 1);
if(c2 == 1)
break;

out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));

/* c3 */
do {
c3
= str.charCodeAt(i++) & 0xff;
if(c3 == 61)
return out;
c3
= base64DecodeChars[c3];
}
while(i < len && c3 == 1);
if(c3 == 1)
break;

out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));

/* c4 */
do {
c4
= str.charCodeAt(i++) & 0xff;
if(c4 == 61)
return out;
c4
= base64DecodeChars[c4];
}
while(i < len && c4 == 1);
if(c4 == 1)
break;
out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
}

return out;
}


这样传递过去的值就可以在服务器端解码操作了。
下面是C#的Base64加码和解码的相关类:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace CNVP.Base64
{
/// <summary>
/// MyBase64 的摘要说明
/// </summary>

public class MyBase64
{
public MyBase64()
{
//
// TODO: 在此处添加构造函数逻辑
//
}

/// <summary>
/// 服务器端Base64编码
/// </summary>
/// <param name=”data”></param>
/// <returns></returns>

public string base64Encode(string data)
{
try
{
byte[] encData_byte = new byte[data.Length];
encData_byte
= System.Text.Encoding.UTF8.GetBytes(data);
string encodedData = Convert.ToBase64String(encData_byte);
return encodedData;
}

catch (Exception e)
{
throw new Exception(Error in base64Encode + e.Message);
}

}

/// <summary>
/// 服务器端Base64解码
/// </summary>
/// <param name=”data”></param>
/// <returns></returns>

public string base64Decode(string data)
{
try
{
System.Text.UTF8Encoding encoder
= new System.Text.UTF8Encoding();
System.Text.Decoder utf8Decode
= encoder.GetDecoder();
byte[] todecode_byte = Convert.FromBase64String(data);
int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
char[] decoded_char = new char[charCount];
utf8Decode.GetChars(todecode_byte,
0, todecode_byte.Length, decoded_char, 0);
string result = new String(decoded_char);
return result;
}

catch (Exception e)
{
throw new Exception(Error in base64Decode + e.Message);
}

}

}

}


var Keyword
=base64encode(utf16to8(document.all.Keyword.value));
Keyword
=Keyword.replace(+,%2B);//替换+,否则在服务器解码的时候会出错
服务器端使用以下代码调用:
CNVP.Base64.MyBase64 base64
= new CNVP.Base64.MyBase64();
Keyword
=base64.base64Decode(Keyword);


[转载]用ISAPI Filter设置HttpOnly属性

mikel阅读(1294)

[转载]用ISAPI Filter设置HttpOnly属性 – tonyqus.cn – 博客园.

作者:Tony Qu

说到ISAPI很多人会觉得很陌生,因为如果你是做ASP.NET开发的话,ISAPI的方式已经过时,取而代之的是HttpHandler和 HttpModule,说到这两个东西很多人估计明白了,ISAPI可以说是早期实现请求拦截和处理的唯一途径,只是随着ASP.NET的流行,渐渐淡出 了开发人员的视野。

此文的开发场景是这样的,我们公司使用古老的ASP语言,但是ASP的Response.Cookies属性中没有HttpOnly(但.Net的 Cookie对象是有HttpOnly属性的),有帖子说可以利用Path属性来设置HttpOnly,可以这么做是因为我们在页面中设置cookie值 的动作都会被转换成Set-Cookie头,如下

Set-Cookie: user=t=bfabf0b1c1133a822; path=/
但如果要让cookie变成HttpOnly,就需要用如下格式:
Set-Cookie: user=t=bfabf0b1c1133a822; path=/;HttpOnly

理论上讲设置Path是完全可行的,因为说白了就是在原来Path的基础上增加;HttpOnly,

但经试验表明这行不通,比如我用下面的代码

Response.Cookies(“user”).Path+=”;HttpOnly”;

得到的结果却是

Set-Cookie: user=t=bfabf0b1c1133a822; path=/3B%;HttpOnly

这显然是不行的,所以我们不得不考虑用ISAPI来实现。

ISAPI基础

首先,请不要把ISAPI Extension和ISAPI Filter混为一谈,这两个东西虽然只差一个字,但却完全是两样东西,所提供的接口是完全不一样的。ISAPI Extension是一个类似页面的dll,你可以对它做post或get提交,如http://localhost/abc.dll?a=1, 从严格意义上讲它没有拦截的功能,和cgi差不多。而ISAPI Filter则是具有过滤功能的,你可以在IIS网站的属性中添加需要加载的ISAPI Filter,例如asp.net的实现也使用了一个ISAPI Filter,叫做aspnet_filter.dll。

ISAPI Filter说到底就是一个DLL,它有两个主要的接口:GetFilterVersion和HttpFilterProc,如下所示:

BOOL WINAPI __stdcall GetFilterVersion(HTTP_FILTER_VERSION *pVer)
{
 /* Specify the types and order of notification */

 pVer->dwFlags = (SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_AUTHENTICATION |
 SF_NOTIFY_URL_MAP | SF_NOTIFY_SEND_RAW_DATA | SF_NOTIFY_LOG | SF_NOTIFY_END_OF_NET_SESSION );

 pVer->dwFilterVersion = HTTP_FILTER_REVISION;

 strcpy(pVer->lpszFilterDesc, "Upper case conversion filter, Version 1.0");

 CFile myFile("c:\\mylist.html", CFile::modeCreate | CFile::modeWrite);
 myFile.SeekToEnd();
 char myText[40];
 strcpy(myText,"<B>GetFilterVersion </B><BR><BR>");
 myFile.Write(myText,strlen(myText));
 myFile.Close();

 return TRUE;
}

GetFilterVersion不仅仅是用来获得Filter版本这么简单,它可以用来过滤需要触发的事件,这些事件的详细信息你可以参考http://msdn.microsoft.com/en-us/library/ms825957.aspx。请注意,这里做的是或操作,而不是与操作,学过数理逻辑的应该明白这个是干嘛用的,就是值的叠加,说的再直接点,EventA|EventB就是我既要Event A也要Event B。

DWORD WINAPI __stdcall HttpFilterProc(HTTP_FILTER_CONTEXT *pfc, DWORD NotificationType, VOID *pvData)
{
 CFile myFile("c:\\mylist.html", CFile::modeWrite);
 myFile.SeekToEnd();

 switch (NotificationType) {

 case SF_NOTIFY_ACCESS_DENIED :

 myFile.Write("SF_NOTIFY_ACCESS_DENIED<BR>",strlen("SF_NOTIFY_ACCESS_DENIED<BR>"));
 break;

 case SF_NOTIFY_AUTH_COMPLETE :

 myFile.Write("SF_NOTIFY_AUTH_COMPLETE<BR>",strlen("SF_NOTIFY_AUTH_COMPLETE<BR>"));
 break;

 case SF_NOTIFY_AUTHENTICATION :

 myFile.Write("SF_NOTIFY_AUTHENTICATION<BR>",strlen("SF_NOTIFY_AUTHENTICATION<BR>"));
 break;

 case SF_NOTIFY_END_OF_NET_SESSION :

 myFile.Write("SF_NOTIFY_END_OF_NET_SESSION<BR>",strlen("SF_NOTIFY_END_OF_NET_SESSION<BR>"));
 break;

 case SF_NOTIFY_END_OF_REQUEST :

 myFile.Write("SF_NOTIFY_END_OF_REQUEST<BR>",strlen("SF_NOTIFY_END_OF_REQUEST<BR>"));
 break;

 case SF_NOTIFY_LOG :

 myFile.Write("SF_NOTIFY_LOG<BR>",strlen("SF_NOTIFY_LOG<BR>"));
 break;

 case SF_NOTIFY_PREPROC_HEADERS :

 myFile.Write("SF_NOTIFY_PREPROC_HEADERS<BR>",strlen("SF_NOTIFY_PREPROC_HEADERS<BR>"));
 break;

 case SF_NOTIFY_READ_RAW_DATA :

 myFile.Write("SF_NOTIFY_READ_RAW_DATA<BR>",strlen("SF_NOTIFY_READ_RAW_DATA<BR>"));
 break;

 case SF_NOTIFY_SEND_RAW_DATA :

 myFile.Write("SF_NOTIFY_SEND_RAW_DATA<BR>",strlen("SF_NOTIFY_SEND_RAW_DATA<BR>"));
 break;

 case SF_NOTIFY_SEND_RESPONSE :

 myFile.Write("SF_NOTIFY_SEND_RESPONSE<BR>",strlen("SF_NOTIFY_SEND_RESPONSE<BR>"));
 break;

 case SF_NOTIFY_URL_MAP :

 myFile.Write("SF_NOTIFY_URL_MAP<BR>",strlen("SF_NOTIFY_URL_MAP<BR>"));
 break;

 case SF_NOTIFY_SECURE_PORT :

 myFile.Write("SF_NOTIFY_SECURE_PORT<BR>",strlen("SF_NOTIFY_SECURE_PORT<BR>"));
 break;

 case SF_NOTIFY_NONSECURE_PORT :

 myFile.Write("SF_NOTIFY_NONSECURE_PORT<BR>",strlen("SF_NOTIFY_NONSECURE_PORT<BR>"));
 break;

 default :
 break;
 }


 myFile.Close();

 return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

HttpFilterProc是主要入口,相当于Console程序中的main。上面这段代码是在这些事件触发时写入一个日志,这样便于调试。

说到这里我们来了解下通常开发一个ISAPI Filter的流程。

a. 获得一个现有的ISAPI Filter项目,当做模板,这个网上很多,google一下就有了。

b. 修改GetFilterVersion中的dwFlags的值来决定需要哪些事件

c. 修改HttpFilterProc中的case分支,删除不需要的事件

d. 在需要处理的事件中写代码。

有一件事必须提醒大家,在写ISAPI时,你千万不要忘了把这两个接口暴露出去,也就是定义DLL的EXPORTS,如下:

LIBRARY "isapi_sample"
EXPORTS
HttpFilterProc
GetFilterVersion

事件的执行顺序

在ASP.NET中我们有Page Life Cycle,ISAPI Filter也是如此,这些时间的执行顺序可以在 http://msdn.microsoft.com/en-us/library/ms524855(VS.90).aspx 上找到,下面的事件就是按执行顺序排列的。

SF_NOTIFY_READ_RAW_DATA

SF_NOTIFY_PREPROC_HEADERS

SF_NOTIFY_URL_MAP

SF_NOTIFY_AUTHENTICATION

SF_NOTIFY_AUTH_COMPLETE

SF_NOTIFY_SEND_RESPONSE

SF_NOTIFY_SEND_RAW_DATA

SF_NOTIFY_END_OF_REQUEST

SF_NOTIFY_LOG

SF_NOTIFY_END_OF_NET_SESSION

通过分析,我们知道要想获得Set-Cookie header必须在ASP把页面处理完之后,因为ASP页面代码有可能会设置Cookie值,所以SF_NOTIFY_PREPROC_HEADERS事 件并不合适,因为它是在收到请求后,处理页面前触发的,我们需要的是在页面处理完,发送前触发的事件,所以 SF_NOTIFY_SEND_RESPONSE最合适。在下一节我们将讲解如何在该事件中添加处理代码。

如何遍历Set-Cookie

HttpFilterProc函数的第三个参数VOID *pvData是对应事件的数据,为了获得header里面的数据,我们会把它转换成PHTTP_FILTER_PREPROC_HEADERS,因为我们先要把Set-cookie的数据读出来,然后才能处理。

代码如下:

 1: case SF_NOTIFY_SEND_RESPONSE :
 2: pPH = (PHTTP_FILTER_PREPROC_HEADERS)pvData;
 3: pPH->GetHeader(pfc, "Set-Cookie:", szBuffer, &dwSize);
 4:
 5: cookieNum=sizeof(strtok(szBuffer,","));
 6: if(cookieNum>0)
 7: {
 8: //handle the cookies that are read from header
 9: ...
 10: }

这里的szBuffer就是我们获得的Set-Cookie的字符串,这里要讲一下Set-Cookie到底是啥,因为很多程序员对Set-Cookie的含义和表示形式不是特别了解。

每次我们在页面中设置Cookie值,无论是ASP还是ASP.NET,都会把设置的操作转换为Set-Cookie中的一段字符串,如果你使用 Fiddler或者HttpFox跟踪这些请求的话,你会发现头里面有一项就是Set-Cookie项,这项仅在有设置Cookie的操作时才会有。另 外,Set-Cookie中的每一个Cookie字符串使用逗号分隔开的,如下

Set-Cookie: test1=a; path=/, test2=b; path=/

这里设置了名为test1和test2的两个cookie值,单个cookie的属性之间使用分号分隔的。也正是因为如此,这段代码中使用 strtok来获得字符串中每一段用逗号分隔的cookie字符串,这里的cookieNum表示Set-cookie中cookie字符串的总数(注 意,不是字符的总数)。

一旦我们获得了每一个cookie的字符串,我们就可以把;HttpOnly附加到这些字符串的最后,并最终把字符串拼起来组成Set-Cookie字符串,关于如何做字符串拼接本文就不多讲了,这完全是C++实现的问题。

如何覆盖Set-Cookie字符串

这里的设置cookie和我们平时在代码里做的可不太一样,因为我们要直接修改请求中的Set-Cookie,之所以是修改而不是增加新的Set-Cookie,是因为Set-Cookie在请求的header中只能有一个,

HTTP_FILTER_SEND_RESPONSE * pResponse=(HTTP_FILTER_SEND_RESPONSE *)pvData;
BOOL fServer = TRUE;
fServer = pResponse->SetHeader(pfc, "Set-Cookie:",szHeader);

上面的代码把pvData转换成HTTP_FILTER_SEND_RESPONSE类型,这样我们就可以对Response进行操作,并通过调用它的SetHeader方法来设置Set-Cookie header。

完整代码下载:isapi_sample.zip (VC6项目),最主要的是MyISAPI.cpp和MyISAPI.def文件,其他都是工程文件。

[转载]重构:代码之丑(一)

mikel阅读(1214)

[转载]代码之丑(一) – 梦想风暴 – 博客大巴.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/80612297.html

诸位看官,上代码:
if (0 == iRetCode) {
this->SendPeerMsg(“000”, “Process Success”, outRSet);
} else {
this->SendPeerMsg(“000”, “Process Failure”, outRSet);
}

乍一看,这段代码还算比较简短。那下面这段呢?
if(!strcmp(pRec->GetRecType(), PUB_RECTYPE::G_INSTALL)) {
CommDM.jkjtVPDNResOperChangGroupInfo(
const_cast(CommDM.GetProdAttrVal(“vpdnIPAddress”,
&(pGroupSubs->m_ProdAttr))),
true);
} else {
CommDM.jkjtVPDNResOperChangGroupInfo(
const_cast(CommDM.GetProdAttrVal(“vpdnIPAddress”,
&(pGroupSubs->m_ProdAttr))),
false);
}

看出来问题了吗?经过仔细的对比,我们发现,对于如此华丽的代码,if/else的执行语句真正的差异只在于一个参数。第一段代码,二者的差异只是发送的消息,第二段代码,差异在于最后那个参数。

看破这个差异之后,新的写法就呼之欲出了,以第一段代码为例:
const char* msg = (0 == iRetCode ? “Process Success” : “Process Failure”);
this->SendPeerMsg(“000”, msg, outRSet);

为了节省篇幅,我选择了条件表达式。我知道,很多人不是那么喜欢它。如果if/else依旧是你的大爱,勇敢追求去吧!

由这段代码调整过程,我们得出一个简单的规则:

  • 让判断条件做真正的选择。

这里判断条件真正判断的内容是消息的内容,而不是消息发送的过程。经过我们的调整,得到消息内容和和发送消息的过程严格分离开来。

消除了代码中的冗余,代码也更容易理解,同时,给未来留出了可扩展性。如果将来iRetCode还有更多的情形,我们只要在消息获取的时候进行调整就好了。当然,封装成一个函数是一个更好的选择,这样代码就变成了:
this->SendPeerMsg(“000”, peerMsgFromRetCode(iRetCode), outRSet);

至于第二段代码的调整,留给你练手了。

这样丑陋的代码是如何从众多代码中脱颖而出的呢?很简单,只要看到,if/else两个执行块里面的内容相差无几,需要我们人工比字符寻找差异,恭喜你,你找到它了。

[转载]重构:代码之丑(二)

mikel阅读(1127)

[转载]代码之丑(二) – 梦想风暴 – 博客大巴.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/81144620.html

这是一个长长的判断条件:
if ( strcmp(rec.type, “PreDropGroupSubs”) == 0
|| strcmp(rec.type, “StopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubs”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “StopUseGroupSubs”) == 0
|| strcmp(rec.type, “PreDropGroupSubsCancel”) == 0)

之所以注意到它,因为最后两个条件是最新修改里面加入的,换句话说,这不是一次写就的代码。单就这一次而言,只改了两行,这是可以接受的。但这是遗留代码。每次可能只改了一两行,通常我们会不只一次踏入这片土地。经年累月,代码成了这个样子。

这并非我接触过的最长的判断条件,这种代码极大的开拓了我的视野。现在的我,即便面对的是一屏无法容纳的条件,也可以坦然面对了,虽然显示器越来越大。

其实,如果这个判断条件是这个函数里仅有的东西,我也就忍了。遗憾的是,大多数情况下,这只不过是一个更大函数中的一小段而已。

为了让这段代码可以接受一些,我们不妨稍做封装:
bool shouldExecute(Record& rec) {
return (strcmp(rec.type, “PreDropGroupSubs”) == 0
|| strcmp(rec.type, “StopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubs”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “StopUseGroupSubs”) == 0
|| strcmp(rec.type, “PreDropGroupSubsCancel”) == 0);
}

if (shouldExecute(rec)) {

}

现在,虽然条件依然还是很多,但和原来庞大的函数相比,至少它已经被控制在一个相对较小的函数里了。更重要的是,通过函数名,我们终于有机会说出这段代码判断的是什么了。

提取函数把这段代码混乱的条件分离开来,它还是可以继续改进的。比如,我们把判断的条件进一步提取:
bool shouldExecute(Record& rec) {
static const char* execute_type[] = {
“PreDropGroupSubs”,
“StopUserGroupSubsCancel”,
“QFStopUserGroupSubs”,
“QFStopUserGroupSubsCancel”,
“QZStopUserGroupSubs”,
“QZStopUserGroupSubsCancel”,
“SQStopUserGroupSubs”,
“SQStopUserGroupSubsCancel”,
“StopUseGroupSubs”,
“PreDropGroupSubsCancel”
};

int size = ARRAY_SIZE(execute_type);
for (int i = 0; i < size; i++) {
if (strcmp(rec.type, execute_type[i]) == 0) {
return true;
}
}

return false;
}

这样的话,再加一个新的type,只要在数组中增加一个新的元素即可。如果我们有兴趣的话,还可以进一步对这段代码进行封装,把这个type列表变成声明式,进一步提高代码的可读性。

发现这种代码很容易,只要看到在长长的判断条件,就是它了。要限制这种代码的存在,我们只要以设定一个简单的规则:

  • 判断条件里面不允许多个条件的组合

在实际的应用中,我们会把“3”定义为“多”,也就是如果有两个条件的组合,可以接受,如果是三个,还是改吧!

虽然通过不断调整,这段代码已经不同于之前,但它依然不是我们心目中的理想代码。出现这种代码,往往意味背后有更严重的设计问题。不过,它并不是这里讨论的内容,这里的讨论就到此为止吧!

sinojelly在《代码之丑(二) 》的评论里问了个问题,“把这个type列表变成声明式”,什么样的声明式?

好吧!我承认,我偷懒了,为了省事,一笔带过了。简单理解声明式的风格,就是把描述做什么,而不是怎么做。一个声明式编程的例子是Rails里面的数据关联,为人熟知的has_many和belongs_to。通过声明,模型类就会具备一些数据关联的能力。

具体到实际开发里,声明式编程需要有两个部分:一方面是一些基础的框架性代码,另一方面是应用层面如何使用。框架代码通常来说,都不像应用层面代码那么好理解,但有了这个基础,应用代码就会变得简单许多。

针对之前的那段代码,按照声明性编程风格,我改造了代码,下面是框架部分的代码:

#define BEGIN_STR_PREDICATE(predicate_name) \
bool predicate_name(const char* field) { \
static const char* predicate_true_fields[] = {

#define STR_PREDICATE_ITEM(item) #item ,

#define END_STR_PREDICATE \
};\
\
int size = ARRAY_SIZE(predicate_true_fields);\
for (int i = 0; i < size; i++) { \
if (strcmp(field, predicate_true_fields[i]) == 0) {\
return true;\
}\
}\
\
return false;\
}

这里用到了C/C++常见的宏技巧,为的就是让应用层面的代码写起来更像声明。对比一下之前的函数,就会发现,实际上二者几乎是一样的。有了框架,就该应用了:

BEGIN_STR_PREDICATE(shouldExecute)
STR_PREDICATE_ITEM(PreDropGroupSubs)
STR_PREDICATE_ITEM(StopUserGroupSubsCancel)
STR_PREDICATE_ITEM(QFStopUserGroupSubs)
STR_PREDICATE_ITEM(QFStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(QZStopUserGroupSubs)
STR_PREDICATE_ITEM(QZStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(SQStopUserGroupSubs)
STR_PREDICATE_ITEM(SQStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(StopUseGroupSubs)
STR_PREDICATE_ITEM(SQStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(StopUseGroupSubs)
STR_PREDICATE_ITEM(PreDropGroupSubsCancel)
END_STR_PREDICATE

shouldExecute就此重现出来了。不过,这段代码已经不再像一个函数,而更像一段声明,这就是我们的目标。有了这个基础,实现一个新的函数,不过是做一段新的声明而已。

接下来就是如何使用了,与之前略有差异的是,这里为了更好的通用性,把字符串作为参数传了进去,而不是原来的整个类对象。
shouldExecute(r.type);

虽然应用代码变得简单了,但写出框架的结构是需要一定基础的。它不像应用代码那样来得平铺直叙,但其实也没那么难,只不过很多人从没有考虑把代码写成这样。只要换个角度去思考,多多练习,也就可以驾轻就熟了。

[转载]重构:代码之丑(三)

mikel阅读(1200)

[转载]代码之丑(三) – 梦想风暴 – 博客大巴.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/82256362.html

又见switch:
switch(firstChar) {
case ‘N’:
nextFirstChar = ‘O’;
break;
case ‘O’:
nextFirstChar = ‘P’;
break;
case ‘P’:
nextFirstChar = ‘Q’;
break;
case ‘Q’:
nextFirstChar = ‘R’;
break;
case ‘R’:
nextFirstChar = ‘S’;
break;
case ‘S’:
nextFirstChar = ‘T’;
break;
case ‘T’:
throw Ebusiness();
default:
}

出于多年编程养成的条件反射,我对于switch总会给予更多的关照。研习面向对象编程之后,看见switch就会想到多态,遗憾的是,这段代码和多态没 什么关系。仔细阅读这段代码,我找出了其中的规律,nextFirstChar就是firstChar的下一个字符。于是,我改写了这段代码:
switch(firstChar) {
case ‘N’:
case ‘O’:
case ‘P’:
case ‘Q’:
case ‘R’:
nextFirstChar = firstChar + 1;
break;
case ‘T’:
throw Ebusiness();
default:
}

现在,至少看起来,这段代码已经比原来短了不少。当然这么做基于一个前提,也就是这些字母编码的顺序确确实实连续的。从理论上说,开始那段代码适用性更强。但在实际开发中,我们碰到字母不连续编码的概率趋近于0。

但这段代码究竟是如何产生的呢?我开始研读上下文,原来这段代码是用当前ID产生下一个ID的,比如当前是N0000,下一个就是N0001。如果数字满 了,就改变字母,比如当前ID是R9999,下一个就是T0000。在这里,字母也就相当于一位数字,根据情况进行进位,所以有了这段代码。

代码上的注释告诉我,字母的序列只有从N到T,根据这个提示,我再次改写了这段代码:
if (firstChar >= ‘N’ && firstChar <= ‘S”) {
nextFirstChar = firstChar + 1;
} else {
throw Ebusiness();
}

这里统一处理了字母为T和default的情形,严格说来,这和原有代码并不完全等价。但这是了解了需求后做出的决定,换句话说,原有代码在这里的处理中存在漏洞。

修改这段代码,只是运用了非常简单的编程技巧。遗憾的是,即便如此简单的编程技巧,也不是所有开发人员都驾轻就熟的,很多人更习惯于“平铺直叙”。 这种直白造就了代码中的许多鸿篇巨制。我听过不少“编程是体力活”的抱怨,不过,能把写程序干成体力活,也着实不值得同情。写程序,不动脑子,不体力才 怪。

无论何时何地,只要switch出现在眼前,请提高警惕,那里多半有坑。

[转载]Visual Studio 2010中敏捷开发流程模板的应用

mikel阅读(1171)

[转载]Visual Studio 2010中敏捷开发流程模板的应用 – 周雪峰的博客 – 博客园.

本文将会为您讲述如何使用TFS 2010 MSF Agile 5.0流程模板为迭代项目而提供的工作簿。首先,让我们看一看Visual Studio 2010 IDE中的Team Explorer插件:

Team Explorer

接下来,我们将会进入到SharePoint project portal中,为大家展示Team Explorer是如何模拟它的结构的。我们首先右击名字是Team Project的标题的那个节点,如下图所示:

模拟结构

这会让我们进入到SharePoint project portal中,如下图所示。注意Team Explorer是如何模拟这个结构的,你可以创建新文件夹,上传文档,或复制/粘贴现有的文档或文件夹。

SharePoint project portal

现在,让我们回到Team Explorer,在Team Explorer中,我们将会选择“Product Planning”工作簿(对于这个Team Project来说,它是“Product Backlog”):

回到Team Explorer

在 双击这个“Product Planning”工作簿以后,我们现在可以看到下面这张“Product Backlog”工作表。在这里,你可以为整个项目批量录入工作项。Stack Rank作业,设置Area Paths,和在项目等级上录入Story Points来平衡workload等工作都可以在这个工作簿中完成。

Product Planning

如果你想修改查询,你可以选择“Configure”下拉列表框,然后选择“List”项,如下图所示:

修改查询

选择List以后,你将会看到一个对话框,这个对话框可以让你选择你喜欢的查询。默认的Product Backlog是一个flat query,所以,我建议你改成hierarchical query。

现 在,我们将会选择“Iterations”工作表,如下图所示,在“Iterations”工作表中,我们可以为定义的每个迭代输入开始和结束的 日期。基于在“Product Backlog”工作表中录入的Story Points,你将会在“Velocity”图表中看到这个workload。在你项目开始的时候,你将会看到有什么事情被计划了,在某个迭代中,对于那 些已经完成的用户故事,你可以看到交付这个用户故事所花费的小时数的增加,以及在这个图表中的颜色编码。还有一点需要注意一下,Team Size字段可以粗略地表示在某个迭代中交付的工作量。

选择“Iterations”工作表

接下来,我们一起来看一下“Interruptions”工作表,它可以有效地屏蔽掉那些在项目进行期间无法工作的那些日期。

回到Team Explorer

现 在,让我们回到Team Explorer中,在Team Explorer中,我们将会选择第一个迭代的Iteration Backlog。对于每个迭代来说,我们都会得到一个iteration backlog。TFS(Team Foundation Server)给每个Team Project预置了3个迭代,但是你可以复制和粘贴迭代文件夹,让它指向Team Queries中的合适的查询。你必须要在Team Queries中执行复制/粘贴操作,为每个迭代修改查询。一般来说,在理想情况下,如果在Team Queries中对一个迭代执行了执行拷贝/粘贴/修改操作,然后紧接着就应该在Shared Documents文件夹下对一个迭代执行复制/粘贴/修改操作。

做Capacity计划

双 击以后,我们将会看到“Iteration Backlog”工作簿,如下图所示。在这个工作簿中,你可以管理来自于那些工作表的所有迭代——把任务安排到特定的迭代中,在“燃烧工作表”中,针对燃 烧图设置各个迭代的日期,为团队成员(私人事件或假期)设置中断,为团队成员做Capacity计划。(和2008版本相比,这是一个巨大的改进,仅次于 按等级划分的工作项)

通过Product Backlog,你可以通过选择“Configure”下拉列表修改underlying query。如下图所示:

当你从那个下拉列表中选择“List”的时候,你将会看到一个对话框,这个对话框可以让你选择不同的underlying query,如下图所示:

当我们选择Settings工作表的时候,我们可以为这个迭代输入一些日期,而且,这个工作表还可以计算天数,如下图所示:

接下来,我们可以选择“Interruptions”工作表,在这个工作表中,我们可以为计划的中断输入一些日期,以及这个特定的迭代中的一些假期。在“Capacity”工作表中,可以从各个capacity中减去这些日期。

接 下来。我们看一看比较重要的“Capacity”工作表。仅次于“Burndown”工作表,在任何一个迭代中,这个工作表都是第二有用的工作 表。请注意,我已经安排到“Iteration Backlog”中的任务都反映在这里了,你也可以看到在“Interruptions”工作表中指定的假期也被考虑进来了(也就是说,对于这个迭代来 说,有效的工作时间是15天,而不是19天)。还有,Hours/Day字段被用来表示某个团队成员的“理想”工作时间(根据我的经验,一般是6个小时, 所以,开发者们进行估算的时候一定要注意,真正的估算值是不包括饮水机旁的闲聊时间,上洗手间的时间,或与其他重要的人进行沟通的时间的。)

在“Individual Capacity”图表中,绿色的区域是团队成员可以工作的时间的总数。在这个迭代中,蓝色的部分是实际被分配的工作量。在理想情况下,分配给一个团队成 员的工作应该和他可以完成的工作量相当。如果一个团队成员落后了,会通过Over字段(这个字段是基于每天录入的时间的,我稍后将会讨论这方面的内容)反 映在这个图表中,然后,这个工作表中的工作可以被重新调整。另外,任何时间你都可以回到“Interruptions”工作表,为某个团队成员增加更多的 时间,然后再回到”Capacity“工作表重新进行调整。

至于团队成员任务的时间录入,在客户的重压之下,这是我的主要工作。十分简单,不用每天都录入时间,燃烧图和报告都是没有意义的。在Task工作项中,你将会看到所有重要的字段,如下图所示:

一 般来说,当团队成员们坐下来,把整个团队当成一个整体来进行估算的时候(我推荐这样做),每个团队成员都会做一个初步的估算。当团队成员开始完成 某个特定的任务的时候,每天,他们都在推进这个任务,他们将会输入要完成这个任务,还需要多少个小时,以及在那天完成的小时数。原始的估算值不应该被改 变,因为TFS(Team Foundation Server)会使用它来计算还没有列入计划的工作。如果在某一天“Remaining”字段的值增加了,而不是减少了,那么就像我上面讨论的那样,这会 被反映到“Iteration Backlog”的“Individual Capacity”工作表中。

我们最后要介绍的工作表是“Burndown”,在项目进度方面,它是一个起决定性作用的视图。团队的主管每天都会使用这个视图来追踪某个迭代的项目进度。这里我就不详细讨论了,因为有很多地方可以学到如何更好地理解一个燃烧图。但是,以后我可能会添加一些新想法的。

(本文来源:51cto 作者:周雪峰)

[转载]QQ限制WEB页面注册方法解析及其如何采用多线程技术实现QQ号码快速批量申请

mikel阅读(1106)

[转载]QQ限制WEB页面注册方法解析及其如何采用多线程技术实现QQ号码快速批量申请 – cntlis – 博客园.

腾讯在其QQ免费注册页面http://reg.qq.com/中,为了限制用户注册,设置了多种限制手段,尤其是在其JS页面中设置了多种算法,防止用户批量注册。

本文主要分析QQ是如何在WEB前台实现防止用户批量的注册,并且提供了相应的技术解决方案,程序早都做好了,没有外放,看到博客园上有其他人对外写了这样的文章,但是比较简陋,因此这里将我的设计方案跟各位分析一下

首先看我的最终实现效果图,比较简陋一些,多线程实现的,如果有什么疑问,可以跟我联系,本人联系QQ:8112857.

在开始注册前边的框框里边输入想要一次性批量申请QQ号码的数量,然后点击开始注册,系统自动的生成相应的线程,然后开始进行排队打码,在每个输入 框输入相应的注册码以后,点击回车,系统会自动的进行注册,并跳转到另外一个框框里边,并将正确的QQ号码自动保存到TXT文本里边。系统我在实现的时 候,没有考虑最后的临界区的问题,因此如果在没有输入验证码,并且关闭的时候,系统会假死,当然了,这个问题不影响使用,下面我说明下我的设计方案。

1.分析QQ注册提交

实际上QQ注册页面利用JavaScript操纵和很多COOKIES信息,而且利用COOKIES信息也进行了一系列的操作,而实际上我们完全可以给屏蔽掉,将关于操纵COOKIE的所有信息都给屏蔽掉,因此就是解析来的步骤

第一步,获取验证码,并且显示出来,这里我使用的是我们公司自己的控件,PNHTTP,你们也可以使用相应的组件,譬如说MSXML之类的只要能够实现GET或者POST方式的

TRegThread(aThread).Bmp:= Http.HttpBmp(‘http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837’); //远程获取验证码,并保存到TBITMAP里边

第二步,用户输入验证码,及其其他的内容信息以后,还不能直接的提交,腾讯在这里对数据进行了一个加操作,首先像checkconn页面发出一个 GET申请,这个操作主要就是获取一串JSON代码,里边包含了需要提交的各变量的名称,也就是FORM里边的INPUT变量的名称,这个变量的名称腾讯 做的比较变态,还进行了一些算法,经过分析,我给还原过来如下

获取表单变量

1 FormParams:= Http.HttpGet(http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269); ///获取checkconn页面内容
2 StrCookie:= Http.CookieMgr.CookieCollection.Cookie[PCCOOKIE,qq.com].Value; ///获取PCCOOKIE这个COOKIE里边保存的COOKIE信息
3 StrCookie:= copy(StrCookie,length(StrCookie)1,2); ///获取COOKIE的倒数两位
4 LBase:= HexToInt(StrCookie); ///将COOKIE倒数两位进行十六进制转换
5
6 ParamArray[0]:= QQ; ///申请类型1
7 ParamArray[1]:= EMAIL; ///申请类型2
8 ParamArray[2]:= zeze; ///QQ昵称
9 ParamArray[3]:= 0; ///QQ性别
10 ParamArray[4]:= 1985; ///出生年
11 ParamArray[5]:= 1; ///出生月
12 ParamArray[6]:= 2; ///出生日
13 ParamArray[7]:= 1; ///忘记了
14 ParamArray[8]:= 2; ///忘记了
15 ParamArray[9]:= abc111111; ///密码
16 ParamArray[10]:= abc111111; ///重复密码
17 ParamArray[11]:= 1; ///国家代码
18 ParamArray[12]:= 11; ///省份代码
19 ParamArray[13]:= 1; ///区域代码
20 ParamArray[14]:= RndStr; ///验证码
21
22 try
23 SListA:= FPNSplit(Copy(FormParams,33,402),,);
24 SListB:= FPNSplit(Copy(FormParams,447,64),,);
25 ///上边的是处理CHECKCONN页面的内容,实际上是JSON格式的,可以直接采用JSON解析,但是我这里嫌麻烦,所以自己用的分割函数直接处理
26 FormParams:= ; ///需要提交的变量名
27 ///下边的是对CHECKCONN返回内容的解密算法
28 for i := 0 to 12 do begin
29 IdxA:= StrToInt(SListB[i]) xor LBase;
30 IdxB:= 12i;
31 IdxA:= IdxA xor 6818;
32 IdxA:= IdxA xor 8315;
33 IdxA:= IdxA xor 5123;
34 IdxA:= IdxA xor 2252;
35 for j := 0 to 5 do
36 IdxA:= IdxA xor 0;
37 IdxA:= IdxA mod 15;
38
39 FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ =+ ParamArray[IdxA]+ &; ///这里是构造提交数据信息
40 end;
41
42 finally
43 SListA.Free;
44 SListB.Free;
45 end;
46
47
48

上边通过FormParams变量,将所需要提交的信息保存了下来,接下来我们开始像服务器提交

提交注册,并检测结果

1 StrResult:= Http.HttpPost(http://reg.qq.com/cgi-bin/getnum,FormParams,True);
2 Reg:= TPerlRegEx.Create(nil);
3 try
4 Reg.Subject:= StrResult;
5 Reg.RegEx:= 您获得的号码为:\<span id\=\”aq\-uin\” class\=\”number\”>([\s\S]*?)\<;
6 if Reg.MatchAgain then begin
7 StrQQ:= Reg.SubExpressions[1];
8 FPNWriteLnText(注册成功的QQ.txt,StrQQ,False);
9 end else begin
10 FPNWriteLnText(注册失败线程.txt,TRegData(aDataObj).FId,False);
11 end;
12 finally
13 Reg.Free;
14 end;
15

以上的过程就完成了腾讯的注册流程,但是仅仅这样是不够的,因为我们所需要的最终目的是多线程,多线程怎么实现呢?我这里采用DELPHI线程池的方式

代码

1 TCoding= record ///这里是记录打码区的状态
2 Status: integer; //忙碌1,空闲0,等待用户输入数据2,用户已经输入,等待处理3
3 ShowBegin: integer; //开始显示验证码的时间
4 end;
5
6 ///线程池中的线程处理类,可以派生,也可以不用派生
7 TRegThread = class(TPNPoolThread)
8 private
9 MyCodeIdx: integer;
10 bmp: TBitmap;
11 procedure ShowImg1;
12 procedure ShowImg;
13 public
14 destructor Destroy;
15 end;
16
17 TRegData= class(TPNTaskObject)
18 private
19 FId: String; //编号
20 public
21 constructor Create(const AId: string);
22 function Duplicate(DataObj: TPNTaskObject;
23 const Processing: Boolean): Boolean; ///判断两个任务是否重复,此函数必须在派生类写明
24 function Info: string; override; ///输出信息,覆盖
25 end;
26
相关打码区函数

1 var
2 MainForm: TMainForm;
3 Codings: array[1..9] of TCoding;
4 CodingCs: TPNCriticalSection; ///申请打码资源的CS
5
6 RegId: integer;
7
8 StrLog: string; ///日志数据
9 PoolReg: TPNThreadPool; ///线程池
10 csLog: TPNCriticalSection; ///保存日志的临界区
11
12 function CodingApply: integer; //申请打码显示资源,如果申请成功,返回显示的标号,否则返回1
13 function CodingRelease(CodeIdx: Integer): string; //释放显示资源,返回的是打码的信息
14 function CodingWait(CodeIdx: Integer): Boolean; //将状态更改为等待
15 function CodingOK(CodeIdx: Integer): Boolean; //将状态更改为处理完毕
16 function CodingStatus(CodeIdx: Integer): integer; //获取当前状态
17

具体的线程池设置代码,对于申请打码区资源,及其释放打码区资源,都写得有具体的方案

代码

1
2 function CodingApply: integer;
3 var
4 i: integer;
5 begin
6 CodingCs.Enter;
7 Result:= 1;
8 try
9 for i := 1 to 9 do begin
10 if Codings[i].Status=0 then begin
11 Result:= i;
12 Codings[i].Status:= 1;
13 Break;
14 end;
15 end;
16 finally
17 CodingCs.Leave;
18 Sleep(0);
19 end;
20 end;
21
22 function CodingRelease(CodeIdx: Integer): string;
23 begin
24 if (CodeIdx<0) or (CodeIdx>9) then Exit;
25 CodingCs.Enter;
26 try
27 try
28 Result:= TEdit(MainForm.FindComponent(Input+ IntToStr(CodeIdx))).Text;
29 except
30 Result:= ;
31 end;
32 Codings[CodeIdx].Status:= 0;
33 finally
34 CodingCs.Leave;
35 Sleep(0);
36 end;
37 end;
38
39 function CodingWait(CodeIdx: Integer): Boolean;
40 begin
41 if (CodeIdx<0) or (CodeIdx>9) then Exit;
42 Result:= True;
43 CodingCs.Enter;
44 try
45 try
46 Codings[CodeIdx].Status:= 2;
47 except
48 Result:= False;
49 end;
50 finally
51 CodingCs.Leave;
52 Sleep(0);
53 end;
54 end;
55
56 function CodingOK(CodeIdx: Integer): Boolean;
57 begin
58 if (CodeIdx<0) or (CodeIdx>9) then Exit;
59 Result:= True;
60 CodingCs.Enter;
61 try
62 try
63 Codings[CodeIdx].Status:= 3;
64 except
65 Result:= False;
66 end;
67 finally
68 CodingCs.Leave;
69 Sleep(0);
70 end;
71 end;
72 function CodingStatus(CodeIdx: Integer): integer;
73 begin
74 if (CodeIdx<0) or (CodeIdx>9) then Exit;
75 CodingCs.Enter;
76 try
77 Result:= Codings[CodeIdx].Status;
78 finally
79 CodingCs.Leave;
80 Sleep(0);
81 end;
82 end;
83
84 constructor TRegData.Create(const AId: string);
85 begin
86 FId:= AId;
87 end;
88
89 function TRegData.Duplicate(DataObj: TPNTaskObject;
90 const Processing: Boolean): Boolean;
91 begin
92 Result := (not Processing) and
93 (FId = TRegData(DataObj).FId);
94 end;
95
96 function TRegData.Info: string;
97 begin
98 Result:= FId=+ FId+ ;;
99 end;
100
101
102 procedure TRegThread.ShowImg1;
103 begin
104 try
105 TImage(MainForm.FindComponent(Img+ IntToStr(MyCodeIdx))).Picture.Assign(bmp);
106 TEdit(MainForm.FindComponent(Input+ IntToStr(MyCodeIdx))).Text:= ;
107 except
108 end;
109 CodingWait(MyCodeIdx);
110 end;
111 procedure TRegThread.ShowImg;
112 begin
113 Synchronize(ShowImg1);
114 end;
115 destructor TRegThread.Destroy;
116 begin
117 try
118 if bmp<>nil then bmp.Free;
119 except
120 end;
121 inherited Destroy;
122 end;
123
124 function HexToInt(const S: String): DWORD;
125 asm
126 PUSH EBX
127 PUSH ESI
128
129 MOV ESI, EAX //字符串地址
130 MOV EDX, [EAX4] //读取字符串长度
131
132 XOR EAX, EAX //初始化返回值
133 XOR ECX, ECX //临时变量
134
135 TEST ESI, ESI //判断是否为空指针
136 JZ @@2
137 TEST EDX, EDX //判断字符串是否为空
138 JLE @@2
139 MOV BL, $20
140 @@0:
141 MOV CL, [ESI]
142 INC ESI
143
144 OR CL, BL //如果有字母则被转换为小写字母
145 SUB CL, 0
146 JB @@2 // < 0 的字符
147 CMP CL, $09
148 JBE @@1 // 0..9 的字符
149 SUB CL, a010
150 CMP CL, $0A
151 JB @@2 // < a 的字符
152 CMP CL, $0F
153 JA @@2 // > f 的字符
154 @@1: // 0..9, A..F, a..f
155 SHL EAX, 4
156 OR EAX, ECX
157 DEC EDX
158 JNZ @@0
159 JMP @@3
160 @@2:
161 XOR EAX, EAX // 非法16进制字符串
162 @@3:
163 POP ESI
164 POP EBX
165 RET
166 end;
167
168 procedure TMainForm.DownProcessRequest(Sender: TPNThreadPool;
169 aDataObj: TPNTaskObject; aThread: TPNPoolThread);
170 var
171 Http: TPNHttp;
172 i,j,LBase,IdxA,IdxB: integer;
173 RndStr,FormParams,StrResult,StrQQ,StrCookie,StrIP: string;
174 SListA,SListB: TStringList;
175 Reg: TPerlRegEx;
176 ParamArray: array[0..14] of string;
177 begin
178 // FPNWriteLnText(日志.txt,TRegData(aDataObj).FId+开始注册,False);
179 Http:= TPNHttp.Create(nil,True,True);
180 Randomize;
181 StrIP:= 1.193.86.+ IntToStr(Random(255)+ 1);
182 // StrIP:= IntToStr(Random(255)+ 1)+.+ IntToStr(Random(255)+ 1)+.+ IntToStr(Random(255)+ 1)+.+ IntToStr(Random(255)+ 1);
183 Http.Request.CustomHeaders.Add(X-Forwarded-For:+ StrIP);
184 try
185 try
186 Http.HttpGet(http://reg.qq.com/);
187 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,首页:+ Http.HttpHeader,False);
188 TRegThread(aThread).MyCodeIdx:= CodingApply;
189 ///等待获取打码资源
190 while TRegThread(aThread).MyCodeIdx=-1 do begin
191 sleep(500);
192 TRegThread(aThread).MyCodeIdx:= CodingApply;
193 end;
194 try
195 TRegThread(aThread).Bmp:= Http.HttpBmp(http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837);
196 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,验证码:+ Http.HttpHeader,False);
197 TRegThread(aThread).ShowImg;
198 finally
199 if TRegThread(aThread).Bmp<>nil then
200 TRegThread(aThread).Bmp.Free;
201 end;
202 while CodingStatus(TRegThread(aThread).MyCodeIdx)=2 do begin
203 Sleep(200);
204 end;
205 RndStr:= CodingRelease(TRegThread(aThread).MyCodeIdx);
206 // FPNWriteLnText(日志.txt,TRegData(aDataObj).FId+:RndStr=+ RndStr,False);
207 FormParams:= Http.HttpGet(http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269);
208 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,CheckConn:+ Http.HttpHeader,False);
209 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,返回的参数集:+ FormParams,False);
210 // FormParams:= Copy(FormParams,33,402);
211 StrCookie:= Http.CookieMgr.CookieCollection.Cookie[PCCOOKIE,qq.com].Value;
212 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,PCCOOKIE值为:+ StrCookie,False);
213 StrCookie:= copy(StrCookie,length(StrCookie)1,2);
214 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,LBASE值为:+ StrCookie,False);
215 LBase:= HexToInt(StrCookie);
216
217 ParamArray[0]:= QQ;
218 ParamArray[1]:= EMAIL;
219 ParamArray[2]:= zeze;
220 ParamArray[3]:= 0;
221 ParamArray[4]:= 1985;
222 ParamArray[5]:= 1;
223 ParamArray[6]:= 2;
224 ParamArray[7]:= 1;
225 ParamArray[8]:= 2;
226 ParamArray[9]:= abc111111;
227 ParamArray[10]:= abc111111;
228 ParamArray[11]:= 1;
229 ParamArray[12]:= 11;
230 ParamArray[13]:= 1;
231 ParamArray[14]:= RndStr;
232 try
233 SListA:= FPNSplit(Copy(FormParams,33,402),,);
234 SListB:= FPNSplit(Copy(FormParams,447,64),,);
235 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,Copy(FormParams,447,64),False);
236 FormParams:= ;
237 for i := 0 to 12 do begin
238 IdxA:= StrToInt(SListB[i]) xor LBase;
239 IdxB:= 12i;
240 IdxA:= IdxA xor 6818;
241 IdxA:= IdxA xor 8315;
242 IdxA:= IdxA xor 5123;
243 IdxA:= IdxA xor 2252;
244 for j := 0 to 5 do
245 IdxA:= IdxA xor 0;
246 IdxA:= IdxA mod 15;
247 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,IdxA:+ IntToStr(IdxA),False);
248
249 FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ =+ ParamArray[IdxA]+ &
250 end;
251
252 // FormParams:= Copy(SList[0],2,28)+ =1&+ Copy(SList[1],2,28)+ =1&+ Copy(SList[2],2,28)+ =pop67579818&
253 // + Copy(SList[3],2,28)+ =1983&+ Copy(SList[4],2,28)+ =+ RndStr+& + Copy(SList[5],2,28)+ =1&
254 // + Copy(SList[6],2,28)+ =lovezeze&+ Copy(SList[7],2,28)+ =pop67579818&+ Copy(SList[8],2,28)+ =0&
255 // + Copy(SList[9],2,28)+ =1&+ Copy(SList[10],2,28)+ =2&+ Copy(SList[11],2,28)+ =11&
256 // + Copy(SList[12],2,28)+ =1;
257 finally
258 SListA.Free;
259 SListB.Free;
260 end;
261 for i := 0 to Http.CookieMgr.CookieCollection.Count 1 do
262 StrCookie:= StrCookie+ Http.CookieMgr.CookieCollection.Items[i].CookieName+ :
263 + Http.CookieMgr.CookieCollection.Items[i].CookieText;
264 StrResult:= Http.HttpPost(http://reg.qq.com/cgi-bin/getnum,FormParams,True);
265 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,POST时候:+ Http.HttpHeader,False);
266 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,提交COOKIE为:+ StrCookie,False);
267 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,提交参数为:+ FormParams,False);
268 // FPNWriteLnText(TRegData(aDataObj).FId+返回数据.txt,StrResult,False);
269 Reg:= TPerlRegEx.Create(nil);
270 try
271 Reg.Subject:= StrResult;
272 Reg.RegEx:= 您获得的号码为:\<span id\=\”aq\-uin\” class\=\”number\”>([\s\S]*?)\<;
273 if Reg.MatchAgain then begin
274 StrQQ:= Reg.SubExpressions[1];
275 FPNWriteLnText(注册成功的QQ.txt,StrQQ,False);
276 end else begin
277 FPNWriteLnText(注册失败线程.txt,TRegData(aDataObj).FId,False);
278 end;
279 // FPNWriteLnText(TRegData(aDataObj).FId+匹配结果.txt,StrResult,False);
280 finally
281 Reg.Free;
282 end;
283 except
284
285 end;
286 finally
287 Http.Free;
288 end;
289 end;
290
291 procedure TMainForm.btn1Click(Sender: TObject);
292 var
293 i: integer;
294 begin
295 for i := 1 to StrToInt(RegNum.Text) do begin
296 RegId:= RegId+ 1;
297 PoolReg.AddRequest(TRegData.Create(IntToStr(RegId)));
298 end;
299 end;
300
301 procedure TMainForm.FormCreate(Sender: TObject);
302 begin
303 RegId:= 0;
304 CodingCs:= TPNCriticalSection.Create;
305 PoolReg := TPNThreadPool.Create(nil);
306 with PoolReg do begin
307 OnProcessRequest := DownProcessRequest; ///线程处理函数
308 AdjustInterval := 5 * 1000; ///减少线程间隔时间,5
309 MinAtLeast := False; ///是否设置最小线程数
310 ThreadDeadTimeout := 10 * 1000; ///线程死亡超时时间
311 ThreadsMinCount := 10; ///最小线程数
312 ThreadsMaxCount := 50; ///最大线程数
313 uTerminateWaitTime := 2 * 1000; ///挂起等待时间
314 end;
315
316 end;
317
318 procedure TMainForm.FormDestroy(Sender: TObject);
319 begin
320 PoolReg.Free;
321 CodingCs.Free;
322 end;
323
324 procedure TMainForm.Input1KeyPress(Sender: TObject; var Key: Char);
325 var
326 CodeIdx: integer;
327 ObjName: string;
328 begin
329 if Key= Char(13) then begin
330 ObjName:= (Sender as TRzEdit).Name;
331 CodeIdx:= StrToInt(Copy(ObjName,6,1));
332 CodingOK(CodeIdx);
333 if CodeIdx=9 then
334 CodeIdx:= 1
335 else
336 CodeIdx:= CodeIdx+ 1;
337 TRzEdit(FindComponent(Input+ IntToStr(CodeIdx))).SetFocus;
338 end;
339 end;
340

本文只是对QQ注册页面进行一些分析,供大家参考,其实大家在做WEB设计的时候,如何防止批量注册这块,也可以参考下QQ,比较变态一些,但是感觉腾讯做的还不完善。