番号を飛ばさないための工夫
2001-05-08 作成 福島
2001-08-19 更新 福島


単純な掲示板

CGI等の掲示板を作成することがよくあります。

掲示板とは、

1. 書きこまれたデータを蓄積する。
2. 蓄積されたデータを表示する。

という 2 つの機能を満たすものを言います。
※本当は「伝言板」と呼ぶのが正しいのですが、「掲示板」という名前が広まってしまいました。


ちょっと複雑な掲示板 単純な掲示板ならそれだけで良いのですが、書きこみを取り消したい場合には、 3. データを削除する。 という機能も必要になります。
単純な掲示板でデータを蓄積する場合、 ・インデックス管理しない場合 ※1 ・インデックス管理する場合 ※2 の 2 つの方法があります。 (データベース利用は除外しています) ここでは便宜上、「インデックス管理する場合」に限っています。 perl で書くと、
for ( $i = 最大記事番号 ; $i >= 0 ; $i -- )
    {
    open MSG, "< $i.txt" ;
    @msg = <MSG>
    close MSG ;
    @msg =~ s/\n/<br>/ ;
    print "($i) " ;
    print @msg ;
    print "<hr>\n" ;
    }
のような形になります。 (各々の記事は 0.txt 等のファイル名になっているものとしています) 実行結果 (ブラウザの表示) は
(2) 書きこみ3

(1) 書きこみ2

(0) 書きこみ1
となります。
ここで、"(1) 書きこみ2" のデータを削除したとき、その結果は
(2) 書きこみ3

(1)
(0) 書きこみ1
となります。※ (1) が余計です。 ちょっとプログラムを変更したところで、
(2) 書きこみ3

(0) 書きこみ1
ぐらいにしかなりません。※ (0) の次が (2) になっています。 プログラムにはもっと大幅な変更が必要です。 やっぱり、
(1) 書きこみ3

(0) 書きこみ1
となって欲しいですよね?
ならば、「インデックスだけを管理するパッケージを作ってしまえ」 と考えたのが、この hindex.pl です。 ('h' はハッシュの意味です。index という関数は既に perl にあるので…) 数字と数字を高速に対応付けるためにハッシュ (perl では連想配列といいます) を利用しています。 このハッシュを保存する必要があるため、実体は DBM です。 インデックスファイルを作成してプログラムの始めにそれを読み込む方法も考えましたが、 そんなことをするよりも、DBM を利用した方が効率がいい (ハズ) なので DBM を利用します。 (DBM では勝手に部分読込み/書き込みを行います) プログラムは、
require 'hindex.pl' ;
$idx = new hindex("idx") ;
for ( $i = $idx->max() - 1 ; $i >= 0 ; $i -- )
    {
    $n = $idx->get($i) ;
    open MSG, "< $n.txt" ;
    @msg = <MSG>
    close MSG ;
    @msg =~ s/\n/<br>/ ;
    print "($i) " ;
    print @msg ;
    print "<hr>\n" ;
    }
です。(変更点は太字で示しています) この変更により、
(1) 書きこみ3

(0) 書きこみ1
が実現できます。 削除するときは、
require 'hindex.pl' ;
$idx = new hindex("idx") ;
$idx->del(1) ;
と書きます。(この例では、"(1) 書込み3"を削除しようとしています)
応用 hindex.pl を作ってみて分かったのですが、モデレート型の掲示板を作成することも出来ます。
require 'hindex.pl' ;
$idxsrc = new hindex("idxsrc") ;
$idxdst = new hindex("idxdst") ;
$msgno = $idxsrc->get(0) ;
$idxdst->ins($msgno) if ($msgno ne '') ;
ここでは、0 番の記事をコピー ($idxsrc -> $idxdst) しています。
表示プログラム1
require 'hindex.pl' ;
$idx = new hindex("idxsrc") ;
for ( $i = $idx->max() - 1 ; $i >= 0 ; $i -- )
    {
    $n = $idx->get($i) ;
    open MSG, "< $n.txt" ;
    @msg = <MSG>
    close MSG ;
    @msg =~ s/\n/<br>/ ;
    print "($i) " ;
    print @msg ;
    print "<hr>\n" ;
    }
