测试开发这个工作最后都有可能会开发测试平台,测试工具等,这也难免会遇到这样的问题 :如何操作远程的服务器?如向服务器发送命令,收集或是判断命令执行结果,以及记录日志,拉取远程服务器上的测试报告等操作。

前阶段用公司的 WF SCF 框架开发测试平台,遇到了这样的问题,研究了几天,发现 java 有这样的功能: JSch !!!

SSH2 的一个纯 Java 实现。它允许你连接到一个 sshd 服务器,使用端口转发, X11 转发,文件传输等等。你可以将它的功能集成到你自己的 程序中。同时该项目也提供一个 J2ME 版本用来在手机上直连 SSHD 服务器。同时该开发包也提供 J2ME 下的 版本 。( http://www.oschina.net/p/jsch

下面介绍一下常用的功能:

一, 远程连接主机

首先要下载对应的 jsch.jar 包,然后添加相应的引用:

Import com.jcraft.jsch.Channel;

Import com.jcraft.jsch.ChannelExec;

Import com.jcraft.jsch.ChannelShell;

Import com.jcraft.jsch.JSch;

Import com.jcraft.jsch.JSchException;

Import com.jcraft.jsch.Session;

1 Jsch 远程连接主机的时候,最常用的方法是通过 IP 地址,端口号,用户名和密码新建 session, 如下所示:

Public JschUtils(String host , int port ,String username ,String password ) throws JSchException{

jsch = new JSch();

session = jsch .getSession( username , host , port );

session .setPassword( password );

java.util.Properties config = new ;

config .put( "StrictHostKeyChecking" , "no" );

session .setConfig( config );

session .connect(3000);

log .info( " 建立连接 " );

通过调用这个 JschUtils 函数,传递 IP 地址,端口号,用户名和密码,即可建立与该服务器的连接,当然这个 session 变量需要定义成全局变量,方便其他函数使用。

2 )如果你是通过添加授权, kerberos, 添加信件 IP 等方式的话,连接方式就不太一样了,具体的要根据具体的情况去网上查询,下面提供一个我们公司的示例,具体操作步骤如下:

Ø 在要操作的服务器上给平台机器添加 work 权限

vi /home/work/.k5login

host/ XXX-YYY—ZZZ

在平台 WF 容器的 conf 文件夹中添加登录配置 loginkeytab.conf

# pwd

/opt/web/CommercialTestPlatform/conf

# cat loginkeytab.conf

com.sun.security.jgss.krb5.initiate{

com.sun.security.auth.module.Krb5LoginModule required

debug="false"

useKeyTab="true"

doNotPrompt="true"

keyTab="/etc/krb5.keytab"

storeKey="true"

principal=" host/ XXX-YYY—ZZZ ";

注意: principal 必须是本机受权的用户名,也是就是 (1) 中添加的。

在平台服务器上执行下面的命令:

/usr/bin/kinit -k -t /etc/krb5.keytab

通过 jsch 连接服务器

public JschUtils(String host , int port ) throws JSchException{

String name = "work" ;

Session session;

try {

System. setProperty ( "java.security.auth.login.config" , "$pwd/loginkeytab.conf" );

System. setProperty ( "javax.security.auth.useSubjectCredsOnly" , "false" );

System. setProperty ( "java.security.krb5.conf" , "/etc/krb5.conf" );

JSch jsch = new JSch();

jsch.setKnownHosts( "/root/.ssh/known_hosts" loggerHelper .info( "Workname:" + name+ " hosts:" +host);

session = jsch.getSession(name, host, port);

session.setTimeout(3000);

session.setServerAliveInterval(1000);

java.util.Properties config = new ;

config.put( "StrictHostKeyChecking" , "no" );

config.put( "PreferredAuthentications " , "gssapi-with-mic,publickey" config.put( "kex" , "diffie-hellman-group1-sha1" );

session.setConfig(config);

session.connect();

session.sendKeepAliveMsg();

} catch (Exception e) {

// 公钥方式登录

JSch jsch = new JSch();

jsch.addIdentity( "/root/.ssh/id_rsa" );

jsch.setKnownHosts( "/root/.ssh/known_hosts" session = jsch.getSession(name, host, port);

session.setTimeout(3000);

java.util.Properties Config = new ;

Config.put( "StrictHostKeyChecking" , "no" );

session.setConfig(Config);

session.connect();

return session;}

在这个函数中我们通过了 kerberos 授权和添加信息的 IP 地址,当然还是需要 IP 地址,端口号,用户名和密码的。此时的 IP 地址指向连接到哪儿台服务器,用户名和密码就是授权的了,此时的用户名和密码被定义成了全局变量,所以不用传参。

二, 远程执行命令

连接远程服务器的目的是为操作服务器,所以我们要解决的第二个知识点就是如何向远程服务器发送命令?当然我们在执行命令的时候,不能只把命令发过去就不管了,要把命令执行的输出返回。如果命令常时间没有任何输出的时候,就需要断开与服务器的连接,故需要一个超时时间。

根据上面的分析,我们的示例代码如下:

public String execAndResult( String cmd , long timeout ) throws Exception{

ChannelExec channelExec = null ;

StringBuffer result = new StringBuffer();

InputStream in = null ;

try {

log .info( " 要执行的命令 :" + cmd );

channelExec = (ChannelExec) session .openChannel( "exec" );

channelExec .setCommand( cmd );

channelExec .setInputStream( null );

channelExec .setErrStream(System. err );

in = channelExec .getInputStream();

channelExec .connect();

int res = -1;

byte [] tmp = newbyte [ 1024 ];

long starttime = System. currentTimeMillis ();

while ( true ) {

while ( in .available() > 0 ) {

int i = in .read( tmp , 0, 1024 );

if ( i < 0 ) break ;

result .append( new String( tmp , 0, i ) + "\n" );

starttime = System. currentTimeMillis ();

if (System. currentTimeMillis () - starttime >= timeout * 1000) {

log .info( " " + timeout + " 秒内没有日志输出,中断程序 " );

break ;

if ( channelExec .isClosed() ) {

res = channelExec .getExitStatus();

break ;

} catch (Exception e ) {

e .printStackTrace();

} finally if ( channelExec != null ) {

channelExec .disconnect();

if ( in != null ) {

in .close();

session .disconnect();

log .info( " 关闭连接 " );

return result .toString();

代码解析:

(1) 函数 execAndResult ()为在远程服务器上执行命令 cmd, 如果命令在 timeout 期间没有任何输出的话则中断连接。

(2) 在调用函数前,需要先调用上面的连接服务器函数,连接到服务器,并初化 session 变量。命令执行的输出存放在 StringBufferresult 变量中,并作为函数的返回值返回给调用者。

(3) 通过 ChannelExec 执行命令和获取命令执行结果, channelExec .getInputStream() 获取命令输出流,如果 in .available()<0 则没有输出任何信息。

在命令没有输出的时候,判断一下是否超时,如果不超时继续读取,如果超时则断开远程连接。

三, 记录远程命令执行日志

很多时候我们执行的命令会有大量的输出,而我们又不能一直盯着输出看,此时我们就需要把输出记录成日志,这样方便我们排查。当然程序有的时候会有日志记录,我们执行的命令就不一定有了,所以我们要人工来添加。

如我们在部署环境的时候,需要记录部署日志,但是同时也要根据输出来判断部署结果。此时就要检测什么时候算是部署成功,什么情况下是部署失败?那么我们就需要设置一个成功的标志,检查到有这个标志输出的时候就算成功,示例代码如下:

publicboolean exec( String cmd ,String outputFileName ,String sucessStr , long timeout )

throws Exception{

BufferedWriter out = new BufferedWriter( new FileWriter( outputFileName ));

ChannelExec channelExec = null ;

InputStream in = null ;

try {

log .info( " 要执行的命令 :" + cmd );

channelExec = (ChannelExec) session .openChannel( "exec" );

channelExec .setCommand( cmd );

channelExec .setInputStream( null );

channelExec .setErrStream(System. err );

channelExec .setPty( true );

in = channelExec .getInputStream();

channelExec .connect();

int res = -1;

byte [] tmp = newbyte [ 1024 ];

long starttime = System. currentTimeMillis ();

while ( true ) {

while ( in .available() > 0 ) {

int i = in .read( tmp , 0, 1024 );

if ( i < 0 ) break ;

String tmp2 = new String( tmp , 0, i ) + "\n" ;

if ( sucessStr != null ) {

if ( tmp2 .indexOf( sucessStr ) > -1) {

out .write( tmp2 );

returntrue ;

out .write( tmp2 );

starttime = System. currentTimeMillis ();

if (System. currentTimeMillis () - starttime >= timeout * 1000) {

log .info( " " + timeout + " 秒内没有日志输出,中断程序 " );

break ;

if ( channelExec .isClosed()) {

log .info( " 没有输出了,关闭了 " + (System. currentTimeMillis () - starttime ));

res = channelExec .getExitStatus();

log .info( "channel 关闭 " );

break ;

} catch (Exception e ) {

e .printStackTrace();

} finally out .close();

if ( channelExec != null ) {

channelExec .disconnect();

if ( in != null ) {

in .close();

session .disconnect();

log .info( " 关闭连接 " );

returnfalse ;

代码解析:

函数 exec ()通过参数来执行命令,并把命令输出给保存到 outputFileName 指定的文件中。

在命令执行的时候,判断输出,如果输出包含 sucessStr 指定的成功标志,则返回 True ;其他的情况如超时,或是报错等都会返回 False

在异常的情况下,需要记录异常并且关闭与远程主机的连接,不然一次连接启动一个进程,会把服务器给卡死的。

(4) 当然我们也可以去掉记录日志的部署,单纯地根据命令的输出来判断命令执行成功或是失败,这个就比较简单,我们在此就不展示代码了。

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