Ganymed SSH-2 for Java是一个纯Java实现的SHH2库,官网为http://www.ganymed.ethz.ch/ssh2/,最新的更新时间为2006年10月,在用之前,请仔细看一下FAQ,真的能避免很多很多问题,下面列出几条重要的:

一,如果使用Session.execCommand()方法,则每个session中只能执行一条命令

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
public class SSHTest {
public static void main(String[] args) {
String hostname = "192.168.192.130";
String username = "hadoop";
String password = "hadoop"; try {
Connection conn = new Connection(hostname);
conn.connect();
boolean isAuthenticated = conn.authenticateWithPassword(
username,password);
if (isAuthenticated == false)
throw new IOException("Authentication failed.");
Session sess = conn.openSession(); sess.execCommand("cd test");//这么写是不行的
sess.execCommand("cat 1.txt"); InputStream stdout = new StreamGobbler(sess.getStdout());
InputStream stderr = new StreamGobbler(sess.getStderr());
BufferedReader stdoutReader = new BufferedReader(
new InputStreamReader(stdout));
BufferedReader stderrReader = new BufferedReader(
new InputStreamReader(stderr)); System.out.println("Here is the output from stdout:");
while (true) {
String line = stdoutReader.readLine();
if (line == null)
break;
System.out.println(line);
} System.out.println("Here is the output from stderr:");
while (true) {
String line = stderrReader.readLine();
if (line == null)
break;
System.out.println(line);
}
sess.close();
conn.close();
} catch (IOException e) {
e.printStackTrace(System.err);
System.exit(2);
}
}
}

执行结果为:

java.io.IOException: A remote execution has already started.
at ch.ethz.ssh2.Session.execCommand(Session.java:244)
at SSHTest.main(SSHTest.java:28)

If you use Session.execCommand(), then you indeed can only execute only one command per session. This is not a restriction of the library, but rather an enforcement by the underlying SSH-2 protocol (a Session object models the underlying SSH-2 session).

There are several solutions:

1,Simple: Execute several commands in one batch, e.g., something like Session.execCommand("echo Hello && echo again")一批次执行多条命令

sess.execCommand("cd test;cat 1.txt");

2,Simple: The intended way: simply open a new session for each command - once you have opened a connection, you can ask for as many sessions as you want, they are only a "virtual" construct为每一个命令打开一个新的会话

Session sess = conn.openSession();
sess.execCommand("cd test");//进入~/test路径
sess.close();
sess = conn.openSession();//由于是新打开的会话,此时在~路径下
sess.execCommand("cat 1.txt");

由于每个命令执行完成后会话会关闭,所以在执行"cat 1.txt"时会报找不到该文件,因为第二次登陆成功后在~路径下,而1.txt文件在~/test路径下

3,Advanced: Don't use Session.execCommand(), but rather aquire a shell with Session.startShell()使用Session.startShell()方法代替Session.execCommand()方法

二,如果使用Sess.execCommand()得到的结果和预期不一样或根本不能执行,那么要注意你的环境变量

The most often source of problems when executing a command with Session.execCommand() are missing/wrong set environment variables on the remote machine. Make sure that the minimum needed environment for XYZ is the same, independentely on how the shell is
being invoked.

Example quickfix for bash users:

1,Define all your settings in the file ~/.bashrc

2,Make sure that the file ~/.bash_profile only contains the linesource ~/.bashrc

3,Before executing Session.execCommand(), do NOT aquire any type of pseudo terminal in the session. Be prepared to consume stdout and stderr data.

在~/test/路径下准备一个test.sh脚本:

#!/bin/sh
opt=$1
if [ ${opt}x = startx ]
then
echo "opt:start"
status=`lsmod | grep ip_tables | wc -l`
if [ ${status} -gt 0 ]
then
sudo service iptables start
else
echo "service iptables already started"
fi
fi

然后尝试调用这个脚本:

sess.execCommand("sh -x ~/test/test.sh start");

运行结果如下:

Here is the output from stdout:
opt:start
service iptables already started
Here is the output from stderr:
+ opt=start
+ '[' startx = startx ']'
+ echo opt:start
++ lsmod
/home/hadoop/test/test.sh:行6: lsmod: 未找到命令
++ grep ip_tables
++ wc -l
+ status=0
+ '[' 0 -gt 0 ']'
+ echo 'service iptables already started'

可以看到脚本打印信息由stdout输出,脚本-x的调试信息由stderr输出,脚本在虚拟机中运行正常,为什么这里却提示"lsmd:未找到命令"呢

虚拟机中的环境变量如下:

[hadoop@localhost test]$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin:/sbin

再来看看代码中建立的会话的环境变量:

sess.execCommand("echo $PATH");
Here is the output from stdout:
/usr/local/bin:/usr/bin
Here is the output from stderr:

相差十万八千里,反正我是懒得整了,咱就直接全路径吧,将脚本中的lsmod修改为/usr/sbin/lsmod

三,当我只从stdout中读取数据时,进程有时候会挂起

In the SSH-2 low level protocol, each channel (e.g., session) has a receive window. When the remote SSH daemon has filled up our receive window, it must wait until we have consumed the input and are ready to accept new data.

在底层SSH2协议中,每个channel(写过socket的应该都知道这个是什么)都有一个接收窗,当远程的SSH进程填充满我们的接收窗,在我们消耗掉接收窗中的数据并可以接收新的数据之前它都会处于等待状态

Unfortunately, the SSH-2 protocol defines a shared window for stderr and stdout. As a consequence, if, for example, the remote process produces a lot of stderr data and you never consume it, then after some time the local receive window will be full and
the sender is blocked. If you then try to read() from stdout, your call will be blocked: there is no stdout data (locally) available and the SSH daemon cannot send you any, since the receive window is full (you would have to read some stderr data first to
"free" up space in the receive window).

不幸的是,底层SSH2协议为stdout和stderr定义了一个共享的接收窗,如果远程的SSH进程产生了很多stderr信息但是你从来都没有消费过它们,一段时间后,本地的接收窗将被填满,此时数据发送端将被挂起,如果这时你试图读取stdout,你的请求也将被挂起。由于接收窗已满,因此接收窗中没有任何可用的stdout信息,并且远程SSH进程也不会发送任何stdout信息(除非先读取一些stderr信息以释放部分接收窗的空间)

Fortunately, Ganymed SSH-2 uses a 30KB window - the above described scenario should be very rare.

幸运的是,Ganymed SSH-2使用30KB大小的接收窗,所以上面描述的情景很少发生

Many other SSH-2 client implementations just blindly consume any remotely produced data into a buffer which gets automatically extended - however, this can lead to another problem: in the extreme case the remote side can overflow you with data (e.g., leading
to out of memory errors).

What can you do about this?

1,Bad: Do nothing - just work with stderr and stdout Inputstreams and hope that the 30KB window is enough for your application. 啥也不做,期盼30KB足够程序使用

2,Better, recommended for most users: use two worker threads that consume remote stdout and stderr in parallel. Since you probably are not in the mood to program such a thing, you can use the StreamGobbler class supplied
with Ganymed SSH-2. The Streamgobbler is a special InputStream that uses an internal worker thread to read and buffer internally all data produced by another InputStream. It is very simple to use:

只需使用Ganymed SSH-2自带的StreamGobbler类即可使用2个工作线程并行消费远程的stdout和stderr,StreamGobbler是一个特殊的输入流,它能使用内部工作线程读取、缓存所有另一个输入流输入的信息,示例如下:

InputStream stdout = new StreamGobbler(mysession.getStdout());
InputStream stderr = new StreamGobbler(mysession.getStderr());

You then can access stdout and stderr in any order, in the background the StreamGobblers will automatically consume all data from the remote side and store in an internal buffer.

然后你就可以以任何顺序访问stdout和stderr,StreamGobblers将会在后台自动消费所有远程端口传递过来的数据并存放在一个内部的buffer中

