我的博客

posts(42) comments(30) trackbacks(5)
  • 博客园
  • 联系
  • 订阅 订阅
  • 管理

News

昵称:Jackey
园龄:5年
粉丝:2
关注:0

搜索

 

常用链接

随笔分类

  • Ajax编程(3)
  • C# Winform(5)
  • Javascript(11)
  • Office操作(2)
  • SQL Server(6)
  • vs2005 C# .net(5)
  • 通用函数(11)

随笔档案

  • 2008年2月 (1)
  • 2007年11月 (1)
  • 2007年10月 (9)
  • 2007年9月 (6)
  • 2007年7月 (1)
  • 2007年6月 (1)
  • 2007年5月 (13)
  • 2007年4月 (4)
  • 2007年1月 (6)

文章分类

  • 成功学(1)
  • 基金由浅到深(8)
  • 我写的文章(2)
  • 心情日记(4)

相册

  • 我的相册

最新评论

阅读排行榜

评论排行榜

推荐排行榜

2008年2月17日

C #中的几个线程同步对象方法


在编写多线程程序时无可避免会遇到线程的同步问题。什么是线程的同步呢?

举个例子:如果在一个公司里面有一个变量记录某人T的工资count=100,有两个主管A和B(即工作线程)在早一些时候拿了这个变量的值回去
,过了一段时间A主管将T的工资加了5块,并存回count变量,而B主管将T的工资减去3块,并存回count变量。好了,本来T君可以得到102块的工资的,现在就变成98块了。这就是线程同步要解决的问题。

在.Net的某些对象里面,在读取里面的数据的同时还可以修改数据,这类的对象就是“线程安全”。但对于自己编写的代码段而言,就必须使用线程同步技术来保证数据的完整性和正确性了。

有几个规律:
1、如果一个对象(或变量)不会同时被多个其他线程访问,那么这个对象是不需使用线程同步的。
2、如果虽然有多个线程同时访问一个对象,但他们所访问的数据或方法并不相同(不交叉),那这种情况也不需使用线程同步。
例如上例中的那个公司里面如果有 T 和 Q 两个人,但他们的工资分别是由 A 和 B 主管的,那么这个工资的处理就不需要线程同步了。
3、如果一个对象会同时被多个其他线程访问,一般只需为这个对象添加线程同步的代码,而其他线程是不需添加额外代码的。

在C#里面用于实现线程同步的常用类有如下几类
1、Mutex类(互斥器),Monitor类,lock方法
2、ManualResetEvent类,AutoResetEvent类(这两个都是由EventWaitHandle类派生出来的)
3、ReaderWriterLock类

同一类的作用都差不多:其中
第一类的作用是:用来保护某段代码在执行的时候以独占的方式执行,这时如果有第二个线程想访问这个对象时就会被暂停。一直等到独占的
代码执行为止。就好比一堆人同时上一个公共厕所一样,使用这个方法就可以解决文章一开始时提出的问题:主管A要处理T君的工资之前,先lock一下T君,然后取出目前的count值,处理完之后再解除T君的锁定。如果主管B在主管A处理工资时也想取出count值,那么它只能是一直地等待A处理完之后才能继续。使用这个方法的一个缺点就是会降低程序的效率。本来是一个多个线程的操作,一旦遇到lock的语句时,那么这些线程只要排队处理,形同一个单线程操作。

下面举个例子说明一下这三个方法的使用:
假定有一个Tools类,里面一个int变量,还有Add和Delete方法,其中Add方法会使int变量的值增加,Delete方法使int变量值减少:

public class Tools
{
private int count = 100;
public void Add(int n)
{
count+=n;
}

public void Delete(int n)
{
count-=n;
}
}

在多个线程同时访问这段代码时,因为一个语句会被编译器编译成多个指令,所以会可能出现这种情况:但某个线程调用Add方法时,这时的count值为 100,而正当要加上n的时候,另外一个线程调用了Delete,它要减去m,结果count加上了n,然后又在原先count=100的值的情况
下减掉了m,最后的结果是count被减去了m,而没有加上n。很明显Add方法和Delete方法是不能同时被调用的,所以必须进行线程同步处理。简单的方法是用lock语句:

public class Tools
{
private object abcde = new object();
private int count = 100;

public void Add(int n)
{
lock(abcde)
{
count+=n;
}
}

public void Delete(int n)
{
lock(abcde)
{
count-=n;
}
}
}

