【Webプログラミング - Code.012】CGI(Perl)ファイル処理 その5:「ファイルロック:mkdir編」

   Code.012                                                 2002年12月30日発行
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━■
                           【 Webプログラミング 】

                       〜 猫的プログラマーとその軌跡 〜
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━■

              ▼毎週月曜日に配信しています。
              ▼等幅フォントでご覧いただくとキレイに見えます。
              ▼登録・解除はこちらから可能です。
                < http://www.ichikoro.com/webp/ >
                  ※ぜひお友達にもご紹介ください(^^)/


こんにちは、編集者の勝部です。
今年最後のメルマガとなりました。

昨日行ったアンケートを一通、一通読ませていただいています。
いや〜、非常に参考になるご回答が多く大変タメになります。
ご協力いただいた皆さん、本当にありがとうございました。

見ていて特に気がついたのは「ポジティブ」な方が多いと言うこと。
性格的に、というわけではなく(そうかもしれませんが(^^;)学習意欲が、
という点にです。

皆さんに満足いただけるような誌面を目指して、来年から色々な工夫をしていき
たいと思います。末永くご愛読くださいませ( ^.^)( -.-)( _ _)

って、いきなり終わりの挨拶みたいになってしまいましたが、
今週も張り切って行って見ましょう!

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 CGI(Perl)ファイル処理 その5:「ファイルロック:mkdir編」
──────────────────────────────────────
前回、前々回とflockを使用したファイルロック(排他制御)について解説しました。
ただflockだけでは対応しきれない場合があります。例えば、下記のような

    (1)ファイルからデータ取り込み
    (2)データ加工
    (3)ファイルに保存
    (4)結果表示

【一連の処理】をロックしたい場合、もう一工夫必要になってきます。
またflockが使えない環境下(Windows9X,Meなど)では、代替え手段を用意
しなければなりません。

今回は排他制御について、もう少し解説したいと思います。

    ↓前回、前々回はコチラ
    http://www.ichikoro.com/webp/bk/00055/
    http://www.ichikoro.com/webp/bk/00056/


    Mission : SSIから呼び出せるアクセスカウンターを作成せよ
    ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
        ○問題
            SSIを使用し、以下のようにCGIを呼び出す。

                <!--#exec cgi="text_count.cgi"-->

            画像ではなくテキストを出力するのカウンターとして
            動作するCGItext_count.cgiを作成せよ。カウント値は
            count.txt に保存せよ。

            CGI内ではカウント値を表示(更新)すると同時に以下の
            ログも別のファイル(log.txt)に記録すること。区切り
            文字はタブ(\t)とし、最後は改行(\n)する。

                ・カウント値
                ・アクセス日(YYYY/MM/DD)
                ・アクセス時間(hh:mm:ss)
                ・IPアドレス


SSIについては以下を参照のこと

    ・とほほのSSI入門
      http://tohoho.wakusei.ne.jp/wwwssi.htm


───────
    ソース
───────
#!/usr/bin/perl

;#
;#テキストカウンター(SSI版)
;#

#----------------------------------------------------------------------#
#                            モジュール                                #
#----------------------------------------------------------------------#
use strict;                #コーディングを厳格化

#----------------------------------------------------------------------#
#                           グローバル変数                             #
#----------------------------------------------------------------------#
package G;
BEGIN{
    #■カウント値を記録するファイル
    $G::DAT_COUNT = "count.txt";

    #■ログを記録するファイル
    $G::DAT_LOG = "log.txt";

    #■ロックディレクトリ
    $G::DIR_LOCK = "lock.dir";
}


#======================================================================#
#                           メインルーチン                             #
#======================================================================#
package main;
{
    my $t = time();
    my $date = util::get_date($t);        #現在の日付取得(YYYY/MM/DD)
    my $time = util::get_time($t);        #現在の時間取得(hh:mm:ss)
    my $count;
    my $loop_count;

    #-----------------------------------#
    #        ロックディレクトリ作成     #
    #-----------------------------------#
    $loop_count = 0;
    while(1){
        if( mkdir($G::DIR_LOCK, 0755) ){   #ディレクトリが作成できれば
            last;                          #ループを抜ける
        }
        else{
            sleep(1);                      #1秒待機
        }

        #-- 10秒以上ロック権が取れない場合は異常終了 --#
        exit(1) if( $loop_count++ > 10 );
    }


    #-----------------------------------#
    #        現在のカウント値取得       #
    #-----------------------------------#
    $count = count::get($G::DAT_COUNT);

    #-----------------------------------#
    #           カウント値更新          #
    #-----------------------------------#
    count::set($G::DAT_COUNT, $count + 1);

    #-----------------------------------#
    #            ログに記録             #
    #-----------------------------------#
    util::append_log($G::DAT_LOG        #ログファイル
                     , "\t"             #区切り文字(タブ)
                     , "\n"             #レコードの最後の文字(改行)

                           #-- ▼以降をデータとして記録する▼ --#
                           , $count                  # カウント値
                           , $date                   # YYYY/MM/DD
                           , $time                   # hh:mm:ss
                           , $ENV{'REMOTE_ADDR'}     # IPアドレス
    );

    #-----------------------------------#
    #           カウント値表示          #
    #-----------------------------------#
    $|=1;
    print "Content-type: text/plain\n\n";
    print $count;

    #-----------------------------------#
    #        ロックディレクトリ削除     #
    #-----------------------------------#
    rmdir($G::DIR_LOCK) or die("Can not remove directory:$G::DIR_LOCK ($!)");

    #-----------------------------------#
    #              正常終了             #
    #-----------------------------------#
    exit(0);
}



;#
;#カウンターパッケージ
;#
package count;
use strict;                #コーディングを厳格化
use Fcntl qw(:flock);      #定数 LOCK_* を取り込む


#--------------------------------------------------------------#
#■カウンターの値を取得
#    内容:指定されたファイルから現在のカウント値を取得し、
#          返却する
#
#    引数:(1)ファイル名:スカラー
#  戻り値:カウント値:スカラー
#--------------------------------------------------------------#
sub get{
    my $file = shift;
    my $val;

    open(DAT, $file) or die("[get_count]Can not open file:$file ($!)");
    flock(DAT, LOCK_SH);
    $val = <DAT>;
    close(DAT);

    return($val);
}

#--------------------------------------------------------------#
#■カウンターの値を更新
#    内容:指定されたファイルを、指定の値で書き換える
#
#    引数:(1)ファイル名:スカラー
#          (2)カウント値:スカラー
#  戻り値:なし
#--------------------------------------------------------------#
sub set{
    my $file = shift;
    my $val  = shift;

    open(DAT, ">>$file") or die("[set_count]Can not open file:$file ($!)");
    flock(DAT, LOCK_EX);
    truncate(DAT, 0);
    print DAT $val;
    close(DAT);
}



;#
;#ユーティリティーパッケージ
;#
package util;
use strict;                #コーディングを厳格化
use Fcntl qw(:flock);      #定数 LOCK_* を取り込む

#--------------------------------------------------------------#
#■ログファイルに追加記録
#    内容:指定ファイルに、ログ情報を記録する。
#
#
#    引数:(1)ファイル名:スカラー
#          (2)区切り文字:スカラー
#          (3)最後の文字:スカラー
#          (4)記録した値:スカラー  ← 複数の値を記録したい場合は、
#                                      これ以降に指定する
#  戻り値:なし
#--------------------------------------------------------------#
sub append_log{
    my $file = shift;
    my $d    = shift;        #区切り文字
    my $l    = shift;        #最後の文字

    open(DAT, ">>$file") or die("[append_log]Can not open file:$file ($!)");
    flock(DAT, LOCK_EX);
    print DAT join($d, @_);      #4番目以降の値を連結
    print DAT $l;                #最後の文字を記録(改行など)
    close(DAT);
}

#--------------------------------------------------------------#
#■YYYY/MM/DDを返す
#    内容:指定された時間を、「YYYY/MM/DD」のフォーマットにした
#          文字列を返却する。
#
#    引数:(1)timeの戻り値:スカラー
#  戻り値:YYYY/MM/DD:スカラー
#--------------------------------------------------------------#
sub get_date{
    my $time = shift;
    my ( $yyyy, $mm, $dd ) = (localtime($time))[5,4,3];

    $yyyy += 1900;
    $mm   += 1 ;

    return( sprintf("%4d/%02d/%02d", $yyyy, $mm, $dd) );
}


#--------------------------------------------------------------#
#■hou:min:secを返す
#    内容:指定された時間を、「hh:mm:ss」のフォーマットにした
#          文字列を返却する。
#
#    引数:(1)timeの戻り値:スカラー
#  戻り値:hh:mm:ss:スカラー
#--------------------------------------------------------------#
sub get_time{
    my $time = shift;
    my ( $hou, $min, $sec ) = (localtime($time))[2,1,0];

    return( sprintf("%02d:%02d:%02d", $hou, $min, $sec) );
}

__END__


───────
   実行方法
───────
テキストエディタ(メモ帳やSimpleText)などで上記のソース(プログラム)を
保存してください。適当な名前(xxxx.cgi)でOKです。
    ※上記のソースは、Windows95,98,Meでは動作しません。
      NT,2000では動作します。

また、別途 count.txt という名前のファイルを用意し、
その中に 1 とだけ記述しておきます。


その他、CGIの詳しい実行方法については Code.001 をご参照ください。
http://www.ichikoro.com/webp/bk/00046/


───────
   解  説
───────
○一連の流れを制御する

    今回取り上げたプログラムを、ざっくりとまとめると
    以下のようになります。

        ロック!
            カウント値取得
            カウント値更新
            ログへ記録
            表示
        ロック解除!

    flockはファイルへの操作を制限するものでしたが、
    上記のソースでは一連の処理その物を制限することが出来ます。

    原理的には至ってシンプル。

        ・指定のディレクトリが存在する間は処理を開始しない。
        ・ディレクトリを作成できたら処理を開始する
        ・処理が終わったらディレクトリを削除する

    ソースと照らし合わせてみてみましょう。

         ・指定のディレクトリが存在する間は処理を開始しない。
         ・ディレクトリを作成できたら処理を開始する

    まずこの二つは冒頭に出てきた以下になります。
    mkdirは make directory の略称でディレクトリを作成する関数です。

>    #-----------------------------------#
>    #        ロックディレクトリ作成     #
>    #-----------------------------------#
>    $loop_count = 0;
>    while(1){
>        if( mkdir($G::DIR_LOCK, 0755) ){   #ディレクトリが作成できれば
>            last;                          #ループを抜ける
>        }
>        else{
>            sleep(1);                      #1秒待機
>        }
>
>        #-- 10秒以上ロック権が取れない場合は異常終了 --#
>        exit(1) if( $loop_count++ > 10 );
>    }

    ループの最後にある一行は無限ループを防止するためです。
    詳しくは後述しますが、プログラムが異常終了した場合などに
    ロックディレクトリが削除されず残ってしまう場合があります。

    そしてロックを解除するのは

        ・処理が終わったらディレクトリを削除する

    次の行です。

>    #-----------------------------------#
>    #        ロックディレクトリ削除     #
>    #-----------------------------------#
>    rmdir($G::DIR_LOCK) or die("Can not remove directory:$G::DIR_LOCK ($!)");

    rmdirは remove directoryの略称です。
    mkdirとは逆に、指定したディレクトリを削除します。



○ロックディレクトリが残る?
    詳しいことは杜甫々氏が以下に書かれていますが、

        ・ファイルのロックに関する基礎知識
          http://tohoho.wakusei.ne.jp/wwwcgi8.htm

    何らかの原因でプログラムが異常終了した場合、
    ロックディレクトリが残ってしまう場合があります。

    それを防ぐために、

>     $SIG{'TERM'} = $SIG{'PIPE'} = $SIG{'HUP'} = "sigexit";
>     sub sigexit { rmdir($G::DIR_LOCK); exit(1); }

    という一文を追加しておきましょう。
        ※杜甫々氏も書かれているように、それでも
         完璧では無かったりしますが...。


○その他の方法を模索する
    その他にもいくつか方法が考えられます。
    今回の例ではディレクトリを使用しましたが、ようは

        ・指定の○○が存在する間は処理を開始しない。
        ・○○を作成できたら処理を開始する
        ・処理が終わったら○○を削除する

    ということが行えれば良いわけです。


    例えばUNIXには symlink という「シンボリックリンク」を作成する
    関数が用意されています。ディレクトリの変わりにこれを用いる方法
    があります。


    また普通にファイルを使用して

        #-----------------------------------#
        #             ロックする            #
        #-----------------------------------#
        open(LOCK, $file);
        flock(LOCK, LOCK_EX);

            ###ここで何か処理をする

        #-----------------------------------#
        #         ロックを解除する          #
        #-----------------------------------#
        close(LOCK);

    何て感じで、あらかじめ用意しておいた適当なファイルをflockで
    ロックし、その間に処理をするというのもナイスです。


・参考
    とほほのWWW入門
    http://tohoho.wakusei.ne.jp/www.htm


───────
   次回予告
───────
次回は、今回突然出てきた mkdir や rmdir など
ディレクトリ関連の関数を解説したいと思います。
またもやPerlに特化したお話です(^^;


───────
   配信予定
───────
ファイル処理編の配信予定一覧です。

    ・CGI(Perl)ファイル処理 その1:「ファイルを読む」
      http://www.ichikoro.com/webp/bk/00053/
    ・CGI(Perl)ファイル処理 その2:「ファイルへの書き込み」
      http://www.ichikoro.com/webp/bk/00054/
    ・CGI(Perl)ファイル処理 その3:「ファイルロック:flock 前編」
      http://www.ichikoro.com/webp/bk/00055/
    ・CGI(Perl)ファイル処理 その4:「ファイルロック:flock 後編」
      http://www.ichikoro.com/webp/bk/00056/

    ・CGI(Perl)ファイル処理 その5:「ファイルロック:mkdir編」<<今回
    ・CGI(Perl)ファイル処理 その6:「ディレクトリ操作」
    ・CGI(Perl)ファイル処理 その7:「DBMを使おう!」


これらの配信予定は都合により追加・変更・中止になる可能性が
あります。取り上げて欲しいテーマやご意見・ご要望はぜひ以下
までおよせください。

    ・BBS
      http://www.ichikoro.com/webp/bbs/

    ・メール
      mm-webp@ichikoro.com

───────
  分からない
───────
いまいちよく分からない場合は、以下へれっつらごー。

  ・サポートBBS
    このメルマガ専用のサポート掲示板です。
    勝部が(分かる範囲内で)ギモンにスパッとお答えします。
    メールで聞くより高速です。お気軽にお書き込みください(^^)/
      http://www.ichikoro.com/webp/bbs/

  ・CGIプログラミングML
    CGIなどWebに関する話題を繰り広げるメーリングリスト。
    このメルマガとは関係ありませんので発言時は注意を。
      http://www.ichikoro.com/cgi/ml/


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 年末年始の発行予定
──────────────────────────────────────
Webプログラミングの気になる(?)年末年始の発行スケジュールは
下記になります。

 ・2002年
   12/30(月) ← 発行します。
                2002年最終号!

 ・2003年
    1/ 6(月) ← お休みします
    1/13(月) ← 発行します。2003年初配信です。
    1/20(月) ← 発行します。これ以降は通常通りです。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                           編    集    後    記
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
今年も残すところ後2日。
本年もWebプログラミングをご愛読いただき、本当にありがとうございました。

しかし、今年も振り返ってみると激動の一年でした。
で、勝部的10大ニュース(自分中心編)!
詳しくはリンク先の編集後記に書いてあります。

    10位 ワイドTV購入
            http://www.ichikoro.com/webp/bk/00015/
     9位 引越し
             http://www.ichikoro.com/webp/bk/00013/
     8位 スポーツクラブ<社内報企画
            http://www.ichikoro.com/webp/bk/00030/
     7位 スーパー歌舞伎
            http://www.ichikoro.com/webp/bk/00034/
     6位 ハリーポッター
            http://www.ichikoro.com/webp/bk/00020/
            http://www.ichikoro.com/webp/bk/00023/
     5位 会社のお姉さん退職、そして...。
             http://www.ichikoro.com/webp/bk/00014/
     4位 巨大プロジェクトのカットオーバー
            http://www.ichikoro.com/webp/bk/00041/

     3位 5年間続けたWebサイトを閉鎖
        http://www.ichikoro.com/webp/bk/00043/
            閉鎖してからいただくメールが心にしみました。
            演劇関連のサイトだったのですが、また別の形でもっと面白いことを
            実現したいなぁと思っています。演劇はライフワークの一つですので。
                http://www.stagefan.com/

     2位 大学入試を決意してからスピード合格
        http://www.ichikoro.com/webp/bk/00045/
        http://www.ichikoro.com/webp/bk/00052/
            9月までは自分でもこんなことになるなんて思ってもみませんでした(^^;
            っていうか、たぶん来年からの方が大変なことは間違いないでしょう
            けど。人生の節目の一つとしてランクイン。

     1位 5年ぶりに好きだった子と会う
        http://www.ichikoro.com/webp/bk/00052/
            奇跡っていうのはこういう事を言うんだなぁと実感した瞬間でした。
            ようやく前進できるような気がします。


全部覚えている方はかなりのWebプログラミング通です(^^;
まだ編集後記では話していませんが、今2輪の免許を取るために教習所に通って
いたり、前々からほしかったLinux専用機も先日ヤフオクで落としてきて遊んで
いたり、ADSLにしたり、長電話魔が復活したりと公私ともに色んなことがあった
一年でした。

このメルマガを配信する30日には実家の島根県へ帰省する予定です。
さてはて来年は何が待っているかは分かりませんが、みなさんとともに、充実した
一年がすごせるよう祈っています。

それでは、また来年お会いしましょう。
良いお年を! (^-^)/~~
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

                   【 Webプログラミング Code Sample 】

                    発  行 : ichikoro.com
                発行責任者 : 勝部 麻季人
                              < katsube@ichikoro.com >
                 Webサイト : < http://www.ichikoro.com/webp/ >
            お問い合わせ先 : < mm-webp@ichikoro.com >

                            Powerd by まぐまぐ
    All Right Reserved, CopyRight(C) 2001 Webプログラミング Code Sample
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■