3,Advanced: you are paranoid and don't like programs that automatically extend buffers without asking you. You then have to implement a state machine. The condition wait facility offered by Session.waitForCondition() is exactly what you need: you can use
it to wait until either stdout or stderr data has arrived and can be consumed with the two InputStreams. You can either use the return value of Session.waitForCondition() or check with InputStream.available() (for stdout and stderr) which InputStream has data
available (i.e., a read() call will not block). Be careful when wrapping the InputStreams, also do not concurrently call read() on the InputStreams while calling Session.waitForCondition() (unless you know what you are doing).

Sess.waitForCondition方法可以获取流的状态信息,通过得到的状态信息来判断下一步做什么

接下来遇到的问题相当的棘手,反正我是没查到问题的原因,当我在项目中使用Ganymed SSH-2调用服务器脚本的时候,进程经常会卡主(不是所有的脚本,少部分脚本,但是我没发现产生异常的脚本和可以正常调用的脚本之间有什么不同,并且可以确定,绝对不是接收窗满引起的,因为只返回了很少的运行结果,并且debug时也可以看到,BufferedReader中创建的长度为8000多的字符数组只被使用了几百的空间),奇怪的是如果我把脚本调用部分单独提取出来放到一个Java项目中执行,运行正常

脚本最后部分如下:

......
if [ -n "$pid" ]
then
echo "success"
exit 0
else
echo "faild."
exit -1
fi
fi
fi

代码如下(程序第一次进入while循环时打出success,第二次进入循环后,在String line = stdoutReader.readLine();这一步卡主):

......
BufferedReader stderrReader = new BufferedReader(
new InputStreamReader(stderr)); System.out.println("Here is the output from stdout:");
while (true) {
String line = stdoutReader.readLine();
if (line == null)
break;
System.out.println(line);
}
......

日志如下:

......
Here is the output from stdout:
success

由于Java源代码调试起来很费劲,最终也没调试问题出在哪里,介绍下解决方式吧,虽然程序进程卡住了,但是后台的脚本其实已经成功运行完成,所以要做的就是为连接返回结果的读取设置超时时间

InputStream stdout = sess.getStdout();
InputStream stderr = sess.getStderr(); byte[] buffer = new byte[100];
String result = "";
while (true) {
if ((stdout.available() == 0)) {
int conditions = sess.waitForCondition(ChannelCondition.STDOUT_DATA |
ChannelCondition.STDERR_DATA | ChannelCondition.EOF, 1000*5);
if ((conditions & ChannelCondition.TIMEOUT) != 0) {
logger.info("time out break");
break;//超时后退出循环,要保证超时时间内,脚本可以运行完成
}
if ((conditions & ChannelCondition.EOF) != 0) {
if ((conditions & (ChannelCondition.STDOUT_DATA |
ChannelCondition.STDERR_DATA)) == 0) {
logger.info("break");
break;
}
}
}
while (stdout.available() > 0) {
int len = stdout.read(buffer);
if (len > 0){
System.err.write(buffer, 0, len);
result += new String(buffer, 0, len);
}
}
while (stderr.available() > 0) {
int len = stderr.read(buffer);
if (len > 0){
System.err.write(buffer, 0, len);
result += new String(buffer, 0, len);
}
}
}

反正我以后是不准备用这个东西了

