Java通讯框架,有时也被称为网络服务器,其实就是封装IO操作,并提供更高级的API接口。目前比较流行的框架就是:MINA、Cindy、QuickServer等。前段时间为了缩短开发时间,也在项目中加入了这些框架,从最开始使用QuickServer 1.4.7出现的报文接收不全异常;到用Cindy 2.4.4作为临时过渡,到最后确定使用MINA 1.0.1,也经历了一个艰辛而痛苦的时期。下面是整理的目前网上关于Java通讯框架比较好的文章,仅供大家参考。



循证架构之QuickServer篇

作者:LeonW

“循证架构”一词虽是新创,但也可算是新瓶装老酒了。Rod用之来循证Spring等轻量级框架之优势、EJB之劣势。

前一段做了个GPRS服务器的系统,也尝试了把“循证架构”的循证思路。竟然要循证,总得先列举下需求:

首先,服务器必须与GPRS发送设备进行通讯, 数据的接收、发送、检查链路状态等基本功能,还需要具体分析包文,组包等业务功能的处理。其次,工期很紧,风险分析中列为第一大风险。再者,服务器的稳定性、可靠性也是一大重点。最后,需要提供对服务器管理的接口。

手头也有些较为成熟的网络通讯的基础代码,拿来复用解决1,3问题也非难事,倒是4需要进行开发。不过早就听闻 Mina、Cindy等大名(下载Mina代码过程中反倒发现本文的主角QuickServer),于是就对这几个框架循证了一遍,寻找合适开发的网络通 讯框架。

从功能上看,Mina、Cindy、QuickServer实现的功能也基本差不多,都是封装基本的网络IO操作,暴露事件接口供二次开发。

Mina需要继承IOHandlerAdapter类或ProtocolHandlerAdapter类,从该类中继承各事件方法实现业务操作。 Cindy也需要继承SessionHandlerAdapter具体类,跟Mina类似。QuickServer则需要实现接口 ClientEventHandler实现业务功能。这些类和接口提供的事件大致雷同:Connected,Closed,Received,Sent, Timeout等等。

Mina和Cindy使用了nio,而QuickServer则同时支持blockingIO和nio,需要进行配置选择(一个遗憾就 是QuickServer的nio模式下竟然不支持Timeout事件)。Mina实现中有一个很好的设计就是剥离IO层和Protocol层,作者的用 意是想在IO层实现对IO的处理,在Protocol实现对业务的处理,很好的一个分层思想。QuickServer则是一个大杂烩,把各种IO处理封装 在 BasicClientHandler中,里面还混杂了blockingIO、NIO等操作。

额外功能上,Mina和Cindy都可以 支持一个进程中开启多个服务端口进行不同处理,而QuickServer则不行。不过QuickServer提供了另外一个非常实用的功能-管理服务端 口,通过其设定的一些指令查询服务器的状态、控制服务器等。此功能成为最后选择的最大优势。其他例如IP过滤的功能在QuickServer中只需要进行 配置即可。

这次没对这三个服务器在性能上进行比较。以后再找机会完成:-)

从代码质量上来看,无疑Mina和Cindy占了 上风,QuickServer的分层实在不咋样。Mina和Cindy有不少相似的地方。而QuickServer的目的与其有所不同,集成许多方便的功 能,甚至还加入数据库连接池的功能(感觉作者有点无聊了)。最后的循证其实还是选了个最实用的工具,的确减少不少的工作量。此后还遇到一些二进制的问题就 不表了。



MINA is a good framwork

作者:fisher

Netty2的作者TrustinLee在为Apache LDAP项目所作的通讯基础框架MINA中显示了在通讯框架方面雄厚的实力,MINA是迄今为止我见过在java领域最好的通讯基础件,看得出,他通过Netty2的经验积累加上对ACE等传统大型框架的理解之后,在制作MINA的一开始就确定了一个近似于完美的架构,同时,我在RoadMap中看到MINA与Spring、JMX和OSGI的结合计划,虽然不知道什么时候能够完成,但光看这个RoadMap已经很让人激动了。

在MINA的服务绑定上,一开始就使用了serviceRegistry类这种中控型的注册绑定方式,看得出他对OSGI有一定研究并已决意向其靠拢。

而借鉴于ACE的Accepter和Connector结构使得Session的使用更加方便,同时分为IO层和Protocol两层的通讯基础件也是使得使用变得很方便。

最后要提一下的是作者使用的FilterChain式结构来加载Filter,使得很多非通讯核心问题得以从基础件中剥离出来,甚至连线程池模式都可以使用Filter来指定,虽然自己制作的线程池要想结合到MINA中需要一些额外的努力,但是仍然极大的增加了框架的灵活性。



MINA vs. QuickServer

作者:fisher

First for all, QuickServer is licensed as LGPL, and MINA as ASL.

从我个人角度而言,去年看过QuickServer的源码,我在项目中采用的每一个框架或类库都会做综合评价,通常不会是一个原因导致我采用或没有采用某个库或框架,具体最后没有采用QuickServer的原因忘记了,但是当时给我的总体感觉是,QuickServer虽然很方便,但不会让我在架构上得到新的好处。而它最大的优点则是,支持JDK1.3(如果没记错的话),另外就是License的问题

下面看一看来自TrusinLee的评论:

Thank for the information about another network application framework.  I found a few differences:

Ø         QuickServer supports blocking mode.  (MINA supports only non-blocking mode, but you can make your operation block at your will.)

