How to use SSH in Java Programmatically

How to use SSH in Java Programmatically

                                      

转自:DarrenWang 之 剑毅阁@BlogCn

How to use SSH in Java Programmatically

在Java程序中使用SSH(How to use SSH in Java Programmatically)
---by Darren.Wang

这个标题不知道能不能表达我的意思,实际上我只是想总结一下可以通过哪些方式或者途径来达到在Java程序中使用SSH相关功能(任务)的目的。前几天有更多free time,所以,为了简化credit的管理工具正式版的发布上传过程,简单实现了一个基于SWT界面的上传应用程序,要完成的功能其实也很简单,但是为了提高上传速度和数据传输的安全性,所以,上传分成几个阶段同时使用SSH来保证上传过程的安全性,之于说上传的阶段等细节不属于我今天要描述的重点,重点是如何在Java中使用SSH,尤其是远程登录到Linux,并执行Shell命令。
现从同事的一个需求说起,他手头的任务中包括检查某台Linux机器的磁盘空间等情况,并随同Email发送。当然别人也给他提出了多种解决方法,不能说不好,但在我看了与程序的集成性上面差一些,所以我觉得给他写一个Utility(坐我旁边,不帮都不行,呵呵)。
实际上,实现原理很简单,直接SSH登录那台Linux机器执行df命令就可以了,其他信息,像当前目录拥有的文件列表等,运行ls
 -ls命令,这些我想大家都很清楚,那么在Java中我们是如何实现类似功能的那?!
  可能有人以前做过类似功能,那他一定听说过JSch或者说OpenSSH(当然,我们会用他的Java实现安ganymed),对,我们也只是为JSch提供了一个简单的Wrapper而已。
  先来看看这个Wrapper是什么样子的吧,然后我再详细说一下这个程序的设计和实现细节:
package org.darrenstudio.ssh;

import java.io.InputStream;
import java.util.Properties;

import org.apache.commons.lang.Validate;
import org.darrenstudio.ssh.callback.SSHExecCallback;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