表示プログラム2
require 'hindex.pl' ;
$idx = new hindex("idxdst") ;
for ( $i = $idx->max() - 1 ; $i >= 0 ; $i -- )
    {
    $n = $idx->get($i) ;
    open MSG, "< $n.txt" ;
    @msg = <MSG>
    close MSG ;
    @msg =~ s/\n/<br>/ ;
    print "($i) " ;
    print @msg ;
    print "<hr>\n" ;
    }
違うインデックスを使用していても、同じメッセージを読み込んでいることに注意!!
注意事項 掲示板の多くの場合、排他ロック処理があり、このロック−アンロック間で 記事の取得や蓄積の処理を行います。
ロック処理 ;
$idx = new hindex("idx") ;
...
アンロック処理 ;
注意しなければならないのは、オブジェクト指向で作成されたプログラムのインスタンスのスコープが この処理に影響されないと言う点です。 インスタンスの生存期間はスコープで決まります。 hindex では、最終的なインデックスの関係 (と最大値) を DBM に格納しますが、 この格納処理が終わる前に排他ロックを外すと整合性が取れなくなります。 格納処理はデストラクタで行っています。 対処の方法は 1. 明示的にフラッシュさせる。
ロック処理 ;
$idx = new hindex("idx") ;
...
$idx->DESTROY() ;
アンロック処理 ;
2. スコープを明示する。
ロック処理 ;
{
$idx = new hindex("idx") ;
...
}
アンロック処理 ;
どちらを使っても結構です。
実験 それでは、実験してみましょう。 ※応用の実験ではありません。 プログラムはここです。
※1 インデックス管理しない場合 インデックス管理しない場合によく使われる方法は、データファイルに記事を追記していく方法です。 単に追記するだけでは最新の記事が最後に表示されてしまうので、それを補うために 1. データファイルをひっくり返す 2. 記事を追記する 3. データファイルをひっくり返す という操作を行い、記事を先頭に持ってくることを行います。 方法は他にもありますが、ここでは割愛します。
※2 インデックス管理した場合 インデックス管理し、書込み記事 1 つに対して 1 つのファイルを用意すると、 ・ファイル数が浪費される ・無駄なディスク空間が増える 等のデメリットがありますが、 ・プログラムを単純・高速に出来る。 ファイル名さえ分かってしまえば、そのファイルをオープンするのは OS の仕事です。 Unix では、「ライトキャッシュ」という機能もあるので、頻繁に更新される掲示板ほどその効果は出ます。 ・ダイナミックな表示を行える。 閲覧する人の都合によって表示行数を自由に変更できます。 特に、PC と携帯を連動させる場合はその効果が大きく出ます。 等のメリットもあります。 ディスクやメモリが安くなってきているため、問題よりも効果を期待できるでしょう。
データベース利用は除外しています データベースのことを少しかじったぐらいの知識しかない人がよく、 「データベースを活用しましょう」などと無責任な発言をしますが、 (Microsoft信者によく見うけられます) そこまで大規模な CGI はあまりありません。 データベースは、その導入や見通し、メンテナンス等の「お守り」が 一番大切なのですが、上記発言をしてしまう人はそのあたりを良く理解していません。 ※Windows の Oracle 等は、インストールが簡単に出来てしまう上、  正常に動き出してしまうのです。  「『Microsoft SQL Server』なら、メンテナンスフリーです。」を本当に信じているエセ技術者が居るのには閉口します。 見通しをはっきり立てずに運用できてしまう組織や規模ならそれでも良いでしょうが、 通常はそうではありません。 また、データベースを活用してしまうと制限ばかり増えかね無いので、除外しています。 Unix と Windows では OS のもつパワーが全く違うので、認識が必要です。