分享自己做的一个系统工具--CME [友情转载]

网友投稿 669 2022-09-25

分享自己做的一个系统工具--CME [友情转载]

分享自己做的一个系统工具--CME [友情转载]

原址

实在不知道该给这个东东起什么名字,但是想了想,跟主机通讯有关,而且采用了多线程机制,能高效点,就结合了我的QQ昵称,称之为

CME (cute messenger的意思)吧

修改历史

###############################

2008-08-28 07:32【cme_scanner_v1.1.rar】: 参考Zer4tul 建议,cme.sh中"#! /usr/local/bin/expect" 改为 "#! /bin/env expect",原因,每个人机器上expect的安装目录可能不一致,为了程序的兼容性更好,也就是在安装时不需要修改cme.sh,进行此修改。

################################

附件是完整的代码,rar格式的,VS打开DSP文件,或者linux下直接make就行(大家顶啊^_^)

序言

之前看到有好几个人发了用expect实现ssh,FTP自动登陆时遇到问题的帖子,自己也想搞明白这是怎么做的,可是一直身懒都没去涉及,

不幸的是(^_^),两周前项目中遇到一个类似问题,就花了几天时间匆匆完成了任务。

这几天闲了点,把与业务无关的部分剔除了,自己另做了个程序,比较通用,简单。主要功能就是:

利用expect脚本实现在远程服务器上执行命令,而cute messenger之所以加上了cute,因为在这里实现了用多线程对命令的

并发执行过程的封装,速度比单线程要快很多。

此贴的目的:

1):希望提供这个工具能帮助改进某些朋友的工作,如果可能的话,呵呵。

(2):其次,作为C语言多线程程序设计的一个应用例子吧,其实更细致点说,也是expect自动化程序在ssh服务上的一个应用实例。

(3):能够收获更多人有益的建议,对它进行改进。

(4):能提高本版的人气,众人拾柴火焰高嘛

初识SSH的惊喜与遗憾

从05年到现在三年使用ssh这个工具,我对它的几乎一无所知,就知道用ssh登录Linux系统执行点命令,或者直接窗口拖动传文件。直到前不久的一 天,闲来无事,在linux下随便看看东西,man了一下ssh,发现有个command字眼,仔细看了下,嚄,原来ssh支持登录时传递命令阿。比如用

[Copy to clipboard]

[ - ]

CODE: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程序来解释。

下面是一个自动登录制定主机并执行用户命令的例子:

[Copy to clipboard]

[ - ]

CODE:########### 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上的日期的话,并不需要直接登录,而只需要执行命令:

[Copy to clipboard]

[ - ]

CODE:./auto_login.sh 192.168.1.100 password “date”

就能看到结果了。

其实你可以用该方法来执行任何命令。

好了,我们现在可以稍微做点复杂的应用了,比如你有10台机器,想看看这10台机器上/tmp目录有多大了,决定是否要删除它们。

假设这10台机器的IP地址是以点分式存储在一个文件ip.conf中的,每行一个地址,而登录它们的root密码都是相同的,为123456,那么你就可以做这样一个脚本来完成你的任务:

[Copy to clipboard]

[ - ]

CODE:############### 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-8-28 07:35 编辑

]

2008-8-27 21:06

-次数: 3

cme_scanner.rar

(8.28 KB)2008-8-28 07:35

-次数: 0

cme_scanner_v1.1.rar

(8.27 KB)

第五:线程池的概念

struct thread_slot_t

{

struct     thread_t thread_list[MAX_THREADS];//线程队列

int      thread_num_run;//本意为运行的线程数,可在本程序中却没有用到,没有意义

};

关键代码片段

下面把比较重要和需要注意的代码片段列出来进行讲述。

第一:线程池创建,调用系统相关的线程创建方法来创建线程组,个数为50个或者在头文件中修改定义,对于这段代码,在windows下和linux下都是 能编译通过并运行的,本程序唯一不能在windows上执行指出就是自动登录脚本的问题,因为我还没有尝试在windows上实现这个脚本,呵呵,没时间 搭建windows上的expect环境。

第二:由于多线程并发引起的频繁连接以及关闭会导致系统迅速出现很多22端口的状态为TIME_WAIT的TCP连接,当数目达到较高时,ssh连接就会失败,所以需要修改系统设置,这里采用如下命令

[Copy to clipboard]

[ - ]

CODE:/sbin/sysctl -w -.ipv4.tcp_max_syn_backlog=4096 > /dev/null

第三,是多线程任务分配方法

在这里我们采用多线程平均分配的方法,而且在分配时,每个线程分配到的总是配置文件中相邻顺序的若干个IP地址,这样方便在打印结果时,使得每个IP地址的出现顺序与配置文件中的出现顺序一致。

比如2000个IP,100个线程,每个线程20个IP,第一个线程分配IP地址为1-20,第二个为21-40,第100个分配为1981-2000.

代码片段如下:

if(cme_config.dev_size % MAX_THREADS == 0)

{

per = cme_config.dev_size / MAX_THREADS;

pos = MAX_THREADS;

}else

{

per = cme_config.dev_size / MAX_THREADS + 1;

pos =   MAX_THREADS + cme_config.dev_size -   MAX_THREADS * per;

}

if(per > MAX_THREAD_DEV)

{

printf("too many device [max = %d ,now = %d]\n", MAX_DEV, cme_config.dev_size);

return 0;

}

for(i = 0; i < MAX_THREADS; i++)

{

int j = 0;

int max = (i < pos) ? per : (per - 1);

for(j = 0; j < max; j++)

{

memcpy(&(thread_slot->thread_list[i].parameter.dev_list[j]),

&dev_list[nRet], sizeof(struct dev_t));

thread_slot->thread_list[i].parameter.dev_num++;

thread_slot->thread_list[i].status = t_state_ready;

nRet++;

}

}

第四,线程执行函数体

该模型比较简单,线程在自己的多个状态之间循环,该做什么事情做什么事情,如果为dead状态就退出,如果为ready状态就执行任务,然后修改为free状态。代码片段如下:

switch(pthread->status)

{

case t_state_over:

case t_state_free:

{

SLEEP(1);

break;

}

case t_state_dead:

{

printf("thread %d now exits..\n", pthread->thread_index);

return 0;

}

case t_state_ready:

{

pthread->status = t_state_running;

do_work_thread(pthread);

pthread->status = t_state_free;

break;

}

case t_state_running:

{

break;

}

default:

{

SLEEP(1);

break;

}

}//end of switch

}//end of whil

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:12强赛赢过日澳,阿曼主帅放言战国足“必须赢”!(国足12强赛战澳大利亚)
下一篇:FreeBSD学习总结[zt]
相关文章

 发表评论

暂时没有评论,来抢沙发吧~