SSH してみる Part 3 (コマンド実行編)

今回は SSH 経由で リモートサーバ上のコマンドを実行する方法です。
SSH してみる Part2 でも 書いている通り 最終的な目標はサーバのモニタリングツールを作ることですが、 まずは vmstat を実行して その結果をそのまま画面に表示することからはじめてみましょう。

今回もサーバには Ubuntu 10.04 LTS を使っています。
サーバ にログインして vmstat をオプションなしで実行してみると 以下のような情報がコンソールに標準出力されます。

procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0      0 235708  24976 100720    0    0     6     3   13   31  0  0 100  0

これと同じことを JavaFX で実現してみましょう。
まずは表示する画面の作成から...
画面は 『SSH してみる (パスワード認証編)』 で作成したものをそのまま利用します。
もちろん パスワード認証部分も...
init 状態 (初期状態) の 真っ黒な画面に 識別子 "foreground" で ラベル を一つ追加します。

実行するとこんなイメージ...


後は 『SSH してみる (パスワード認証編)』 で作成したコード中の

Alert.inform("Auth success");
を以下のように変更するだけです。
もちろん、今回もエラー処理等は考慮していません。
// コマンドを実行するためのチャネルを開く
def channel = session.openChannel("exec") as ChannelExec;

channel.setCommand("vmstat");
channel.setErrStream(System.err);   // TODO エラー処理

def in = channel.getInputStream();

// コマンドを送信する
channel.connect();

// アプリケーション終了時にチャネルを閉じるためのフック
FX.addShutdownAction(function(): Void { channel.disconnect(); });

try {

    def r = new BufferedReader(new InputStreamReader(in, "UTF-8"));

    // チャネルが開いている間 処理を続ける
    while (true) {

        // ブロックせずに読み込めるか確認
        while (r.ready()) {

            // 一行分のデータを読み込み画面に表示
            foreground.text = "{foreground.text}\n{r.readLine()}"
        }
        
        // チャネルが閉じていたら終了
        if (channel.isClosed()) {
            break;
        }

        // 暫く待ってもう一度データを読み込む        
        Thread.sleep(500);
    }

} catch (e: InterruptedException) {
    // ignore
}

ポイントは

  • コマンドを送信する前に InputStream を取得しておく
  • Reader#ready() で読み込み可能か確認する
の2つです。

1つ目ですが Channel#connect() を呼び出す前に InputStream を取得しておかないと タイミングによっては サーバからの出力が受け取れないことがあるからです。というのも...
サーバからの出力を受け取るための InputStream は Channel#getInputStream() を実行したときに初めて生成されるのです。
その為 Channel#connect() して サーバ上で コマンドを実行する前に準備しておかないとダメなのです。

2つ目ですが BufferedReader で いつもの

var line: Object;
while ((line = r.readLine()) != null) {
    ...
}
のように readLine() をいきなり呼んでしまっては readLine() でブロックされてしまいます。
その為 ready() を実行して ブロックされずに読み込めるか 確認してから読み込まないとダメなのです。
でないと サーバからの出力が返ってこない場合、永遠に readLine() で待たされてしまうことに...
今回はサンプルなので問題ないですが、通常 タイムアウト処理 や 再実行処理等 を ちゃんと実装しないといけないのでこれは重要です。