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

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

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

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

※お詫び
  前回(Code.009)のメルマガに誤植がありました。
  詳しくは以下のページをご覧ください。
    http://www.ichikoro.com/webp/bbs/sylpheed.cgi?c=r&n=12

  今後このようなことが無い様に十分チェックを行い、
  配信いたします。大変、申し訳ございませんでした。

  また情報をお寄せいただいた方々、ありがとうございます。
  この場を借りてお礼申し上げます。


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 CGI(Perl)ファイル処理 その3:「ファイルロック:flock 前編」
──────────────────────────────────────
前回、アクセスログを記録するCGIを作成しました。

しかしこのCGIには同時にアクセスされるとデータファイルが壊れてしまう
という欠点があったのです。そこで今回は「ファイルロック」を導入して
みようと思います。

↓前回の内容はコチラ
http://www.ichikoro.com/webp/bk/00054/


    Mission : ファイルロックに対応したアクセスログを記録するCGIを作成せよ
    ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
        ○問題
            前回作成したCGIを、ファイルが壊れにくくなるよう改良せよ。


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

;#
;#ログ記録CGI ファイルロック対応版
;#

#----------------------------------------------------------------------#
#                            モジュール                                #
#----------------------------------------------------------------------#
use strict;                #コーディングを厳格化
use Fcntl qw(:flock);      #定数 LOCK_* を取り込む

#----------------------------------------------------------------------#
#                           グローバル変数                             #
#----------------------------------------------------------------------#
package G;
BEGIN{
    #■画像ファイル
    $G::IMG_FILE = "1pix.gif";
}


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

    #-----------------------------------#
    #            ファイル名作成         #
    #-----------------------------------#
    $file  =  $date;         # $file = YYYY/MM/DD
    $file  =~ s/\///g;       # $file = YYYYMMDD
    $file .=  ".dat";        # $file = YYYYMMDD.dat


    #-----------------------------------#
    #             文字列作成            #
    #-----------------------------------#
    $str = join("\t"
                        , $date                   # YYYY/MM/DD
                        , $time                   # hh:mm:ss
                        , $ENV{'HTTP_REFERER'}    # このCGIを呼び出したページ
    );

    $str .= "\n";    #最後に改行


    #-----------------------------------#
    #              書き込む             #
    #-----------------------------------#
    open(DAT, ">>$file") or die("Can not open file : $file ($!)");
    flock(DAT, LOCK_EX);            #ロック!
    print DAT $str;
    flock(DAT, LOCK_UN);            #ロック解除
                                    # closeすると自動的に解除されるため
                                    #  必要が無ければしなくても良い。
    close(DAT);


    #-----------------------------------#
    #              画像表示             #
    #-----------------------------------#
    $|=1;
    print "Content-type: image/gif\n\n";
    open(IMG, $G::IMG_FILE) or die("Can not open file : $file ($!)");
    binmode(IMG);               #バイナリだと指定(UNIXで使用する場合は必要ない)
    print while(<IMG>);
    close(IMG);


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


package util;

#--------------------------------------------------------------#
#■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では動作します(XPは未確認)。

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


───────
    解  説
───────

○flock関数を使おう!
    排他制御(今回の場合、同時に書き込みが行われない制御)を最も
    簡単に実装する方法に、flock関数を使うという手法があります。

    flockは、下記のように

>        use Fcntl qw(:flock);
>
>        open(DAT, ">>file.txt") or die("can not open file");
>        flock(DAT, LOCK_EX);

    ファイルを開いた後に、ロックしたい対象のファイルハンドルと、
    どの形式でロックするかを指定するだけでOKです。

        flock([ファイルハンドル], [ロックモード]);


    ロックモードには下記のような物があります。

        ・LOCK_SH(共有ロック)
            他プロセス(他に動いているCGIなど)が、既にこのモードで
            ロックしている場合、ロックが解除されるまで待つ。

            解除され次第、他のプロセスからの書き込みをブロックする。
            読み込みはOK。

        ・LOCK_EX(排他ロック)
            他プロセス(他に動いているCGIなど)が、既にこのモードで
            ロックしている場合、ロックが解除されるまで待つ。

            解除され次第、他のプロセスからの書き込み・読み込みを
            ブロックする。

        ・LOCK_NB(ブロックしない)
            他のモード(LOCK_SHなど)と同時に指定します。
            例えば

                flock(DAT, (LOCK_SH|LOCK_NB))

            とした場合、他のプロセスにより既にロックされている場合は、
            解除を待たずにすぐさま偽値を、成功すれば真を返します。

        ・LOCK_UN(ロック解除)
            ロックを解除します


    ちなみにこれらのロックモードの情報はあらかじめ

        use Fcntl qw(:flock);

    とFcntlモジュールをあらかじめ呼んでおかないと、使用できません。
    もしFcntlが使用できない場合は、以下の数値を使用することで同様の
    処理が行えます。

        LOCK_SH ..... 1  flock(DAT, 1);
        LOCK_EX ..... 2  flock(DAT, 2);
        LOCK_NB ..... 4  flock(DAT, 4);
        LOCK_UN ..... 8  flock(DAT, 8);

    どうしても上記の文字列で指定したい場合、

        #-------------------------#
        #       パターン1         #
        #-------------------------#
        sub LOCK_EX{
            return(2);
        }

        flock(DAT, LOCK_EX);

        #-------------------------#
        #       パターン2         #
        #-------------------------#
        use constant LOCK_EX => 1;

        flock(DAT, LOCK_EX);

    といった形で代用が可能です。



