技術メモ

神奈川在住のITエンジニアの備忘録。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

JavaのExecutorServiceで簡単マルチスレッド

Javaでマルチスレッドなプログラムを書く場合、Java1.5から導入された ExecutorService を使うと簡単に書ける。
以下は ExecutorService を使った簡単なサンプルプログラムである。

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureSample {

    public static void main(String[] args) {
        try{
            FutureSample futureSample = new FutureSample();
            futureSample.exec();
        }
        catch(Exception ex){
            System.out.println(ex.getMessage());
            System.out.println(ex.getStackTrace());
        }
    }

    private void exec() throws InterruptedException, ExecutionException {
        //--------------------------------
        // Definition of a task(Callable).
        //--------------------------------
        Callable<Date> task = new Callable<Date>() {
            public Date call() throws InterruptedException {
                System.out.println("Task started."); /*---(3)---*/
                Thread.sleep(3000);
                Date time = new Date();
                System.out.println("Task Ended."); /*---(4)---*/
                return time;
            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();

        // Execute the task and get a future for the task.
        System.out.println("Execute a task..."); /*---(1)---*/
        Future<Date> future = executor.submit(task);

        // Other tasks can be done with the task here, and this fact is important for using Future.
        System.out.println("Execute other task."); /*---(2)---*/
        Thread.sleep(5000);
        System.out.println("Other task finished."); /*---(5)---*/

        // Get the result of the task if it has been done. Otherwise this thread waits here.
        Date time = future.get();
        System.out.println("Time(Task result) is " + time); /*---(6)---*/

        executor.shutdown();
    }
}

これを実行すると、ソース中のコメントに書いた番号(1~6)の順に出力される。


ここでのポイントは、

        Future<Date> future = executor.submit(task);

と書くだけで、task を非同期に実行でき、結果は任意のタイミングで、

        future.get();

で取り出せるということ。もちろん、future.get() した時点で task が終了していなければここで待たされる。

executor に task を submit するだけで非同期にタスクを実行できる(マルチスレッドを実現できる)なんて便利ですね~。

指定されたディレクトリ配下のアーカイブファイルを再帰的に展開するスクリプトを作成した。

指定されたディレクトリ配下にあるアーカイブファイルを再帰的に展開するperlスクリプト extractRecursively.pl を作成した。
使い方は簡単で、「perl extractRecursively.pl "ディレクトリ"」として実行するだけ。

use strict;
use warnings;
use Archive::Zip;
use File::Basename;
use Getopt::Long;

my $DEF_OF_ARC = '(\.zip$)|(\.jar$)|(\.war$)|(\.ear$)|(\.sar$)';

my $deleteArchiveFile;
GetOptions(
  'deleteArchiveFile'  => \$deleteArchiveFile,
);

my $dir = $ARGV[0];
if(! -d $dir){
    die "Invalid argument.";
}
extract($dir);

sub extract{
    my $dir = shift;
    
    opendir(my $dh, $dir);
    my @fileList = readdir($dh);
    closedir($dh);
    
    foreach my $file (sort @fileList){
        if($file =~ /^\.{1,2}$/){
            next;
        }
        if(($file =~ /$DEF_OF_ARC/) && (-f "$dir/$file")){
            extractZip("$dir/$file");
            if(defined($deleteArchiveFile)){
                unlink("$dir/$file");
            }
        }
        if(-d "$dir/$file"){
            extract("$dir/$file");
        }
    }
}

sub extractZip{
    my $zip = shift;
    my $dir = dirname($zip);
    my $archive = Archive::Zip->new($zip);
    my @members = $archive->memberNames();
    
    foreach my $member (@members) {
        $archive->extractMember($member, "$dir/$member");
        if(($member =~ /$DEF_OF_ARC/) && (-f "$dir/$member")){
            extractZip("$dir/$member");
            if(defined($deleteArchiveFile)){
                unlink("$dir/$member");
            }
        }
        if(-d $member){
            extract("$dir/$member");
        }
    }
}


アーカイブファイルの種類は、ソースの最初の方の、

my $DEF_OF_ARC = '(\.zip$)|(\.jar$)|(\.war$)|(\.ear$)|(\.sar$)';

で指定できるので、必要に応じてここを編集する。

また、実行時に -d オプションを付けて、「perl extractRecursively.pl "ディレクトリ" -d」として実行すると、展開元のアーカイブファイルを展開後に消すことができる。

ソースの作りとしては、extract()とextractZip()が再帰的に自分を呼びつつ、お互いを呼び合うかたちになっている。もっとシンプルに書けそうな気もするが、とりあえず今はこれで使う。

Zabbixについて

OSSの運用管理ソフトZabbixについて調べる機会があったので、ここにメモしておく。

 

  • ラトビアのZabbix社が開発している。
  • サーバ側のフロントエンドはPHPで、バックエンドはC言語で書かれている。
  • DBはpostgresやMySQLなどからいろいろ選べる。
  • 機能毎にプロセスが別れており、拡張しやすい疎結合な作りになっている。
  • 運用管理の自由度が高く、いろいろ作りこめる。外部ツールとの連携もしやすい。
  • 監視対象にZabbixエージェントが入っていれば、CPU使用率やメモリ使用率などの細かい情報が取得できる。
  • Zabbixエージェントが入っていなくても、SNMP、ICMP、IPMI、SSHtelnetで監視できる。
  • 監視の際は、監視テンプレートを適用する。
  • 監視テンプレートは数多くあり、最適な監視テンプレートを選ぶにはノウハウが必要。
  • 大規模構成にも対応できる。分散監視も可能。

 

自由度が高いことを逆に捉えると、ユーザ側でいろいろ設定や作り込みをしないと満足な運用がしにくい、ということになるだろう。中身を知ってしっかり作り込みたいユーザ向けと思われる。

ファイルに書かれた文字列で検索するスクリプト

ファイルAに書かれた文字列で、ファイルB内を検索するperlスクリプト(findStr.pl)を作成した。使い方は簡単で「perl findStr.pl "ファイルA" "ファイルB"」で実行する。

例えば、ファイルAの中身が

aaa
bbb
ccc

となっていて、ファイルBの中身が

aaa
ddd
eee

となっていたら、「aaa」を出力する。

use strict;
use warnings;
use Getopt::Long;

my $notExist = 0;
my $fromFile;
my $toFile;

GetOptions(
  'notExist'   => \$notExist,
  'fromFile=s' => \$fromFile,
  'toFile=s'   => \$toFile,
) or die 'Usage: findStr.pl {-f|fromFile} <Keyword file> {-t|toFile} <target file> [-n|notExist]';

open(my $fromFh, "< $fromFile") or die "Can't open $fromFile: $!";
open(my $toFh, "< $toFile")     or die "Can't open $toFile: $!";

while (my $checkLine = <$fromFh>) {
    next if ($checkLine =~/^#/);
    chomp($checkLine);
    
    my $found = 0;
    while (my $tgtLine = <$toFh>) {
        next if ($tgtLine =~/^#/);
        
        # I should't use regular expressions here.
        my $checkLineQuoted = quotemeta($checkLine);
        
        # If $tgtLine has a pattern of $checkLineQuoted.
        if ($tgtLine =~/$checkLineQuoted/) {
            $found = 1;
            last;
        }
    }
    # Perl keeps a position of $toFh even if a while loop ends, so it is necessary to seek the position to the begining of the target file.
    seek($toFh, 0, 0);
    
    if ($notExist) { # Not found string.
        if ($found == 0) {
            print "$checkLine\n";
        }
    }
    else { # Found string.
        if ($found != 0) {
            print "$checkLine\n";
        }   
    }
}

close($fromFh);
close($toFh);


コメントにも書いたが、このスクリプトのポイントは以下。

    seek($toFh, 0, 0);

内側のwhile文で回しているファイルのファイルポインタは、内側のwhile文を抜けて再度内側のwhile文に入った時にリセットされない(先頭まで戻されない)ので、seek関数を使ってファイルポインタを先頭まで戻す必要がある。こうしないと、外側のwhileループの1回目しかちゃんと検索できなくなってしまう。こういうのは言葉で説明しようとするとなかなか難しい。。

コマンドプロンプト上でのダブルクォート(")の挙動

Windowsコマンドプロンプト上でのダブルクォート(")について、ちょっと分かりにくい挙動があったので、ここにメモしておく。

 

例えば、コマンドプロンプト上で、

test.bat  "abc def"

 は問題なく実行できる。

 

しかし、

test.bat "abc def" < 

 は「コマンドの構文が誤っています。」となり実行できない。これは、「<」がダブルクォートの範疇からはずれているので、コマンドプロンプトが、ただの文字ではなくファイルからの入力である、と解釈したためだと考えられる。

 

では、

test.bat "abc def" "< 

 だと、どうだろうか?これは問題なく実行できる。test.bat 内では、第1引数は「"abc def"」になり、第二引数は「"<」となる。「<」はただの文字として解釈されている。

ダブルクォートが閉じていないのにコマンドプロンプト上でちゃんと解釈されるのは不思議だ。他の言語ならエラーになるだろう。

 

念のため、以下はどうだろう?

test.bat "abc def" "<"

これも問題なく実行できる。test.bat 内では、第1引数は「"abc def"」になり、第二引数は「"<"」となる。

 

ここまでを纏めると、ダブルクォートはコマンドプロンプト上で特殊な意味を持つ文字をエスケープできる(ただの文字化できる)。また、ダブルクォートで囲われていなくても開始のダブルクォートがあれば、エスケープは有効になる、ということが分かった。

 

なお、上記はWindows7で実機検証した結果をもとに書いているが、他のバージョンのWindowsでも挙動は変わらないと思う。たぶん。

SNMPのoctet string型のデータの中に制御文字は入り得るのか?

SNMPには文字列を表現する型としてoctet string型がある。この型のデータの中に改行コード等の制御文字は入り得るのかについて調べてみた。

 

まず、RFCを見てみた。

RFC 2578 - Structure of Management Information Version 2 (SMIv2)

The OCTET STRING type represents arbitrary binary or textual data.
Although the SMI-specified size limitation for this type is 65535
octets, MIB designers should realize that there may be implementation
and interoperability limitations for sizes in excess of 255 octets. 

 上記の通り、octet string型のデータの中には、任意のバイナリorテキストデータが入ると言っていて、制御文字はダメとは言っていない。なので、RFC規約的には入り得るのだと思われる。

 

ただ、SNMPエージェントから送られてきたoctet string型のデータの中に制御文字が入っていることは見たことがないし、

13.15 SNMPトラップのvarbindが文字化けして表示される

のように、制御文字が入ることを制限している製品もあるので、慣習的に制御文字は入れないのではないだろうか。

 

制御文字が入ることを制限している製品に対して、例えば改行コードが入ったoctet string型のデータを送りたい場合は、送る側で改行コードを「\r\n」という文字列に変換してから送る等すれば良いと思う。

ファイルの中身を16進ダンプするスクリプト

引数で受け取ったファイルの中身を16進ダンプして、出力用のファイルに出す perl スクリプトを作成した。どういう時に使うかというと、例えば、入力に16進ダンプの文字列を受け付けるプログラムを使いたい時に、このスクリプトで対象の文字列を16進ダンプにしてから、そのプログラムに入力させる、といった感じ。
使い方は簡単で「perl dumpHexStr.pl "ファイル"」と実行すると、ファイルの中身を16進ダンプしたものを "dumped_ファイル" に出力する。

dumpHexStr.pl

use strict;
use warnings;

my $inFile = $ARGV[0];
if (!-f $inFile) {
    die "There is not $inFile file...";
}
my $outFile = "dumped_" . $inFile;

open(my $inFh,  "< $inFile")  or die("Error :$!");
open(my $outFh, "> $outFile") or die("Error :$!");

while (my $line = <$inFh>) {
    chomp($line);
    my $hexDump = unpack("H*", $line);
    print($outFh $hexDump);
}

close($inFh);
close($outFh);

perlは、こういったテキスト処理を簡単に書けるのが良いですね(^^)