其中abcde是一个private级的内部变量,它不表示任何的意义,只是作为一种“令牌”的角色。
当执行Add方法中的lock(abcde)方法时,这个令牌就在Add方法的手中了,如果这时有第二个线程也想拿这个令牌,没门,惟有等待。一旦第一
个lock语句的花括号范围结束之后,这时令牌就被释放了,同时会迅速落到第二个线程的手中,并且排除其他后来的人。

使用Monitor类的方法大致一样:

public class Tools
{
private object abcde = new object();
private int count = 100;

public void Add(int n)
{
Monitor.Enter(abcde);
count+=n;
Monitor.Exit(abcde);
}

public void Delete(int n)
{
Monitor.Enter(abcde);
count-=n;
Monitor.Exit(abcde);
}
}

Monitor的常用方法:Enter和Exit都是静态方法,作用跟lock语句的两个花括号一样。
而使用 Mutex 就不需声明一个“令牌”对象了,但要实例化之后才可以使用:

public class Tools
{
private Mutex mut = new Mutex();
private int count = 100;

public void Add(int n)
{
mut.WaitOne();
count+=n;
mut.ReleaseMutex();
}

public void Delete(int n)
{
mut.WaitOne();
count-=n;
mut.ReleaseMutex();
}
}

其中的WaitOne为等待方法,一直等到Mutex 被释放为止。初始的情况下,Mutex 对象是处于释放状态的,而一旦执行了WaitOne方法之后,它
就被捕获了,一直到被调用了ReleaseMutex方法之后才被释放。
使用这三种方法都有一个要注意的问题,就是在独占代码段里面如果引起了异常,可能会使“令牌”对象不被释放,这样程序就会一直地死等下去了。
所以要在独占代码段里面处理好异常。例如下面这样的代码就是错误的:

public void Add(int n)
{
try
{
mut.WaitOne();
count+=n;
//....这里省略了N行代码
//....这里是有可能引起异常的代码
//....这里省略了N行代码
mut.ReleaseMutex();
}
catch
{
Console.Writeline("error.");
}
}

上面的代码一旦在try和catch里面发生了异常,那么Mutex就不能被释放,后面的程序就会卡死在WaitOne()一行,而应该改成这样:

public void Add(int n)
{
mut.WaitOne();
try
{
count+=n;
//....这里省略了N行代码
//....这里是有可能引起异常的代码
//....这里省略了N行代码
}
catch
{
Console.Writeline("error.");
}
mut.ReleaseMutex();
}

现在谈一下第二种:
ManualResetEvent类,AutoResetEvent类

上面这两个类都是由EventWaitHandle类派生出来的,所以功能和调用方法都很相似。
这两个类常用于阻断某个线程的执行,然后在符合条件的情况下再恢复其执行。
举个例子,你想送花给一个MM,托了一个送花的小伙子送了过去,而你希望当MM收到花之后就立即打个电话过去告诉她。

但问题是你不知道花什么时候才送到MM的手里,打早了打迟了都不好,这时你可以使用ManualResetEvent对象帮忙。当委

托小伙子送花过去的时候,使用ManualResetEvent的WaitOne方法进行等待。当小伙子把花送到MM的手中时,再调用一下

ManualResetEvent的Set方法,你就可以准时地打电话过去了。
另外ManualResetEvent还有一个Reset方法,用来重新阻断调用者执行的,情况就好比你委托了这个小伙子送花给N个MM,

而又想准时地给这N个MM打电话的情况一样。

using System;
using System.Threading;

public class TestMain
{
private static ManualResetEvent ent = new ManualResetEvent(false);

public static void Main()
{
Boy sender = new Boy(ent);
Thread th = new Thread(new ThreadStart(sender.SendFlower));
th.Start();

ent.WaitOne(); //等待工作
Console.WriteLine("收到了吧,花是我送嘀:)");
Console.ReadLine();
}

}

public class Boy
{
ManualResetEvent ent;

public Boy(ManualResetEvent e)
{
ent = e;
}

public void SendFlower()
{
Console.WriteLine("正在送花的途中");
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
Console.Write("..");
}
Console.WriteLine("\r\n花已经送到MM手中了,boss");

ent.Set(); //通知阻塞程序
}
}

而AutoResetEvent类故名思意,就是在每次Set完之后自动Reset。让执行程序重新进入阻塞状态。
即AutoResetEvent.Set() 相当于 ManualResetEvent.Set() 之后又立即 ManualResetEvent.Reset(),
其他的就没有什么不同的了。
举个送花给N个MM的例子:

using System;
using System.Threading;

