|
最近复习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 \n",i); if(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == SOCKET_ERROR) { //如果连接失败直接进行下个端口的扫描 continue; } //否则认为此端口开放 printf("\n\t端口 %d 开放!\n\n",i); } closesocket(sockfd); WSACleanup(); return 0; } |
整个代码的大体流程就是这样,非常简单,估计没人看不懂,先看一下效果,进入程序目录,拿www.sohu.com来测试吧。
不过这个程序中存在着一些非常明显的问题:程序执行速度极慢(即使是在扫描本机的时候每个端口也需要将近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方法中调用它。在线程类的函数中除了构造函数外其他函数尽量不要带参数,否则很容易出错,需要使用参数的地方就使用成员局部变量解决。
[1] [2] 下一页 |