***HPクリエイターのためのCGI講座 第42回***
3.3 実用的なカウンター
では、実用的なカウンターを作成しましょう
このカウンターの設計方針は以下の2点です。
1.ケタ制限をなくす
前項のカウンターでは4ケタカウンターを表示する方法を学びました。
ここでは、ケタ制限をなくし、より汎用的なカウンターを考えます。
2.ファイルへの同時書込問題の解決
ファイルからの読み出しでは、問題にならないのですが、ファイルへの書込みが発生する場合、注意すべき重要な事柄があります。
ファイルへの同時書込みの問題です。
<ファイルへの同時書込み>
HPは不特定多数の人が、不定期にアクセスします。
全く同時に、同じHPにアクセスされることも想定しなければなりません。
もし、同時に複数の人から、HPをアクセスされたら、どのような問題が発生するのでしょうか?
鈴木さんと、田中さんが同時に、同じHPへアクセスした場合を考えてみましょう。
カウンター値は、ファイルに格納してありましたね。
今、鈴木さんが、HPを開いたとします。
その時CGIプログラムは、カウンター値をファイルから読み出し1upして、カウンター値をファイルへ書き込もうとします。
カウンター値を書き込む前にファイルを書込みモードでオープンしますね。
具体的には、次の命令を実行します。
open(OUT,">esy_cnt.dat");
この時に、田中さんが、同じHPのページを開いたとします。
CGIプログラムはカウンター値をファイルから読み出そうとしますが、開いたカウンター値のファイルには、中身がありません。
(鈴木さんが起動させたCGIプログラムが、まだカウンター値を書き込んでいない)
ファイルの中身がないので、田中さんが読み出したカウンター値は0になっています。
田中さんが起動したCGIは、カウンター値を1upして、ファイルに書き込もうとするので、カウンター値=1を書き込みます。
つまり、この時点でカウンター値は、破壊されます。
このような問題を防ぐために、ファイルへの書き込み時は、他の人からのファイル・アクセスを禁止する必要があります。
以上、2点を考慮したカウンターを作成してあるので、「実用的なカウンター」をクリックしてください。
最上部に複数ケタタのカウンターを、黄色の枠に、このカウンターを表示しているCGIプログラムを表示しています。
カウンター値には、巨大な値をあらかじめ設定してあります。
HPのHTMLソースコードをご覧ください。
下記の記述が、ありますね。
<P><IMG src="cnt.cgi"></P>
この記述にから、このページが開かれる度に、cnt.cgiが起動されます。
タグの書き方は、今までのカウンターと同じです。
では、cnt.cgi について、説明しましょう。
require 'gifcat.pl';
とほほさんが作成したサブルーチンの格納されているファイルを宣言
。
$lock_file = "edperl13_3.lock";
書き込み衝突問題を回避するためのファイル名。
使い方は、プログラム内で説明します。
&lock;
書き込みの衝突を回避するためにファイル・ロック用サブルーチンをコール
open(IN,"cnt.dat") || &error(1);
前項と同じように、カウンター値用ファイル(cnt.dat)を読み込みモードでオープンします。
error サブルーチンにはパラメータを渡しています。
このように、サブルーチンにパラメータを渡すときは、カッコで囲んで、渡したい値を記
述します。
$cnt = <IN>;
カウンタ値の読込み。
close(IN);
ファイルのクローズ。
$cnt++;
カウンタ値を1UP。
open(OUT,">cnt.dat") || &error(1);
cnt.datを書込みオープン。
print OUT $cnt;
カウンタ値の書込み。
close(OUT);
ファイルのクローズ。
unlink($lock_file);
ロックを解除
while(0 != $cnt){
$num_gif = $cnt % 10; # 各ケタの値を計算
unshift(@files, "c1_$num_gif.gif");# 各ケタのgifファイル名を配列に設定
$cnt = int($cnt / 10);
}
各ケタのgifファイルを配列 @file へ順次格納
上記アルゴリズムの解説 $cnt=208 だった場合を例にすると |
||
1周目の ループ |
while(0 != $cnt){ | 最初のループでは $cnt=208 なので判定は真、 ループの中へ |
$num_gif = $cnt % 10; | 208 % 10 は 8 $num_gifには 8 が設定される |
|
unshift(@files, "c1_$num_gif.gif"); | 'c1_8.gif'を配列 @files に設定 $files[0] は 'c1_8.gif' |
|
$cnt = int($cnt / 10); | $cnt / 10 は 20.8なので$cnt は 20 になる
|
|
} | ||
2周目の ループ |
while(0 != $cnt){ | $cnt は 20 なのでループの中へ |
$num_gif = $cnt % 10; | 20 % 10 は 0 $num_gifには 0 が設定される |
|
unshift(@files, "c1_$num_gif.gif"); | 'c1_0.gif'を配列 @files に設定 $files[0] は 'c1_0.gif' $files[1] は 'c1_8.gif' |
|
$cnt = int($cnt / 10); | $cnt は 2 になる | |
} | ||
3周目の ループ |
while(0 != $cnt){ | $cnt は 2 なのでループの中へ |
$num_gif = $cnt % 10; | 2 % 10 は 2 $num_gifには 2 が設定される |
|
unshift(@files, "c1_$num_gif.gif"); | 'c1_2.gif'を配列 @files に設定 $files[0] は 'c1_2.gif' $files[1] は 'c1_0.gif' $files[2] は 'c1_8.gif' |
|
$cnt = int($cnt / 10); | $cnt は 0 になる | |
} | ||
4周目の ループ |
while(0 != $cnt){ | $cnt は 0 なのでループの外へ |
: | ||
} | ||
ループから抜けると 、配列@filesには次のように設定される $files[0] には 'c1_2.gif' $files[1] には 'c1_0.gif' $files[2] には 'c1_8.gif' |
print "Content-type: image/gif\n\n";
gifデータを送ることをブラウザへ通知
print &gifcat'gifcat(@files);
gif ファイル名を gifcat'gifcat へ渡す。
これは、次のように記述したものと同じ
print &gifcat'gifcat($files[0],$files[1],$files[2],・・・);
exit;
ここでプログラム終了。
必要はありませんが、プログラムを見やすくするために、記述しています。
sub lock {
$retry = 5; # ロック解除リトライ回数
while (!symlink(".", $lock_file)){
if (--$retry <= 0) { &error(0); }
sleep(1);# 1秒待つ
}
}
書き込み衝突のための、ロジックです。
カウンターファイルにカウンター値を書き込む前に、$lock_file
にシンボリックリンクを貼ります。
もし、シンボリックリンクにすれば、現在、他のユーザーがカウンターフィルへ書き込み
中であると判断し、1秒待って、再度シンボリックリンクにトライします。
これを5回繰り返しても、シンボリックリンクを貼れなければ、エラーとします。
この手法はunix系のサーバーにのみ、使用できます(WinNT等には使えません)。
シンボリックリンク |
unixではファイル名だけ用意して、実態は別のファイル(ディレクトリ)に置くことができます。
これをシンボリックリンクと言います。 このシンボリックリンクを実現する組み込み関数が、Perlでは symlink です。 例では、自分自身のディレクトリにリンクを張っています。 (パラメータに "." が指定されている) ここでは、どこにリンクを貼るかは重要ではなく、リンクを貼ることが可能かどうかが、重要になります。 symlink は、既にリンクが貼られていると、2重にリンクを貼れないので、失敗します(偽を返す)。 この結果を元に、他のユーザーが、現在カウンターファイルへ書き込み中かどうかを判定します。 (もし他のユーザーが先に書き込み中だと、そのユーザーが先にこの関数を呼び出しているので、既にリンクが貼られているはず) sleep 関数は、処理を秒単位で中断します。 この例では、1秒間中断した後(パラメータが 1)プログラムを再開します。 |
sub error {
エラー処理用のサブルーチンです。
if ((-e $lock_file) && ($_[0] !=0)){
unlink($lock_file); }
パラメータは配列 @_ に格納されています。
$lock_file が存在し(シンボリックリンクが貼られているとファイルは存在すると
みなされる)、パラメータが 0 でない場合、シンボリックリンクをはずす。
@err_msg = (
'47','49','46','38','39','61','3b','00','0f','00','80','00','00','fe','74','97',
'ff','ff','ff','21','f9','04','01','00','00','01','00','2c','00','00','00','00',
'3b','00','0f','00','00','08','92','00','01','08','1c','48','90','60','80','83',
'08','13','2a','5c','c8','b0','a1','c3','82','10','07','3a','9c','48','b1','22',
'c2','88','11','2d','6a','dc','78','51','22','c7','8f','20','3b','0a','0c','49',
'92','a3','c1','8a','1e','0d','a6','f4','78','10','a2','42','96','18','33','8a',
'74','19','b3','25','c6','84','12','63','de','9c','99','33','40','46','00','3e',
'61','0a','2d','18','54','27','d0','99','0b','4f','22','c5','b9','72','e8','51',
'8a','2c','99','3e','5d','2a','d5','e6','48','aa','13','a3','8a','4c','7a','f5',
'e5','55','ad','4a','b3','76','ad','ea','75','ac','d5','b3','52','a7','3e','34',
'8b','b6','ec','d4','93','60','8d','76','d5','da','d6','ad','cb','ba','45','8d',
'6e','e5','aa','36','6d','5c','b5','01','01','00','3b');
Error のgifパターンです。
エラー表示に gif
ファイルを使わないのは、エラーの発生した原因がわからず、原因として、ファイルシステムの異常も考えられるためです。
つまり、エラー表示用の
gif ファイルを用意していても、読み出せない可能性があるからです。
print "Content-type: image/gif\n\n";
foreach (@err_msg) {
$data = pack('C*',hex($_));
Error
のgifパターンをブラウザへ送っています。
print $data;
}
exit;
}
エラー処理を呼び出す個所を見直してください。
&error(0) と &error(1) の2つの形がありますね。
カッコで囲まれた数字の 0 と 1 は &error
へのパラメータです。
このパラメータは、シンボリックリンクを解除するかしないかのスイッチに使います。
最初に使われている行は、次のとおりです。
open(OUT,">cnt.dat") || &error(1);
この行は、シンボリックリンクを貼った後に実行されますね。
カウンターファイルへのアクセスエラーが発生した場合に、このまま終了するとシンボ
リックリンクは残ったままになります。
そこで、error 関数へのパラメータに 1 を渡して、シンボリックリンクの解除を指示し
ています。
次に使われている個所は、lock 関数の次の行です。
if (--$retry <= 0) { &error(0); }
ここは、既に他のユーザーが、シンボリックリンクを貼っている時のエラー処理でしたね。
他のユーザによって、シンボリックリンクが貼られているのに、勝手にリンクを外す訳にはいきません。
error 関数に 0 のパラメータを渡すことで、リンクを外さないように指示しています。
********************************************************************************
講師:ALK alk@arkland.co.jp
運営:アークランド(株) http://www.arkland.co.jp/