public class SSHExecutor {
private JSch jsch;
private Session session;
private boolean login;

public synchronized void login(SSHLoginOptions options) throws SSHExecException
{
try
{
if(jsch == null)
jsch =new JSch();
session = jsch.getSession(options.getUsername(),options.getHost(),options.getPort());
session.setPassword(options.getPassword());
Properties prop = new Properties();
prop.setProperty("StrictHostKeyChecking","no";//StrictHostKeyChecking: ask | yes | no
session.setConfig(prop);
session.connect();
login = true;
}
catch(Exception e)
{
throw new SSHExecException(e);
}
}

public synchronized void execute(String command,SSHExecCallback callback) throws SSHExecException
{
if(!login)
throw new SSHExecException("login first before executing the remote command!";
Validate.notEmpty(command);
    Channel channel = null;
    try
    {
     channel =session.openChannel("exec";
    ((ChannelExec)channel).setCommand(command);
    InputStream in=channel.getInputStream();
//     OutputStream out=channel.getOutputStream();
    InputStream err = ((ChannelExec)channel).getErrStream();
    
    // to retrieve the interactive password request information, this pty is a must
    ((ChannelExec)channel).setPty(true);
    
    channel.connect();
    
    byte[] tmp=new byte[2048];
    while(true)
    {
     while(in.available() > 0)
     {
     int i=in.read(tmp, 0, 2048);
     String line = new String(tmp, 0, i);
     callback.dumpConsole(line);
     }
    
     while(err.available() > 0)
     {
     int size = err.read(tmp,0,2048);
     String line = new String(tmp,0,size);
     callback.dumpErrStream(line);
     }
    
     if(channel.isClosed())
     {
     int exitStatus = channel.getExitStatus();
     if(exitStatus != 0)
     throw new SSHExecException("Error Exit Status with Value:"+exitStatus);
     break;
     }
     try{Thread.sleep(1000);}catch(Exception ee){}
    }
    }
    catch(Exception e)
    {
     throw new SSHExecException(e);
    }
    finally
    {
     if(channel != null)
     {
     channel.disconnect();
     channel = null;
     }
    }
    
}

public synchronized void dispose()
{
if(session != null)
{
session.disconnect();
session = null;
}
login = false;
}
}

  我们给出一个Executor,他负责为我们执行Shell命令,他首先要求我们登录到要执行命令的Linux机器(即login方法),然后,如果登录成功,client端就可以调用execute方法来执行相应的shell命令,执行后,在finally中dispose掉该Executor。
  对于login方法来说,因为需要提供login相关信息,而且这些信息参数较多,3-4个,当然,相对来说也不是很多,但是,我们还是采用将他们归并到一个参数类的做法(我想Effective Java大家都读过),这就有了我们的SSHLoginOptions类:
  public class SSHLoginOptions implements Serializable {

private static final long serialVersionUID = -8018206086412607771L;

private String host;
private String username;
private String password;
private int    port = 22;

public SSHLoginOptions(String host,String username,String password)
{
this(host,username,password,22);
}
public SSHLoginOptions(String host,String username,String password,int port)
{
this.host     = host;
this.username = username;
this.password = password;
this.port     = port;
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String toString() {
return new ToStringBuilder(this).append("host", host).append(
"username", username).append("password", password).append(
"port", port).toString();
}
}
  如果登录不成功的话(可能因为网络不通等原因),我们需要抛出一个异常以便告诉Client端该事件,并终止以下步骤,所以,我们采用抛出自定义的SSHExecException:
  public class SSHExecException extends NestableException {

private static final long serialVersionUID = -2804917566444475128L;

public SSHExecException(String cause)
{
super(cause);
}
public SSHExecException(Throwable t)
{
super(t);
}
public SSHExecException(String cause,Throwable t)
{
super(cause,t);
}
}
  (这里定义为一个Checked异常,实际上,感觉定义为unchecked异常更恰当一些,因为如果失败,Client端也做不了什么)
  登录成功后,login标志被置为true,这样execute方法才可以被调用。
  execute方法有两个参数,第一个参数为String类型,表示将被执行Shell命令;第二个参数较为特殊,他是我们自己定义的一个接口:
  public interface SSHExecCallback {
void dumpConsole(String line);
void dumpErrStream(String errline);
}
  这个接口的实现负责处理Linux的正常输出和Error输出,至于说如何处理这些输出,你可以按照自己的需要给出自己的实现,比如,只是简单的打印到控制台:
  public class DefaultSSHExecCallback implements SSHExecCallback {

public void dumpConsole(String line) {
System.out.println(line);
}

public void dumpErrStream(String errline) {
System.err.println(errline);
}

  在execute方法一开始,我们会检查是否登录成功,如果没有,那同样会抛出SSHExecException,以示说该类没有为Shell命令的执行准备好相应的状态,从而阻止随后的不安全操作。
  之后,我们会打开一个Exec Channel,通过这个Channel来执行Shell命令,这可以很容易的从Executor的源码中看出来,如果执行过程中出现异常,我们会抛给Client端我们的自定义异常,当然,不管执行成功或者失败与否,我们都会关掉该Channel以释放连接,否则,主程序会挂在那里。在execute方法中,唯一需要关注的一个地方就是((ChannelExec)channel).setPty(true);这一句,如果没有他,那你的控制台将什么东西都没有,你将得不到任何想要的信息。
  为了说命令执行完成后释放资源,我们给出一个dispose方法,这也是很自然的,这里不再赘述。
 
  下面是该类的一个TestCase,大家可以很容易看出该类的使用,很简单。
  public class SSHExecutorTest extends TestCase {

private SSHExecutor executor;

public static void main(String[] args) {
junit.textui.TestRunner.run(SSHExecutorTest.class);
}

protected void setUp() throws Exception {
super.setUp();
executor = new SSHExecutor();
SSHLoginOptions loginOptions = new SSHLoginOptions("server","user","pwd";
executor.login(loginOptions);
}

protected void tearDown() throws Exception {
super.tearDown();
executor.dispose();
executor = null;
}

public void testExecuteWithCommandUname() throws SSHExecException
{
String command = "uname";
GenericSSHExecCallback callback = new GenericSSHExecCallback();
executor.execute(command,callback);
assertEquals("The Operating System of m.livedoor.cn should be Linux","Linux",StringUtils.trimToEmpty(callback.getOutput()));
}
}

至此,我们的Wrapper类就算完成了,让我们回过头来看看,我们能归纳出什么东西。
到目前为止,我所可以提供的相关信息有两类,一类就是执行Scp相关操作,一类就是基于SSH的Shell命令的执行,那么要完成这两类功能,现在有什么东西可以让我们避免去重新发明轮子那?!
对于Scp相关任务来说,除了前面blog曾经提到过的通过Ant来实现外,你也可以通过JSch来完成,不要忘了,Ant的Scp Task也是通过JSch完成的,除此之外,OpenSSH的Java实现---ganymed也可以很容易的实现scp功能,而且,代码也看起来很简洁:
/**
 * @author darrenwang
 * @since  1.0
 */
public class SCPExecutor {
private Connection connection;
private boolean login;

public synchronized void login(SSHLoginOptions options) throws SSHExecException
{
try
{
connection = new Connection(options.getHost());
connection.connect();
login = connection.authenticateWithPassword(options.getUsername(),options.getPassword());
if(!login)
throw new SSHExecException("Authentication failed.";
}
catch(Exception e)
{
throw new SSHExecException(e);
}
}

public synchronized void doScp(File file, String todir) throws SSHExecException
{
if(!login)
throw new SSHExecException("login() first before executing the scp task!";
try
{
SCPClient client = connection.createSCPClient();
client.put(file.getAbsolutePath(),todir);
}
catch(Exception e)
{
throw new SSHExecException(e);
}
}
public synchronized void dispose()
{
if(connection != null)
{
connection.close();
connection = null;
}
login = false;
}
}
除了Scp,那剩下很大一部分任务可能都是基于SSH的Shell命令执行啦,这个同样,你可以通过Ant,JSch和ganymed来实现,这里就不做赘述了,因为通过上面的SSHExecutor和以前的blog,你可以很容易的给出实现。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值