茫茫網海中的冷日
         
茫茫網海中的冷日
發生過的事,不可能遺忘,只是想不起來而已!
 恭喜您是本站第 1670362 位訪客!  登入  | 註冊
主選單

Google 自訂搜尋

Goole 廣告

隨機相片
IMG_60D_00007.jpg

授權條款

使用者登入
使用者名稱:

密碼:


忘了密碼?

現在就註冊!

爪哇咖啡屋 : [轉貼]Ganymed ssh 小使用

發表者 討論內容
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]Ganymed ssh 小使用
Ganymed ssh小使用

java實現ssh協議ganymed實現SCP和SFTP
Ganymed SSH

目錄
1 SSH簡介 1
2 JAVA實現SSH的項目 1
3 GANYMED SSH 1
3.1 依賴說明 1
3.2 實現簡單命令 1
3.2 實現SCP命令

1 SSH簡介
SSH協議的簡介可見:http://xmong.iteye.com/blog/1698124

2 Java實現SSH的項目
Java 實現SSH協議的項目有很多,如JFTP,trilead SSH,JSCH,ganymed SSH等
下面我們主要說的是關於ganymed SSH的一些小使用。
Ganymed SSH-2 for Java是用純Java實現SSH-2協議的一個項目。可以通過它直接在Java程序中連接SSH服務器,實現基於SSH協議的服務訪問。 如遠程命令執行和shell訪問,本地和遠程端口轉發,本地數據流轉發,X11轉發,SCP,SFTP等功能。

3 Ganymed SSH
3.1 依賴說明
Ganymed SSH地址:http://www.ganymed.ethz.ch/ssh2/,下載ganymed-ssh2-build210.zip包,解壓後可見src(源代碼),ganymed-ssh2-build210.jar (jar包),javadoc(常用api文檔),examples(簡單使用案例)。可以通過簡單使用案例學習ganymed ssh的使用。

使用方法:將 ganymed-ssh2-build210.jar 加入到java項目的lib中即可使用。

3.2 實現簡單命令
    package com.example;

    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)
        {
                    //服務器ip
            String hostname = "172.30.5.162";
                    //用戶名
            String username = "xmong";
                    //密碼
            String password = "xmong";

            try
            {
                /* Create a connection instance */
                //構造一個連接器,傳入一個需要登陸的ip地址,連接服務
                Connection conn = new Connection(hostname);

                /* Now connect */
                conn.connect();

                /* Authenticate.
                 * If you get an IOException saying something like
                 * "Authentication method password not supported by the server at this stage."
                 * then please check the FAQ.
                 */
                //用戶驗證,傳入用戶名和密碼
                boolean isAuthenticated = conn.authenticateWithPassword(username, password);

                if (isAuthenticated == false)
                    throw new IOException("Authentication failed.");

                /* Create a session */
                //打開一個會話session,執行linux命令
                Session sess = conn.openSession();


                sess.execCommand("pwd");

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

                /*
                 * This basic example does not handle stderr, which is sometimes dangerous
                 * (please read the FAQ).
                 */
                //接收目標服務器上的控制台返回結果,輸出結果。
                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);
                }

                /* Show exit status, if available (otherwise "null") */
                //得到腳本運行成功與否的標誌 :0-成功 非0-失敗
                System.out.println("ExitCode: " + sess.getExitStatus());

                /* Close this session */
                //關閉session和connection
                sess.close();

                /* Close the connection */

                conn.close();

            }
            catch (IOException e)
            {
                e.printStackTrace(System.err);
                System.exit(2);
            }
        }
    }


輸出結果:
    Here is some information about the remote host:
    /home/xmong
    ExitCode: null