Ganymed SSH-2 for Java的更多相关文章

  1. Java SSH远程执行Shell命令、shell脚本实现(Ganymed SSH)

    jar包下载地址: http://www.ganymed.ethz.ch/ssh2/ 此源码的好处就是没有依赖很多其他的包,拷贝过来干干净净.具体代码实现可以看下文,或参考官方文档,在下载的压缩包里g ...

  2. Linux SSH下安装Java并设置环境

    我是用Xshell进行远程连接阿里云服务器的,所以jdk不好下载. 我使用的是Winscp远程软件,在window上下载了jdk然后再上传到Linux服务器上 下面是安装的步骤 1.下载jdk8 登录 ...

  3. MyEclipse创建SSH项目(Java web由maven管理)

    JavaEE后台开发,MyEclipse创建SSH项目,MyEclipse创建Java web 由maven管理的SSH项目. Demo工程源码github地址 1.创建SSH项目 1.创建web工程 ...

  4. ssh工具 (Java)

    执行shell命令.下载文件... package com.sunsheen.blockchain.admin.utils; import java.io.BufferedReader; import ...

  5. ssh框架出现Java.lang.NoSuchMethodError: antlr.collections.AST.getLine()I错误

    原因:因为Struts自带的antlr-2.7.2.jar,比Hibernate自带的antlr-2.7.7.jar的版本要低,存在jar包冲突现象,因此要删除前一个低版本的. 由于myeclipse ...

  6. java:Spring框架3(AOP,SSH集成Demo)

    1.AOP: Spring提供了4种实现AOP的方式: 1.经典的基于代理的AOP 2.@AspectJ注解驱动的切面 3.纯POJO切面 4.注入式AspectJ切面 aop.xml: <?x ...

  7. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:SSH框架(Struts2+Spring+Hibernate)搭建整合详细步骤

    在实际项目的开发中,为了充分利用各个框架的优点,通常都会把 Spring 与其他框架整合在一起使用. 整合就是将不同的框架放在一个项目中,共同使用它们的技术,发挥它们的优点,并形成互补.一般而言,在进 ...

  8. java连接远程服务器之manyged-ssh2 (windows和linux)

    一.所需要的jar包 需要借助Ganymed SSH的jar包:  ganymed-ssh2-262.jar 下载地址: http://www.ganymed.ethz.ch/ssh2/ API详情: ...

  9. SSH框架

    一,Struts2框架部署 1,搭建Struts2的jar包 1.1,Struts2所需各个jar包的作用 asm-3.3.jar                                  操 ...

  10. 【Jsch】使用SSH协议连接到远程Shell执行脚本

    如果大家熟悉Linux的话,一定对ssh,sftp,scp等命令非常熟悉,ssh是一个安全协议,用来在不同系统或者服务器之间进行安全连接,SSH 在连接和传送的过程中会加密所有的数据. 但是SSH一般 ...

随机推荐

  1. Guid 的几种形式

    Guid.NewGuid().ToString()得几种格式显示 1.Guid.NewGuid().ToString("N") 结果为:       38bddf48f43c485 ...

  2. Axis2 java调用.net webservice接口的问题(郑州就维)

    这是一个古老的问题,古老到从我若干年前遇到这样的问题就是一个解决之道:反复尝试.其实标准是什么,标准就是一个束缚,一种按既定规则的束缚,错点点,你的调用就可能不成功,不成功后你要花费大量的力气查找原因 ...

  3. Android ImageView的属性android:scaleType

    ImageView的属性android:scaleType,即ImageView.setScaleType(ImageView.ScaleType) imageView.setScaleType(Im ...

  4. golang安装卸载 linux+windows+raspberryPI 平台

    参考  https://golang.org/doc/install 自ECUG2013洗脑回来,就渴望早点接触Go 听着许式伟和谢孟军的演讲 发现go的网络库的确很强大,高负载利器,语言的一些精简导 ...

  5. SQL Server 2008空间数据应用系列九:使用空间工具(Spatial Tools)导入ESRI格式地图数据

    转自:http://www.cnblogs.com/beniao/archive/2011/03/22/1989310.html 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Micros ...

  6. table显示json数据传递

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. 【原】 Spark中Worker源码分析(一)

    Worker作为对于Spark集群的健壮运行起着举足轻重的作用,作为Master的奴隶,每15s向Master告诉自己还活着,一旦主人(Master>有了任务(Application),立马交给 ...

  8. 成功安装mysql(mysql-5.5.32-winx64.msi)后,为何服务管理器里找不到MYSQL服务名?

    解决方案: 1.打开cmd,切换到mysql的bin目录下 2. D:\Program Files\MySQL5.1\bin>mysqld.exe -install 成功后会出现:Service ...

  9. 字符串逆转(递归和非递归java)

    package 乒乒乓乓; public class 递归逆转字符串 {    //非递归逆转    public static String reverse(String s)    {       ...

  10. Msys下gcc的配置

    打开文件/etc/profile,添加如下路径, C_INCLUDE_PATH=/e/msys/1.0/include export C_INCLUDE_PATH CPLUS_INCLUDE_PATH ...