壊れたメガネ

ホッチキスの達人の意識の高いブログ。

土曜だし NetBeans で Java デーモンプログラム書こうぜ!

デーモンプログラムとは



デーモンプログラムとは、画面やコンソールから切り離されたバックグラウンドで動くプログラムの事です。
バックグラウンドという言葉に対して、フォアグラウンドという言葉があります。
デスクトップやコンソールで作業を行うとき、大抵のプログラムはこのフォアグラウンドで動いています。
フォアグラウンドで動いているプログラムはユーザーがコンソールを終了したり、ログオフすればともに終了します。
しかし、デーモンプログラムはユーザーがコンソールを終了したり、ログオフしても動きつづけます。とっても便利です。
サーバー、ボットなどと呼ばれるプログラムの多くはこのデーモンプログラムです。

今回の目的


最終的に次のコマンドでデーモンプログラムを実行します。コマンドオプションについては後述します。

$ jsvc -pidfile ~/tmp/mydaemon.pid -java-home /usr/lib/jvm/java -cp MyDaemon.jar MyDaemon

目的までの流れは


  1. デーモン実行に必要な Jsvc のビルド

  2. 実際にデーモンとして処理を行う Java プログラムのビルド

  3. コマンドの実行


と言う感じです。

Jsvc のビルド



ディストリビューションによってはソフトウェアリポジトリが jsvc を提供している場合があります。僕が使っている ubuntu-12.04 ではデフォルトのリポジトリによって jsvc が提供されていました。
Jsvc のビルドにはいくつか必要なものがあります。

  1. ANSI-C 準拠の コンパイラ (GCC がよい)

  2. GNU Make

  3. Java 2 Platform に準拠している SDK

Jsvc のソースコードと commons-daemon の jar ライブラリのダウンロード


http://commons.apache.org/daemon/download_daemon.cgi から次のものをダウンロードします。(ただし、 v.e.r にバージョン番号が入ります。)

  • commons-daemon-v.e.r-native-src.tar.gz

  • commons-daemon-v.e.r-bin.tar.gz (Java プログラムを作る際に必要なものです。)


Jsvc をビルド


ここからはターミナルでビルド作業を進めます。
ダウンロードした commons-daemon-v.e.r-native-src.tar.gz を展開します。(同様に commons-daemon-v.e.r-bin.tar.gz も展開して下さい。)

$ cd ~/Downloads/
$ tar zxvf commons-daemon-v.e.r-native-src.tar.gz

展開してできたフォルダ内にある unix と言うフォルダに移動します。

$ cd ~/Downloads/commons-daemon-1.0.8-native-src/unix
$ ls -l
total 148
-rw-rw-r--. 1 oasynnoum oasynnoum   2086 Nov 18 18:14 CHANGES.txt
-rwxrwxr-x. 1 oasynnoum oasynnoum 112872 Nov 18 18:14 configure
-rw-rw-r--. 1 oasynnoum oasynnoum   4923 Nov 18 18:14 configure.in
-rw-rw-r--. 1 oasynnoum oasynnoum   2601 Nov 18 18:14 INSTALL.txt
-rw-rw-r--. 1 oasynnoum oasynnoum   1146 Nov 18 18:14 Makedefs.in
-rw-rw-r--. 1 oasynnoum oasynnoum   1238 Nov 18 18:14 Makefile.in
drwxrwxr-x. 2 oasynnoum oasynnoum   4096 Jan 20 17:01 man
drwxrwxr-x. 2 oasynnoum oasynnoum   4096 Jan 20 17:01 native
drwxrwxr-x. 2 oasynnoum oasynnoum   4096 Jan 20 17:01 support

support/buildconf.sh を実行します。

$ ./support/buildconf.sh
./support/buildconf.sh: configure script generated successfully

./configure を実行します。
その際に --with-java オプションを指定します。ここでは例として /usr/lib/jvm/java を指定します。
--with-java オプションは省略可能ですが、デフォルトでは /usr/java に指定されます。
必要に応じて configure オプションを指定してください。 configure オプションは以下のコマンドで確認できます。

$ ./configure --help

./configure の実行

$ ./configure --with-java=/usr/lib/jvm/java

以下のコマンドで、 Jsvc 実行ファイルを実際に生成します

$ make

jsvc の実行ファイルが確認できるはずです。

$ ls -l jsvc
-rwxrwxr-x. 1 oasynnoum oasynnoum 118202 Jan 21 13:52 jsvc

この jsvc を PATH の通ったディレクトリにコピーすると使いやすいです。ホームディレクトリの bin にコピーするのが一番簡単です。

$ cp ./jsvc ~/bin/

