ganymed-ssh2实现java ssh协议采集

ganymed-ssh2实现java ssh协议采集

  (2013-12-10 10:02:12)
标签: 

ssh

 

java

 

it

 
转自 http://blog.csdn.net/pengchang_1981/article/details/8095683

   我的博客第一篇讲的就是用Maverick组件实现java ssh协议采集,可惜Maverick是个商业软件,不开放源码且只有45天的试用期。实际上在网上也能搜到不少实现java ssh的开源组件,例如orion-ssh2trilead-ssh2ganymed-ssh2mindterm等组件,实际上 oriontrileadganymed都是用的相近的源码,这个可以从源码结构看出来。我就用ganymed-ssh2进行研究。

                  google上找到的ganymed-ssh2的官网是http://www.ganymed.ethz.ch/ssh2/,进去看官网的英文简介可以看到该网站已经不维护该项目,并已经迁移到http://www.cleondris.ch/,在这个网站点击右上角的Contact,再点击open source就可以看到这个项目的新家,http://www.cleondris.ch/opensource/ssh2/,上面简单介绍了该项目能远程连接上远程机器,支持命令模式和shell模式,本地和远程端口转发,没有任何JCE依赖等,最后特别指出这个项目是为瑞士苏黎世的一个项目所创建。下面提供了2010-08-23发布的ganymed-ssh2-build251beta1.zip可供下载使用,下面还有在线文档和FAQ供开发者参考。

                  将该文件下载下来解压后可以看到目录结构很简单清晰,ganymed-ssh2-build251beta1.jar就放在外层目录下,examples 里面放了几个怎么使用的例子,faq里面是个faq的网页,javadocapi文档,src里面是源码,我就直接参照例子里最基础的 Basic.java进行模仿做一个使用例子。Basic.java的源码如下:

                 

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 Basic

{

    public static void main(String[] args)

    {

       String hostname = "127.0.0.1";

       String username = "joe";

       String password = "joespass";

 

       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("uname -a && date && uptime && who");

 

           System.out.println("Here is some information about the remote host:");

 

          

 

           InputStream stdout = new StreamGobbler(sess.getStdout());

 

           BufferedReader br = new BufferedReader(new InputStreamReader(stdout));

 

           while (true)

           {

              String line = br.readLine();

              if (line == null)

                  break;

              System.out.println(line);

           }

 

          

 

           System.out.println("ExitCode: " + sess.getExitStatus());

 

          

 

           sess.close();

 

          

 

           conn.close();

 

       }

       catch (IOException e)

       {

           e.printStackTrace(System.err);

           System.exit(2);

       }

    }

}

              我参照该代码写了自己的工具类,自己的需求是要把命令执行结果返回,而不是像例子逐行打印,对该例子打印结果部分稍作修改,写完后跑起来居然发送的命令很 多都没结果返回值,少数有结果返回值的也都在一行而没有分行,没分行的原因很好找是我还是用的

             

  1. String line br.readLine();  


           
 这里必须用另br.,read的方法来做,才能在结果中保留换行符。但是没有结果值的问题让我困惑了半天,官方的例子都没结果值,这到底怎么搞得,网上别 人博客也都是这样写的啊。想来想去,就去看apiSession这个类中有哪些方法,我注意到startShell()方法,在Maverick项目中不是也用到类似的方法了吗?马上就试下,将这个方法调用放在execCommand方 法之前,跑起来这回更离谱了,在跑到exexCommand方法时居然报出了IOException,异常内容是A remote execution has already started.,意思很好理解是就是有个远程方法已经执行了,这下就又搞昏头了,看来这样也不好使啊。脑子一动,开源软件最大的优点不是咋可以看看源码 吗?打开源码Session.java看到两个方法的源码如下:

          

 

    public void execCommand(String cmd) throws IOException

    {

       if (cmd == null)

           throw newIllegalArgumentException("cmd argument may not be null");

 

       synchronized (this)

       {

          

           if (flag_closed)

              throw new IOException("This session is closed.");

 

           if (flag_execution_started)

              throw new IOException("A remote execution has already started.");

 

           flag_execution_started = true;

       }

 

       cm.requestExecCommand(cn, cmd);

    }

 

   

    public void startShell() throws IOException

    {

       synchronized (this)

       {

          

           if (flag_closed)

              throw new IOException("This session is closed.");

 

           if (flag_execution_started)

              throw new IOException("A remote execution has already started.");

 

           flag_execution_started = true;

       }

 

       cm.requestShell(cn);

  1. }  

 

            这两个方法的源码还不晦涩,一看就明了,在startShell中把flag_execution_started置为true,那个 execCommand不是有个if (flag_execution_started)就抛new IOException("A remote execution has already started.")吗,看来很明显,这两个方法根本不能同时用,那咋搞,继续整呗,程序员哪能轻易缴械投降。我参照Maverick的代码发现要有打开 伪终端的方法,我发现这个Session里也有