public class TestMain
{
private static AutoResetEvent ent = new AutoResetEvent(false);

public static void Main()
{
Boy sender = new Boy(ent);

for (int i = 0; i < 3; i++)
{
Thread th = new Thread(new ThreadStart(sender.SendFlower));
th.Start();
ent.WaitOne(); //等待工作
Console.WriteLine("收到了吧,花是我送嘀:)\r\n\r\n");
}

Console.ReadLine();
}

}

public class Boy
{
AutoResetEvent ent;

public Boy(AutoResetEvent e)
{
ent = e;
}

public void SendFlower()
{
Console.WriteLine("正在送花的途中");
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
Console.Write("..");
}
Console.WriteLine("\r\n花已经送到MM手中了,boss");

ent.Set(); //通知阻塞程序,这里的效果相当于 ManualResetEvent的Set()方法+Reset()方法
}
}

要注意的是ManualResetEvent和AutoResetEvent 的构造函数都有一个bool的参数,用这个参数可以指定初始情况下,同步对象的处于阻塞(设置为false)还是非阻塞(设置为true)的状态。
另外WaitOne方法也可以带两个参数:
WaitOne (int millisecondsTimeout,bool exitContext)
millisecondsTimeout:等待的毫秒数,或为 Timeout.Infinite (-1),表示无限期等待。
exitContext:为 true,则等待之前先退出上下文的同步域(如果在同步上下文中),然后在稍后重新获取它;否则为false。
就是说,等待是可以加上一个期限的,如果等待的同步对象一直都不Set()的话,那么程序就会卡死,所以在WaitOne方法里面可以放置一个时间期限,单位是毫秒。

posted @ 2008-02-17 15:38 Jackey 阅读(142) 评论(0) 编辑

2007年11月7日

C#对称加密

摘要: /// <summary> /// 对称加密算法类,使用系统自带的函数 /// </summary> public class SymmetricMethod { private SymmetricAlgorithm mobjCryptoService; private string Key; /// <summary> /// 对称加密类的构造函数 /// &...阅读全文

posted @ 2007-11-07 11:53 Jackey 阅读(1617) 评论(1) 编辑

2007年10月27日

C#获取cpu序列号,硬盘ID,网卡MAC地址

首先在添加引用中选中System.Management
再在开始部门:
using System.Management;
using System.Management.Instrumentation;

private void GetInfo()  
  {  
   string cpuInfo = "";//cpu序列号  
   ManagementClass cimobject = new ManagementClass("Win32_Processor");  
   ManagementObjectCollection moc = cimobject.GetInstances();  
   foreach(ManagementObject mo in moc)  
   {  
    cpuInfo = mo.Properties["ProcessorId"].Value.ToString();  
    Response.Write ("cpu序列号:"+cpuInfo.ToString ());  
   }  

   //获取硬盘ID  
   String HDid;  
   ManagementClass cimobject1 = new ManagementClass("Win32_DiskDrive");  
   ManagementObjectCollection moc1 = cimobject1.GetInstances();  
   foreach(ManagementObject mo in moc1)  
   {  
    HDid = (string)mo.Properties["Model"].Value;  
    Response.Write ("硬盘序列号:"+HDid.ToString ());  
   }  


   //获取网卡硬件地址  

    
    
   ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");  
   ManagementObjectCollection moc2 = mc.GetInstances();  
   foreach(ManagementObject mo in moc2)  
   {  
    if((bool)mo["IPEnabled"] == true)  
     Response.Write("MAC address\t{0}"+mo["MacAddress"].ToString());  
    mo.Dispose();  
   }  
  }

posted @ 2007-10-27 15:22 Jackey 阅读(531) 评论(0) 编辑

2007年10月11日

多线程操作控件C#

不要在创建控件以外的线程操作控件,Net   2.0已经把这个作为异常了。可以使用Control的Invoke方法,将操作放到UI线程上。  
  一个简单的例子  
   
  private   void   Form1_Load(object   sender,   System.EventArgs   e)  
  {  
          System.Threading.Thread   tNew   =   new   System.Threading.Thread         (new           System.Threading.ThreadStart(this.Test));  
          tNew.Start();  
  }  
   
  delegate   void   SetVisibleDelegate();  
   
  private   void   SetVisible()   //控件操作  
  {  
        this.button1.Visible   =   true;  
  }  
   
  private   void   Test()  
  {  
        this.Invoke(new   SetVisibleDelegate(SetVisible));  
  }

posted @ 2007-10-11 01:26 Jackey 阅读(498) 评论(0) 编辑