PATH の通ったディレクトリは次のコマンドで確認できます

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/oasynnoum/bin

Jsvc パッケージの Makefile には install という rule が定義されていないので make install を行っても次の様になります。

$ make install
make: *** No rule to make target `install'.  Stop.


Java プログラムの作成



ここでは NetBeans IDE を例にとって Java プログラムの作成を行います。

新規 Java プロジェクトを作成します。


[カテゴリ] から [Java] を選択し、 [プロジェクト] の [Java アプリケーション] を選び、 [次へ] を押します。
f:id:oasynnoum:20120121173400p:image

[プロジェクト名] に MyDaemon と入力し、 [主クラスを作成] にチェックを入れ、[完了] を押してください。
f:id:oasynnoum:20120121173401p:image

Commons Daemon のライブラリを追加します。


MyDaemon プロジェクトのツリーを展開し、 [ライブラリ] の上で右クリックします。
開いたメニューから [JAR/フォルダを追加] を押します。
f:id:oasynnoum:20120121173402p:image

ファイル選択ダイアログが開くので、先ほど入手した jar ファイルを選び、 [選択] を押します。
f:id:oasynnoum:20120121173403p:image

デーモンのコード


次の Java コードをプロジェクト作成時に自動生成された MyDaemon.java に上書きコピー&ペーストしてください。

package mydaemon;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
import org.apache.commons.daemon.DaemonInitException;

public class MyDaemon implements Daemon {

    private Thread processor = null;

    private boolean stop = false;

    @Override
    public void start() {
        this.stop = false;
        this.processor.start();
    }

    @Override
    public void stop() {
        this.stop = true;
    }

    @Override
    public void destroy() {}

    @Override
    public void init(DaemonContext dc) throws DaemonInitException, Exception {
        this.processor = new Thread(new Runnable() {

            @Override
            public synchronized void run() {
                String lineSeparator = System.getProperty("line.separator");
                String outputFileName = System.getProperty("user.home") + System.getProperty("file.separator") + "mydaemon-currenttime";
                FileOutputStream erasor = null;
                try {
                    File outputFile = new File(outputFileName);
                    if (!outputFile.exists()) {
                        boolean resultCreateNewFile = outputFile.createNewFile();
                        if (!resultCreateNewFile) {
                            throw new Exception();
                        }
                    }
                    
                    erasor = new FileOutputStream(outputFileName);
                            
                    while (!stop) {
                        try {
                            erasor = new FileOutputStream(outputFileName);
                            erasor.write((Long.toString(System.currentTimeMillis()) + lineSeparator).getBytes());
                            wait(1000);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(MyDaemon.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                } catch (IOException ex) {
                    Logger.getLogger(MyDaemon.class.getName()).log(Level.SEVERE, null, ex);
                } catch (Exception ex) {
                    Logger.getLogger(MyDaemon.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    try {
                        if (erasor != null) erasor.close();
                    } catch (IOException ex) {
                        Logger.getLogger(MyDaemon.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        });
    }
}

MyDaemon.jarの構築


[主プロジェクトを削除して構築] ボタンを押し、プロジェクトを構築します。
f:id:oasynnoum:20120121173404p:image

MyDaemon プロジェクトのディレクトリに dist というディレクトリが出来、その中に MyDaemon.jar が確認できるはずです。
f:id:oasynnoum:20120121173405p:image


デーモンの実行



実行


これで、冒頭で示したコマンドを実行できます。

$ jsvc -pidfile ~/tmp/mydaemon.pid -java-home /usr/lib/jvm/java -cp MyDaemon.jar mydaemon.MyDaemon

コマンドは3つのオプション -pidfile -java-home -cp に続き、最後に実際に処理を行うクラスの名前を指定します。
コマンドオプションの意味はそれぞれ次のとおりです。



  • -pidfile /path/to/pid/file

    デーモンのプロセスIDが記録されるファイルのパス。書き込み可能である必要があります。


  • -java-home /path/to/java/installed/directory

    Java がインストールされているディレクトリを指定します。


  • -cp classpath

    クラスパスを指定します。ここでは先ほどビルドしてできた jar ファイルを指定します。


デーモンがうまく動いて入れば、ホームディレクトリに mydaemon-currenttime というファイルが作成され、ファイルの中にはミリ秒単位の現在時刻が確認できます。

$ cat ~/mydaemon-currenttime
1327128544130

停止


デーモンの停止は次のコマンドで行います。

$ jsvc -pidfile ~/tmp/mydaemon.pid -java-home /usr/lib/jvm/java -cp MyDaemon.jar -stop mydaemon.MyDaemon