Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

クリック回数計測

研究内容

「IDでジャンプするリンク」のクリック回数を計測します。

詳細

なんの役に立つか

まあ、なんの役に立つのと言われればちょっと困りますが、 たとえばトップページから各コンテンツへのリンクを全部IDでジャンプに しておけば、コンテンツが見られた回数が分かるとか。 でも検索エンジンで飛び込んでくる場合とかあるし、 ページの閲覧回数をカウントするならもっといい方法がありそうですし、 これは例えが悪いかな。 他の例えだと、 Locationでのジャンプ先はhtmファイル以外でもなんでもいいので、 作成したCGIプログラムを無料でダウンロードできるように して、それぞれのCGIプログラムのダウンロード回数が分かるとか。 このサイトでは今のところ無縁のものですけど (^^; そんな感じで使えます。

それよりなにより、数を数えるっていうのは、 アクセスカウンターとかもそうだし、 CGIプログラムではよくあるものですから、 その練習になりますよね!

結果

数を数える方法

クリック回数はファイルに保存します。 クリックされたら(CGIプログラムが呼ばれたら)、 クリック回数が保存されているファイルを読み込んで、 回数を1つ増やして、 あらためてファイルに保存し直します。 これで、数を数えることができますね。 「ファイルを開く」「データを読む」「回数を増やす」「データを書き込む」「ファイル閉じる」 という処理です。

回数ファイルの仕様

各IDごとに回数を数えます。すると データは、IDとクリック回数が1対1に対応していますから、 連想配列が使えます。ということは、 回数を保存するファイルの仕様は、 既に作った readhashfile関数で扱える形式、つまり ID<>クリック回数 という形式がいいです。 そうすれば、readhashfile関数を利用して読み込みはバッチリです!

ファイルを読んで回数を増やすまで

ファイルを開いて、ファイルを読む(readhashfile関数を使います)。 ここまではOKです。次は、回数を増やすっていう処理ですが、これはどうやるんでしょう?

今まで、変数というと文字列だけでした。 数値って扱ったことがないですね。 Perlでは数値はどうやって扱ったらいいんでしょうか。 これ、実は簡単です。今までも使ってきた、変数。$〜っていうやつですが、 これって実は文字列も数値もどっちも表すことができるんです。 で、四則演算 + - * / はそのまんまの記号でできますし。 たとえば

$a = 1;
$b = 2;
$c = $a + $b;

とすれば、$c は 3 になります。 回数を増やすっていうのは、1 を足すっていうことですから、 回数が $table{'ID'} に入っていたら、

$table{'ID'} = $table{'ID'} + 1;

とすればいいんです。簡単ですね。

オートインクリメント演算子 ++

数値の入った変数に 1 足すっていうのは、$a = $a + 1; でOKなんですが、 Perlにはこれを簡単に行う便利な機能があります。オートインクリメント演算子 ++ っていうものです。 これは、1足す っていう処理をしてくれます。これを使えば、上の回数を増やすっていうのは

++$table{'ID'};

となります。$table{'ID'} を2回書かなくて済むので楽だし、見やすくなりましたね! 是非こっちを使いましょう。

連想配列をファイルに書き込む関数

回数を増やすことができたら、ファイルへ書き込んで、閉じて終わりです。

ファイルへの書き込みは、readhashfile関数と対になる機能ですね。 連想配列を、区切り <> でファイルに書き込む関数っていうことになります。 これを writehashfileという名前の関数として作りましょう!

連想配列を関数に渡すとどうなる?

これをやるには、まず、連想配列を関数の引数として受け取る必要があります。 関数の引数は、関数側では配列 @_、個々のデータには $_[番号] でアクセスできるんでした。 ということは、連想配列を引数にした場合、変数の中に連想配列が入っているのかな? つまり、1個目の引数で連想配列を渡したら、$_[0]が連想配列になる? だとしたら、アクセスするには $$_[0]{キー} となるんでしょうか。 なんだかよく分からないですね…。これ、答えは、違います。 1個目の引数で連想配列を渡すと、データがバラバラになって、 配列になって関数に渡されます。例えば、("A", "a", "B", "b") という連想配列が あったとしましょう。この形、連想配列の初期化のところに出てきましたね? (キー, 値, キー, 値, ...) という形です。これを関数に渡すと、関数側では、 ("A", "a", "B", "b") という配列が @_ に入って渡されます! 関数側で配列にアクセスすると、この場合、$_[0] は A、$_[1] は a が入っています。 このように、連想配列を関数に渡すと、データが展開されて、配列になって渡されるんですね。 連想配列の中身を関数に渡すことは可能なようですが、連想配列のまま渡るわけではないようですから、 処理に注意が必要ですね。

それでは、連想配列が渡せるのは分かりましたが、問題は、引数の数です。 連想配列にどれだけのデータが入っているのか分かりません。 その数をどうやって知るのでしょうか? …大丈夫です!関数の引数は、配列 @_ に入っているんでした。 今まででてきませんでしたが、配列 @_ の数は @_ です。 簡単ですね?これは配列の一般的な性質です。覚えておきましょう。

引数は、あともう1つ。ファイルにアクセスするので、この writehashfile 関数には ファイル名も渡す必要があります。そうすると、渡すものは「ファイル名」と「連想配列」ですね。

引数の順番

さて引数は2つです。引数には順番がありますが、どちらを1個目に、どちらを2個目にしましょう? これ、もちろん決めなければいけません。$_[0] と $_[1] のどちらになにが入っているか 決まっていないと、困ります。 で、引数の順番、どちらでもいいのですが、結構深い問題です。 「ファイルに連想配列を書き込む」 「連想配列をファイルに書き込む」どっちも意味は同じですが、どっちがいいですか? こういう、どっちでもいいものって、特に理由が無いなら、 なんでもいいので統一した方がいいんですよね。 統一してあった方が、引数の順番を間違えるミスを防げるので。 ということで、決めましょう! データが動く処理の場合、動かないものが前、動くものが後ろ。 ファイルに連想配列を書き込みましょう。 ファイル名が前で連想配列が後ろ。なんか曖昧ですが、そんなイメージで決めました。

writehashfile関数の作成

まず、こんな感じですね。

sub writehashfile
{
	local $fname, $n;

	$fname = $_[0];
	$n     = @_;
}

$fname はファイル名。$n は引数の数です。@_ が引数の数になるんでしたね。 ここまでの部分は、$_[0] とかだと分かりにくいので、分かりやすい名前の付いた変数に 値をコピーしただけです。

続いて、ファイル処理です。ファイルを開くところと閉じるところを足します。

sub writehashfile
{
	local $fname, $n;

	$fname = $_[0];
	$n     = @_;

	open FILE, "> $fname";
	close FILE;
}

open で、書き込みのときは > となるのが ポイントです。

次は、ファイルへの書き込みです。 連想配列は、$_[1] にキー、$_[2] にその値、$_[3] にキー、$_[4] にその値…というふうに なっているんでした。($_[0]はファイル名です。)これを、次のような形でファイルに書き込みます。

キー<>値
キー<>値

これにはどうしたらいいかというと、for 文というものを使います。 for 文というのは、次のようなものです。

for (1初期化式; 2条件式; 4繰り返し時に実行される式) {
	3処理
}

これは、(1)まず初期化式が実行され、(2)条件式が満たされていたら、 (3)処理を行って、(4)繰り返し時に実行される式を処理して、(2)に戻る、ということを 繰り返す繰り返し文です。

連想配列のデータは配列の $_[1] から $_[$n-1] に入っています。 配列の番号(インデックスとか添字とかいいます)は 0 から始まっているので、 配列の要素の数が $n なら最後の要素は $_[$n-1] となることに注意してください。 $_[$n] ではありません。で、for文を使って、こんなふうになります。

for ($i = 1; $i < $n; $i = $i + 2) {
	$key = $_[$i];
	$val = $_[$i+1];
	print FILE "$key<>$val\n";
}

ちょっと分かりにくくて、すいません。今の知識だとこれしかやり方が 思いつかなくて…。説明すると、$i という変数があって、これが 1 から始まります。 $i < n の条件が満たされる間処理が繰り返されます。 処理は、$_[$i] にキーが、$_[$i+1] にそのキーに対応する値が入っているので、 それらを例の <> で区切ってファイルに出力します。 \n で改行するのを忘れずに。 1回処理を行うたびに $i = $i + 2 によって $i が 2 増えます。 2 増えるということは、次の キー<>値 に進むっていうことです。 そんで、まだ $i < $n が成り立っていたら、つまりまだキーと値のペアが残っていたら、 処理を繰り返します。 …どうでしょう?

今は、少しでも分かりやすくするために、for文の処理のところで $key と $val という変数に値をコピーしました。 これは、分かりやすくなるのはいいんですが、実は無駄な処理です。 こういう無駄な処理が for 文の中のような何度も繰り返されるところに あると、無駄な処理のせいで全体の実行時間が長くなってしまったりするかも知れません。 ですので、一応、意味が分かったところで、無駄なコピーはせずに 直接 print 文で $_[$i] と $_[$i+1] を出力することにしましょう。 まとめると、こうなります。

sub writehashfile
{
	local $fname, $n, $i;

	$fname = $_[0];
	$n     = @_;

	open FILE, "> $fname";
	for ($i = 1; $i < $n; $i = $i + 2) {
		print FILE "$_[$i]<>$_[$i+1]\n";
	}
	close FILE;
}

これで、writehashfile関数は完成です!

ファイルのロック処理が大切です

ファイルになにかデータを保存するときに避けて通れないのがロック処理! ロック処理というのは、ファイルへアクセスしている間(開いてから閉じるまで)、 他のプログラムがそのファイルにアクセスできないようにすることをいいます。 なんでこのロックという処理が必要かというと、 複数のプログラムが同時に1つのファイルにアクセスすると、 データが壊れてしまうことがあるからです。 なんでデータが壊れるかというと、 例えば、ファイルを全部読み込んで、なにもせずにファイルにまた書き込むっていう 処理が同時に起きた場合、タイミングによっては、こんな感じでファイルが壊れます。 (下に向かって処理が進みます。プログラムAとBは同時に実行されています。)

(ファイルにデータが2行ありました)
プログラムAプログラムB
読み込みで開いた-
ファイル全部読んだ(2行)-
ファイル閉じた-
書き込みで開いた-
書き込み中(1行目)-
-読み込みで開いた
-ファイル全部読んだ(1行だけ
-ファイル閉じた
書き込み中(2行目)-
ファイル閉じた-
-書き込みで開いた
-書き込んだ(1行だけ
-ファイル閉じた
プログラムAプログラムB
(ファイルのデータが1行になりました)

特にファイルへの上書き書き込みの時には絶対にロック処理が必要です。 上書き書き込みをするときっていうのは、 いったんファイルが空っぽになるみたいです。 なので、書き込み中に他のプログラムがそのファイルを読み込むと、 全部書き込まれていない状態でファイルを読んでしまいます。 上に書いたような状況です。こうなったら、確実にデータは壊れます。 書き込み開始から、書き込み終了まで、他のプログラムには ファイルにアクセスさせない!ロックする!これ、重要です。 その方法を研究しないといけません。

ファイルのロックってどうやるの?

ファイルのロックにはいろいろなやり方があるようなのです。 それぞれのやり方にはメリット、デメリットがありますが、 その全部を勉強する必要はないですよね。 最も汎用性があるという、mkdir を使ったロック処理を勉強します。 ファイルのロック処理はこれからも何度も使うものですから、 例によって関数にして、ライブラリ関数として作成します。

mkdir 関数と言うのは、ディレクトリを作るという関数です。 mkdir 関数を使って、ファイル処理をする前にまずディレクトリを作成して、 ファイル処理が終わったらディレクトリを削除するということにしましょう。 ディレクトリを削除するには rmdir という関数を使います。 ディレクトリ名はなんでも構いません。 こうすると、ファイル処理を行っている間だけディレクトリが存在して、 ファイル処理が終わるとディレクトリが無くなります。

さて、mkdir関数は、既に作成しようとしているディレクトリが存在していると、 エラーになります。ディレクトリが存在しているっていうのは、どういうことでしょう? そう、他の誰かが、「今ファイル処理してますよ!終わったらディレクトリを削除しますから、 それまで待ってくださいね!」ということを主張しているっていうことです。 ですので、ディレクトリを作るのに失敗したら、 しばらく待ってあげてから、もう1度ディレクトリを作るのにトライしてみる… こんな感じで、ディレクトリの存在を通してロック処理を行います。

さて、ロック処理を行うときに注意することがありまして。それは、 ロック中、ここではディレクトリを作ってからディレクトリを削除するまでの間ですが、 この間になにかエラーが起きて、ディレクトリを削除しないでプログラムが終わってしまった場合です。 そうすると、「ちょっと待ってね!」という印であるディレクトリがずっと残ったまま。 他のプロセスは、永久に待たされることになります。 これが起きたらさあ大変。なんとかして避けなければなりませんね。

この回避方法は、ディレクトリがあったら、そのディレクトリの作成された時間をしらべて、 古いディレクトリだったら、なにかエラーが起きたのかな?と予想して、 無条件にそのディレクトリを削除しちゃう!という処理を行います。 これで、エラーが起きたときに永久に他のプロセスが待たされるということは無くなります。 でもこれ、注意が必要です。「古い」って、どれくらい古かったら古いと判断していいんでしょうね? …これに関しては、まあてきとうに30秒とか、そんな感じです。

ロック関数を作る

ロック関数を作ります。関数名はそのまんま lock。 引数にディレクトリ名を渡します。 ディレクトリ名は、他のディレクトリと重ならないものならなんでもOK。 ロック処理用ですっていうのが分かるように、ほにゃ.lock みたいな名前にするといいでしょう。 実際の関数は、こんな感じ。解説は後で。

sub lock
{
	local $ok;
	local $lockdir;

	# ディレクトリ名です
	$lockdir = $_[0];

	# 成功したかどうか。成功したら1がセットされます
	$ok      = 0;

	# 1 〜 3 まで3回ループします
	for (1 .. 3) {
		# ディレクトリを作ります
		# ディレクトリに書き込み権限が無いと作成に失敗するので注意
		if (mkdir($lockdir, 0755)) {
			$ok = 1;
			# forループを抜けます
			last;
		}
		else {
			# ロック中みたいです
			# 1秒待ちます
			sleep(1);
		}
	}

	if ($ok == 0) {
		# まだロックできていません
		# ディレクトリの作成からの経過時間が30秒以上なら、
		# エラーとみなしてディレクトリを削除、
		# あらたにディレクトリを作成してロック成功とします
		if ((-M $lockdir)*60*60*24 >= 30) {
			rmdir($lockdir);
			if (mkdir($lockdir, 0755)){
				$ok = 1;
			}
		}
	}

	return $ok;
}

たくさんコメントを入れましたが、上から、初めて出てきたものについて 解説します。

for 文は繰り返しを行うものでしたが、(1 .. 3) というのが出てきました。 これは、1 〜 3 まで 3 回繰り返します、という意味になります。

mkdir関数は、mkdir(ディレクトリ名, パーミッション) という形で使います。 ここではパーミッションに 0755 という値を設定しています。 先頭の 0 は、これが8進数ですよ、ということを表しています。 パーミッションについてはだいぶ前に研究しました。744とかそういう数字で表すことがある っていうのもやりました。これ実は8進数というものなんですね。 8進数というのは、8で繰り上がるような数の数え方です。0 1 2 3 4 5 6 7 ときたら、次は 10 に なります。この 8 進数を使うと、ちょうど rwxrwxrwx が3桁の 777 で表されるんですね。 パーミッションを表すのに便利なので、これが使われるんです。

で、mkdir関数が成功するとtrueになってif文の中が実行されます。 mkdirが成功したということは、ロック成功です。 last というのがでてきましたが、これは for の中から抜け出すっいう命令です。 for 以外の繰り返し文(whileとか)でも使えるみたいです。

mkdir が失敗した場合は、elseの中が実行されます。sleepというのは、 眠る、何もしないで時間が過ぎるのを待ちますっていう面白い関数です。 引数は眠る秒数。ここでは1としていますので、1秒待つっていうことですね。 これは、他のプロセスがロック中だったら、そのロックが解除されるまで待ちましょうっていう 意味です。for の繰り返し回数が3なのと合わせると、最大で3秒間待つことになります。 待つ間、1秒に1回、ロックを試みます。 3回ロックを試みて、3回とも失敗した場合、つまり3回とも 他のプロセスがロック中だった場合、ループを抜けます。 つまり、あきらめちゃうっていうことですね。 これ、あきらめちゃっていいの?っていう疑問がありますよね。 実際、3回じゃなくて10回とか、20回とか、それくらい何度も トライしてもいいかも知れません。 でも、この関数はCGIプログラムで使われるわけですから、 ロックできるまで待っている間っていうのは、 ブラウザにはなにも表示されない状態になっちゃうんですよね。 なにも表示されない状態が何十秒も続いたら、ブラウザを閉じられちゃいますよ。 なので、3回、合計3秒を最大にして、それ以上待たせないようにしようっていうわけです。 そんな感じで、3回じゃなきゃ駄目だっていうわけじゃないんですが、3回になってます。

さて、for (1 .. 3) の次は if ($ok == 0) です。 もしロックが成功していたら、$ok は 1 になっています。 $ok が 0 というのは、ロックできなかったっていうことですね。 ロックできなかった場合っていうのはディレクトリが存在した場合ですが、 ここでちょっと別の処理、先ほど説明した、 ロックの途中でプログラムが終了しちゃって、ディレクトリが残っちゃってる場合の 処理を行います。

(-M $lockdir) というのがありますが、これは、ディレクトリが作成されてからの経過時間を 得るものです。初めて出てきた形式です。-M というのはファイル検査演算子というものの1つです。 ファイル検査演算子といのは、ファイル、またはディレクトリに対してなにか調べたいときに使います。 -(文字) ファイル名 という形で使います。(文字)を変えると違う情報が得られますが、ここで 使っている -M というのは、そのファイル(ディレクトリ)が作成されてからの経過日数という ものを得るものです。注意したいのは、これは、なんでも、この命令を使って 経過日数を調べた瞬間の経過日数じゃなくて、CGIプログラムが起動された瞬間での 経過日数なんだそうです。つまり、CGIプログラムの中で、何回 -M で経過日数を調べても、 同じ値が返ってくるっていうことですね。あと、返ってくるのは日数です。1日経過していたら1。 これじゃ、1分前とか、そういう細かい経過時間が分からないような感じがしますが大丈夫。 返ってくるのは整数じゃなくて、小数点以下もOKの実数です。で、ちょっと演算して、 経過日数を経過秒数に変換します。どうやるかというと、 1日っていうのは60秒×60分×24時間で計(60×60×24)秒です。 なので、経過日数にこれをかけてやると、経過秒数になります。 割るんじゃありませんよ。1日のときに(60×60×24)秒になるんですから。 こうして経過秒数を調べて、それが30より大きかったら、つまり 30秒以上経過していたら、これはなにか異常が起きたんだ、と判断します。

異常が起きたというのは、そのディレクトリはもうロック処理とは 関係なくて、原因は分からないけど削除されずに残っちゃったんだということです。 なので、強制的に削除しちゃいます。ディレクトリを削除するには rmdir 関数を使います。 そして、もう一度ディレクトリを作ってみます。作れたらOK。 作れなかったら…失敗。あきらめちゃいます。 ここで失敗する、つまりディレクトリを作れないというのは、rmdirでディレクトリを削除した 次の瞬間、別のプログラムがそのディレクトリを作成した場合。 可能性はほとんどありませんが、無いわけではないですね。

これで関数の内容については終わり。この関数、いくつか注意しないといけない点が あります。それは、確実にロックに成功するわけではないということです。 ロックを試みるのは3回ですから、 たまたま、その3回とも他のプログラムに先取りされてロックに失敗することもあります。 そうするとロック失敗です。 なので、このlock関数を呼ぶときは、必ず成功か失敗かちゃんと確認して、 失敗なら「ちょっと今忙しいからまた後でお願いします!」といったメッセージを 表示するようにするとか、しないといけません。

ロックをトライする回数を増やせば失敗する確率も減りますので、 もしロック失敗がたびたび起こるようなら、回数を増やすといいですね。 でも今のところ、これでいきます。

アンロック関数を作る

アンロックというのは、ロックを解除することです。 mkdir を使ったロックでは、ロック解除とはディレクトリを削除すること。 こちらは簡単です。引数にディレクトリ名を渡すことにして、こうなります。

sub unlock
{
	rmdir($_[0]);
}

特に問題はありませんよね。

ロック処理の動作確認

ここで作った関数、本当にちゃんとロック処理ができているんでしょうか? 普通のプログラムと違って、正しく動作しているかどうかを確認するのはちょっと大変です。 なにしろ、同じプログラムが同時に複数実行されたときにロックが必要になるわけで、 単にプログラムが1回正しく動いたからって、ロックが正常に働いているか分からないです。 試すには、同時に同じプログラムを実行させてみる。これしかありません。 しかも、わざとロックの衝突が起こるように、できるだけたくさんのプログラムが 同時にファイルにアクセスするようにしないといけません。 これって、実行するのはちょっと難しいですよね。 で、どうしようかと思ったんですが、幸い管理人はRedhatというOSが動いているパソコンを 操作できる環境にありまして、それで、このロック処理を試してみました。 ファイルから数字を1個読み込んで、1増やして、ファイルに書き込む。 それだけを100回行うプログラムを作って、それを10個同時に起動してみました。 全部終わったとき、数字が1000になっていれば成功です。 ロック処理を行っていないときは、1000になりません。900代後半だったりしました。 ロック処理を行うと、ちゃんと1000になりました。ということで、 ここで紹介した関数はちゃんと動いてることを確認しました。 ここに報告しておきます。

ロック処理の心構え

これでロック処理を行う関数ができました。 これからファイル処理を行うときはこのロック関数を使うようにします。 注意しないといけないこととして、先に書きました、lock関数の成功/失敗をちゃんと チェックすることの他に、できるだけ、ロックしている時間を短くするっていうのがあります。 ロック処理は、ファイルを開く直前にロック、ファイルを閉じたらアンロック。 しかも、この間の時間をできるだけ短くしましょう。 というのは、ロックしている時間が長ければ長いほど、 他のプログラムがロックに失敗する可能性が高くなるからです。 逆に、ロックしている時間が短いほど、他のプログラムがロックに 失敗する確率は低くなります。 ロックしている間はファイルの読み書きだけを するようにして、他の処理はロックをする前か、アンロックをした後かに行うようにしましょう。

作ってみます

これで必要な研究は終わったようです。作ってみましょう!

作成したCGIプログラム

gotobyid.cgi
#!/usr/bin/perl

require 'hashfile.pl';
require 'lock.pl';

$id = $ENV{"QUERY_STRING"};

%urltable = plab::readhashfile("jumptable.cgi");

if (! exists $urltable{$id}) {
	print "Content-type: text/html\n";
	print "\n";
	print "ID:$idは登録されていません。";
	exit;
}

$url = $urltable{$id};

$lockdir    = "lock";
$countfname = "clickcount.txt";

if (! plab::lock($lockdir)) {
	plab::printlockerror();
	exit;
}

%count = plab::readhashfile($countfname);
$count{$id} = $count{$id} + 1;
plab::writehashfile($countfname, %count);
plab::unlock($lockdir);

print "Location: $url\n";
print "\n";
hashfile.pl
package plab;

# 引数 (ファイル名)
sub readhashfile
{
	local %table;
	local $fname;
	local($name, $val);

	$fname = $_[0];

	open FILE, "< $fname";
	while (<FILE>) {
		chomp $_;
		($name, $val) = split('<>', $_);
		if ($name ne "") {
			$table{$name} = $val;
		}
	}

	close FILE;
	return %table;
}

# 引数 (ファイル名, 連想配列)
sub writehashfile
{
	local($fname, $n, $i);

	$fname = $_[0];
	$n     = @_;

	open FILE, "> $fname";
	for ($i = 1; $i < $n; $i = $i + 2) {
		print FILE "$_[$i]<>$_[$i+1]\n";
	}
	close FILE;
}

1;
lock.pl
package plab;

# 引数 (ディレクトリ名)
sub lock
{
	local $ok;
	local $lockdir;

	$lockdir = $_[0];
	$ok      = 0;

	for (1 .. 3) {
		if (mkdir($lockdir, 0755)) {
			$ok = 1;
			last;
		}
		else {
			sleep(1);
		}
	}

	if ($ok == 0){
		if ((-M $lockdir)*60*60*24 >= 30) {
			rmdir($lockdir);
			if (mkdir($lockdir, 0755)) {
				$ok = 1;
			}
		}
	}

	return $ok;
}

# 引数 (ディレクトリ名)
sub unlock
{
	rmdir($_[0]);
}

sub printlockerror
{
	print "Content-type: text/html\n";
	print "\n";
	print "ファイルがビジーです。ロックに失敗しました。<br>\n";
	print "しばらく経ってからもう1度アクセスしてみてください。<br>\n";
}

1;

結果

次のリンクをクリックしてください。

gotobyid.cgi?yahooを実行してYahooを開きます!
gotobyid.cgi?googleを実行してGoogleを開きます!
gotobyid.cgi?errを実行してエラーのテストをします!

現在の各IDのクリック回数はこちらです。

現在の各IDのクリック回数 clickcount.txt

あとがき

動きました

プログラムは無事動きました。ロック処理が正しく行われているかどうかは このプログラムでは判断できませんけど…

今回、連想配列をファイルに保存する関数、ロック関数、アンロック関数を作りました。 これらは結構役に立つんじゃないかなぁ。でもちょっと疲れたなー

ロックエラー表示関数

ロックに失敗したとき用の、エラーメッセージを表示する printlockerror関数も、 ロック処理関連の関数なので lock.pl の中に入れました。

分かったこと

  1. 1増やす、という処理は オートインクリメント演算子 ++ が便利です。
  2. 連想配列を関数に渡すとデータがばらばらになって渡されます。 キー、値、キー、値…というように。
  3. 関数に渡された引数の数は @_ です。
  4. ファイルの書き込みオープンは open FILE, "> ファイル名"; とします。
  5. for (初期化式; 条件式; 繰り返し時に実行される式) 〜 という繰り返し用の命令があります。
  6. ファイル処理をするときは必ずロックしないとファイルが壊れてしまいます。
  7. ファイルロックの方法としては mkdir を使ったものが一番汎用的です。
  8. mkdir のファイルロックでは、rmdir がアンロックです。
  9. for (1 .. 3) で 1〜3 まで3回繰り返すっていう意味になります。
  10. for文を途中で抜けるには last を使います。
  11. sleep(眠る秒数) っていう面白い関数があります。
  12. ファイル検査演算子っていうものがあります。-M で作成からの日数が得られます。
  13. ロックは必ず成功するわけじゃないので失敗時の処理を忘れずに行いましょう。
  14. ロック中の時間ができるだけ短くなるようなプログラムを心がけましょう。
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。