4.アクセスカウンタを作ってみよう


HOME] [0章] [1章] [2章] [3章] [4章] [5章] [?章

(1)カウンタの仕組みと同時アクセス問題

 アクセスカウンタの動作は、基本的にはとても簡単なものです。 カウントを憶えておくファイルを作っておいて、プログラムは そこから数値を読み出して、インクリメントし、もとのファイルに 書き込みます。その後、この数値をユーザーのブラウザに表示します。 表示を文字列にするか、グラフィックにするか、色々なバリエーションが あるわけです。
 基本的な動作は簡単ですが、非同期的にユーザーがデータファイルを 更新するプログラムに共通の問題である「同時アクセス」の問題が 生じます。上述したアルゴリズムそのままでは、複数のユーザーが 同時にアクセスした時に、下図に示すようなカウンタの破壊が起きることが あります。



 同時アクセス問題を厳密に解決するのは難しいのですが、 まあ、ほとんど問題が生じないという程度の解決は比較的簡単です。 次の項で例を示します。

(2)SSI利用簡単カウンタ

 SSIを用いて、簡単なカウンタを作ってみましょう。ここで作るカウンタは 数字を単純に書き出すだけのものです。
 CでCGIは使えるけれど、SSIは使えない人は、後の方の項を参照して下さい。 フレーム利用版やgd利用版はSSIが使えなくとも大丈夫です。



まずは、こんな簡単なカウンタを作りましょう。
派手さは全くありませんが、simple is the best!
必要なプログラムは極単純でカウンタの仕組みが見え易いので 見てみてください。

<html>
<head>
<title></title>
</head>
<body>
あなたは <!--#exec cmd="./count1"--> 番目のお客様よ!
</body>
</html>
SSIを含んだHTMLファイルです。
このHTMLファイルがサーバーから送出される前に、 サーバー側で、<!--#exec cmd="./count1"--> の部分が、プログラムcount1の出力に置き換えられます。
ここが、クライアントのリクエストで起動するCGIとは違うところです。



#include <stdio.h>
#include <unistd.h>

main()
{
        long i;
        int retcode;
        FILE *fp;
        char *link="count.link",*count="count.dat";
        for(i=0;i<5;i++){
                retcode=symlink(count,link);
                if(retcode==0) break;
                sleep(5);
        }
        fp=fopen(count,"r+");
        rewind(fp);
        fscanf(fp,"%6ld",&i);
        i++;
        rewind(fp);
        fprintf(fp,"%06ld",i);
        printf("%06ld",i);
        fclose(fp);
        unlink(link);
}

 ソースコードはこれだけです。
同時アクセスを避けるためのファイルロックには、 あちこちで紹介されているシンボリックリンクを用いる方法を 採用しました。まず、カウントファイルにシンボリックリンクを 張ろうとします。複数ユーザーがアクセスした場合、後の人は、 既に同じ名前のリンクが出来ているので、エラーとなります。 5秒待って5回リトライします。それでもだめな時は、 なんかの理由で、ロックファイルが不正に残ってしまったものとして、 ロックを無視して次に進みます。
 カウンタ本体は、カウントファイルのデータをインクリメントして、 その数値を標準出力に書き出しているだけです。
 仕事が終ったら、ロックファイルになっているリンクを 削除します。
 カウンタを設置する時に、"000000"というないようのファイル count.datを準備しておいて下さいね。パーミッションは666です。  また、カウンタを置くディレクトリのパーミッションも666など nobokyが書き込めるようにして下さい。そうでないと、シンボリック リンクを作成できないからです。

(3)SSI利用グラフィカルバージョン



 上の例で示したブラウザへの標準出力の部分で、 <IMG>タグを出力すれば、左のような グラフィッカルなカウンタができあがります。 ソースを下に示しておきます。


#include <stdio.h>
#include <unistd.h>

main()
{
        long i;
        char s_count[6],obuf[50];
        int retcode;
        FILE *fp;
        char *link="count.link",*count="count.dat";
        for(i=0;i<5;i++){
                retcode=symlink(count,link);
                if(retcode==0) break;
                sleep(5);
        }
        fp=fopen(count,"r+");
        rewind(fp);
        fscanf(fp,"%6ld",&i);
        i++;
        rewind(fp);
        fprintf(fp,"%06ld",i);
        sprintf(s_count,"%06ld",i);
        for(i=0;i<6;i++){
          sprintf(obuf,
            "<img src=\"images/n%c.gif\" align=middle hspace=0>\0",
            s_count[i]);
          printf("%s",obuf);
        }
        fclose(fp);
        unlink(link);
}
 左はグラフィッカルカウンタのソースコードの 例です。前項の例と同様にSSIを使って呼び出します。
 カウンタの数字をsprintf()関数で文字列に変換して、 一桁ごとに分解し、一桁毎の画像をリクエストする <IMG>タグを作成します。
 このプログラムを使うためには、 0から9までの画像ファイルを準備する必要があります。

(4)SSIが使えないときには(フレーム利用版)


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define MAXLEN 4096

main()
{
        char buf[MAXLEN];
        FILE *fp;
        void counter();
        char *docfile="index1.html";
        char *mark="<!--counter-->";
        printf("Content-type: text/html\n\n");
        fp=fopen(docfile,"r ");
        while(fgets(buf,MAXLEN,fp)){
                if(strstr(buf,mark)) counter();
                else puts(buf);
        }
        fclose(fp);
        fflush(stdout);
}

void counter()
{
        long i;
        char s_count[6],obuf[50];
        int retcode;
        FILE *fp;
        char *link="count.link",*count="count.dat";
        for(i=0;i<5;i++){
                retcode=symlink(count,link);
                if(retcode==0) break;
                sleep(5);
        }
        fp=fopen(count,"r+");
        rewind(fp);
        fscanf(fp,"%6ld",&i);
        i++;
        rewind(fp);
        fprintf(fp,"%06ld",i);
        sprintf(s_count,"%06ld",i);
        for(i=0;i<6;i++){
          sprintf(obuf,
            "<img src=\"images/n%c.gif\" align=middle hspace=0>\0",
            s_count[i]);
          printf("%s",obuf);
        }
        fclose(fp);
        unlink(link);
}
 CGIは使えるけれど、SSIは使えない場合のカウンタの例です。 カウンタを設置するページを例えばindex1.htmlという名前で 作成します。このファイルの中で、カウンタを置く場所に という行を記述します。
 ユーザーには、このCGIファイル、例えば http://www.tatoeba.com/index1.cgi を公開します。

 そうすると、index1.cgiがindex1.htmlをオープンして、 一行ずつ読み込みます。
 <!--counter-->以外の行はそのまま 標準出力に書き出します。
 <!--counter-->を見つけると、そのまま出力する替わりに、 カウンタプログラムの出力が送出されます。 この例で示したカウンタプログラムは前項のグラフィカル バージョンを用いています。 ここは好みに合わせて替えて下さい。
 このバージョンの問題は、ユーザーに公開するURLを .cgiにしないといけないことです。

この問題は、フレームを使うことで、解決出来ます。
下のようなindex.htmlを準備して、そこからindex1.cgiを リクエストさせれば、ユーザーにはindex.htmlを公開すれば 良いことになります。


<html>
<head><title>
テストページ
</title></head>
<frameset cols="100%,*" FRAMEBORDER="no">
	<FRAME SRC="index1.cgi" SCROLLING="auto" MARGINWIDTH="0" NAME="content">
	<FRANE SRC="dummy.html" SCROLLING="no" MARGINWIDTH="0" NAME="dummy">
</frameset>
<noframes>
<body>
<a href="index1.cgi">ここから入ってください</a><p>
</body>
</noframes>
</html>

(5)gdを使って作るグラフィカルカウンタ

 次にgdを用いて、合成した画像を出力するカウンタを紹介します。
 画像がひとつに合成されているので、ネットのトラフィックを倹約 できます。またブラウザをリサイズしても、数字が途中の桁で改行 されません。
 その上、<IMG SRC="count4.cgi">のように呼び出すので SSIが使えなくともCGIが使えれば大丈夫です。


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "gd/gd.h"
int main(){
        gdImagePtr im,im_in;
        FILE *in;
        int brown,i;
        long count;
        char s_count[6],inputfile[40];
        int width=19,height=28,xoffs=4,yoffs=4;
        long counter();
        count=counter();
        sprintf(s_count,"%06ld",count);
        im=gdImageCreate(122,36);
        brown=gdImageColorAllocate(im,64,0,0);
        gdImageFill(im,1,1,brown);
        for(i=0;i<6;i++){
                sprintf(inputfile,"images/n%c.gif\0",s_count[i]);
                in=fopen(inputfile,"r ");
                im_in=gdImageCreateFromGif(in);
                fclose(in);
                gdImageCopy(im,im_in,xoffs+width*i,yoffs,0,0,width,height);
                gdImageDestroy(im_in);
        }
        printf("Content-type: image/gif\n\n");
        gdImageGif(im,stdout);
        fflush(stdout);
        gdImageDestroy(im);
}
 左にソースコードを示します。下に示したcounter()ルーチン でカウント数を得ます。そして、それぞれの数字に対応して 準備したgifファイルから画像を読み込んで、合成してひとつの 画像にしてから、出力します。
 gdImageCreate()でまず、ブランク画像を生成します。 次に、バックグラウンドの色を定義、アローケートして、 その色で画像を塗りつぶします。
 次に、上位の桁から一桁ずつに対応するgifファイルから gdImageCreateFromGif()で画像を読み込んで、 gdImageCopy()で画像を合成します。
 画像データはMIMEヘッダを送出した後、 gdImageGif()を用いて、標準出力に送出します。
使いおわった画像データ領域は、 gdImageDestroy()で忘れずに開放します。

long counter()
{
        long i;
        char s_count[6],obuf[50];
        int retcode;
        FILE *fp;
        char *link="count.link",*count="count.dat";
        for(i=0;i<5;i++){
                retcode=symlink(count,link);
                if(retcode==0) break;
                sleep(5);
        }
        fp=fopen(count,"r+");
        rewind(fp);
        fscanf(fp,"%6ld",&i);
        i++;
        rewind(fp);
        fprintf(fp,"%06ld",i);
        fclose(fp);
        unlink(link);
        return(i);
}
このカウンタの本体については、 前の項でも説明してあるので、そちらを参照して下さい。

Copyright 1999 Motoi Fujita