○ロックを解除したい場合は?
    簡単です。
    処理が終了したら、

>        flock(DAT, LOCK_UN);

    と、同じようにファイルハンドルと「ロック解除」と指定する
    だけでOKです。

    ただし、

        open(DAT, ">>file.txt");
        flock(DAT, LOCK_EX);
        print DAT $string;
        close(DAT);

    というように、そのままcloseした場合でも自動的に解除されるため、
    必要性がない場合はそのまま閉じても問題ありません。また、プログ
    ラムが途中で異常終了した場合も、自動的に解除されます。


───────
   次回予告
───────
こんなに便利なflockですが、いくつかの落とし穴があり、
使い方を誤まると、flockしていないのと同じくらい簡単に
壊れてしまうのです。

次回はそんな事例をお目にかけましょう〜。


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

    ・CGI(Perl)ファイル処理 その1:「ファイルを読む」
      http://www.ichikoro.com/webp/bk/00053/
    ・CGI(Perl)ファイル処理 その2:「ファイルへの書き込み」
      http://www.ichikoro.com/webp/bk/00054/

    ・CGI(Perl)ファイル処理 その3:「ファイルロック:flock 前編」<<今回
    ・CGI(Perl)ファイル処理 その4:「ファイルロック:flock 後編」
    ・CGI(Perl)ファイル処理 その5:「ファイルロック:mkdir編」
    ・CGI(Perl)ファイル処理 その6:「ディレクトリ操作」


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

    ・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/


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                           編    集    後    記
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
先週、我が家にもADSLがやってきました。
Yahoo!BBの契約数が150万人を突破するなど、飛ぶ鳥を落とす勢いの中、
私が選択したのは、値下げしてもやはり値段的に高いフレッツADSL。

Y!ってINSネット64からの移行は受け付けてるんですが、INSネット64ライトは
断られるんですよね。サポートに問い合わせたら出来る言うてたのに!(怒)
    ※その後、Y!のお兄ちゃんに「サポートが何でそんなこと言ったのか
      分かりません」と言われる始末(ーー;) 頼むよ〜(泣)

で、めんどくさくなって思わずNTTというわけです。
電話口に出た人に仕事柄色々と聞いていたのですが(^^;
埼玉県のADSL契約数のうち6割はY!だそうで、NTTが抜かれてしまったんですよ、
とポツリと言われました。最近やけにPRを頑張っているのはそのせいだったのか。


それはともかく最高です!
動画もスイスイですよ、奥さん。設定も難しくなく、30分程度あれば(慣れた人な
らもっと早い)あっと言う間に高速体験が可能です。

そうそう、「フレッツスクウェア」というブロードバンドという状況を満喫できる
Webサイトがフレッツユーザー向けに提供されていたりします。一番最初に目にとま
ったのは赤坂泰彦さんが司会を務める「フレッツインタビュースクウェア」。
高校受験の時(と言っても推薦でしたが(^^;)、赤坂さんのミリオンナイツを聞いて
ころを思い出し、何だか懐かしい感じがして胸が熱くなりました。

ちなみにゲストは新山千春、橋本真也、ベッキー、来月は藤咲奈々子と意外(?)
に豪華。無料ですので、興味のある方は一度ご覧くださいませ。
    ※しつこいようですがフレッツユーザーだけです(ニヤリ)

    ・フレッツス・クウェア
     東日本 http://www.ntt-east.co.jp/flets/square/index.html
      西日本 http://www.ntt-west.co.jp/flets/office-s/index.html
          ※一部地域除く


また来週お会いしましょう (^-^)/~~
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

                   【 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
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■