public void requestPTY(java.lang.String term,

                    int term_width_characters,

                    int term_height_characters,

                    int term_width_pixels,

                    int term_height_pixels,

                    byte[] terminal_modes)方法,参照该方法的参数说明

                                        term - The TERM environment variable value (e.g., vt100)

                                        term_width_characters - terminal width, characters (e.g., 80)
                                        term_height_characters - terminal height, rows (e.g., 24)
                                              term_width_pixels - terminal width, pixels (e.g., 640)
                                        term_height_pixels - terminal height, pixels (e.g., 480)
                                         terminal_modes - encoded terminal modes (may be null)   

       我在执行命令前加上 session.requestPTY("vt100", 80, 24, 640, 480, null);然后再进行测试,事不过三,终于成功采到值。完整代码如下:

           

    importjava.io.BufferedReader; 

    import java.io.IOException; 

    import java.io.InputStream; 

    importjava.io.InputStreamReader; 

     

    import ch.ethz.ssh2.Connection; 

    import ch.ethz.ssh2.Session; 

    importch.ethz.ssh2.StreamGobbler; 

     

     

    public final class SSHGanymedUtil { 

     

         

        private Logger logger; 

     

         

        private String hostname; 

     

         

        private int port; 

     

         

        private String username; 

     

         

        private String password; 

     

         

        private long taskId; 

     

         

        private Connection connection; 

     

         

        public SSHGanymedUtil(String _hostname, int _port, String _username, 

                String _password) { 

            this.hostname = _hostname; 

            this.port = _port; 

            this.username = _username; 

            this.password = _password; 

        } 

     

         

        public SSHGanymedUtil(String _hostname, int _port, String _username, 

                String _password, Long id) { 

            this(_hostname, _port, _username, _password); 

            this.taskId = id; 

            logger Logger.getLogger("/util/SSHGanymedUtil/_" + id, 

                    Logger.ALL, true); 

        } 

     

         

        public void login() throws Exception { 

            logger.infoT("start task id is " + taskId); 

     

            // 建立连接 

            connection = new Connection(hostname, port); 

            try { 

                // 连接上 

                connection.connect(); 

     

                // 进行校验 

                boolean isAuthenticated = connection.authenticateWithPassword( 

                        username, password); 

     

                logger.infoT("isAuthenticated = " + isAuthenticated); 

                if (isAuthenticated == false) 

                    throw new IOException("Authentication failed."); 

     

            } catch (Exception e) { 

                logger.exception(e); 

                throw new Exception("UserOrPasswordError"); 

            } 

        } 

     

         

        public String execCommand(final String command) { 

            logger.infoT("start exexCommand"); 

            final StringBuilder sb = newStringBuilder(256); 

            // 连接的通道 

            Session sess = null; 

            try { 

                // 创建session 

                sess = connection.openSession(); 

     

                // 这句非常重要,开启远程的客户端 

                sess.requestPTY("vt100", 80, 24, 640, 480, null); 

     

                // 开启后睡眠4 

                Thread.sleep(4000); 

     

                // 开启终端 

                // sess.startShell(); 

                // 执行命令 

                sess.execCommand(command); 

     

                // 起始时间,避免连通性陷入死循环 

                long start = System.currentTimeMillis(); 

                // // 增加timeOut时间 

                // sess.waitForCondition(ChannelCondition.TIMEOUT, 5000); 

     

                InputStream stdout = newStreamGobbler(sess.getStdout()); 

                BufferedReader br = newBufferedReader( 

                        newInputStreamReader(stdout)); 

     

                char[] arr = new char[512]; 

                int read; 

     

                int i = 0; 

     

                while (true) { 

                    // 将结果流中的数据读入字符数组 

                    read = br.read(arr, 0, arr.length); 

     

                    // 推延5秒就退出[针对连通性测试会陷入死循环] 

                    if (read < 0 || (System.currentTimeMillis() - start) > 5000) 

                        break; 

     

                    // 将结果拼装进StringBuilder 

                    sb.append(new String(arr, 0, read)); 

                    i++; 

                } 

     

                logger.infoT("ExitCode: " + sess.getExitStatus() + "i = " + i); 

     

            } catch (Throwable e) { 

                logger.exception(e); 

            } finally { 

                // 关闭通道 

                if (sess != null) 

                    sess.close(); 

            } 

            return sb.toString(); 

        } 

     

         

        public void closeConnection() { 

            logger.infoT("end task id is " + taskId + "disconnet"); 

            if (connection != null) 

                connection.close(); 

        } 

     

        public String getHostname() { 

            return hostname; 

        } 

     

        public void setHostname(String hostname) { 

            this.hostname = hostname; 

        } 

     

        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 getPassword() { 

            return password; 

        } 

     

        public void setPassword(String password) { 

            this.password = password; 

        } 

     

         

        public static void main(String[] args) { 

            char[] c = new char[3072]; 

     

            for (int i = 0; i < c.length; i++) { 

                c[i] = 'a'; 

            } 

     

            String s = new String(c, 0, 3072); 

            System.out.println(s); 

     

        } 

     

    }   


         
 下面谈下后续修改的3点体会作为FAQ吧。

  1.    对于GanymedMaverick采集相同机器得到返回的结果值小有差异,Ganymed在采集Soralis系统的机器时会多返回一行系统的提示信息,对于结果的处理要特殊处理。
  2.    我 的采集包括连通性的测试,就是模拟输出ping命令,这点Maverick的监听关闭机制处理相对巧妙,不必额外增加代码处理,而这里ping命令会让 stdout不停的输出(就像在本机ping一台机器不停的返回结果),形成死循环,所以我在代码中增加判断,如果读取结果的while(true)循环 执行超过5秒,就break出循环,也可以解决ping命令的死循环问题。
  3.    对于GanymedMaverick两者特别要小心的是,有几个命令我用Maverick采集一点问题都没有,但是用Ganymed执行返回值却出错, 显示为“syntax error The source line is 1.”等错误信息,意思好像是命令有错,但我把命令通过SecureCRT输 进去测试却能返回值,这个也把我搞困惑了很久,后来我将能正确返回值的命令和这几个出错的命令进行比较,终于发现原因所在,原来正确的命令输入到终端后, 都需要手动按回车键出现执行结果,而这些出错的命令都隐藏包含了“\r\n”回车换行符了,输入进去什么都不干就自动出现结果,所以将命令更换为无“\r \n”的就一切正常了。

           总结:  对于软件包中examples中给出Basic.java在实际执行中可能需要加上sess.requestPTY("vt100", 80, 24, 640, 480, null);开启虚拟终端才能执行命令返回值,另外对于sess.getStdout()流数据的读取,例子给出了String line = br.readLine();但我们不能把读取的line直接append上去,那样得到结果就会成为一行,必须定义一个char数组char[] arr = new char[512],并采用read = br.read(arr, 0, arr.length); ,再sb.append(new String(arr, 0, read));,才能得到还原真实情况的结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值