2007年10月9日

验证码识别技术

模拟精灵是首个公开最有效的验证码识别技术的软件,
使用模拟精灵制作了大量的免费、商用群发软件,对很多复杂BT的验证码都能成功的识别。
但是验证码仍然需要精湛的技术与足够的耐心。请牢记这一点。
验证码识别不适合浮躁的人去做。

验证码识别是一项特殊的技术,任何一个公开的验证码识别代码都会很快的失效。
因为代码的公开后相关网站都会很快的更改验证码。
所以下面我只会介绍其原理。

在这里讨论验证码识别技术纯粹基于技术研究目的。
公开此技术也是为了让更多的网站采取更有效的防范措施。
禁止任何人利用这里介绍的验证码识别技术滥发垃圾信息。

本文介绍的验证码识别适用于比较复杂的图片验证码,也是大多数网站采用的方法。
有一些网站的验证码极简单,例如在网页中直接显示验证码字符而不是图片,或者图片的文件名直接就是验证码上的字符。
或者有其他规律可循,或者有其他明显的漏洞可以利用(例如通过改写访问验证码页面的源代码使验证码不刷新)。

这一类的验证码识别极其简单,只要熟练掌握web库、element库的函数即可,不需要使用下面介绍的方法。

一、下载验证码样本

打开c:\test文件夹,选“查看缩略图”,
然后重复运行下面的LAScript脚本,每运行一次,就查看c:\test下自动生成的图片,把图片上的字符改为文件名.
例如图片上面显示5,就把文件名改为5.jpg.

如果变化比较复杂的验证码,可以对每个字符多用几个样本,第一个字符为验证码字符,第二个字符可以为任意字符。
例如:5a.jpg , 5b.jpg , 5c.jpg ...........等等。
样本多就会识别能力就越强。

img = image.new();

--下载图像,没有后缀名要显示指定*.bmp格式

img:getURL("http://www.***.com/test.asp","*.png");
assert(img:ok(),"下载验证码失败");

img:Crop(4 ,3 , 56 ,18 )
img:save("c:\\test\\test.jpg") --保存到硬盘


--折分图片,指定一行四列
img2,img3,img4,img5 = img:split(1,4);

img2:save("c:\\test\\0001.jpg")
img3:save("c:\\test\\0002.jpg")
img4:save("c:\\test\\0003.jpg")
img5:save("c:\\test\\0004.jpg")

image.del(img);


如何确定图片后缀名

在整个验证码识别过程中,格式与后缀名一定不能搞错,否则就会失败。
通常:asp的验证码是bmp格式,php的验证码是png格式,其他验证码很多是jpg格式。
简单的,在验证码上右键点选“图片另存为”,就可以看到格式(不一定准确)。

另外,你可以用UltraEdit等以二进制方式打开看文件头部

首先下载:
str = web.getURL("http://www.***.com/test.asp")
string.save( str,"c:\\test.bin")

然后用UE打开test.bin看文件头部(第一行)

jpg文件头部有 JFIF 字眼
png文件头部 有 PNG 字眼
gif文件头部有 GIF字眼

如果你搞不清楚,这时候就不要指定后缀名
img:getURL("http://vwww.***.com/test.asp","")
这样就可以下载了

二、生成验证码样本数据库

复制下面的代码并粘贴到fap程序的「脚本区块」内,然后点击"回放运行",最后再点击"读取源代码"。

你就可以在ApeML源代码最后面的「数据区块」中看到生成的验证码样本了。
将「数据区块」的内容复制需要使用验证码识别的fap模拟程序中覆盖「数据区块」即可。

local tkey ={A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0};
 
--在字典中添加所有数字键
for i =0,9,1 do
    tkey[ tostring(i) ] = 0;
end;

