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. [Hadoop源码解读](五)MapReduce篇之Writable相关类

    前面讲了InputFormat,就顺便讲一下Writable的东西吧,本来应当是放在HDFS中的. 当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节 ...

  2. Linux -- Ubuntu搭建java开发环境

    Steps 1 Check to see if your Ubuntu Linux operating system architecture is 32-bit or 64-bit, open up ...

  3. 使用C#在word中插入页眉页脚

    //插入页脚 public void InsertFooter(string footer) { if (ActiveWindow.ActivePane.View.Type == WdViewType ...

  4. zookeeper使用和原理探究(一)(转)

    zookeeper介绍zookeeper是一个为分布式应用提供一致性服务的软件,它是开源的Hadoop项目中的一个子项目,并且根据google发表的<The Chubby lock servic ...

  5. linux和Dos下换行符转换

    一.windows的文件到linux下的转换方法: 1. 用sed命令替换 sed -e 's/^M//g' original.txt > target.txt (注意^M 在Linux/Uni ...

  6. Visual Studio 2013中的新项目对话框

    在Visual Studio 2013,我们推出了添加新的项目对话框. 此对话框取代了是曾在2012年这个的对话框作品,所有ASP.NET项目(MVC,Web窗体和Web API). 这就是我们如何提 ...

  7. Timer和counter

    什么是Timer,什么是Counter 几乎每个嵌入式板都会有counter和timer,重要性比肩gpio.本质上来看timer和counter几乎是一样的东西,底层都是一个硬件counter,如果 ...

  8. HW2.13

    import java.util.Scanner; public class Solution { public static void main(String[] args) { Scanner i ...

  9. UVa11404 - Palindromic Subsequence(区间DP+打印路径)

    题目大意 给定一个字符串,要求你删除尽量少的字符,使得原字符串变为最长回文串,并把回文串输出,如果答案有多种,则输出字典序最小的 题解 有两种解法,第一种是把字符串逆序,然后求两个字符串的LCS,并记 ...

  10. [二]SpringMvc实践-注解

    1.@RequestMapping("/list")映射访问路径 2.@RequestParam(value="id",required=false)请求参数, ...