- 论坛徽章:
- 0
|
实在不知道该给这个东东起什么名字,但是想了想,跟主机通讯有关,而且采用了多线程机制,能高效点,就结合了我的QQ昵称,称之为
CME (cute messenger的意思)吧:wink:
联系方式:EMAIL:duanjigang1983@126.com
修改历史
###############################
2008-08-28 07:32【cme_scanner_v1.1.rar】: 参考Zer4tul 建议,cme.sh中"#! /usr/local/bin/expect" 改为 "#! /bin/env expect",原因,每个人机器上expect的安装目录可能不一致,为了程序的兼容性更好,也就是在安装时不需要修改cme.sh,进行此修改。
2008-09-03 13:25 【cme_scanner_v1.2.tar.gz】:根据建议ecjtubaowp,将函数"int init_thread_slot(struct thread_slot_t* thread_slot)"中的状态初始化语句 "thread_slot->thread_list . status=t_state_free;"挪到线程创建之前了,还有就是"do_work_dev(struct dev_t *pDev, FILE* fp)"函数中语句
- if(flag >= 1)
- {
- szline[strlen(szline) - 1] = '\0';
- fprintf(fp, "%s\n", szline);
- flag++;
- }
复制代码 改成- if(flag >= 1)
- {
- fprintf(fp, "%s\n", szline);
- flag++;
- }
复制代码 ,因为前面已经把换行符去掉了。要不然会少一个字符的
################################
附件是完整的代码,rar格式的,VS打开DSP文件,或者linux下直接make就行(大家顶啊^_^)
序言
之前看到有好几个人发了用expect实现ssh,FTP自动登陆时遇到问题的帖子,自己也想搞明白这是怎么做的,可是一直身懒都没去涉及,
不幸的是(^_^),两周前项目中遇到一个类似问题,就花了几天时间匆匆完成了任务。
这几天闲了点,把与业务无关的部分剔除了,自己另做了个程序,比较通用,简单。主要功能就是:
利用expect脚本实现在远程服务器上执行命令,而cute messenger之所以加上了cute,因为在这里实现了用多线程对命令的
并发执行过程的封装,速度比单线程要快很多。
此贴的目的:
1):希望提供这个工具能帮助改进某些朋友的工作,如果可能的话,呵呵。
(2):其次,作为C语言多线程程序设计的一个应用例子吧,其实更细致点说,也是expect自动化程序在ssh服务上的一个应用实例。
(3):能够收获更多人有益的建议,对它进行改进。
(4):能提高本版的人气,众人拾柴火焰高嘛:mrgreen:
初识SSH的惊喜与遗憾
从05年到现在三年使用ssh这个工具,我对它的几乎一无所知,就知道用ssh登录Linux系统执行点命令,或者直接窗口拖动传文件。直到前不久的一天,闲来无事,在linux下随便看看东西,man了一下ssh,发现有个command字眼,仔细看了下,嚄,原来ssh支持登录时传递命令阿。比如用- ssh [email]root@192.168.1.100[/email] “date”
复制代码 就能查看192.168.1.100上的日期了。
呵呵,这个功能确实让我欢喜了一阵子,可是后来,又发现ssh有个缺点,就是不能自动登录,每次都要手动输入密码,太烦人了。加上前一段时间项目里经常涉及到好多台机器的相同操作,我越发觉得这个限制很烦人。于是便想搞个SSH自动登录。就在baidu上面找。
发现的大多数帖子都说要给目的机器拷贝一个密钥文件,这样以后就不需要输入密码了。
但是又觉得不对劲没,如果从A访问B时不需要输入密码,那么B机器岂不是很不安全,如果有人用ROOT登录了A机器,岂不是就跟登录了B机器一样,因为虽然他并不知道登陆B机器的密码,但是以前那个密钥文件却已经埋下了隐患。这个时候,恍然明白,原来我想要得并不是不需要输入密码的功能,而是能自动输入密码的功能。另外,还有一个问题,即时对其它Linux机器的访问不要密码,但是要实现自动登录(或者无密码登录),第一次总要手动拷贝密钥文件到目的机器,如果有2000台目标机器,岂不是会累死。所以我放弃了这种方法的尝试。
expect又带来希望
为了实现ssh自动登录功能,我又开始在网络上搜寻,在Q群里问了下,有人说用expect脚本可以做,便在baidu搜索expect脚本,真是兴奋阿,在CU某人的博客里找到了一个用epect实现的ssh自动登录的例子,其实就是一段代码,用expect程序来解释。
下面是一个自动登录制定主机并执行用户命令的例子:- ########### auto_login.sh #####################
- 1 #!/usr/local/bin/expect
- 2 set PASSWD [lindex $argv 1]
- 3 set IP [lindex $argv 0]
- 4 set CMD [lindex $argv 2]
- 5 spawn ssh $IP $CMD
- 6 expect "(yes/no)?" {
- 7 send "yes\r"
- 8 expect "password:"
- 9 send "$PASSWD\r"
- 10 } "password:" {send "$PASSWD\r"} "*host " {exit 1}
- 11 expect eof
复制代码 我没有对expect的语法进行很详细的研究,只是大概理解了这段代码,下面根据自己的理解说下它的意思:
第一行制定使用/usr/local/bin目录下的expect命令对后面的程序进行解释。
第二行,三行,四行,分别从命令行参数中获取要登录的主机IP地址,登陆密码,以及要执行的命令。
第五行,大概就是要触发这样一个事件,执行ssh $IP $CMD命令。
第6行道第11行就是expect的整个交互过程了。
如果读取到(yes/no)?提示符,就输入yes并回车,如果读取到password:提示输入密码的字符串,就输入用户登录密码(root用户)。
当然如果不是第一次登陆,以前已经登录过的话,当输入ssh $IP $CMD回车后,会直接提示输入密码也就是说会读到字符串”* password:”,这个时候会输入密码回车(send "$PASSWD\r").
另外,如果主机不可达的话,(yes/no)?和”password:”的可能都不会出现,系统会提示:
“No route to host”这个时候,我们退出程序。
所以,如果你想查看192.168.1.100上的日期的话,并不需要直接登录,而只需要执行命令:- ./auto_login.sh 192.168.1.100 password “date”
复制代码 就能看到结果了。
其实你可以用该方法来执行任何命令。
好了,我们现在可以稍微做点复杂的应用了,比如你有10台机器,想看看这10台机器上/tmp目录有多大了,决定是否要删除它们。
假设这10台机器的IP地址是以点分式存储在一个文件ip.conf中的,每行一个地址,而登录它们的root密码都是相同的,为123456,那么你就可以做这样一个脚本来完成你的任务:- ############### single_thread_auto_run.sh ############
- #!/bin/sh
- cat ip.conf | while read ip
- do
- echo “####### $ip #########” >> result.txt
- /usr/local/bin/expect auto_login.sh $ip 123456 “du –sh /tmp” >> result.txt
- echo “Running command on $ip over”
- done
复制代码 如果没有什么问题的话,对于10台机器也就是1分钟左右的时间或者四五十秒的时间就能够执行结束,而且结果会存储在result.txt文件中。
但是,现实情况并不像实验中的那么简单,现在大型企业的服务器或者Linux主机动不动几百台,或者动辄上千台,如果将上面的脚本应用到一个2000台主机的任务当中去,我测试过,通过auto_login.sh执行一个主机大概需要3-5秒的时间,这样理论上讲,对于2000台机器,在正常联通情况下,要串行执行完所有任务,也就是说在每台机器上完成这个任务,大概需要6000秒到1万秒的时间,大概为1小时40分钟到2小时40分钟的时间,这对于一个普通的网管人员或者上层管理人员来说肯定是不能忍受的。看来,expect是提供了曙光,可是并不能完全解决问题啊。继续郁闷^_^
多线程加速
在第三节的最后,我们落脚到了性能的瓶颈问题上,由于数量引起的性能下降是不可避免的,所以我们尝试寻找一种方法解决此问题,也就是提高性能。可能有的人立即会想到为何不启动多个single_thread_auto_run.sh程序?这个是一个不错的建议,可是启动多少个才算合适呢?而且这样也需要为每个进程分配制定的IP地址个数,而且每个进程执行的结果是独立的,进程数目少了执行慢,进程数目多了又需要把IP地址分成更多的份数,将来的结果又很分散,还需要手动合并或者又需要写程序去合并,这么做又会增加一些额外的难度和工作。
如果能让程序自动分配IP地址,并自己汇总执行结果,而且能执行更快的话,那更好了。
这个时候,我想到了用C实现的多线程程序来完成这个任务。
CME的设计与实现
CME的功能和原理其实很简单,它的目的就是实现高效的执行我们的shell脚本想要实现的功能,原理也没有多复杂,下面我大概描述大致的工作过程。
首先,程序从命令行读取IP地址的来源文件,登录密码和命令字符串。
接着,从数据文件中读取IP地址列表填充到数据结构中,也就是设备队列中。
下来,程序创建线程池对象。
然后,将设备队列中的所有IP地址对应的设备信息加入到线程队列中,平均分配给每个线程。
分配任务完成后,主线程等待线程池中工作线程执行结束。
线程池的工作线程都已经结束时,主线程遍历线程队列,打印每个线程执行的结果。
最后,退出程序。
数据结构
下面列出并介绍下程序中用到的5个结构体:
第一
struct config_t
{
char data_file[FILE_NAME_LEN];//存储IP列表的文件
char password[PASS_LEN];//存储密码
char command[CMD_LEN];//存储命令
int dev_size;//设备数量
};
| 用来存储整个程序的配置信息。
第二 ,IP地址对应设备的抽象
struct dev_t
{
unsigned long addr; //ip 地址
int valid; //有效标志位
int retur_value; //返回值类型
char result_file[FILE_NAME_LEN];//存储返回结果的文件
int id; 设备ID
}; |
第三,线程体的参数结构体
struct thread_param_t
{
struct dev_t dev_list[MAX_THREAD_DEV];//该线程要处理的IP地址列表
int dev_num;//IP地址的个数
int valid;//参数有效性
};
|
第四,线程结构体,为了使程序不依赖于平台,采用了linux和windows通用的的结构定义
线程的创建也是linux和windows都支持的。
struct thread_t
{
#ifdef __LINUX__
pthread_t thread_id;//线程标识符
#endif
#ifdef _WIN32
DWORD thread_id;
#endif
int thread_index; //线程编号
enum thread_status_t status;//线程的执行状态
struct thread_param_t parameter; //线程参数
char result_file[FILE_NAME_LEN];//结果文件名
int sunccess_num;//执行成功的数目
int fail_num;//执行失败的数目
};
|
[ 本帖最后由 duanjigang 于 2008-9-3 14:12 编辑 ] |
|