3.2 實現SCP命令
    package com.test;

    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.SCPClient;
    import ch.ethz.ssh2.Session;
    import ch.ethz.ssh2.StreamGobbler;

    public class TestScp {


        public static void main(String[] args)
        {
            String hostname = "172.30.5.162";
            String username = "xmong";
            String password = "xmong";

            try
            {
                /* Create a connection instance */
                //構造一個連接器,傳入一個需要登陸的ip地址,連接服務
                Connection conn = new Connection(hostname);
                conn.connect();

                //用戶驗證,傳入用戶名和密碼
                boolean isAuthenticated = conn.authenticateWithPassword(username, password);

                if (isAuthenticated == false)
                    throw new IOException("Authentication failed.");

                //創建一個copy文件客戶端
                SCPClient scpClient = conn.createSCPClient();
                scpClient.put("D:/test.txt ", "./");//從本地複製文件到遠程目錄
                scpClient.get("./test.txt", "E:/");//從遠程獲取文件

                /**
                 * 通過SFTP遠程讀取文件內容
                 * test.txt文件內容為sftp---test
                 */
                SFTPv3Client sftpClient = new SFTPv3Client(conn);
                SFTPv3FileHandle sftpHandle = sftpClient.openFileRW("./test.txt");
                byte[] bs = new byte[11];
                int i = 0;
                long offset = 0;
                while(i!=-1){
                    i = sftpClient.read(sftpHandle, offset, bs, 0, bs.length);
                    offset += i;
                }
                System.out.println(new String(bs));

                //打開一個會話session,執行linux命令
                Session sess = conn.openSession();
                sess.execCommand("ls");
                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);
                }

                //得到腳本運行成功與否的標誌 :0-成功 非0-失敗
                System.out.println("ExitCode: " + sess.getExitStatus());

                //關閉session和connection
                sess.close();
                conn.close();

            }
            catch (IOException e)
            {
                e.printStackTrace(System.err);
                System.exit(2);
            }
        }

    }


輸出結果:
    sftp---test
    Here is some information about the remote host:
    test.txt
    ExitCode: 0


原文出處:Ganymed ssh小使用 - Java - ITeye博客
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]Ganymed SSH-2 for Java
Ganymed SSH-2 for Java

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 - CSDN博客
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[分享]冷日 Ganymed SSH-2 Sample Code
冷日幾經測試,終於把 Key Base Auth 搞定了!!!

先看看比較簡單的『帳密法』:
	static void connect2Server(String hostname, String username, String password) {
		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);
		}
	}


然後,就是 Key Base 囉:
	static void PublicKeyAuthentication(String hostname, String username, String keyfilePass) {
		File keyfile = new File("C:\\Temp\\keystore\\openssh"); // or "~/.ssh/id_dsa"
		System.out.println("Win-Way : Connect SSH Server with Public Key - [" + keyfile.toString() + "]!");
		try {
			Connection conn = new Connection(hostname);
			conn.connect();
			boolean isAuthenticated = conn.authenticateWithPublicKey(username, keyfile, keyfilePass);
			if (isAuthenticated == false)
				throw new IOException("Authentication failed.");
			Session sess = conn.openSession();
			sess.execCommand("uname -a && date && uptime && who");
			InputStream stdout = new StreamGobbler(sess.getStdout());
			BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
			System.out.println("Here is some information about the remote host:");
			while (true) {
				String line = br.readLine();
				if (line == null)
					break;
				System.out.println(line);
			}
			sess.close();
			conn.close();
		} catch (IOException e) {
			e.printStackTrace(System.err);
			System.exit(2);
		}
	}


首先必須紀錄的是,要丟去 Unix Like Server 上的要使用 Public key for pasting into OpenSSH authorized_keys file: 內的資訊! 不能用 puttygen Save 出來的 public key!

另外很重要必須筆記的另一件則是:
keyfile = new File("C:\\Temp\\keystore\\openssh");

也就是 puttygen 產出的 Public 不能直接拿來用!!!
務必要進行 Conversions -> Export OpenSSH Key 後才能用!!!


Ganymed SSH-2 for Java 官網:http://www.ganymed.ethz.ch/ssh2/
前一個主題 | 下一個主題 | 頁首 | | |



Powered by XOOPS 2.0 © 2001-2008 The XOOPS Project|