当前位置:编程学习 > 网站相关 >>

BCB快速打造多线程端口扫描器

我把另外两篇列出来:

自己利用BCB写病毒专杀  http://www.zzzyk.com/kf/200711/20534.html

利用BCB自己打造QQ炸弹  http://www.zzzyk.com/kf/200710/19348.html

文章作者:灰狐
原始出处:灰狐s Blog ( www.HuiHu32.cN )

很久没发些什么了,整天只来学习而不奉献总感觉有点什么,呵呵,就把这篇拙作发出来吧,通过本文您能学到的东西是理解BCB中对于多线程类的详细使用方法。最近已经陆续开始考试了,唉,一个比一个头大。

注:本文已发表在2007年《黑客防线》第12期,网络首发地址为作者灰狐s Blog(www.huihu32.cn),
    转载请注明出处!

        最近复习Winsocket编程,才发现自己以前很多东西都忘的差不多了,以前只是简单地看书,然后把书上那段TCP的代码抄下来运行一遍成功后就自认为已经理解了网络通信编程。现在才知道自己是多么可笑,唉,希望大家引以为戒喔。
    对于编程,根据个人的学习经验:多写是必须的,不管你看过多少代码。通常可以先定下一个难度不算太大(起码你知道大概可以怎样实现)的任务,然后就着手认真去做,遇到了问题就查资料,或参考别人的某个功能实现思路,坚持做完后你就会进步很多。
    今天我们就要做一个扫描TCP端口是否开放的程序,原理很简单,就是写一个TCP的客户端程序,然后connect某个端口,根据是否响应来确认是否开放。当然了,它很容易被误导(譬如有防火墙的因素),不过我们重在练习,先做出来再说。(其实,本文的重点在于后面的多线程类使用,千万别到这里就扔下了哦)
    首先用VC写一个演示的控制台程序,查看是否能达到目的,代码如下:(注:本文所有演示代码均有不同程度删减,完整版本请查看附带的源工程)
#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32")

#define START 80        //起始端口
#define END  1025    //终止端口

int main(int argc,char *argv[])
{
    int i;
    WSADATA ws;
    SOCKET sockfd;
    struct sockaddr_in their_addr;
//初始化加载库
    WSAStartup(MAKEWORD(2,2),&ws);
//设置连接信息
    their_addr.sin_family = AF_INET;
    their_addr.sin_addr.S_un.S_addr = inet_addr(argv[1]);    //根据命令行参数1确定扫描IP
    for(i=START;i<=END;i++)
    {    //循环建立socket后连接
        sockfd = socket(AF_INET,SOCK_STREAM,0);
        their_addr.sin_port = htons(i);
        printf("正在扫描端口:%d ",i);
        if(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == SOCKET_ERROR)
        {    //如果连接失败直接进行下个端口的扫描
            continue;
        }
//否则认为此端口开放
        printf(" 端口 %d 开放! ",i);
    }
    closesocket(sockfd);
    WSACleanup();
    return 0;
}
整个代码的大体流程就是这样,非常简单,估计没人看不懂,先看一下效果,进入程序目录,拿www.sohu.com来测试吧,看我的截图图1:

    不过这个程序中存在着一些非常明显的问题:程序执行速度极慢(即使是在扫描本机的时候每个端口也需要将近1秒的时间);受网络影响明显,比如我在测试sohu的时候一次扫描的很正常,但另一次却卡在81端口了;界面不够美观,呵呵,总得考虑用户体验度吧。