Ø         QuickServer provides GUI-based admin.  (MINA doesn't have one yet, but will have full JMX support soon, which is a standard.)

Ø         QuickServer uses java.util.logging.  (MINA uses SLF4J, which is a safe replacement of commons-logging.)

Ø         QuickServer uses its own XML settings.  (MINA provides Spring framework integration instead.)

Ø         QuickServer can specify maximum number of clients allowed.  (MINA can do this using a filter, but not implemented by default.  Of course, this will be implemented as an overload prevention filter.)

Ø         QuickServer team has one crew.  (MINA has three crews.)

Ø         QuickServer project started in 2003.  (MINA started in 2005.)

Ø         QuickServer has a difference event handler interface from MINA.  (You'll have to compare it by yourself.  IMHO, MINA has one simple enough handler which covers all QuickServer provides.)

Ø         QuickServer doesn't support UDP at all.  (MINA does)

Ø         QuickServer doesn't support client-side API at all.  (MINA does)

Ø         QuickServer integrated authentication and text protocol in its core.  (MINA didn't and they are considered as a cross-cutting concern that a filter should take care of.  IMHO, MINA is more extensible here.)



Cindy2.x比MINA性能好是可以预见的,原因在于MINA提供的ByteBuffer和FilterChain。

Cindy3.x源代码我没有看,所以不好评价。

关于MINA的效率问题,在MINA的maillist中也被提出,似乎有相应的issue正要被加入到它的Issue Tracker中。

Cindy3.x才刚刚开始,我认为多给Crmky一些时间,他一定可以将架构设计的更好。

MINA在设计上也有少许问题,他的IoFilterChain将FilterManager和FilterChain合而为一,在看其代码的时候会觉得很乱。另外,为了保证包的顺序,一个IoSession上的Handler在上一次read调用没有返回前,是不会被再次调用的。我认为MINA的基础架构在1.0和1.1版本之间还会变化,以适应新加入的configuration方式。另外,MINA会产生一些内存垃圾,我用profiler检查过MINA,似乎是SocketIoProcessor中的某个计数器在不停的产生2byte的什么东东(记不太情了),不过似乎Trustin也注意到这个问题了,最近他说会在1.0release之后改善效率和内存的问题。

你可以到Crmky的blog上发帖子,看看他是否愿意提供一个Cindy3.X和MINA的对比。

总体来说,java的通讯框架设计并不特别注重效率,而追求架构上的优雅,当然,这也和java中本来能够进行效率调优的手段就不多有关系,如果真要优化,可能还是需要使用JDK5.0以上提供的高效的内存操作,另外,据说在Linxu2.6内核以后,Mustang的NIO使用了Linux的epoll来实现select(),也许会对目前的IO效率有所帮助。

posted @ 2010-07-17 15:26 小马歌 阅读(5607) | 评论 (0)编辑 收藏
 
from : http://www.chinaunix.net/jh/17/1286359.html

mysql slow log 是用来记录执行时间较长(超过long_query_time秒)的sql的一种日志工具.
启用 slow log
有两种启用方式:
1, 在my.cnf 里 通过 log-slow-queries[=file_name]2, 在mysqld进程启动时,指定--log-slow-queries[=file_name]选项
比较的五款常用工具
mysqldumpslow, mysqlsla, myprofi, mysql-explain-slow-log, mysqllogfilter
mysqldumpslow, mysql官方提供的慢查询日志分析工具. 输出图表如下:


主要功能是, 统计不同慢sql的
出现次数(Count),
执行最长时间(Time),
累计总耗费时间(Time),
等待锁的时间(Lock),
发送给客户端的行总数(Rows),
扫描的行总数(Rows),
用户以及sql语句本身(抽象了一下格式, 比如 limit 1, 20 用 limit N,N 表示).


mysqlsla, hackmysql.com推出的一款日志分析工具(该网站还维护了 mysqlreport, mysqlidxchk 等比较实用的mysql工具)


整体来说, 功能非常强大. 数据报表,非常有利于分析慢查询的原因, 包括执行频率, 数据量, 查询消耗等.

格式说明如下:
总查询次数 (queries total), 去重后的sql数量 (unique)
输出报表的内容排序(sorted by)
最重大的慢sql统计信息, 包括 平均执行时间, 等待锁时间, 结果行的总数, 扫描的行总数.
Count, sql的执行次数及占总的slow log数量的百分比.
Time, 执行时间, 包括总时间, 平均时间, 最小, 最大时间, 时间占到总慢sql时间的百分比.
95% of Time, 去除最快和最慢的sql, 覆盖率占95%的sql的执行时间.
Lock Time, 等待锁的时间.
95% of Lock , 95%的慢sql等待锁时间.
Rows sent, 结果行统计数量, 包括平均, 最小, 最大数量.
Rows examined, 扫描的行数量.
Database, 属于哪个数据库
Users, 哪个用户,IP, 占到所有用户执行的sql百分比
Query abstract, 抽象后的sql语句
Query sample, sql语句
除了以上的输出, 官方还提供了很多定制化参数, 是一款不可多得的好工具.
mysql-explain-slow-log, 德国人写的一个perl脚本.
http://www.willamowius.de/mysql-tools.html
http://www.bt285.cn/content.php?id=1196863


功能上有点瑕疵, 不仅把所有的 slow log 打印到屏幕上, 而且统计也只有数量而已. 不推荐使用.


mysql-log-filter, google code上找到的一个分析工具.提供了 python 和 php 两种可执行的脚本.
http://code.google.com/p/mysql-log-filter/


功能上比官方的mysqldumpslow, 多了查询时间的统计信息(平均,最大, 累计), 其他功能都与 mysqldumpslow类似.
特色功能除了统计信息外, 还针对输出内容做了排版和格式化, 保证整体输出的简洁. 喜欢简洁报表的朋友, 推荐使用一下.


myprofi, 纯php写的一个开源分析工具.项目在 sourceforge 上.
http://myprofi.sourceforge.net/

功能上, 列出了总的慢查询次数和类型, 去重后的sql语句, 执行次数及其占总的slow log数量的百分比.
从整体输出样式来看, 比mysql-log-filter还要简洁. 省去了很多不必要的内容. 对于只想看sql语句及执行次数的用户来说, 比较推荐.


总结

工具/功能 一般统计信息 高级统计信息 脚本 优势
mysqldumpslow 支持 不支持 perl mysql官方自带
mysqlsla 支持 支持 perl 功能强大,数据报表齐全,定制化能力强.
mysql-explain-slow-log 支持 不支持 perl
mysql-log-filter 支持 部分支持 python or php 不失功能的前提下,保持输出简洁
myprofi 支持 不支持 php 非常精简
本文出自:http://www.blogjava.net/wangdei1/archive/2008/10/07/233030.html
posted @ 2010-07-09 11:22 小马歌 阅读(121) | 评论 (0)编辑 收藏
 

from : http://www.cnblogs.com/edrh/archive/2007/09/04/881054.html

1.首先确认你日志是否启用了
mysql>show variables like 'log_bin';
2.如果启用了,即ON
那日志文件就在mysql的安装目录的data目录下
3.怎样知道当前的日志
mysql> show master status;
                                                                                                                                               
4.看二进制日志文件用mysqlbinlog
shell>mysqlbinlog mail-bin.000001
或者shell>mysqlbinlog mail-bin.000001 | tail

Windows 下用类似的命令。

posted @ 2010-07-09 10:24 小马歌 阅读(819) | 评论 (0)编辑 收藏
 

from : http://www.nioxiao.com/sharing-php-session-data-between-servers

一、问题起源

稍大一些的网站,通常都会有好几个服务器,每个服务器运行着不同功能的模块,使用不同的二级域名,而一个整体性强的网站,用户系统是统一的,即一套用户名、密码在整个网站的各个模块中都是可以登录使用的。各个服务器共享用户数据是比较容易实现的,只需要在后端放个数据库服务器,各个服务器通过统一接口对用户数据进行访问即可。但还存在一个问题,就是用户在这个服务器登录之后,进入另一个服务器的别的模块时,仍然需要重新登录,这就是一次登录,全部通行的问题,映射到技术上,其实就是各个服务器之间如何实现共享 SESSION 数据的问题。

二、PHP SESSION 的工作原理

在解决问题之前,先来了解一下 PHP SESSION 的工作原理。在客户端(如浏览器)登录网站时,被访问的 PHP 页面可以使用 session_start() 打开 SESSION,这样就会产生客户端的唯一标识 SESSION ID(此 ID 可通过函数 session_id() 获取/设置)。SESSION ID 可以通过两种方式保留在客户端,使得请求不同的页面时,PHP 程序可以获知客户端的 SESSION ID;一种是将 SESSION ID 自动加入到 GET 的 URL 中,或者 POST 的表单中,默认情况下,变量名为 PHPSESSID;另一种是通过 COOKIE,将 SESSION ID 保存在 COOKIE 中,默认情况下,这个 COOKIE 的名字为 PHPSESSID。这里我们主要以 COOKIE 方式进行说明,因为应用比较广泛。

那么 SESSION 的数据保存在哪里呢?当然是在服务器端,但不是保存在内存中,而是保存在文件或数据库中。默认情况下,php.ini 中设置的 SESSION 保存方式是 files(session.save_handler = files),即使用读写文件的方式保存 SESSION 数据,而 SESSION 文件保存的目录由 session.save_path 指定,文件名以 sess_ 为前缀,后跟 SESSION ID,如:sess_c72665af28a8b14c0fe11afe3b59b51b。文件中的数据即是序列化之后的 SESSION 数据了。如果访问量大,可能产生的 SESSION 文件会比较多,这时可以设置分级目录进行 SESSION 文件的保存,效率会提高很多,设置方法为:session.save_path="N;/save_path",N 为分级的级数,save_path 为开始目录。当写入 SESSION 数据的时候,PHP 会获取到客户端的 SESSION_ID,然后根据这个 SESSION ID 到指定的 SESSION 文件保存目录中找到相应的 SESSION 文件,不存在则创建之,最后将数据序列化之后写入文件。读取 SESSION 数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 SESSION 变量。

三、多服务器共享 SESSION 的主要障碍及解决办法

通过了解 SESSION 的工作原理,我们可以发现,在默认情况下,各个服务器会各自分别对同一个客户端产生 SESSION ID,如对于同一个用户浏览器,A 服务器产生的 SESSION ID 是 30de1e9de3192ba6ce2992d27a1b6a0a,而 B 服务器生成的则是 c72665af28a8b14c0fe11afe3b59b51b。另外,PHP 的 SESSION 数据都是分别保存在本服务器的文件系统中。如下图所示:

image

确定了问题所在之后,就可以着手进行解决了。想要共享 SESSION 数据,那就必须实现两个目标:一个是各个服务器对同一个客户端产生的 SESSION ID 必须相同,并且可通过同一个 COOKIE 进行传递,也就是说各个服务器必须可以读取同一个名为 PHPSESSID 的 COOKIE;另一个是 SESSION 数据的存储方式/位置必须保证各个服务器都能够访问到。简单地说就是多服务器共享客户端的 SESSION ID,同时还必须共享服务器端的 SESSION 数据。

第一个目标的实现其实很简单,只需要对 COOKIE 的域(domain)进行特殊地设置即可,默认情况下,COOKIE 的域是当前服务器的域名/IP 地址,而域不同的话,各个服务器所设置的 COOKIE 是不能相互访问的,如 www.aaa.com 的服务器是不能读写 www.bbb.com 服务器设置的 COOKIE 的。这里我们所说的同一网站的服务器有其特殊性,那就是他们同属于同一个一级域,如:aaa.infor96.com 和 www.infor96.com 都属于域 .infor96.com,那么我们就可以设置 COOKIE 的域为 .infor96.com,这样 aaa.infor96.com、www.infor96.com 等等都可以访问此 COOKIE。PHP 代码中的设置方法如下:

 


<?php
ini_set('session.cookie_domain''.infor96.com');
?>

这样各个服务器共享同一客户端 SESSION ID 的目的就达到了。

第二个目标的实现可以使用文件共享方式,如 NFS 方式,但设置、操作上有些复杂。我们可以参考先前所说的统一用户系统的方式,即使用数据库来保存 SESSION 数据,这样各个服务器就可以方便地访问同一个数据源,获取相同的 SESSION 数据了。

解决办法如下图所示:

image

四、代码实现

首先创建数据表,MySQL 的 SQL 语句如下:

 

   CREATE TABLE `sess` (
`sesskey` varchar(32) NOT NULL default '',
`expiry` bigint(20) NOT NULL default '0',
`data` longtext NOT NULL,
PRIMARY KEY  (`sesskey`),
KEY `expiry` (`expiry`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

sesskey 为 SESSION ID,expiry 为 SESSION 过期时间,data 用于保存 SESSION 数据。

默认情况下 SESSION 数据是以文件方式保存,想要使用数据库方式保存,就必须重新定义 SESSION 各个操作的处理函数。PHP 提供了session_set_save_handle() 函数,可以用此函数自定义 SESSION 的处理过程,当然首先要先将 session.save_handler 改成 user,可在 PHP 中进行设置:

 


<?php 
session_module_name('user');
?>

接下来着重讲一下 session_set_save_handle() 函数,此函数有六个参数:

 

session_set_save_handler ( string open, string close, string read, string write, string destroy, string gc )

各个参数为各项操作的函数名,这些操作依次是:打开、关闭、读取、写入、销毁、垃圾回收。PHP 手册中有详细的例子,在这里我们使用 OO 的方式来实现这些操作,详细代码如下:

 


<?php
define('MY_SESS_TIME'3600);   //SESSION 生存时长
//类定义
class My_Sess
{
    function init()
    {
        $domain '.infor96.com';
        //不使用 GET/POST 变量方式
        ini_set('session.use_trans_sid',    0);
        //设置垃圾回收最大生存时间
        ini_set('session.gc_maxlifetime',   MY_SESS_TIME);
        //使用 COOKIE 保存 SESSION ID 的方式
        ini_set('session.use_cookies',      1);
        ini_set('session.cookie_path',      '/');
        //多主机共享保存 SESSION ID 的 COOKIE
        ini_set('session.cookie_domain',    $domain);
        //将 session.save_handler 设置为 user,而不是默认的 files
        session_module_name('user');
        //定义 SESSION 各项操作所对应的方法名:
        session_set_save_handler(
            array('My_Sess''open'),   //对应于静态方法 My_Sess::open(),下同。
            array('My_Sess''close'),
            array('My_Sess''read'),
            array('My_Sess''write'),
            array('My_Sess''destroy'),
            array('My_Sess''gc')
        );
    }   //end function
    function open($save_path$session_name) {
        return true;
    }   //end function
    function close() {
        global $MY_SESS_CONN;
        if ($MY_SESS_CONN) {    //关闭数据库连接
            $MY_SESS_CONN->Close();
        }
        return true;
    }   //end function
    function read($sesskey) {
        global $MY_SESS_CONN;
        $sql 'SELECT data FROM sess WHERE sesskey=' $MY_SESS_CONN->qstr($sesskey) . ' AND expiry>=' time();
        $rs =& $MY_SESS_CONN->Execute($sql);
        if ($rs) {
            if ($rs->EOF) {
                return '';
            } else {    //读取到对应于 SESSION ID 的 SESSION 数据
                $v $rs->fields[0];
                $rs->Close();
                return $v;
            }   //end if
        }   //end if
        return '';
    }   //end function
    function write($sesskey$data) {
        global $MY_SESS_CONN;
        
        $qkey $MY_SESS_CONN->qstr($sesskey);
        $expiry time() + My_SESS_TIME;    //设置过期时间
        
        //写入 SESSION
        $arr = array(
            'sesskey' => $qkey,
            'expiry'  => $expiry,
            'data'    => $data);
        $MY_SESS_CONN->Replace('sess'$arr'sesskey'$autoQuote true);
        return true;
    }   //end function
    function destroy($sesskey) {
        global $MY_SESS_CONN;
        $sql 'DELETE FROM sess WHERE sesskey=' $MY_SESS_CONN->qstr($sesskey);
        $rs =& $MY_SESS_CONN->Execute($sql);
        return true;
    }   //end function
    function gc($maxlifetime null) {
        global $MY_SESS_CONN;
        $sql 'DELETE FROM sess WHERE expiry<' time();
        $MY_SESS_CONN->Execute($sql);
        //由于经常性的对表 sess 做删除操作,容易产生碎片,
        //所以在垃圾回收中对该表进行优化操作。
        $sql 'OPTIMIZE TABLE sess';
        $MY_SESS_CONN->Execute($sql);
        return true;
    }   //end function
}   ///:~
//使用 ADOdb 作为数据库抽象层。
require_once('adodb/adodb.inc.php');
//数据库配置项,可放入配置文件中(如:config.inc.php)。
$db_type 'mysql';
$db_host '192.168.212.1';
$db_user 'sess_user';
$db_pass 'sess_pass';
$db_name 'sess_db';
//创建数据库连接,这是一个全局变量。
$GLOBALS['MY_SESS_CONN'] =& ADONewConnection($db_type);
$GLOBALS['MY_SESS_CONN']->Connect$db_host$db_user$db_pass$db_name);
//初始化 SESSION 设置,必须在 session_start() 之前运行!!
My_Sess::init();
?>

五、遗留问题

如果网站的访问量很大的话,SESSION 的读写会频繁地对数据库进行操作,这样效率就会明显降低。考虑到 SESSION 数据一般不会很大,可以尝试用 C/Java 写个多线程的程序,用 HASH 表保存 SESSION 数据,并通过 socket 通信进行数据读写,这样 SESSION 就保存在内存中,读写速度应该会快很多。另外还可以通过负载均衡来分担服务器负载。不过这些都只是我自己的一些想法和假设,并没有实践过

posted @ 2010-07-05 11:52 小马歌 阅读(157) | 评论 (0)编辑 收藏
 
from:http://www.nioxiao.com/php-memcached

一、memcached 简介

在很多场合,我们都会听到 memcached 这个名字,但很多同学只是听过,并没有用过或实际了解过,只知道它是一个很不错的东东。这里简单介绍一下,memcached 是高效、快速的分布式内存对象缓存系统,主要用于加速 WEB 动态应用程序。

二、memcached 安装

首先是下载 memcached 了,目前最新版本是 1.1.12,直接从官方网站即可下载到 memcached-1.1.12.tar.gz。除此之外,memcached 用到了 libevent,我下载的是 libevent-1.1a.tar.gz

接下来是分别将 libevent-1.1a.tar.gz 和 memcached-1.1.12.tar.gz 解开包、编译、安装:

# tar -xzf libevent-1.1a.tar.gz
# cd libevent-1.1a
# ./configure --prefix=/usr
# make
# make install
# cd ..
# tar -xzf memcached-1.1.12.tar.gz
# cd memcached-1.1.12
# ./configure --prefix=/usr
# make
# make install

安装完成之后,memcached 应该在 /usr/bin/memcached。

三、运行 memcached 守护程序

运行 memcached 守护程序很简单,只需一个命令行即可,不需要修改任何配置文件(也没有配置文件给你修改 :) ):

/usr/bin/memcached -d -m 128 -l 192.168.1.1 -p 11211 -u httpd

参数解释:

-d 以守护程序(daemon)方式运行 memcached;
-m 设置 memcached 可以使用的内存大小,单位为 M;
-l 设置监听的 IP 地址,如果是本机的话,通常可以不设置此参数;
-p 设置监听的端口,默认为 11211,所以也可以不设置此参数;
-u 指定用户,如果当前为 root 的话,需要使用此参数指定用户。

当然,还有其它参数可以用,man memcached 一下就可以看到了。

四、memcached 的工作原理

首先 memcached 是以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,客户端可以由各种语言编写,目前已知的客户端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客户端在与 memcached 服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符 key,存取操作均通过这个 key 进行,保存到 memcached 中的对象实际上是放置内存中的,并不是保存在 cache 文件中的,这也是为什么 memcached 能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。

image001.png

三、PHP 如何作为 memcached 客户端

有两种方法可以使 PHP 作为 memcached 客户端,调用 memcached 的服务进行对象存取操作。

第一种,PHP 有一个叫做 memcache 的扩展,Linux 下编译时需要带上 –enable-memcache[=DIR] 选项,Window 下则在 php.ini 中去掉 php_memcache.dll 前边的注释符,使其可用。

除此之外,还有一种方法,可以避开扩展、重新编译所带来的麻烦,那就是直接使用 php-memcached-client

本文选用第二种方式,虽然效率会比扩展库稍差一些,但问题不大。

四、PHP memcached 应用示例

首先 下载 memcached-client.php,在下载了 memcached-client.php 之后,就可以通过这个文件中的类“memcached”对 memcached 服务进行操作了。其实代码调用非常简单,主要会用到的方法有 add()、get()、replace() 和 delete(),方法说明如下:

add ($key, $val, $exp = 0)
往 memcached 中写入对象,$key 是对象的唯一标识符,$val 是写入的对象数据,$exp 为过期时间,单位为秒,默认为不限时间;

get ($key)
从 memcached 中获取对象数据,通过对象的唯一标识符 $key 获取;

replace ($key, $value, $exp=0)
使用 $value 替换 memcached 中标识符为 $key 的对象内容,参数与 add() 方法一样,只有 $key 对象存在的情况下才会起作用;

delete ($key, $time = 0)
删除 memcached 中标识符为 $key 的对象,$time 为可选参数,表示删除之前需要等待多长时间。

下面是一段简单的测试代码,代码中对标识符为 'mykey' 的对象数据进行存取操作:


<pre>
<?php
//  包含 memcached 类文件
require_once('memcached-client.php');
//  选项设置
$options = array(
    'servers' => array('192.168.1.1:11211'), //memcached 服务的地址、端口,可用多个数组元素表示多个 memcached 服务
    'debug' => true,  //是否打开 debug
    'compress_threshold' => 10240,  //超过多少字节的数据时进行压缩
    'persistant' => false  //是否使用持久连接
    );
//  创建 memcached 对象实例
$mc = new memcached($options);
//  设置此脚本使用的唯一标识符
$key 'mykey';
//  往 memcached 中写入对象
$mc->add($key'some random strings');
$val $mc->get($key);
echo "n".str_pad('$mc->add() '60'_')."n";
var_dump($val);
//  替换已写入的对象数据值
$mc->replace($key, array('some'=>'haha''array'=>'xxx'));
$val $mc->get($key);
echo "n".str_pad('$mc->replace() '60'_')."n";
var_dump($val);
//  删除 memcached 中的对象
$mc->delete($key);
$val $mc->get($key);
echo "n".str_pad('$mc->delete() '60'_')."n";
var_dump($val);
?>
</pre>

是不是很简单,在实际应用中,通常会把数据库查询的结果集保存到 memcached 中,下次访问时直接从 memcached 中获取,而不再做数据库查询操作,这样可以在很大程度上减轻数据库的负担。通常会将 SQL 语句 md5() 之后的值作为唯一标识符 key。下边是一个利用 memcached 来缓存数据库查询结果集的示例(此代码片段紧接上边的示例代码):


<?php
$sql 'SELECT * FROM users';
$key md5($sql);   //memcached 对象标识符
if ( !($datas $mc->get($key)) ) {
    //  在 memcached 中未获取到缓存数据,则使用数据库查询获取记录集。
    echo "n".str_pad('Read datas from MySQL.'60'_')."n";
    $conn mysql_connect('localhost''test''test');
    mysql_select_db('test');
    $result mysql_query($sql);
    while ($row mysql_fetch_object($result))
        $datas[] = $row;
    //  将数据库中获取到的结果集数据保存到 memcached 中,以供下次访问时使用。
    $mc->add($key$datas);
} else {
    echo "n".str_pad('Read datas from memcached.'60'_')."n";
}
var_dump($datas);
?>

可以看出,使用 memcached 之后,可以减少数据库连接、查询操作,数据库负载下来了,脚本的运行速度也提高了。

之前我曾经写过一篇名为《PHP 实现多服务器共享 SESSION 数据》文章,文中的 SESSION 是使用数据库保存的,在并发访问量大的时候,服务器的负载会很大,经常会超出 MySQL 最大连接数,利用 memcached,我们可以很好地解决这个问题,工作原理如下:

  • 用户访问网页时,查看 memcached 中是否有当前用户的 SESSION 数据,使用 session_id() 作为唯一标识符;如果数据存在,则直接返回,如果不存在,再进行数据库连接,获取 SESSION 数据,并将此数据保存到 memcached 中,供下次使用;
  • 当前的 PHP 运行结束(或使用了 session_write_close())时,会调用 My_Sess::write() 方法,将数据写入数据库,这样的话,每次仍然会有数据库操作,对于这个方法,也需要进行优化。使用一个全局变量,记录用户进入页面时的 SESSION 数据,然后在 write() 方法内比较此数据与想要写入的 SESSION 数据是否相同,不同才进行数据库连接、写入数据库,同时将 memcached 中对应的对象删除,如果相同的话,则表示 SESSION 数据未改变,那么就可以不做任何操作,直接返回了;
  • 那么用户 SESSION 过期时间怎么解决呢?记得 memcached 的 add() 方法有个过期时间参数 $exp 吗?把这个参数值设置成小于 SESSION 最大存活时间即可。另外别忘了给那些一直在线的用户延续 SESSION 时长,这个可以在 write() 方法中解决,通过判断时间,符合条件则更新数据库数据。

五、相关资源

posted @ 2010-07-05 11:50 小马歌 阅读(175) | 评论 (0)编辑 收藏
 

在Linux下安装PHP的PEAR,其实不复杂,怎么搜了一下,网上好多介绍,看起来都是一样的文章,其实很简单的。
1)下载
curl -o go-pear.php http://pear.php.net/go-pear
2)运行go-pear.php
debian:~# php go-pear.php
3)这里按回车继续安装,CTRL+C放弃安装。
If you wish to abort, press Control-C now, or press Enter to continue:
4)有没有代理,没有代理直接回车
HTTP proxy (http://user:password@proxy.myhost.com:port), or Enter for none::
安装路径,默认回车

1. Installation prefix ($prefix) : /root
2. Temporary files directory : $prefix/temp
3. Binaries directory : $prefix/bin
4. PHP code directory ($php_dir) : $prefix/PEAR
5. Documentation base directory : $php_dir/docs
6. Data base directory : $php_dir/data
7. Tests base directory : $php_dir/tests

1-7, ‘all’ or Enter to continue:

按Y
Would you like to install these as well? [Y/n] :

然后就直接安装了。

安装的全过程在这里

  1. debian:~# php go-pear.php
  2.  
  3. Welcome to go-pear!
  4.  
  5.  
  6.  
  7. Go-pear will install the 'pear' command and all the files needed by
  8.  
  9. it.  This command is your tool for PEAR installation and maintenance.
  10.  
  11.  
  12.  
  13. Go-pear also lets you download and install the following optional PEAR
  14.  
  15. packages: PEAR_Frontend_Web-beta, PEAR_Frontend_Gtk2, MDB2.
  16.  
  17.  
  18.  
  19.  
  20.  
  21. If you wish to abort, press Control-C now, or press Enter to continue:
  22.  
  23.  
  24.  
  25. HTTP proxy (http://user:password@proxy.myhost.com:port), or Enter for none::
  26.  
  27.  
  28.  
  29. Below is a suggested file layout for your new PEAR installation.  To
  30.  
  31. change individual locations, type the number in front of the
  32.  
  33. directory.  Type 'all' to change all of them or simply press Enter to
  34.  
  35. accept these locations.
  36.  
  37.  
  38.  
  39.  1. Installation prefix ($prefix) : /root
  40.  
  41.  2. Temporary files directory     : $prefix/temp
  42.  
  43.  3. Binaries directory            : $prefix/bin
  44.  
  45.  4. PHP code directory ($php_dir) : $prefix/PEAR
  46.  
  47.  5. Documentation base directory  : $php_dir/docs
  48.  
  49.  6. Data base directory           : $php_dir/data
  50.  
  51.  7. Tests base directory          : $php_dir/tests
  52.  
  53.  
  54.  
  55. 1-7, 'all' or Enter to continue:
  56.  
  57.  
  58.  
  59. The following PEAR packages are bundled with PHP: PEAR_Frontend_Web-beta,
  60.  
  61. PEAR_Frontend_Gtk2, MDB2.
  62.  
  63. Would you like to install these as well? [Y/n] : Y
  64.  
  65.  
  66.  
  67. Loading zlib: ok
  68.  
  69.  
  70.  
  71. Bootstrapping Installer...................
  72.  
  73. Bootstrapping PEAR.php............(remote) ok
  74.  
  75. Bootstrapping Archive/Tar.php............(remote) ok
  76.  
  77. Bootstrapping Console/Getopt.php............(remote) ok
  78.  
  79.  
  80.  
  81. Extracting installer..................
  82.  
  83. Downloading package: PEAR.............ok
  84.  
  85. Downloading package: Structures_Graph....ok
  86.  
  87.  
  88.  
  89. Preparing installer..................
  90.  
  91. Updating channel "pear.php.net"
  92.  
  93. Channel "pear.php.net" is up to date
  94.  
  95. Updating channel "pecl.php.net"
  96.  
  97. Channel "pecl.php.net" is up to date
  98.  
  99.  
  100.  
  101. Installing selected packages..................
  102.  
  103. Downloading and installing package: PEAR.............warning: pear/PEAR requires package "pear/Archive_Tar" (recommended version 1.3.2)
  104.  
  105. warning: pear/PEAR requires package "pear/Structures_Graph" (recommended version 1.0.2)
  106.  
  107. warning: pear/PEAR requires package "pear/Console_Getopt" (recommended version 1.2.3)
  108.  
  109. pear/PEAR can optionally use package "pear/XML_RPC" (version >= 1.4.0)
  110.  
  111. downloading PEAR-1.7.2.tgz ...
  112.  
  113. Starting to download PEAR-1.7.2.tgz (302,744 bytes)
  114.  
  115. .........................................done: 302,744 bytes
  116.  
  117. install ok: channel://pear.php.net/PEAR-1.7.2
  118.  
  119. PEAR: Optional feature webinstaller available (PEAR's web-based installer)
  120.  
  121. PEAR: Optional feature gtkinstaller available (PEAR's PHP-GTK-based installer)
  122.  
  123. PEAR: Optional feature gtk2installer available (PEAR's PHP-GTK2-based installer)
  124.  
  125. PEAR: To install optional features use "pear install pear/PEAR#featurename"
  126.  
  127. Installing bootstrap package: Structures_Graph.......install ok: channel://pear.php.net/Structures_Graph-1.0.2
  128.  
  129. Downloading and installing package: Archive_Tar-stable.......downloading Archive_Tar-1.3.2.tgz ...
  130.  
  131. Starting to download Archive_Tar-1.3.2.tgz (17,150 bytes)
  132.  
  133. ...done: 17,150 bytes
  134.  
  135. install ok: channel://pear.php.net/Archive_Tar-1.3.2
  136.  
  137. Downloading and installing package: Console_Getopt-stable.......downloading Console_Getopt-1.2.3.tgz ...
  138.  
  139. Starting to download Console_Getopt-1.2.3.tgz (4,011 bytes)
  140.  
  141. ...done: 4,011 bytes
  142.  
  143. install ok: channel://pear.php.net/Console_Getopt-1.2.3
  144.  
  145. Downloading and installing package: PEAR_Frontend_Web-beta.......downloading PEAR_Frontend_Web-0.7.3.tgz ...
  146.  
  147. Starting to download PEAR_Frontend_Web-0.7.3.tgz (53,152 bytes)
  148.  
  149. ...done: 53,152 bytes
  150.  
  151. downloading HTML_Template_IT-1.2.1.tgz ...
  152.  
  153. Starting to download HTML_Template_IT-1.2.1.tgz (21,565 bytes)
  154.  
  155. ...done: 21,565 bytes
  156.  
  157. install ok: channel://pear.php.net/HTML_Template_IT-1.2.1
  158.  
  159. install ok: channel://pear.php.net/PEAR_Frontend_Web-0.7.3
  160.  
  161. Downloading and installing package: PEAR_Frontend_Gtk2.......Did not download optional dependencies: pear/XML_Parser, pear/XML_Util, use --alldeps to download automatically
  162.  
  163. Did not download optional dependencies: pear/System_Command, use --alldeps to download automatically
  164.  
  165. pear/PEAR_Frontend_Gtk2 requires PHP extension "php-gtk" (version >= 2.0.0dev)
  166.  
  167. pear/Config can optionally use package "pear/XML_Parser"
  168.  
  169. pear/Config can optionally use package "pear/XML_Util"
  170.  
  171. pear/Gtk2_EntryDialog requires PHP extension "php-gtk" (version >= 2.0.0dev)
  172.  
  173. pear/Gtk2_FileDrop requires PHP extension "php-gtk" (version >= 2.0.0dev)
  174.  
  175. pear/MIME_Type can optionally use package "pear/System_Command"
  176.  
  177. downloading Config-1.10.11.tgz ...
  178.  
  179. Starting to download Config-1.10.11.tgz (0 bytes)
  180.  
  181. ...done: 0 bytes
  182.  
  183. could not extract the package.xml file from "/root/temp/download/Config-1.10.11.tgz"
  184.  
  185. Download of "pear/Config" succeeded, but it is not a valid package archive
  186.  
  187. Error: cannot download "pear/Config"
  188.  
  189. downloading MIME_Type-1.1.3.tgz ...
  190.  
  191. Starting to download MIME_Type-1.1.3.tgz (8,805 bytes)
  192.  
  193. ...done: 8,805 bytes
  194.  
  195. install ok: channel://pear.php.net/MIME_Type-1.1.3
  196.  
  197. Downloading and installing package: MDB2.............downloading MDB2-2.4.1.tgz ...
  198.  
  199. Starting to download MDB2-2.4.1.tgz (119,790 bytes)
  200.  
  201. ...done: 119,790 bytes
  202.  
  203. install ok: channel://pear.php.net/MDB2-2.4.1
  204.  
  205. MDB2: Optional feature fbsql available (Frontbase SQL driver for MDB2)
  206.  
  207. MDB2: Optional feature ibase available (Interbase/Firebird driver for MDB2)
  208.  
  209. MDB2: Optional feature mysql available (MySQL driver for MDB2)
  210.  
  211. MDB2: Optional feature mysqli available (MySQLi driver for MDB2)
  212.  
  213. MDB2: Optional feature mssql available (MS SQL Server driver for MDB2)
  214.  
  215. MDB2: Optional feature oci8 available (Oracle driver for MDB2)
  216.  
  217. MDB2: Optional feature pgsql available (PostgreSQL driver for MDB2)
  218.  
  219. MDB2: Optional feature querysim available (Querysim driver for MDB2)
  220.  
  221. MDB2: Optional feature sqlite available (SQLite2 driver for MDB2)
  222.  
  223. MDB2: To install optional features use "pear install pear/MDB2#featurename"
  224.  
  225.  
  226.  
  227. ******************************************************************************
  228.  
  229. WARNING!  The include_path defined in the currently used php.ini does not
  230.  
  231. contain the PEAR PHP directory you just specified:
  232.  
  233. </root/PEAR>
  234.  
  235. If the specified directory is also not in the include_path used by
  236.  
  237. your scripts, you will have problems getting any PEAR packages working.
  238.  
  239.  
  240.  
  241.  
  242.  
  243. Would you like to alter php.ini </etc/php5/cli/php.ini>? [Y/n] : y
  244.  
  245.  
  246.  
  247. php.ini </etc/php5/cli/php.ini> include_path updated.
  248.  
  249.  
  250.  
  251. Current include path           : .:/usr/share/php:/usr/share/pear
  252.  
  253. Configured directory           : /root/PEAR
  254.  
  255. Currently used php.ini (guess) : /etc/php5/cli/php.ini
  256.  
  257. Press Enter to continue:
  258.  
  259.  
  260.  
  261. The 'pear' command is now at your service at /root/bin/pear
  262.  
  263.  
  264.  
  265. ** The 'pear' command is not currently in your PATH, so you need to
  266.  
  267. ** use '/root/bin/pear' until you have added
  268.  
  269. ** '/root/bin' to your PATH environment variable.
  270.  
  271.  
  272.  
  273. Run it without parameters to see the available actions, try 'pear list'
  274.  
  275. to see what packages are installed, or 'pear help' for help.
  276.  
  277.  
  278.  
  279. For more information about PEAR, see:
  280.  
  281.  
  282.  
  283.   http://pear.php.net/faq.php
  284.  
  285.   http://pear.php.net/manual/
  286.  
  287.  
  288.  
  289. Thanks for using go-pear!
posted @ 2010-06-28 11:22 小马歌 阅读(3224) | 评论 (0)编辑 收藏
 
Linux系统出现问题时,我们不仅需要查看系统日志信息,而且还要使用大量的性能监测工具来判断究竟是哪一部分(内存、CPU、硬盘……)出了问题。在Linux系统中,所有的运行参数保存在虚拟目录/proc中,换句话说,我们使用的性能监控工具取到的数据值实际上就是源自于这个目录,当涉及到系统高估时,我们就可以修改/proc目录中的相关参数了,当然有些是不能乱改的。下面就让我们了解一下这些常用的性能监控工具。
1、uptime
uptime命令用于查看服务器运行了多长时间以及有多少个用户登录,快速获知服务器的负荷情况。

uptime的输出包含一项内容是load average,显示了最近1,5,15分钟的负荷情况。它的值代表等待CPU处理的进程数,如果CPU没有时间处理这些进程,load average值会升高;反之则会降低。
load average的最佳值是1,说明每个进程都可以马上处理并且没有CPU cycles被丢失。对于单CPU的机器,1或者2是可以接受的值;对于多路CPU的机器,load average值可能在8到10之间。
也可以使用uptime命令来判断网络性能。例如,某个网络应用性能很低,通过运行uptime查看服务器的负荷是否很高,如果不是,那么问题应该是网络方面造成的。
以下是uptime的运行实例:
9:24am up 19:06, 1 user, load average: 0.00, 0.00, 0.00
也可以查看/proc/loadavg和/proc/uptime两个文件,注意不能编辑/proc中的文件,要用cat等命令来查看,如:
liyawei:~ # cat /proc/loadavg
0.00 0.00 0.00 1/55 5505

2、dmesg
dmesg命令主要用来显示内核信息。使用dmesg可以有效诊断机器硬件故障或者添加硬件出现的问题。
另外,使用dmesg可以确定您的服务器安装了那些硬件。每次系统重启,系统都会检查所有硬件并将信息记录下来。执行/bin/dmesg命令可以查看该记录。
dmesg输入实例:
ReiserFS: hda6: checking transaction log (hda6)
ReiserFS: hda6: Using r5 hash to sort names
Adding 1044184k swap on /dev/hda5. Priority:-1 extents:1 across:1044184k
parport_pc: VIA 686A/8231 detected
parport_pc: probing current configuration
parport_pc: Current parallel port base: 0×378
parport0: PC-style at 0×378 (0×778), irq 7, using FIFO [PCSPP,TRISTATE,COMPAT,ECP]
parport_pc: VIA parallel port: io=0×378, irq=7
lp0: using parport0 (interrupt-driven).
e100: Intel(R) PRO/100 Network Driver, 3.5.10-k2-NAPI
e100: Copyright(c) 1999-2005 Intel Corporation
ACPI: PCI Interrupt 0000:00:0d.0[A] -> GSI 17 (level, low) -> IRQ 169
e100: eth0: e100_probe: addr 0xd8042000, irq 169, MAC addr 00:02:55:1E:35:91
usbcore: registered new driver usbfs
usbcore: registered new driver hub
hdc: ATAPI 48X CD-ROM drive, 128kB Cache, UDMA(33)
Uniform CD-ROM driver Revision: 3.20
USB Universal Host Controller Interface driver v2.3

3、top
top命令显示处理器的活动状况。缺省情况下,显示占用CPU最多的任务,并且每隔5秒钟做一次刷新。
Process priority的数值决定了CPU处理进程的顺序。LIUNX内核会根据需要调整该数值的大小。nice value局限于priority。priority的值不能低于nice value(nice value值越低,优先级越高)。您不可以直接修改Process priority的值,但是可以通过调整nice level值来间接地改变Process priority值,然而这一方法并不是所有时候都可用。如果某个进程运行异常的慢,可以通过降低nice level为该进程分配更多的CPU。
Linux 支持的 nice levels 由19 (优先级低)到-20 (优先级高),缺省值为0。
执行/bin/ps命令可以查看到当前进程的情况。

4、iostat
iostat由Red Hat Enterprise Linux AS发布。同时iostat也是Sysstat的一部分,可以下载到,网址是
http://perso.wanadoo.fr/sebastien.godard/
执行iostat命令可以从系统启动之后的CPU平均时间,类似于uptime。除此之外,iostat还对创建一个服务器磁盘子系统的活动报告。该报告包含两部分:CPU使用情况和磁盘使用情况。
iostat显示实例:
avg-cpu: %user %nice %system %iowait %steal %idle
0.16 0.01 0.03 0.10 0.00 99.71
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
hda 0.31 4.65 4.12 327796 290832
avg-cpu: %user %nice %system %iowait %steal %idle
1.00 0.00 0.00 0.00 0.00 100.00
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
hda 0.00 0.00 0.00 0 0
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 0.00 0.00 0.00 99.01
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
hda 0.00 0.00 0.00 0 0
CPU占用情况包括四块内容
%user:显示user level (applications)时,CPU的占用情况。
%nice:显示user level在nice priority时,CPU的占用情况。
%sys:显示system level (kernel)时,CPU的占用情况。
%idle: 显示CPU空闲时间所占比例。
磁盘使用报告分成以下几个部分:
Device: 块设备的名字
tps: 该设备每秒I/O传输的次数。多个I/O请求可以组合为一个,每个I/O请求传输的字节数不同,因此可以将多个I/O请求合并为一个。
Blk_read/s, Blk_wrtn/s: 表示从该设备每秒读写的数据块数量。块的大小可以不同,如1024, 2048 或 4048字节,这取决于partition的大小。
例如,执行下列命令获得设备/dev/sda1 的数据块大小:
dumpe2fs -h /dev/sda1 |grep -F “Block size”
输出结果如下
dumpe2fs 1.34 (25-Jul-2003)
Block size: 1024
Blk_read, Blk_wrtn: 指示自从系统启动之后数据块读/写的合计数。
也可以查看这几个文件/proc/stat,/proc/partitions,/proc/diskstats的内容。

5、vmstat
vmstat提供了processes, memory, paging, block I/O, traps和CPU的活动状况
procs ———–memory———- —swap– —–io—- -system– —–cpu——
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 513072 52324 162404 0 0 2 2 261 32 0 0 100 0 0
0 0 0 513072 52324 162404 0 0 0 0 271 43 0 0 100 0 0
0 0 0 513072 52324 162404 0 0 0 0 255 27 0 0 100 0 0
0 0 0 513072 52324 162404 0 0 0 28 275 51 0 0 97 3 0
0 0 0 513072 52324 162404 0 0 0 0 255 21 0 0 100 0 0
各输出列的含义:
Process
– r: The number of processes waiting for runtime.
– b: The number of processes in uninterruptable sleep.
Memory
– swpd: The amount of virtual memory used (KB).
– free: The amount of idle memory (KB).
– buff: The amount of memory used as buffers (KB).
Swap
– si: Amount of memory swapped from the disk (KBps).
– so: Amount of memory swapped to the disk (KBps).
IO
– bi: Blocks sent to a block device (blocks/s).
– bo: Blocks received from a block device (blocks/s).
System
– in: The number of interrupts per second, including the clock.
– cs: The number of context switches per second.
CPU (these are percentages of total CPU time)
- us: Time spent running non-kernel code (user time, including nice time).
– sy: Time spent running kernel code (system time).
– id: Time spent idle. Prior to Linux 2.5.41, this included IO-wait time.
– wa: Time spent waiting for IO. Prior to Linux 2.5.41, this appeared as zero.

6、sar
sar是Red Hat Enterprise Linux AS发行的一个工具,同时也是Sysstat工具集的命令之一,可以从以下网址下载:
http://perso.wanadoo.fr/sebastien.godard/
sar用于收集、报告或者保存系统活动信息。sar由三个应用组成:sar显示数据、sar1和sar2用于收集和保存数据。
使用sar1和sar2,系统能够配置成自动抓取信息和日志,以备分析使用。配置举例:在/etc/crontab中添加如下几行内容
同样的,你也可以在命令行方式下使用sar运行实时报告。如图所示:
从收集的信息中,可以得到详细的CPU使用情况(%user, %nice, %system, %idle)、内存页面调度、网络I/O、进程活动、块设备活动、以及interrupts/second
liyawei:~ # sar -u 3 10
Linux 2.6.16.21-0.8-default (liyawei) 05/31/07
10:17:16 CPU %user %nice %system %iowait %idle
10:17:19 all 0.00 0.00 0.00 0.00 100.00
10:17:22 all 0.00 0.00 0.00 0.33 99.67
10:17:25 all 0.00 0.00 0.00 0.00 100.00
10:17:28 all 0.00 0.00 0.00 0.00 100.00
10:17:31 all 0.00 0.00 0.00 0.00 100.00
10:17:34 all 0.00 0.00 0.00 0.00 100.00

7、KDE System Guard
KDE System Guard (KSysguard) 是KDE图形方式的任务管理和性能监视工具。监视本地及远程客户端/服务器架构体系的中的主机。

8、free
/bin/free命令显示所有空闲的和使用的内存数量,包括swap。同时也包含内核使用的缓存。
total used free shared buffers cached
Mem: 776492 263480 513012 0 52332 162504
-/+ buffers/cache: 48644 727848
Swap: 1044184 0 1044184

9、Traffic-vis
Traffic-vis是一套测定哪些主机在IP网进行通信、通信的目标主机以及传输的数据量。并输出纯文本、HTML或者GIF格式的报告。
注:Traffic-vis仅仅适用于SUSE LINUX ENTERPRISE SERVER。
如下命令用来收集网口eth0的信息:
traffic-collector -i eth0 -s /root/output_traffic-collector
可以使用killall命令来控制该进程。如果要将报告写入磁盘,可使用如下命令:
killall -9 traffic-collector
要停止对信息的收集,执行如下命令:killall -9 traffic-collector
注意,不要忘记执行最后一条命令,否则会因为内存占用而影响性能。
可以根据packets, bytes, TCP连接数对输出进行排序,根据每项的总数或者收/发的数量进行。
例如根据主机上packets的收/发数量排序,执行命令:
traffic-sort -i output_traffic-collector -o output_traffic-sort -Hp
如要生成HTML格式的报告,显示传输的字节数,packets的记录、全部TCP连接请求和网络中每台服务器的信息,请运行命令:
traffic-tohtml -i output_traffic-sort -o output_traffic-tohtml.html
如要生成GIF格式(600X600)的报告,请运行命令:
traffic-togif -i output_traffic-sort -o output_traffic-togif.gif -x 600 -y 600
GIF格式的报告可以方便地发现网络广播,查看哪台主机在TCP网络中使用IPX/SPX协议并隔离网络,需要记住的是,IPX是基于广播包的协议。如果我们需要查明例如网卡故障或重复IP的问题,需要使用特殊的工具。例如SUSE LINUX Enterprise Server自带的Ethereal。
技巧和提示:使用管道,可以只需执行一条命令来产生报告。如生成HTML的报告,执行命令:
cat output_traffic-collector | traffic-sort -Hp | traffic-tohtml -o output_traffic-tohtml.html
如要生成GIF文件,执行命令:
cat output_traffic-collector | traffic-sort -Hp | traffic-togif -o output_traffic-togif.gif -x 600 -y 600

10、pmap
pmap可以报告某个或多个进程的内存使用情况。使用pmap判断主机中哪个进程因占用过多内存导致内存瓶颈。
pmap
liyawei:~ # pmap 1
1: init
START SIZE RSS DIRTY PERM MAPPING
08048000 484K 244K 0K r-xp /sbin/init
080c1000 4K 4K 4K rw-p /sbin/init
080c2000 144K 24K 24K rw-p [heap]
bfb5b000 84K 12K 12K rw-p [stack]
ffffe000 4K 0K 0K —p [vdso]
Total: 720K 284K 40K
232K writable-private, 488K readonly-private, and 0K shared

11、strace
strace截取和记录系统进程调用,以及进程收到的信号。是一个非常有效的检测、指导和调试工具。系统管理员可以通过该命令容易地解决程序问题。
使用该命令需要指明进程的ID(PID),例如:
strace -p
# strace –p 2582
rt_sigprocmask(SIG_SETMASK, [], NULL, = 0
read(7, “\”\\\”\\\\\\\”\\\\\\\\\\\\\\\”\\\\\\\\\\\\\\\\\\\\\\\\”…, 16384) = 321
write(3, “}H\331q\37\275$\271\t\311M\304$\317~)R9\330Oj\304\257\327″…, 360) = 360
select(8, [3 4 7], [3], NULL, NULL) = 2 (in [7], out [3])
rt_sigprocmask(SIG_BLOCK, [CHLD], [], = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, = 0
read(7, “\”\\\”\\\\\\\”\\\\\\\\\\\\\\\”\\\\\\\\\\\\\\\\\\\\\\\\”…, 16384) = 323
write(3, “\204\303\27$\35\206\\\306VL\370\5R\200\226\2\320^\253\253″…, 360) = 360
select(8, [3 4 7], [3], NULL, NULL) = 2 (in [7], out [3])
rt_sigprocmask(SIG_BLOCK, [CHLD], [], = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, = 0
read(7, “\”\\\”\\\\\\\”\\\\\\\\\\\\\\\”\\\\\\\\\\\\\\\\\\\\\\\\”…, 16384) = 323
write(3, “\243\207\204\277Cw162\2ju=\205\’L\352?0J\256I\376\32″…, 360) = 360
select(8, [3 4 7], [3], NULL, NULL) = 2 (in [7], out [3])
rt_sigprocmask(SIG_BLOCK, [CHLD], [], = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, = 0
read(7, “\”\\\”\\\\\\\”\\\\\\\\\\\\\\\”\\\\\\\\\\\\\\\\\\\\\\\\”…, 16384) = 320
write(3, “6\270S\3i\310\334\301\253!ys\324\’\234%\356\305\26\233″…, 360) = 360
select(8, [3 4 7], [3], NULL, NULL) = 2 (in [7], out [3])
rt_sigprocmask(SIG_BLOCK, [CHLD], [], = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, = 0

12、ulimit
ulimit内置在bash shell中,用来提供对shell和进程可用资源的控制
liyawei:~ # ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
pending signals (-i) 6143
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 6143
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
-H和-S选项指明所给资源的软硬限制。如果超过了软限制,系统管理员会收到警告信息。硬限制指在用户收到超过文件句炳限制的错误信息之前,可以达到的最大值。
例如可以设置对文件句炳的硬限制:ulimit -Hn 4096
例如可以设置对文件句炳的软限制:ulimit -Sn 1024
查看软硬值,执行如下命令:
ulimit -Hn
ulimit -Sn
例如限制Oracle用户. 在/etc/security/limits.conf输入以下行:
soft nofile 4096
hard nofile 10240
对于Red Hat Enterprise Linux AS,确定文件/etc/pam.d/system-auth包含如下行
session required /lib/security/$ISA/pam_limits.so
对于SUSE LINUX Enterprise Server,确定文件/etc/pam.d/login 和/etc/pam.d/sshd包含如下行:
session required pam_limits.so
这一行使这些限制生效。

13、mpstat
mpstat是Sysstat工具集的一部分,下载地址是
http://perso.wanadoo.fr/sebastien.godard/
mpstat用于报告多路CPU主机的每颗CPU活动情况,以及整个主机的CPU情况。
例如,下边的命令可以隔2秒报告一次处理器的活动情况,执行3次
mpstat 2 3
liyawei:~ # mpstat 2 3
Linux 2.6.16.21-0.8-default (liyawei) 05/31/07
10:23:03 CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
10:23:05 all 0.50 0.00 0.00 1.99 0.00 0.00 0.00 97.51 271.64
10:23:07 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 261.00
10:23:09 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 261.50
Average: all 0.17 0.00 0.00 0.67 0.00 0.00 0.00 99.17 264.73
如下命令每隔1秒显示一次多路CPU主机的处理器活动情况,执行3次
mpstat -P ALL 1 3
liyawei:~ # mpstat -P ALL 1 10
Linux 2.6.16.21-0.8-default (liyawei) 05/31/07
10:23:31 CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
10:23:32 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 273.00
10:23:32 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 272.00
10:23:33 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 254.00
10:23:33 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 254.00
10:23:34 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 271.00
10:23:34 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 271.00
10:23:35 all 0.00 0.00 0.00 1.98 0.00 0.00 0.00 98.02 254.46
10:23:35 0 0.00 0.00 0.00 1.98 0.00 0.00 0.00
posted @ 2010-06-17 15:21 小马歌 阅读(225) | 评论 (0)编辑 收藏
 

一.安装包

下载XtraBackup:

http://www.percona.com/mysql/xtrabackup/0.8/source/

这里下载的是0.8版本的源码包

下载MySQL源码包:

http://dev.mysql.com/downloads/mysql/5.0.html#source

因为是编译安装XtraBackup,需要使用MySQL的源代码

对于64位的系统,可以直接下载编译好的包:

http://www.percona.com/mysql/xtrabackup/0.8/Linux/xtrabackup-0.8.tar.gz

 

二、 编译安装

测试发现在mysql4.1下面编译XtraBackup会出错,使用mysql5.0编译就可以,生成的可执行文件对mysql4.1同样适用

你也可以下载对应平台的二进制版本XtraBackup,免去编译安装的步骤,不过目前二进制版本支持的平台并不多,所以这里列出编译的步骤。

#如果使用的是MySQL5.1,目录改为/mysql-5.1.*/ storage/innobase/

cd mysql-5.0.85/ innobase #进入源代码目录

patch –p2 < . /xtrabackup-0.8-src/fix_innodb_for_backup.patch #给InnoDB源码打上补丁

#如果使用的是MySQL5.1,使用fix_innodb_for_backup51.patch

cd .. #进入目录mysql-5.0.85

./configure

make #我们只需要Make就可以了,不需要Make install

cd innobase/xtrabackup-0.8-src

make #编译XtraBackup;MySQL5.1必须要使用XtraBackup0.9

编译成功后,我们会获得可执行文件xtrabackup、innobackupex。为了方便使用,分别将其拷贝到某个$PATH路径下。(我这里/u01/mysql/bin/是$PATH路径的一部分)

cp xtrabackup /usr/local/bin/

cp innobackupex-1.5.1 /usr/local/bin/

此步必须否则运行innobackupex-1.5.1会报错,找不到xtrabackup.

安装中要注意的问题:

编译XtraBackup;MySQL5.1必须要使用XtraBackup0.9。

另外,使用MySQL5.1编译,需要简单的修改一下XtraBackup的Makefile:

#MySQL 5.0

#INC= -I. -I.. -I./../include -I./../../include

#INNODBOBJS=

#MySQL 5.1

INC= ……

MYSQLOBJS=

安装完成后,我们就可以使用命令innobackupex来备份我们的数据库了。

三、 备份

XtraBackup使用脚本innobackupx-1.5.1来备份你的数据库。关于innobackupx-1.5.1的详细参数,请参考http://www.percona.com/docs/wiki/percona-xtrabackup:xtrabackup_manual。这里列出比较常用的备份命令:普通备份、打包(Tar)备份、压缩(Tar gzip)备份。

需要注意的是,innobackupex-1.5.1会根据你的/etc/my.cnf配置,来确定你的MySQL数据位置(datadir),所以你并不需要告诉innobackupex-1.5.1你的MySQL数据在哪儿。注意my.cnf里面必须指定datadir,innodb_data_home_dir,innodb_data_file_path,innodb_log_group_home_dir,innodb_log_arch_dir等信息

1. 普通备份

innobackupex-1.5.1 --user=root --password=123 /data/innobackup/ 2>/data/1.log

备份时间3分38秒;3G的表空间,InnoDB Free 2595840 Kb。

这里我们使用mysql的root用户备份,密码为123。备份的目标目录(target dir)是/data/innobackup/,innobackupex-1.5.1将在该目录下生成备份。例如/data/innobackup/2009-09-01_11-59-29就是我执行脚本后生成的备份,所有的备份文件都在该./2009-09-01_11-59-29目录下。

这里的2>/data/1.log,是将备份过程中的输出信息重定向到1.log。另外innobackupex-1.5.1还支持增量备份,这里并没有测试使用。

 

那如何恢复呢?为了模拟恢复过程,我先将MySQL停止,并删除MySQL的data目录下所有数据文件。

cd mysql/data/ ;rm --rf *;

innobackupex-1.5.1 --apply-log /data/innobackup/2009-09-01_11-59-29

innobackupex-1.5.1 --copy-back /data/innobackup/2009-09-01_11-59-29

cd /usr/local/mysql/data/

chown -R mysql:dba . #(这时候可能还需要修改datadir修改文件的所有者)

命令innobackupex-1.5.1需要指定具体哪个备份目录,先应用备份log,然后copy back。这里的备份目录是/data/innobackup/2009-09-01_11-59-29。恢复时间约3分钟。

值得一提的是,默认innobackupex-1.5.1会将二进制日志信息存放在文件xtrabackup_binlog_info中发(方便做Slave)。

innobackupex-1.5.1会根据/et/my.cnf来确定MySQL的数据位置。

恢复数据到其他机器:

首先将备份的目录rsync或者scp到目标机器,同步保证目标机器的my.cnf和源机器一样,然后执行和上面相同的操作,同时还可以使用

 innobackupex-1.5.1的--remote-host参数直接将数据备份到远程机器上面.

在远程机器上面恢复数据之后,就可以备份目录里面xtrabackup_binlog_info 信息来修改master的日志名和position

CHANGE MASTER TO MASTER_LOG_FILE='recorded_log_file_name',   MASTER_LOG_POS=recorded_log_position;

2. 打包(Tar)备份

这里基本和前面的普通备份类似,就不做详细说明了,之列出对应的命令。值得一提的是,恢复解压缩时,必须使用-i参数。

innobackupex-1.5.1 --user=root --password=123 --stream=tar /data/backup/ 2>/data/backup/2.log 1>/data/backup/2.tar

大约2分55秒。这里使用1>做标准输出重定向。

数据恢复:

tar --ixvf 2.tar #解压压缩 这里-i参数是必须的。

innobackupex-1.5.1 --apply-log /data/backup/innobackup

innobackupex-1.5.1 --copy-back /data/backup/innobackup

cd /usr/local/mysql/data/

chown -R mysql:mysql *  . #(这时候可能还需要修改datadir修改文件的所有者

mysqld_safe --user=mysql &

3. 压缩(tar gzip)备份

innobackupex-1.5.1 --user=root --password=123 --stream=tar /u01/backup/1/ 2>/u01/backup/2.log | gzip > /u01/backup/1/1.tar.gz

大约3分钟。这里使用了管道|将innobackupex-1.5.1作为gzip的标准输入。它的恢复过程,只需要使用tar --izxvf 解压对应的文件后,操作完全同普通备份。

四、 关于XtraBackup

XtraBackup还在快速的成长阶段,由http://www.percona.com/维护。这里列出一些相关资源:

· 主页: http://www.percona.com/docs/wiki/percona-xtrabackup:start

· Google Group: http://groups.google.com/group/percona-discussion?hl=en

· Launchpad: https://launchpad.net/percona-xtrabackup

· Manual http://www.percona.com/docs/wiki/percona-xtrabackup:xtrabackup_manual

· Ningoo.net: 

http://www.ningoo.net/html/2009/using_xtrabackup_backup_mysql_database.html

参考文献

http://www.percona.com/docs/wiki/percona-xtrabackup:start

http://www.ningoo.net/html/2009/using_xtrabackup_backup_mysql_database.html

http://www.innodb.com/doc/hot_backup/manual.html#innobackup

http://www.ibm.com/developerworks/cn/linux/l-diffp/index.html

posted @ 2010-06-17 15:16 小马歌 阅读(1280) | 评论 (0)编辑 收藏
 
一、概述

我们通过Shell可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如telnet服务器等进行交互的功能。而Expect就使用来实现这种功能的工具。

Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。Expect的作者Don Libes在1990年开始编写Expect时对Expect做有如下定义:Expect是一个用来实现自动交互功能的软件套件(Expect [is a] software suite for automating interactive tools)。使用它系统管理员的可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。甚至可以实现实现简单的BBS聊天机器人。 :)

Expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。Expect需要Tcl编程语言的支持,要在系统上运行Expect必须首先安装Tcl。


二、Expect工作原理

从最简单的层次来说,Expect的工作方式象一个通用化的Chat脚本工具。Chat脚本最早用于UUCP网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。

Chat脚本由一系列expect-send对组成:expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。例如下面的Chat脚本实现等待标准输出出现Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应sillyme。




QUOTE:
Login: somebody Password: sillyme




这个脚本用来实现一个登录过程,并用特定的用户名和密码实现登录。

Expect最简单的脚本操作模式本质上和Chat脚本工作模式是一样的。

例子:
1、实现功能
下面我们分析一个响应chsh命令的脚本。我们首先回顾一下这个交互命令的格式。假设我们要为用户chavez改变登录脚本,要求实现的命令交互过程如下:



QUOTE:
# chsh chavez
Changing the login shell for chavez
Enter the new value, or press return for the default
Login Shell [/bin/bash]: /bin/tcsh
#


可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录shell。我们必须在提示信息后面输入用户的登录shell或者直接回车不修改登录shell。


2、下面是一个能用来实现自动执行该命令的Expect脚本:

  1. #!/usr/bin/expect
  2. # Change a login shell to tcsh

  3. set user [lindex $argv 0]
  4. spawn chsh $user
  5. expect "]:"
  6. send "/bin/tcsh "
  7. expect eof
  8. exit
复制代码


这个简单的脚本可以解释很多Expect程序的特性。和其他脚本一样首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。

第二个参数使用Expect的spawn命令来启动脚本和命令的会话,这里启动的是chsh命令,实际上命令是以衍生子进程的方式来运行的。

随后的expect和send命令用来实现交互过程。脚本首先等待输出中出现]:字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征字符串往往是等待输入的最后的提示符的特征信息)。对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect将发送/bin/tcsh和一个回车符给chsh命令。最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。

3、决定如何响应

管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。我们可以通过后面的例子看到expect可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。下面的例子是一个更复杂的expect-send例子:

  1. expect -re "\[(.*)]:"
  2. if {$expect_out(1,string)!="/bin/tcsh"} {
  3. send "/bin/tcsh" }
  4. send " "
  5. expect eof
复制代码


在这个例子中,第一个expect命令现在使用了-re参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,因为它对于expect和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。

当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。如果不是则发送/bin/tcsh给chsh命令作为输入,如果是则仅仅发送一个回车符。这个简单的针对具体情况发出不同相响应的小例子说明了expect的强大功能。

在一个正则表达时中,可以在()中包含若干个部分并通过expect_out数组访问它们。各个部分在表达式中从左到右进行编码,从1开始(0包含有整个匹配输出)。()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。

4、使用超时

下一个expect例子中将阐述具有超时功能的提示符函数。这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。

  1. #!/usr/bin/expect
  2. # Prompt function with timeout and default.
  3. set prompt [lindex $argv 0]
  4. set def [lindex $argv 1]
  5. set response $def
  6. set tout [lindex $argv 2]
复制代码

脚本的第一部分首先是得到运行参数并将其保存到内部变量中。

  1. send_tty "$prompt: "
  2. set timeout $tout
  3. expect " " {
  4. set raw $expect_out(buffer)
  5. # remove final carriage return
  6. set response [string trimright "$raw" " "]
  7. }
  8. if {"$response" == "} {set response $def}
  9. send "$response "
  10. # Prompt function with timeout and default.
  11. set prompt [lindex $argv 0]
  12. set def [lindex $argv 1]
  13. set response $def
  14. set tout [lindex $argv 2]
复制代码


这是脚本其余的内容。可以看到send_tty命令用来实现在终端上显示提示符字串和一个冒号及空格。set timeout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-l参数用来关闭任何超时设置)。

然后expect命令就等待输出中出现回车字符。如果在超时之前得到回车符,那么set命令就会将用户输入的内容赋值给变脸raw。随后的命令将用户输入内容最后的回车符号去除以后赋值给变量response。

然后,如果response中内容为空则将response值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。最后send命令将response变量的值加上回车符发送给标准输出。

一个有趣的事情是该脚本没有使用spawn命令。 该expect脚本会与任何调用该脚本的进程交互。

如果该脚本名为prompt,那么它可以用在任何C风格的shell中。


% set a='prompt "Enter an answer" silence 10'
Enter an answer: test

% echo Answer was "$a"
Answer was test
prompt设定的超时为10秒。如果超时或者用户仅仅输入了回车符号,echo命令将输出

Answer was "silence"

5、一个更复杂的例子

下面我们将讨论一个更加复杂的expect脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。这个例子用来实现发送write命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。

  1. #!/usr/bin/expect
  2. # Write to multiple users from a prepared file
  3. # or a message input interactively

  4. if {$argc<2} {
  5. send_user "usage: $argv0 file user1 user2 ... "
  6. exit
  7. }
复制代码

send_user命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。

  1. set nofile 0
  2. # get filename via the Tcl lindex function
  3. set file [lindex $argv 0]
  4. if {$file=="i"} {
  5. set nofile 1
  6. } else {
  7. # make sure message file exists
  8. if {[file isfile $file]!=1} {
  9. send_user "$argv0: file $file not found. "
  10. exit }}
复制代码


这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。

变量file被设置为脚本的第一个参数的值,是通过一个Tcl函数lindex来实现的,该函数从列表/数组得到一个特定的元素。[]用来实现将函数lindex的返回值作为set命令的参数。

如果脚本的第一个参数是小写的"i",那么变量nofile被设置为1,否则通过调用Tcl的函数isfile来验证参数指定的文件存在,如果不存在就报错退出。

可以看到这里使用了if命令来实现逻辑判断功能。该命令后面直接跟判断条件,并且执行在判断条件后的{}内的命令。if条件为false时则运行else后的程序块。

  1. set procs {}
  2. # start write processes
  3. for {set i 1} {$i<$argc}
  4. {incr i} {
  5. spawn -noecho write
  6. [lindex $argv $i]
  7. lappend procs $spawn_id
  8. }
复制代码

最后一部分使用spawn命令来启动write进程实现向用户发送消息。这里使用了for命令来实现循环控制功能,循环变量首先设置为1,然后因此递增。循环体是最后的{}的内容。这里我们是用脚本的第二个和随后的参数来spawn一个write命令,并将每个参数作为发送消息的用户名。lappend命令使用保存每个spawn的进程的进程ID号的内部变量$spawn_id在变量procs中构造了一个进程ID号列表。

  1. if {$nofile==0} {
  2. setmesg [open "$file" "r"]
  3. } else {
  4. send_user "enter message,
  5. ending with ^D: " }
复制代码


最后脚本根据变量nofile的值实现打开消息文件或者提示用户输入要发送的消息。

  1. set timeout -1
  2. while 1 {
  3. if {$nofile==0} {
  4. if {[gets $mesg chars] == -1} break
  5. set line "$chars "
  6. } else {
  7. expect_user {
  8. -re " " {}
  9. eof break }
  10. set line $expect_out(buffer) }

  11. foreach spawn_id $procs {
  12. send $line }
  13. sleep 1}
  14. exit
复制代码

上面这段代码说明了实际的消息文本是如何通过无限循环while被发送的。while循环中的 if判断消息是如何得到的。在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时while循环也就结束了。(break命令实现终止循环) 。

在交互模式下,expect_user命令从用户接收消息,当用户输入ctrl+D时结束输入,循环同时结束。 两种情况下变量$line都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。

foreach循环遍历spawn的所有进程,这些进程的ID号都保存在列表变量$procs中,实现分别和各个进程通信。send命令组成了foreach的循环体,发送一行消息到当前的write进程。while循环的最后是一个sleep命令,主要是用于处理非交互模式情况下,以确保消息不会太快的发送给各个write进程。当while循环退出时,expect脚本结束。


三、参考资源

Expect软件版本深带有很多例子脚本,不但可以用于学习和理解expect脚本,而且是非常使用的工具。一般可以在/usr/doc/packages/expect/example看到它们,在某些linux发布中有些expect脚本保存在/usr/bin目录下。

Don Libes, Exploring Expect, O'Reilly & Associates, 1995.

John Ousterhout, Tcl and the Tk Toolkit, Addison-Wesley, 1994.

一些有用的expect脚本

autoexpect:这个脚本将根据自身在运行时用户的操作而生成一个expect脚本。它的功能某种程度上类似于在Emacs编辑器的键盘宏工具。一个自动创建的脚本可能是创建自己定制脚本的好的开始。

kibitz:这是一个非常有用的工具。通过它两个或更多的用户可以连接到同一个shell进程。

tkpasswd: 这个脚本提供了修改用户密码的GUI工具,包括可以检查密码是否是基于字典模式。这个工具同时是一个学习expect和tk的好实例。


另附:


QUOTE:
创建时间:2001-04-29
文章属性:转载
文章来源:中国科大BBS站
文章提交:quack (quack_at_xfocus.org)

[版权声明]
  
  Copyright(c) 1999
    
  本教程由*葫芦娃*翻译,并做了适当的修改,可以自由的用于非商业目的。
  但Redistribution时必须拷贝本[版权声明]。      

[BUG]

  有不少部分,翻译的时候不能作到“信,达”。当然了,任何时候都没有做到“雅”,希望各位谅解。

[原著]
 
  Don Libes: National Institute of Standards and Technology
    libes@cme.nist.gov

[目录]
  
  1.摘要
  2.关键字
  3.简介
  4.Expect综述
  5.callback
  6.passwd 和一致性检查
  7.rogue 和伪终端
  8.ftp
  9.fsck
  10.多进程控制:作业控制
  11.交互式使用Expect
  12.交互式Expect编程
  13.非交互式程序的控制
  14.Expect的速度
  15.安全方面的考虑
  16.Expect资源
  17.参考书籍

1.[摘要]

  现代的Shell对程序提供了最小限度的控制(开始,停止,等等),而把交互的特性留给了用户。 这意味着有些程序,你不能非交互的运行,比如说passwd。 有一些程序可以非交互的运行,但在很大程度上丧失了灵活性,比如说fsck。这表明Unix的工具构造逻辑开始出现问题。Expect恰恰填补了其中的一些裂痕,解决了在Unix环境中长期存在着的一些问题。

  Expect使用Tcl作为语言核心。不仅如此,不管程序是交互和还是非交互的,Expect都能运用。这是一个小语言和Unix的其他工具配合起来产生强大功能的经典例子。
 
  本部分教程并不是有关Expect的实现,而是关于Expect语言本身的使用,这主要也是通过不同的脚本描述例子来体现。其中的几个例子还例证了Expect的几个新特征。
 
2.[关键字]
  
  Expect,交互,POSIX,程序化的对话,Shell,Tcl,Unix;

3.[简介]
 
  一个叫做fsck的Unix文件系统检查程序,可以从Shell里面用-y或者-n选项来执行。 在手册[1]里面,-y选项的定义是象这样的。

  “对于fsck的所有问题都假定一个“yes”响应;在这样使用的时候,必须特别的小心,因为它实际上允许程序无条件的继续运行,即使是遇到了一些非常严重的错误”
  
  相比之下,-n选项就安全的多,但它实际上几乎一点用都没有。这种接口非常的糟糕,但是却有许多的程序都是这种风格。 文件传输程序ftp有一个选项可以禁止交互式的提问,以便能从一个脚本里面运行。但一旦发生了错误,它没有提供的处理措施。

  Expect是一个控制交互式程序的工具。他解决了fsck的问题,用非交互的方式实现了所有交互式的功能。Expect不是特别为fsck设计的,它也能进行类似ftp的出错处理。

  fsck和ftp的问题向我们展示了象sh,csh和别的一些shell提供的用户接口的局限性。 Shell没有提供从一个程序读和象一个程序写的功能。这意味着shell可以运行fsck但只能以牺牲一部分fsck的灵活性做代价。有一些程序根本就不能被执行。比如说,如果没有一个用户接口交互式的提供输入,就没法运行下去。其他还有象Telnet,crypt,su,rlogin等程序无法在shell脚本里面自动执行。还有很多其他的应用程序在设计是也是要求用户输入的。

  Expect被设计成专门针和交互式程序的交互。一个Expect程序员可以写一个脚本来描述程序和用户的对话。接着Expect程序可以非交互的运行“交互式”的程序。写交互式程序的脚本和写非交互式程序的脚本一样简单。Expect还可以用于对对话的一部分进行自动化,因为程序的控制可以在键盘和脚本之间进行切换。

bes[2]里面有详细的描述。简单的说,脚本是用一种解释性语言写的。(也有C和C++的Expect库可供使用,但这超出了本文的范围).Expect提供了创建交互式进程和读写它们的输入和输出的命令。 Expect是由于它的一个同名的命令而命名的。

  Expect语言是基于Tcl的。Tcl实际上是一个子程序库,这些子程序库可以嵌入到程序里从而提供语言服务。 最终的语言有点象一个典型的Shell语言。里面有给变量赋值的set命令,控制程序执行的if,for,continue等命令,还能进行普通的数学和字符串操作。当然了,还可以用exec来调用Unix程序。所有这些功能,Tcl都有。Tcl在参考书籍 Outerhour[3][4]里有详细的描述。

  Expect是在Tcl基础上创建起来的,它还提供了一些Tcl所没有的命令。spawn命令激活一个Unix程序来进行交互式的运行。 send命令向进程发送字符串。expect命令等待进程的某些字符串。 expect支持正规表达式并能同时等待多个字符串,并对每一个字符串执行不同的操作。expect还能理解一些特殊情况,如超时和遇到文件尾。

  expect命令和Tcl的case命令的风格很相似。都是用一个字符串去匹配多个字符串。(只要有可能,新的命令总是和已有的Tcl命令相似,以使得该语言保持工具族的继承性)。下面关于expect的定义是从手册[5]上摘录下来的。

      expect patlist1 action1 patlist2 action2.....

    该命令一直等到当前进程的输出和以上的某一个模式相匹配,或者等    到时间超过一个特定的时间长度,或者等到遇到了文件的结束为止。
    
    如果最后一个action是空的,就可以省略它。

    每一个patlist都由一个模式或者模式的表(lists)组成。如果有一个模式匹配成功,相应的action就被执行。执行的结果从expect返回。
    被精确匹配的字符串(或者当超时发生时,已经读取但未进行匹配的字符串)被存贮在变量expect_match里面。如果patlist是eof或者timeout,则发生文件结束或者超时时才执行相应的action.一般超时的时值是10秒,但可以用类似"set timeout 30"之类的命令把超时时值设定为30秒。
    
    下面的一个程序段是从一个有关登录的脚本里面摘取的。abort是在脚本的别处定义的过程,而其他的action使用类似与C语言的Tcl原语。

      expect "*welcome*"        break     
           "*busy*"        {print busy;continue}
          "*failed*"        abort 
          timeout        abort

    模式是通常的C Shell风格的正规表达式。模式必须匹配当前进程的从上一个expect或者interact开始的所有输出(所以统配符*使用的非常)的普遍。但是,一旦输出超过2000个字节,前面的字符就会被忘记,这可以通过设定match_max的值来改变。

  expect命令确实体现了expect语言的最好和最坏的性质。特别是,expect命令的灵活性是以经常出现令人迷惑的语法做代价。除了关键字模式(比如说eof,timeout)那些模式表可以包括多个模式。这保证提供了一种方法来区分他们。但是分开这些表需要额外的扫描,如果没有恰当的用["]括起来,这有可能会把和当成空白字符。由于Tcl提供了两种字符串引用的方法:单引和双引,情况变的更糟。(在Tcl里面,如果不会出现二义性话,没有必要使用引号)。在expect的手册里面,还有一个独立的部分来解释这种复杂性。幸运的是:有一些很好的例子似乎阻止了这种抱怨。但是,这个复杂性很有可能在将来的版本中再度出现。为了增强可读性,在本文中,提供的脚本都假定双引号是足够的。

  字符可以使用反斜杠来单独的引用,反斜杠也被用于对语句的延续,如果不加反斜杠的话,语句到一行的结尾处就结束了。这和Tcl也是一致的。Tcl在发现有开的单引号或者开的双引号时都会继续扫描。而且,分号可以用于在一行中分割多个语句。这乍听起来有点让人困惑,但是,这是解释性语言的风格,但是,这确实是Tcl的不太漂亮的部分。

5.[callback]

  令人非常惊讶的是,一些小的脚本如何的产生一些有用的功能。下面是一个拨电话号码的脚本。他用来把收费反向,以便使得长途电话对计算机计费。这个脚本用类似“expect callback.exp 12016442332”来激活。其中,脚本的名字便是callback.exp,而+1(201)644-2332是要拨的电话号码。

    #first give the user some time to logout
    exec sleep 4
    spawn tip modem
    expect "*connected*"
    send "ATD [llindex $argv 1] "
    #modem takes a while to connect
    set timeout 60
    expect "*CONNECT*"

  第一行是注释,第二行展示了如何调用没有交互的Unix程序。sleep 4会使程序阻塞4秒,以使得用户有时间来退出,因为modem总是会回叫用户已经使用的电话号码。

  下面一行使用spawn命令来激活tip程序,以便使得tip的输出能够被expect所读取,使得tip能从send读输入。一旦tip说它已经连接上,modem就会要求去拨打大哥电话号码。(假定modem都是贺氏兼容的,但是本脚本可以很容易的修改成能适应别的类型的modem)。不论发生了什么,expect都会终止。如果呼叫失败,expect脚本可以设计成进行重试,但这里没有。如果呼叫成功,getty会在expect退出后检测到DTR,并且向用户提示loging:。(实用的脚本往往提供更多的错误检测)。

  这个脚本展示了命令行参数的使用,命令行参数存贮在一个叫做argv的表里面(这和C语言的风格很象)。在这种情况下,第一个元素就是电话号码。方括号使得被括起来的部分当作命令来执行,结果就替换被括起来的部分。这也和C Shell的风格很象。

  这个脚本和一个大约60K的C语言程序实现的功能相似。
    

6.[passwd和一致性检查]

  在前面,我们提到passwd程序在缺乏用户交互的情况下,不能运行,passwd会忽略I/O重定向,也不能嵌入到管道里边以便能从别的程序或者文件里读取输入。这个程序坚持要求真正的与用户进行交互。因为安全的原因,passwd被设计成这样,但结果导致没有非交互式的方法来检验passwd。这样一个对系统安全至关重要的程序竟然没有办法进行可靠的检验,真实具有讽刺意味。

  passwd以一个用户名作为参数,交互式的提示输入密码。下面的expect脚本以用户名和密码作为参数而非交互式的运行。

    spawn oasswd [lindex $argv 1]
    set password [lindex $argv 2]
    expect "*password:"
    send "$password "
    expect "*password:"
    send "$password "
    expect eof

  第一行以用户名做参数启动passwd程序,为方便起见,第二行把密码存到一个变量里面。和shell类似,变量的使用也不需要提前声明。

  在第三行,expect搜索模式"*password:",其中*允许匹配任意输入,所以对于避免指定所有细节而言是非常有效的。 上面的程序里没有action,所以expect检测到该模式后就继续运行。

  一旦接收到提示后,下一行就就把密码送给当前进程。表明回车。(实际上,所有的C的关于字符的约定都支持)。上面的程序中有两个expect-send序列,因为passwd为了对输入进行确认,要求进行两次输入。在非交互式程序里面,这是毫无必要的,但由于假定passwd是在和用户进行交互,所以我们的脚本还是这样做了。

  最后,"expect eof"这一行的作用是在passwd的输出中搜索文件结束符,这一行语句还展示了关键字的匹配。另外一个关键字匹配就是timeout了,timeout被用于表示所有匹配的失败而和一段特定长度的时间相匹配。在这里eof是非常有必要的,因为passwd被设计成会检查它的所有I/O是否都成功了,包括第二次输入密码时产生的最后一个新行。

  这个脚本已经足够展示passwd命令的基本交互性。另外一个更加完备的例子回检查别的一些行为。比如说,下面的这个脚本就能检查passwd程序的别的几个方面。所有的提示都进行了检查。对垃圾输入的检查也进行了适当的处理。进程死亡,超乎寻常的慢响应,或者别的非预期的行为都进行了处理。

    spawn passwd [lindex $argv 1]
    expect     eof            {exit 1}     
        timeout            {exit 2}    
        "*No such user.*"    {exit 3}    
        "*New password:"    
    send "[lindex $argv 2 "
    expect     eof            {exit 4}    
        timeout            {exit 2}    
        "*Password too long*"    {exit 5}    
        "*Password too short*"    {exit 5}    
        "*Retype ew password:"
    send "[lindex $argv 3] "
    expect     timeout            {exit 2}    
        "*Mismatch*"        {exit 6}    
        "*Password unchanged*"    {exit 7}    
        " "        
    expect    timeout            {exit 2}    
        "*"            {exit 6}    
        eof

   
  这个脚本退出时用一个数字来表示所发生的情况。0表示passwd程序正常运行,1表示非预期的死亡,2表示锁定,等等。使用数字是为了简单起见。expect返回字符串和返回数字是一样简单的,即使是派生程序自身产生的消息也是一样的。实际上,典型的做法是把整个交互的过程存到一个文件里面,只有当程序的运行和预期一样的时候才把这个文件删除。否则这个log被留待以后进一步的检查。

  这个passwd检查脚本被设计成由别的脚本来驱动。这第二个脚本从一个文件里面读取参数和预期的结果。对于每一个输入参数集,它调用第一个脚本并且把结果和预期的结果相比较。(因为这个任务是非交互的,一个普通的老式shell就可以用来解释第二个脚本)。比如说,一个passwd的数据文件很有可能就象下面一样。

    passwd.exp    3    bogus    -        -
    passwd.exp    0    fred    abledabl    abledabl
    passwd.exp    5    fred    abcdefghijklm    -
    passwd.exp    5    fred    abc        -
    passwd.exp    6    fred    foobar        bar    
    passwd.exp    4    fred    ^C        -

  第一个域的名字是要被运行的回归脚本。第二个域是需要和结果相匹配的退出值。第三个域就是用户名。第四个域和第五个域就是提示时应该输入的密码。减号仅仅表示那里有一个域,这个域其实绝对不会用到。在第一个行中,bogus表示用户名是非法的,因此passwd会响应说:没有此用户。expect在退出时会返回3,3恰好就是第二个域。在最后一行中,^C就是被切实的送给程序来验证程序是否恰当的退出。

  通过这种方法,expect可以用来检验和调试交互式软件,这恰恰是IEEE的POSIX 1003.2(shell和工具)的一致性检验所要求的。进一步的说明请参考Libes[6]。

7.[rogue 和伪终端]

  Unix用户肯定对通过管道来和其他进程相联系的方式非常的熟悉(比如说:一个shell管道)。expect使用伪终端来和派生的进程相联系。伪终端提供了终端语义以便程序认为他们正在和真正的终端进行I/O操作。

  比如说,BSD的探险游戏rogue在生模式下运行,并假定在连接的另一端是一个可寻址的字符终端。可以用expect编程,使得通过使用用户界面可以玩这个游戏。

  rogue这个探险游戏首先提供给你一个有各种物理属性,比如说力量值,的角色。在大部分时间里,力量值都是16,但在几乎每20次里面就会有一个力量值是18。很多的rogue玩家都知道这一点,但没有人愿意启动程序20次以获得一个好的配置。下面的这个脚本就能达到这个目的。

    for {} {1} {} {
        spawn rogue
        expect "*Str:18*"    break    
            "*Str:16*"    
        close
        wait
    }
    interact

  第一行是个for循环,和C语言的控制格式很象。rogue启动后,expect就检查看力量值是18还是16,如果是16,程序就通过执行close和wait来退出。这两个命令的作用分别是关闭和伪终端的连接和等待进程退出。rogue读到一个文件结束符就推出,从而循环继续运行,产生一个新的rogue游戏来检查。

  当一个值为18的配置找到后,控制就推出循环并跳到最后一行脚本。interact把控制转移给用户以便他们能够玩这个特定的游戏。

  想象一下这个脚本的运行。你所能真正看到的就是20或者30个初始的配置在不到一秒钟的时间里掠过屏幕,最后留给你的就是一个有着很好配置的游戏。唯一比这更好的方法就是使用调试工具来玩游戏。

  我们很有必要认识到这样一点:rogue是一个使用光标的图形游戏。expect程序员必须了解到:光标的运动并不一定以一种直观的方式在屏幕上体现。幸运的是,在我们这个例子里,这不是一个问题。将来的对expect的改进可能会包括一个内嵌的能支持字符图形区域的终端模拟器。

8.[ftp]

  我们使用expect写第一个脚本并没有打印出"Hello,World"。实际上,它实现了一些更有用的功能。它能通过非交互的方式来运行ftp。ftp是用来在支持TCP/IP的网络上进行文件传输的程序。除了一些简单的功能,一般的实现都要求用户的参与。

  下面这个脚本从一个主机上使用匿名ftp取下一个文件来。其中,主机名是第一个参数。文件名是第二个参数。

        spawn    ftp    [lindex $argv 1]
        expect "*Name*"
        send     "anonymous "
        expect "*Password:*"
        send [exec whoami]
        expect "*ok*ftp>*"
        send "get [lindex $argv 2] "
        expect "*ftp>*"

  上面这个程序被设计成在后台进行ftp。虽然他们在底层使用和expect类似的机制,但他们的可编程能力留待改进。因为expect提供了高级语言,你可以对它进行修改来满足你的特定需求。比如说,你可以加上以下功能:

    :坚持--如果连接或者传输失败,你就可以每分钟或者每小时,甚
        至可以根据其他因素,比如说用户的负载,来进行不定期的
        重试。
    :通知--传输时可以通过mail,write或者其他程序来通知你,甚至
        可以通知失败。
    :初始化-每一个用户都可以有自己的用高级语言编写的初始化文件
        (比如说,.ftprc)。这和C shell对.cshrc的使用很类似。

  expect还可以执行其他的更复杂的任务。比如说,他可以使用McGill大学的Archie系统。Archie是一个匿名的Telnet服务,它提供对描述Internet上可通过匿名ftp获取的文件的数据库的访问。通过使用这个服务,脚本可以询问Archie某个特定的文件的位置,并把它从ftp服务器上取下来。这个功能的实现只要求在上面那个脚本中加上几行就可以。

  现在还没有什么已知的后台-ftp能够实现上面的几项功能,能不要说所有的功能了。在expect里面,它的实现却是非常的简单。“坚持”的实现只要求在expect脚本里面加上一个循环。“通知”的实现只要执行mail和write就可以了。“初始化文件”的实现可以使用一个命令,source .ftprc,就可以了,在.ftprc里面可以有任何的expect命令。

  虽然这些特征可以通过在已有的程序里面加上钩子函数就可以,但这也不能保证每一个人的要求都能得到满足。唯一能够提供保证的方法就是提供一种通用的语言。一个很好的解决方法就是把Tcl自身融入到ftp和其他的程序中间去。实际上,这本来就是Tcl的初衷。在还没有这样做之前,expect提供了一个能实现大部分功能但又不需要任何重写的方案

9.[fsck]

  fsck是另外一个缺乏足够的用户接口的例子。fsck几乎没有提供什么方法来预先的回答一些问题。你能做的就是给所有的问题都回答"yes"或者都回答"no"。

  下面的程序段展示了一个脚本如何的使的自动的对某些问题回答"yes",而对某些问题回答"no"。下面的这个脚本一开始先派生fsck进程,然后对其中两种类型的问题回答"yes",而对其他的问题回答"no"。

    for {} {1} {} {
        expect
            eof        break        
            "*UNREF FILE*CLEAR?"    {send "r "}    
            "*BAD INODE*FIX?"    {send "y "}    
            "*?"            {send "n "}    
    }

  在下面这个版本里面,两个问题的回答是不同的。而且,如果脚本遇到了什么它不能理解的东西,就会执行interact命令把控制交给用户。用户的击键直接交给fsck处理。当执行完后,用户可以通过按"+"键来退出或者把控制交还给expect。如果控制是交还给脚本了,脚本就会自动的控制进程的剩余部分的运行。

    for {} {1} {}{
        expect             
            eof        break        
            "*UNREF FILE*CLEAR?"    {send "y "}    
            "*BAD INODE*FIX?"    {send "y "}    
            "*?"            {interact +}    
    }

  如果没有expect,fsck只有在牺牲一定功能的情况下才可以非交互式的运行。fsck几乎是不可编程的,但它却是系统管理的最重要的工具。许多别的工具的用户接口也一样的不足。实际上,正是其中的一些程序的不足导致了expect的诞生。



10.[控制多个进程:作业控制]


  expect的作业控制概念精巧的避免了通常的实现困难。其中包括了两个问题:一个是expect如何处理经典的作业控制,即当你在终端上按下^Z键时expect如何处理;另外一个就是expect是如何处理多进程的。

  对第一个问题的处理是:忽略它。expect对经典的作业控制一无所知。比如说,你派生了一个程序并且发送一个^Z给它,它就会停下来(这是伪终端的完美之处)而expect就会永远的等下去。

  但是,实际上,这根本就不成一个问题。对于一个expect脚本,没有必要向进程发送^Z。也就是说,没有必要停下一个进程来。expect仅仅是忽略了一个进程,而把自己的注意力转移到其他的地方。这就是expect的作业控制思想,这个思想也一直工作的很好。

  从用户的角度来看是象这样的:当一个进程通过spawn命令启动时,变量spawn_id就被设置成某进程的描述符。由spawn_id描述的进程就被认为是当前进程。(这个描述符恰恰就是伪终端文件的描述符,虽然用户把它当作一个不透明的物体)。expect和send命令仅仅和当前进程进行交互。所以,切换一个作业所需要做的仅仅是把该进程的描述符赋给spawn_id。

  这儿有一个例子向我们展示了如何通过作业控制来使两个chess进程进行交互。在派生完两个进程之后,一个进程被通知先动一步。在下面的循环里面,每一步动作都送给另外一个进程。其中,read_move和write_move两个过程留给读者来实现。(实际上,它们的实现非常的容易,但是,由于太长了所以没有包含在这里)。

    spawn chess            ;# start player one
    set id1    $spawn_id
    expect "Chess "
    send "first "            ;# force it to go first
    read_move

    spawn chess            ;# start player two
    set id2    $spawn_id
    expect "Chess "
    
    for {} {1} {}{
        send_move
        read_move
        set spawn_id    $id1
        
        send_move
        read_move
        set spawn_id    $id2
    }

   有一些应用程序和chess程序不太一样,在chess程序里,的两个玩家轮流动。下面这个脚本实现了一个冒充程序。它能够控制一个终端以便用户能够登录和正常的工作。但是,一旦系统提示输入密码或者输入用户名的时候,expect就开始把击键记下来,一直到用户按下回车键。这有效的收集了用户的密码和用户名,还避免了普通的冒充程序的"Incorrect password-tryagain"。而且,如果用户连接到另外一个主机上,那些额外的登录也会被记录下来。

    spawn tip /dev/tty17        ;# open connection to
    set tty $spawn_id        ;# tty to be spoofed

    spawn login
    set login $spawn_id

    log_user 0
    
    for {} {1} {} {
        set ready [select $tty $login]
        
        case $login in $ready {
            set spawn_id $login
            expect         
              {"*password*" "*login*"}{
                  send_user $expect_match
                  set log 1
                 }    
              "*"        ;# ignore everything else
            set spawn_id    $tty;
            send $expect_match
        }
        case $tty in $ready {
            set spawn_id    $tty
            expect "* *"{
                    if $log {
                      send_user $expect_match
                      set log 0
                    }
                   }    
                "*" {
                    send_user $expect_match
                   }
            set spawn_id     $login;
            send $expect_match
        }
    }
        

   这个脚本是这样工作的。首先连接到一个login进程和终端。缺省的,所有的对话都记录到标准输出上(通过send_user)。因为我们对此并不感兴趣,所以,我们通过命令"log_user 0"来禁止这个功能。(有很多的命令来控制可以看见或者可以记录的东西)。

   在循环里面,select等待终端或者login进程上的动作,并且返回一个等待输入的spawn_id表。如果在表里面找到了一个值的话,case就执行一个action。比如说,如果字符串"login"出现在login进程的输出中,提示就会被记录到标准输出上,并且有一个标志被设置以便通知脚本开始记录用户的击键,直至用户按下了回车键。无论收到什么,都会回显到终端上,一个相应的action会在脚本的终端那一部分执行。

   这些例子显示了expect的作业控制方式。通过把自己插入到对话里面,expect可以在进程之间创建复杂的I/O流。可以创建多扇出,复用扇入的,动态的数据相关的进程图。

   相比之下,shell使得它自己一次一行的读取一个文件显的很困难。shell强迫用户按下控制键(比如,^C,^Z)和关键字(比如fg和bg)来实现作业的切换。这些都无法从脚本里面利用。相似的是:以非交互方式运行的shell并不处理“历史记录”和其他一些仅仅为交互式使用设计的特征。这也出现了和前面哪个passwd程序的相似问题。相似的,也无法编写能够回归的测试shell的某些动作的shell脚本。结果导致shell的这些方面无法进行彻底的测试。

   如果使用expect的话,可以使用它的交互式的作业控制来驱动shell。一个派生的shell认为它是在交互的运行着,所以会正常的处理作业控制。它不仅能够解决检验处理作业控制的shell和其他一些程序的问题。还能够在必要的时候,让shell代替expect来处理作业。可以支持使用shell风格的作业控制来支持进程的运行。这意味着:首先派生一个shell,然后把命令送给shell来启动进程。如果进程被挂起,比如说,发送了一个^Z,进程就会停下来,并把控制返回给shell。对于expect而言,它还在处理同一个进程(原来那个shell)。

  expect的解决方法不仅具有很大的灵活性,它还避免了重复已经存在于shell中的作业控制软件。通过使用shell,由于你可以选择你想派生的shell,所以你可以根据需要获得作业控制权。而且,一旦你需要(比如说检验的时候),你就可以驱动一个shell来让这个shell以为它正在交互式的运行。这一点对于在检测到它们是否在交互式的运行之后会改变输出的缓冲的程序来说也是很重要的。

  为了进一步的控制,在interact执行期间,expect把控制终端(是启动expect的那个终端,而不是伪终端)设置成生模式以便字符能够正确的传送给派生的进程。当expect在没有执行interact的时候,终端处于熟模式下,这时候作业控制就可以作用于expect本身。

11.[交互式的使用expect]

  在前面,我们提到可以通过interact命令来交互式的使用脚本。基本上来说,interact命令提供了对对话的自由访问,但我们需要一些更精细的控制。这一点,我们也可以使用expect来达到,因为expect从标准输入中读取输入和从进程中读取输入一样的简单。 但是,我们要使用expect_user和send_user来进行标准I/O,同时不改变spawn_id。

  下面的这个脚本在一定的时间内从标准输入里面读取一行。这个脚本叫做timed_read,可以从csh里面调用,比如说,set answer="timed_read 30"就能调用它。

    #!/usr/local/bin/expect -f
    set timeout [lindex $argv 1]
    expect_user "* "
    send_user $expect_match

   第三行从用户那里接收任何以新行符结束的任何一行。最后一行把它返回给标准输出。如果在特定的时间内没有得到任何键入,则返回也为空。

   第一行支持"#!"的系统直接的启动脚本。(如果把脚本的属性加上可执行属性则不要在脚本前面加上expect)。当然了脚本总是可以显式的用"expect scripot"来启动。在-c后面的选项在任何脚本语句执行前就被执行。比如说,不要修改脚本本身,仅仅在命令行上加上-c "trace...",该脚本可以加上trace功能了(省略号表示trace的选项)。

   在命令行里实际上可以加上多个命令,只要中间以";"分开就可以了。比如说,下面这个命令行:

    expect -c "set timeout 20;spawn foo;expect"

   一旦你把超时时限设置好而且程序启动之后,expect就开始等待文件结束符或者20秒的超时时限。 如果遇到了文件结束符(EOF),该程序就会停下来,然后expect返回。如果是遇到了超时的情况,expect就返回。在这两中情况里面,都隐式的杀死了当前进程。

   如果我们不使用expect而来实现以上两个例子的功能的话,我们还是可以学习到很多的东西的。在这两中情况里面,通常的解决方案都是fork另一个睡眠的子进程并且用signal通知原来的shell。如果这个过程或者读先发生的话,shell就会杀司那个睡眠的进程。 传递pid和防止后台进程产生启动信息是一个让除了高手级shell程序员之外的人头痛的事情。提供一个通用的方法来象这样启动多个进程会使shell脚本非常的复杂。 所以几乎可以肯定的是,程序员一般都用一个专门C程序来解决这样一个问题。

   expect_user,send_user,send_error(向标准错误终端输出)在比较长的,用来把从进程来的复杂交互翻译成简单交互的expect脚本里面使用的比较频繁。在参考[7]里面,Libs描述怎样用脚本来安全的包裹(wrap)adb,怎样把系统管理员从需要掌握adb的细节里面解脱出来,同时大大的降低了由于错误的击键而导致的系统崩溃。

   一个简单的例子能够让ftp自动的从一个私人的帐号里面取文件。在这种情况里,要求提供密码。 即使文件的访问是受限的,你也应该避免把密码以明文的方式存储在文件里面。把密码作为脚本运行时的参数也是不合适的,因为用ps命令能看到它们。有一个解决的方法就是在脚本运行的开始调用expect_user来让用户输入以后可能使用的密码。这个密码必须只能让这个脚本知道,即使你是每个小时都要重试ftp。

   即使信息是立即输入进去的,这个技巧也是非常有用。比如说,你可以写一个脚本,把你每一个主机上不同的帐号上的密码都改掉,不管他们使用的是不是同一个密码数据库。如果你要手工达到这样一个功能的话,你必须Telnet到每一个主机上,并且手工输入新的密码。而使用expect,你可以只输入密码一次而让脚本来做其它的事情。

   expect_user和interact也可以在一个脚本里面混合的使用。考虑一下在调试一个程序的循环时,经过好多步之后才失败的情况。一个expect脚本可以驱动哪个调试器,设置好断点,执行该程序循环的若干步,然后将控制返回给键盘。它也可以在返回控制之前,在循环体和条件测试之间来回的切换。





文件ftp-down
  1. #!/usr/bin/expect -f
  2. set ipaddress [lindex $argv 0]
  3. set username [lindex $argv 1]
  4. set password [lindex $argv 2]

  5. spawn ftp $ipaddress
  6. expect "*Name*"
  7. send "$username\n"
  8. expect "*Password:*"
  9. send "$password\n"
  10. expect "*ok*ftp>*"
  11. send "get teste\n"
  12. expect "*ftp>*"
  13. send "quit\n"
  14. expect eof
复制代码


执行时如下
  1. ./ftp-down 192.168.1.1 temp temp
复制代码

脚名名称    FTP主机地址 用户名 密码
posted @ 2010-06-17 15:13 小马歌 阅读(324) | 评论 (0)编辑 收藏
 
rpm -qpl xxxxxx.rpm
1.如何安装rpm软件包
rmp软件包的安装可以使用程序rpm来完成。执行下面的命令
rpm -i your-package.rpm
其中your-package.rpm是你要安装的rpm包的文件名,一般置于当前目录下。
安装过程中可能出现下面的警告或者提示:
... conflict with ... 可能是要安装的包里有一些文件可能会覆盖现有
的文件,缺省时这样的情况下是无法正确安装的可以用
rpm --force -i 强制安装即可
... is needed by ...
... is not installed ... 此包需要的一些软件你没有安装可以用
rpm --nodeps -i 来忽略此信息
也就是说,rpm -i --force --nodeps 可以忽略所有依赖关系和文件问题,什么包
都能安装上,但这种强制安装的软件包不能保证完全发挥功能
2.如何安装.src.rpm软件包
有些软件包是以.src.rpm结尾的,这类软件包是包含了源代码的rpm包,在安装时
需要进行编译。这类软件包有两种安装方法,
方法一:
1.执行rpm -i your-package.src.rpm
2. cd /usr/src/redhat/SPECS
3. rpmbuild -bp your-package.specs 一个和你的软件包同名的specs文件
4. cd /usr/src/redhat/BUILD/your-package/ 一个和你的软件包同名的目录
5. ./configure 这一步和编译普通的源码软件一样,可以加上参数
6. make
7. make install
方法二:
1.执行rpm -i you-package.src.rpm
2. cd /usr/src/redhat/SPECS
前两步和方法一相同
3. rpmbuild -bb your-package.specs 一个和你的软件包同名的specs文件
这时,在/usr/src/redhat/RPM/i386/ (根据具体包的不同,也可能是i686,noarch等等)
在这个目录下,有一个新的rpm包,这个是编译好的二进制文件。
执行rpm -i new-package.rpm即可安装完成。
3.如何卸载rpm软件包
使用命令 rpm -e 包名,包名可以包含版本号等信息,但是不可以有后缀.rpm
比如卸载软件包proftpd-1.2.8-1,可以使用下列格式:
rpm -e proftpd-1.2.8-1
rpm -e proftpd-1.2.8
rpm -e proftpd-
rpm -e proftpd
不可以是下列格式:
rpm -e proftpd-1.2.8-1.i386.rpm
rpm -e proftpd-1.2.8-1.i386
rpm -e proftpd-1.2
rpm -e proftpd-1
有时会出现一些错误或者警告:
... is needed by ... 这说明这个软件被其他软件需要,不能随便卸载
可以用rpm -e --nodeps强制卸载
4.如何不安装但是获取rpm包中的文件
使用工具rpm2cpio和cpio
rpm2cpio xxx.rpm | cpio -vi
rpm2cpio xxx.rpm | cpio -idmv
rpm2cpio xxx.rpm | cpio --extract --make-directories
参数i和extract相同,表示提取文件。v表示指示执行进程
d和make-directory相同,表示根据包中文件原来的路径建立目录
m表示保持文件的更新时间。
5.如何查看与rpm包相关的文件和其他信息
下面所有的例子都假设使用软件包mysql-3.23.54a-11
1.我的系统中安装了那些rpm软件包
rpm -qa 讲列出所有安装过的包
如果要查找所有安装过的包含某个字符串sql的软件包
rpm -qa |grep sql
2.如何获得某个软件包的文件全名
rpm -q mysql 可以获得系统中安装的mysql软件包全名,从中可以获得
当前软件包的版本等信息。这个例子中可以得到信息mysql-3.23.54a-11
3.一个rpm包中的文件安装到那里去了?
rpm -ql 包名
注意这里的是不包括.rpm后缀的软件包的名称
也就是说只能用mysql或者mysql-3.23.54a-11而不是mysql-3.23.54a-11.rpm。
如果只是想知道可执行程序放到那里去了,也可以用which,比如
which mysql
4.一个rpm包中包含那些文件
一个没有安装过的软件包,使用rpm -qlp ****.rpm
一个已经安装过的软件包,还可以使用rpm -ql ****.rpm
5.如何获取关于一个软件包的版本,用途等相关信息?
一个没有安装过的软件包,使用rpm -qip ****.rpm
一个已经安装过的软件包,还可以使用rpm -qi ****.rpm
 
6.某个程序是哪个软件包安装的,或者哪个软件包包含这个程序
rpm -qf `which 程序名` 返回软件包的全名
rpm -qif `which 程序名` 返回软件包的有关信息
rpm -qlf `which 程序名` 返回软件包的文件列表
注意,这里不是引号,而是`,就是键盘左上角的那个键。
也可以使用rpm -qilf,同时输出软件包信息和文件列表

7.某个文件是哪个软件包安装的,或者哪个软件包包含这个文件
注意,前一个问题中的方法,只适用与可执行的程序,而下面的方法,不仅可以
用于可执行程序,也可以用于普通的任何文件。前提是知道这个文件名。
首先获得这个程序的完整路径,可以用whereis或者which,然后使用rpm -qf例如:
# whereis ftptop
ftptop: /usr/bin/ftptop /usr/share/man/man1/ftptop.1.gz
# rpm -qf /usr/bin/ftptop
proftpd-1.2.8-1
# rpm -qf /usr/share/doc/proftpd-1.2.8/rfc/rfc0959.txt
proftpd-1.2.8-1
总结:
获得软件包相关的信息用rpm -q,q表示查询query,后面可以跟其他选项,比如
i 表示info,获得软件包的信息;
l 表示list,获得文件列表;
a 表示all,在所有包中执行查询;
f 表示file,根据文件进行相关的查询;
p 表示package,根据软件包进行查询
需要的查询条件可以使用grep产生,或者从"` `"中的命令行产生
6.关于rpm软件包的一些相关知识
1.什么是rpm
rpm 即RedHat Package Management,是RedHat的发明之一
2.为什么需要rpm
在一个操作系统下,需要安装实现各种功能的软件包。这些软件包一般都有各自的
程序,但是同时也有错综复杂的依赖关系。同时还需要解决软件包的版本,以及安装,
配置,卸载的自动化问题。为了解决这些问题,RedHat针对自己的系统提出了一个
较好的办法来管理成千上百的软件。这就是RPM管理系统。在系统中安装了rpm管理系统
以后,只要是符合rpm文件标准的打包的程序都可以方便的安装,升级,卸载
3.是不是所有的linux都使用rpm
任何系统都需要包管理系统,因此很多linux都使用rpm系统。但rpm系统是为RH专门
但是TL,Mandrake等系统也都使用rpm。由于rpm的源程序可以在别的系统上进行编译,
所以有可能在别的系统上也使用rpm
除了rpm,其他一些系统也有自己的软件包管理程序,例如debian的deb包,
slakware也都有自己的包管理系统
4.rpm包的文件名为什么那么长
rpm包的文件名中包含了这个软件包的版本信息,操作系统信息,硬件要求等等。
比如mypackage-1.1-2TL.i386.rpm,其中mypackage是在系统中登记的软件包的名字
1.1是软件的版本号,2是发行号,TL表示用于TL操作系统,还可能是RH等。i386表示
用于intel x86平台,还可能是sparc等。
5.软件包文件名中的i386,i686是什么意思
rpm软件包的文件名中,不仅包含了软件名称,版本信息,还包括了适用的硬件架构
的信息。
i386指这个软件包适用于intel 80386以上的x86架构的计算机(AI32)
i686指这个软件包适用于intel 80686以上(奔腾pro以上)的x86架构的计算机(IA32)
noarch指这个软件包于硬件架构无关,可以通用。
i686软件包的程序通常针对CPU进行了优化,所以,向后兼容比较用以,i386的包在
x86机器上都可以用。向前一般不兼容。不过现在的计算机,奔腾pro以下的CPU已经很少
用,通常配置的机器都可以使用i686软件包

6.不同操作系统发行的rpm包可否混用?
对于已经编译成二进制的rpm包,由于操作系统环境不同,一般不能混用。
对于以src.rpm发行的软件包,由于需要安装时进行本地编译,所以通常可以在不同
系统下安装。
7.使用rpm时遇到的一些特殊问题
Q 我用rpm -e **.rpm无法删除rpm包
A 包名不要包括rpm,
rpm -e 包名,可以包含版本号等信息,但是不可以有后缀.rpm
Q 在MS的系统下有没有读RPM文件的工具?
A wincmd with rpm plugins.....
Q 是否可以通过ftp安装安装升级rpm包?
A 可以。rpm -ivh ftp://xxxxxxxx/PATH2SomeRPM
Q rpm安装时已有的包版本过高怎么办?
A 有时由于安装的软件包太老,而系统中相关的软件包版本比较新,所以可能需要
安装的包依赖的一些文件会找不到。这时有两种解决办法,
第一是在系统文件中找到和需要的文件功能相同或相似的文件,做一个符号链接到
需要的目录下。
第二是下载安装新版本的软件包。
posted @ 2010-06-03 15:32 小马歌 阅读(598) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 60 61 62 63 64 65 66 67 68 下一页 Last