--如果一个字符有多个样本,例如 5A.jpg 5B.jpg 5C.jpg
for k,v in pairs(tkey) do 
    if((#k)~=2)then --如果元素键名不是两位字符
        tkey[k.."A" ]=0;
        tkey[k.."B" ]=0;
        tkey[k.."C" ]=0;
        tkey[k]=nil;--删除单字符的键名
    end;
end;
 
--k参数为键,v参数表示值 一个典型的tkeyle迭代器回调函数
loadtkey = function(k,v)
    local img = image.new();
   
    img:load("C:\\test\\"..k..".jpg");
    assert(img:ok(),"C:\\test\\"..k..".jpg".."\n不是有效的图片");
   
    img:bpp(1);
    img:bpp(24);
    --通过上面两句,轻松去掉验证码上的杂色杂点
   
    img:Crop( 1 , 0 , 9 , 10);--修剪单个字符
    img:median(2);--中值滤波进一步去杂点
   
    tkey[k]= string.encode( img:getBytes("*.jpg") , ""); --因为转换到字符串还是二进制,所以用base64进行编码
    image.del(img);
end;
 
--遍历表tkey的所有元素,调用loadtkey加载图片文件
for k,v in pairs(tkey) do
    loadtkey(k,v);
end;
 
--把所有图片保存到数据岛,
ape:saveTable(tkey,"验证码样本")

三、验证码识别

将下面的代码添加到fap模拟程序最前面的init脚本区块中即可

--从数据区块读取base64编码的图片数据
codekey = ape:loadTable("验证码样本");
local timg = {}; --这是一个图像数组,用来储存还原后的验证码样本的图片数据
--必须进行一个转换,因为codekey里面只是base64编码的普通字符串,而timg 将是真正的图片对象(二进制数据)
 
--还原到图片对象
toImage = function(k,v)
    local img = image.new();
    local str = string.decode( v ,"");--首先进行base64解码,将纯文本转换为二进制数据
    img:setBytes( str ,"*.jpg");--将二进制数据还原为图像
    timg[k] = img;
end;
 
--载入验证码样本
tkey = ape:loadTable("验证码样本");
for k,v in pairs(tkey) do  --验证样本
    toImage(k,v); --转换为图像
end;
   
--转换图片验证码到字符串的函数
function ImgToString(img)
    function test(imgX) --test是一个被包含在函数中的内部函数
        sleep(0);
        local limit = (60 * 20) + (60 * 20); --最小相似度 local关键字声明为局部变量
        local chr = "A"; --读取的字符
   
   
        --testimg是一个被包含在函数中的内部函数,作为table.foreach的回调函数,k参数表示键,v参数表示值
        testimg = function(k,v)

            --调用image.testXX()函数得出相似度,类似的函数还有image.testX() image.test()
            local n = imgX:testXX(timg[k]);
            if(n<limit)then --比较最小相似度
                  limit = n;
                  chr = k.."";
            end;
        end;
   
        --遍历timg表,并调用testimg函数
        for k,v in pairs(timg) do 
           testimg(k,v); 
        end;

        return string.left(chr,1); --返回读取到的字符串首字符(如果每个字符有多个样本)
    end;
   
   
    --修剪图片   
    image.Crop(img, 4 ,3 , 56 ,18 )
    img:bpp(1);
    img:bpp(24);
    --上面的过程必须与下载样本时的代码完全一致。
   
    --使用split函数分割图片
    local img2,img3,img4,img5 = img:split(1,4);
    win.messagePrint("正在检测图片,请稍候....");
    return test(img2)..test(img3)..test(img4)..test(img5);
 
end;

需要识别验证码的地方添加类似下面的代码:

img = image.new()
img:getURL("http://www.***.com/test.asp","*.jpg")

--因为刷新了验证码与页面不一致,把验证码画到屏幕上
local x,y = mouse.getPos()
img:paint(x,y,60 ,20 )

local str = ImgToString(img);

--下面我们把验证码的每个字符都转换为大写,并控制键盘顺序按键
code1 = string.upper( string.sub(str,1,1) );
code2 = string.upper( string.sub(str,2,2) );
code3 = string.upper( string.sub(str,3,3) );
code4 = string.upper( string.sub(str,4,4) );
key.press(100,code1,code2,code3,code4);

上面我们用了模拟按键的方法输入验证码。
实际上大多时候可以用更简单的方法,如下:

ele = wb:getEle("验证码控件名字");
ele:setAttribute("value",str)

为什么我的验证码与页面上不一样

因为我们使用img:getURL读取验证码时已经刷新了验证码。
所以验证码与页面上显示的并不一样,您只需要识别最新的验证码即可。

如何直接获取页面的上图片,而不是重新下载

有些验证码是绑定页面的,必须识别页面上的验证码才行。
那么可以使用image.capture函数直接抓屏屏幕上的图片即可。
请参考:image.capture函数。

更好的方法是使用ele:exec("Copy")函数直接拷贝页面上的图片到剪贴板。
然后使用 img:getClipBD() 获取图片。
请参考:ele:exec("Copy")函数 img:getClipBD()函数

四、关于剪切图片



看上面的示意图,Crop就是选取绿色方框内的区域去清除绿色方框外面的区域.
必须保证里面的面积正好可以平均分成四块(假设这里是四个验证码字符)

这样以后调用 img:split(1,4) 就正好分成四个字符了
分成四份的小图片其宽度应当正好是上面的红色小方块的宽度。
高度与绿色方框一样,我这里画的参次不齐是为了让大家看清楚。

如果你Crop的参数值不对,那么split就出错了.
下载验证码图片以后,可以使用图像编辑软件打开高倍放大。

五、使用种子填充算法去除验证码上的干扰线

模拟精灵识别验证码的能用是强大的,一个函数即可以去除杂色杂点。

img:bpp(1)
img:bpp(24)

经过上面两句代码的处理,速度很快,所有背景、干扰点、杂色荡然无存。

但是有时候验证码中有大量的干扰线,并且位置随机变动的太历害,
这时候我们在处理验证码以前首先去除这些干扰线并准确的去除背景提取字符.

下面是一个模拟精灵初步处理后的验证码图片.已经去除了杂色、杂点.但是上面还是有干扰线.

一个可选的办法是用中值滤波再处理一下。img:median(2); 一个函数调用就可以,但
是这样虽然去掉了干扰线,原来的字符也被少量的破坏了。

下面是使用种子填充算法去除干扰线的源代码,不但能去除杂点,
而且可以去除周围的空白(提取位置随机变化的验证码),
稍加修改还能有更多的用途.

下面是自动处理以后的效果

下面是全部的源代码:

--[[
用一个table结构{x=0; y=0}表示图像上的「坐标点」
用一组点构成table结构表示图像上的一条「线」。所有相连的黑色的点被认为是一条「连通线」。
找出最长的一条「连通线」,被认为是字符,其他的认为是杂点。
 
 
算法原理与种子填充算法相似。
 
首先让用img:bpp函数处理为黑白图片,并初步去除杂色。
 
先找到一个黑点,创建一个表示「坐标点」对象,并添加到「连通线」中。
然后在黑点周围8个点中,再找黑色的点,找到就添加到「连通线」,这样一直递归下去
直到遍历图像所有点,可能有几块。
 
清除杂点使用方法
image.scan(img);
 
清除杂点并切去掉周围的空白
image.scan(img,true);
--]]
f
unction image.scan(img,crop)
   
    --用一个table数组记录所有的「连通线」
     assert(img:ok(),"image.scan 的参数必须是一个有效的图片");
   
     local tlines ={};
    
     --首先计算出图片的高度宽度,避免重复的调用
     local w = img:width();
     local h = img:height();
         
 
    --[[以table形式定义一个数组,对应图象中的每个点。
    作用相当一个开关,首先值为false,但黑点首次被遍历到时。把这个值变为true。
    下次,再找到这个点时忽略。避免重复加入连通线。 
    --]]
    local tchked ={};
    for i=0,w,1  do
        tchked[i]={};
        for j=0,h,1  do
            tchked[i][j]=false;
        end;
    end;
    
    -----去噪
    img:bpp(1);
    img:bpp(24);
    
    --首先计算出各点的颜色值,避免在循环递归中重复的取
    local tcl={};
    for i=0,w,1  do
        tcl[i]={};
        for j=0,h,1   do
            tcl[i][j]=img:getPos(i,j);
        end;
    end;
 
   
    --[[
    算点数函数
    参数x,y 坐标
    参数tab 所属连通线;
    --]]
    local   function  seed(x,y,tab)
   
        ---出界了则返回
        if(x<0 or y<0 or x>w or y>h) then
            return;
        end;
             
        ---点的颜色为白色时,返回,不处理。
        if(tcl[x][y]==16777215)  then
            return;
        end;
       
        ---值为1,则计数加1,返回
        if ( tchked[x][y]) then
            return ;
        else
            table.insert(tab,{x=x,y=y} );--添加到连通线里
            tchked[x][y]=true;---当值为0时,把值置为1。
            seed(x+1,y-1,tab);
            seed(x,y-1,tab);
            seed(x-1,y-1,tab);
            seed(x-1,y,tab);
            seed(x+1,y,tab);
            seed(x-1,y+1,tab);
            seed(x,y+1,tab);
            return seed(x+1,y+1,tab); --这里可以用一个尾调用(参考教程中的函数部份),加快递归的速度。
        end;
    end;
 
   
    ---------------------------
      
    ----遍历图像中的所有点
    for i=0,w,1   do
        for j=0,h,1  do
            ---如果是黑色的点,而且没有被计过数,则调用seed函数。
            if(tcl[i][j]==0 and (not tchked[i][j])) then       
                local tab = {}
                seed(i,j,tab);
                table.insert(tlines,tab); --添加一条连通线
   
            end;
        end;
    end;
         
    --现在tlines 里记录了的有的连通线,我们现在需要根据连通线的长度排序 
    sproc =  function(l,l2)  
        return table.maxn(l) > table.maxn(l2);--长的连通线排到前面
    end;
    table.sort(tlines,sproc)
              
    --把图像全部画成白色的点     
    for i=0,w,1  do
        for j=0,h,1  do
            img:setPos( i , j, 16777215);
        end;
    end;
         
    --然后把最长的一条连通线画上去
    for i,point in  ipairs(tlines[1])  do
        img:setPos( point.x, point.y , 0);  
    end;
   
 
    --如果需要去掉周围的空白
    if(crop)then
        local n = table.maxn(tlines[1])
           
        --排序最长连通线中的所有坐标点
        sproc =  function(pt,pt2)  
            return  (pt.x <pt2.x );--*左的排前面
        end;
        table.sort(tlines[1],sproc);
        local x,x2 = tlines[1][1].x, tlines[1][n].x;
   
        --排序最长连通线中的所有坐标点
        sproc =  function(pt,pt2)  
            return (pt.y <pt2.y );--*上的排前面
        end;
        table.sort(tlines[1],sproc);
        local y,y2 = tlines[1][1].y, tlines[1][n].y;
       
        img:Crop( x,y,x2+1,y2)
    end;
   
end;
    

 转自:http://www.yhhe.net/ape/book/fap/f2/ix.html

posted @ 2007-10-09 18:17 Jackey 阅读(4700) 评论(3) 编辑

网上几种常见校验码图片分析

前几天受刺激了,准备把CSDN的校验码图片修改。就上网找了一些参考示例。和分析了一些校验码的功能。不敢独享,整理到一起,跟大家分享。

至于CSDN新的校验码写法,不是这里面的任何一种。也不是网上可以找到的。这个不好公开,如果想研究校验码图片,建议研究我这里给的几个推荐链接。

一些常见的验证码效果图:


功能分析:
一般校验码具有的功能:
1、显示内容
显示内容一般是数字或者数字+字符,还有更变态的,显示的内容是随机汉字。
如何随机生成汉字,请看:
《用C#生成随机中文汉字验证码的基本原理》
http://wulei8899.cnblogs.com/archive/2005/06/29/183200.html

2、内容显示
内容显示这里,有很多做法,常见的有以下几种:
2.1 以一个不常被看到的字体显示内容,
2.2 字体随机倾斜显示
2.3 每个字随机显示不同颜色
2.4 内容随机显示在不同位置。
2.5 文字采用就渐变颜色,同一个字就有好几种颜色组成。

3、背景显示
常见背景显示方案:
3.1、使用干扰线来,后面随机显示数条干扰线,这些线的颜色跟字体的颜色类似
3.2、干扰点,后面随机显示数个干扰点,这些点的颜色跟字体的颜色类似
3.3、干扰色块,后面随机出现一个色块进行干扰

常见几种开源的校验码生成代码


效果图:
相关文章:ASP.net 验证码(C#) 
http://blog.csdn.net/SW515/archive/2005/02/03/279364.aspx
我个人在这些开源校验码中,最喜欢这个,校验码不会复杂到用户识别不了,而程序判断又有相当的难度


相关文章:ASP 生成 彩色 可变长 验证码程序
http://www.codefans.com/ArticleView/Article_6728.html

Asp.Net 动态生成验证码
http://sleeping.cnblogs.com/archive/2005/12/19/299980.html

象 DEV-Club 那样的彩色校验码
http://www.blueidea.com/tech/program/2003/709.asp

C#的彩色验证码
http://www.codefans.com/ArticleView/Article_6745.html

VerifyCode(asp),gif验证码生成技术
http://www.codefans.com/ArticleView/Article_6735.html

region 实现一个验证码的类
http://www.innerv.com/blogview.asp?logID=509&cateID=5

如何在asp.net中动态生成验证码
http://www.dwww.cn/new/2005911154522249.html

256色BMP图片验证码识别脚本
http://www.51cto.com/html/2005/1101/10438.htm

QQ验证码识别源代码(C#/NET1.1)
http://www.intodigi.com/Net/Website/Program/NET/12082.html



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=723752

posted @ 2007-10-09 13:16 Jackey 阅读(496) 评论(0) 编辑

破解图片验证码限制的一般思路

相信大家都有在论坛灌水的经历吧?但是如果你连续发表统一内容的东西在论坛中,不久论坛的整个板块就全是你发表的信息了,其他信息全被挤在了后面。假设你发表的信息够多,版主都删不完时,那么这个论坛基本就报废了!其他的商业论坛也意识到了防止恶意灌水的重要性,纷纷使用了一些防止恶意灌水的措施,有发贴间隔时间的限制、同一IP地址的发贴数限制、内容不能重复等,但是这些都不是今天的重点,由于论坛使用的是一个公用发贴表单,需要突破唯一的难点是验证码。(为了防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试或是防止不断提交某网络信息而采用的一种网络通行方式。随机生成字符信息放入图片中,防止计算机直接识别。比如招商银行的网上个人银行,腾讯的QQ社区等等)

  验证码分为如下几类:数字型、字符型、符号型、综合型。

  一般验证码属于数字型的,不过有的图片中生成了不少彩色和黑白的噪音点(指验证图片上的斑点)。那么我们应该怎么去识别呢?传统的验证码识别方式很简单,由于数字的位置是固定的,所以我们只需要提取每一幅数字的图片(没有噪音点的)然后把每一个特殊数字独有的象素位置记录下来,然后在网络上提取需要破解的特征码,祛除噪音点,对其位置和记录位置进行比对,那就是么就能确定相应的数字了。

  总结一下传统的就是:

  1.先分析验证码,前景颜色是否不定

  2.然后把验证码的宽度/验证码文字个数,比如一验证码下载后宽度为60,有4个数字,那么就60/4=15,然后保存每个字,如果只有数字保存0-9数字到位图文件,如果英文那更麻烦点,0-9,A-Z都要保存到位图,位图的前景色都不变,保持一种颜色,背景随便你改不改

  3.如果前景要变则将文字统一为同种颜色,每个数字0-9的点阵都有个公共点,取该公共点颜色然后把前景全部统一成一种颜色,比如白色{255,255,255},位图的结构是BGR,而不是RGB

  4.然后进行比较,如果验证码的一点为白色,第2步保存的位图同一点也是白色,那么频率增加1

  5.最后频率最高的就是验证码了!

  接下来要做的就是做个post程序了,这个太简单了,代码你就自己写吧!不过也要做到如下细节:做成多线程程序进行发贴,不然程序会失去响应的。最好是可以导入大量的代理IP的,然后就是发贴的内容最后加上几个随机字符,这样可以防止重复贴的过滤!最重要的,只是做测试,发贴量不要太多、不要乱发广告贴!

  其实上述方法已经不是只是对验证码进行识别了,完全可以用到现实生活中去,比如说手写体识别、车牌识别等等,但是现实生活中我们还要进行更多的加工,比如说圆形检测,多边形检测等等。所以说从网络安全技术中,也有很多东西能造福社会,还等待我们继续创造!
转自:http://www.williamlong.info/archives/347.html

posted @ 2007-10-09 09:52 Jackey 阅读(570) 评论(0) 编辑

2007年10月6日

Dotnet终极压缩

摘要: 其实是没有什么是终极的,一个抓眼球的标题而已。CLR2.0里带来很多新东东,其中System.IO.Compression就是一个新的命名空间,里面包括两个类GzipStream和DeflaterStream,它们都可以用于解压缩,但是不支持ZIP、RAR等常用的压缩文件,RAR是涉及到专利的问题,而ZIP虽然格式是公开的,但是我想微软不太想支持,它可能更想大家用它自家的私房菜CAB格式,可是Do...阅读全文

posted @ 2007-10-06 01:37 Jackey 阅读(651) 评论(2) 编辑

2007年10月5日

DataGridView重绘代码参考--C#

摘要: 1、CellFormatting事件,一般重绘单元格属性。 private Bitmap highPriImage; private Bitmap mediumPriImage; private Bitmap lowPriImage;private void dataGridView1_CellFormatting(object sender, System.Windows.Forms.Data...阅读全文

posted @ 2007-10-05 03:46 Jackey 阅读(2646) 评论(0) 编辑

2007年10月4日

DataGridView右键选中某单元格-C#

摘要: //添加下面事件即可private void DataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) { if (e.RowIndex >= 0) { if (e.Button == System.Windows.Forms.MouseButtons.Right) { this.Rows[e.Ro...阅读全文

posted @ 2007-10-04 19:09 Jackey 阅读(384) 评论(0) 编辑

仅列出标题  下一页
 
Powered by:
博客园
Copyright © Jackey