为了解决以上问题,今天我完全使用BCB 6.0来做这个程序,开发平台是Win XP SP2,采用多线程技术。(程序完整源工程已经打包附上,附有详细注释,可直接编译运行)
    在附带的源程序中我做了详细的注释,本文中着重要讲的地方是怎样利用BCB中提供的多线程功能来使我们的程序执行速度得到大幅度提高。
    直接使用API进行多线程编程通常来说要考虑的东西比较多,幸运的是BCB提供了一个功能强大的多线程类,但我们不能直接使用它,需要先派生出一个子类,然后在这个子类中完成具体的功能。派生子类的方法是使用向导:File|New|Thread Object,在Class Name中输入TScanThread后直接确定,进入代码编辑,这时可以看到向导生成的类中包含了两个方法:TScanThread和Execute,分别用来进行初始化和执行具体代码。下面我详细讲一下怎样使用它,因为这个类并不能直接使用主窗体中的控件,但很显然我们通常都需要与窗体中的各种控件进行联系。
    假如在线程类中你要向主窗体的ScanResultMemo控件中添加扫描结果,可以这样做:首先在线程类的头文件中声明一个局部变量TMemo *AMemo;然后修改TScanThread这个构造函数,给添加一个形参改成如下:__fastcall TScanThread(bool CreateSuspended,TMemo *ResultMemo),并在实现过程中加入代码AMemo = ResultMemo;将构造函数的参数传递给局部变量AMemo,而在此类中是可以不受限制地使用AMemo这个变量的;在主窗体代码中创建线程的时候把实参传递给线程类的构造函数如下:
TScanThread test=new TScanThread(false,ScanResultMemo);
这样就完成了主窗体与线程类的联系。
    从上面的过程中可以看出是比较麻烦的,所以我们通常需要实现做好规划,尽量减少需要传递的参数数量,否则如果线程类比较多的话最后它们之间错综复杂的交错关系一定会让你大脑崩溃的。
    另外,通常不要把需要完成的功能代码直接写在线程类的Execute方法中,比较好的做法是先写一个成员函数,然后在Execute方法中调用这个函数;比如本程序中就是先声明了函数void __fastcall ScanPort();完成扫描功能,然后在Execute方法中调用它。在线程类的函数中除了构造函数外其他函数尽量不要带参数,否则很容易出错,需要使用参数的地方就使用成员局部变量解决。
    汗,上面说的似乎比较混乱(什么似乎,本来就乱,该打~~),不过大家一定要理顺关系哦,不然就麻烦了,连它是怎么运作的都不懂怎么控制它呢?这种机制初学起来似乎很是让人迷茫,但只要搞懂了它运行的流程就一点难度都没有了,真正的RAD啊。
说了这么多,估计有些人已经头晕了,还是看代码直接感受一下吧:
线程类的构造函数如下,注意参数已经修改了,头文件中也要修改:
__fastcall TScanThread::TScanThread(bool CreateSuspended,int iPort,char *sIp,TMemo *ResultMemo) : TThread(CreateSuspended)
{    //port、AMemo、IpBuffer是分别在头文件中定义的局部变量
    port = iPort;
    AMemo = ResultMemo;
    sprintf(IpBuffer,"%s",sIp);
}
新添加一个执行核心功能(即完成扫描)的函数如下:
void __fastcall TScanThread::ScanPort()
{
    WSAStartup(MAKEWORD(2,2),&ws);
    sockfd = socket(AF_INET,SOCK_STREAM,0);

    their_addr.sin_family = AF_INET;
    their_addr.sin_addr.S_un.S_addr = inet_addr(IpBuffer);
    their_addr.sin_port = htons(port);

    if(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == SOCKET_ERROR)
    {
        return;
    }
    AMemo->Lines->Add(" 开放端口:"+AnsiString(port));

    closesocket(sockfd);
    WSACleanup();

    return;
}
跟最上面的那段测试代码重复了,这里仍然贴出来是为了让大家真正从代码中理解参数传递的过程,这里使用的是构造函数中初始化后的局部变量。
Execute方法的实现为:
void __fastcall TScanThread::Execute()
{
    ScanPort();
}
很简单,只有一句函数调用。其实Borland推荐的做法是Synchronize(ScanPort);这样调用可以自动管理防止不同线程实体访问资源冲突,但也可以不用这种方法。
我们再来看一下在主窗体中该怎样启动这些线程,我们需要将IP,端口等传递给它们。注意不要忘了包含线程类的头文件。
在头文件中定义:TScanThread *thread[1000];
实现启动线程的核心代码为:
for(i=StrToInt(StartPortEdit->Text);i<=StrToInt(EndPortEdit->Text);i++)
{
thread[i-StrToInt(StartPortEdit->Text)

补充:综合编程 , 安全编程 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,