CGI教學(xué):第三章 計(jì)數(shù)器的編寫方法
發(fā)布時(shí)間:2008-08-06 閱讀數(shù): 次 來源:網(wǎng)樂原科技
一、記錄(log)文件
1、grep
2、page-stats
3、wusage
二、創(chuàng)建自己的計(jì)數(shù)器
1、使用DBM文件
2、文本文件
3、文件鎖定
4、輸出計(jì)數(shù)結(jié)果
5、www Homepage Access Counter
6、使用GD圖形庫
計(jì)數(shù)器(Access Counter)可以記錄網(wǎng)頁被訪問的次數(shù),在萬維網(wǎng)上的使用十分普遍,其編寫方法很多,從簡單的SSI命令到用CGI程序生成內(nèi)嵌圖像等。計(jì)數(shù)器除了記錄點(diǎn)擊次數(shù)外,還可以記錄訪問者的IP、OS、瀏覽器類型等內(nèi)容,使你對自己網(wǎng)站的訪問情況有個(gè)全面的了解,本章主要介紹點(diǎn)擊次數(shù)的統(tǒng)計(jì)和顯示方法。
一、記錄(log)文件
1、grep
對于Web服務(wù)器而言,都有記錄文件記錄著詳細(xì)的訪問信息,其名稱通常為access_log,下面是一個(gè)例子:
01: dialup-9.austin.io.com - - [02/Oct/1995:20:18:05 -0500] "GET /phoenix/ HTTP/1.0" 200 2330
02: crossnet.org - - [08/Oct/1995:19:56:45 -0500] "HEAD / HTTP/1.0" 200 0
03: dialup-2.austin.io.com - - [09/Oct/1995:07:54:56 -0500] "GET /leading-rein/orders HTTP/1.0" 401 -
04: onramp1-9.onr.com - - [10/Oct/1995:11:11:40 -0500] "GET / HTTP/1.0" 200 1529
05: onramp1-9.onr.com - - [10/Oct/1995:11:11:43 -0500] "GET /accn.jpg HTTP/1.0" 200 20342
06: onramp1-9.onr.com - - [10/Oct/1995:11:11:46 -0500] "GET /home.gif HTTP/1.0" 200 1331
07: dialup-3.austin.io.com - - [12/Oct/1995:08:04:27 -0500] "GET /cgi-bin/env.cgi?
08: SavedName=+&First+Name=Eric&Last+Name=Herrmann&Street=&City=&State=&
09: zip=&Phone+Number=%28999%29+999-9999+&Email+Address=&
10: simple=+Submit+Registration+ HTTP/1.0" 200 1261
11: dialup-20.austin.io.com - - [14/Oct/1995:16:40:04 -0500] "GET /leading-rein/index.cgi?unique_id=9658-199.170.89.58-813706781 HTTP/1.0" 200 1109
注;當(dāng)主頁在srm.conf中被命名為welcome.html、index.cgi、index.shtml等時(shí),對其的訪問記錄,可能只含有目錄名而不包含該文件名。
我們可以用UNIX命令grep來統(tǒng)計(jì)主頁被訪問的次數(shù),grep命令通常輸出每一行匹配結(jié)果,但可以加上參數(shù)-c以輸出匹配行的數(shù)目,grep詳見UNIX幫助。下面是一個(gè)簡單的例子grep.cgi:
1: #!/usr/local/bin/perl
2: print "content-type: text/html\n\n";
3: $num = `grep -c 'GET / HTTP' /your-server-root/logs/access_log` ;
4: $num += `grep -c 'GET /index.shtml' /your-server-root/logs/access_log` ;
5: $num += `grep -c 'GET /index.html' /your-server-root /logs/access_log` ;
6: print "$num\n";
現(xiàn)在就可以在主頁中加上SSI指令來顯示計(jì)數(shù)了,例如:
01: <html>
02: <head><title>grep test</title>
03: <body>
04: <hr noshade>
05: This page has been accessed
06: <!--#exec cgi="grep1.cgi" --> times.
07: <hr noshade>
08: </body>
09: </html>
別忘了把此文件擴(kuò)展名改為.shtml。在grep.cgi中,grep命令中包圍模式的單引號告訴UNIX shell不改變該串的內(nèi)容以精確匹配。
這種方法有許多缺陷,首先是效率低,用grep來匹配花時(shí)間較長,可能要幾秒鐘的時(shí)間,這對一個(gè)簡單的文本計(jì)數(shù)器而言太長了。其次,對每一個(gè)需要計(jì)數(shù)器的頁面CGI文件均不相同。最后一個(gè)對某些人來說不算是個(gè)問題,就是要把Web服務(wù)器設(shè)置成允許SSI執(zhí)行,即將其目錄映射略加修改。
2、page-stats
有一個(gè)叫page-stats的程序較好地解決了grep的問題。它查看HTTP daemon的access_log并尋找在標(biāo)識文件中指定網(wǎng)頁的訪問,然后計(jì)算其數(shù)目并生成一個(gè)HTML形式的統(tǒng)計(jì)頁面。這樣,你既得到了頁面的詳細(xì)統(tǒng)計(jì)信息,同時(shí)又得到了可顯示的結(jié)果頁面,這樣的例子可在http://www.nease.net/tppmsgs/msgs0.htm#35找到。還可以用grep命令在統(tǒng)計(jì)頁面中查找所需信息并生成自己的顯示形式,這樣速度就快多了。
注意不應(yīng)在建立自己的統(tǒng)計(jì)時(shí)運(yùn)行該程序,否則會導(dǎo)致沖突。應(yīng)該把它放到任務(wù)列表中用UNIX命令cron定時(shí)執(zhí)行,每天、每小時(shí)甚至每幾分鐘運(yùn)行一次。cron詳見UNIX幫助。
3、wusage
另外一個(gè)廣為應(yīng)用的服務(wù)器統(tǒng)計(jì)程序是由Thomas Boutell(boutell@boutell.com)編寫的應(yīng)用于整個(gè)服務(wù)器的wusage,它生成很詳細(xì)的信息,包括服務(wù)器怎樣、何時(shí)及從何處被訪問等等。它每周運(yùn)行一次,可以生成漂亮的圖表結(jié)果,十分直觀。
使用wrsage要求使用ncSA或CERN的Web Server或任何有標(biāo)準(zhǔn)記錄文件格式的服務(wù)器,還需要有C編譯器,wusage可在http://www.nease.net/tppmsgs/msgs0.htm#36得到。
隨著時(shí)間推移,access_file會越來越龐大,必須定期截留,這時(shí)先查看最近一周wusage是否已生成了完整的報(bào)表,確定統(tǒng)計(jì)結(jié)束時(shí)間,然后把a(bǔ)ccess_log中該時(shí)間前的訪問記錄刪掉,并把wusage生成的結(jié)果保存在一個(gè)目錄中,以便wusage可以生成過去訪問情況的圖表。
二、創(chuàng)建自己的計(jì)數(shù)器
除了使用access_log記錄文件外,我們可以創(chuàng)建自己的計(jì)數(shù)器。這時(shí)首先必須決定用何種形式存貯計(jì)數(shù)結(jié)果,是用文本文件還是用DBM文件,然后要決定是否進(jìn)行文件同步訪問的保護(hù),這是用文件鎖定來實(shí)現(xiàn)的,最后就是確定數(shù)據(jù)的存貯格式了。
1、使用DBM文件
對DBM文件而言,常用的函數(shù)有dbmopen()、dbmclose()、reset()、each()、values()和keys(),用于計(jì)數(shù)器時(shí),主要使用前兩個(gè)函數(shù)。dbmopen()函數(shù)把DBM文件與關(guān)聯(lián)數(shù)組綁定,調(diào)用語法為:
dbmopen (%array_name, DB_filename, Read_write_mode);
如果這時(shí)指定的數(shù)據(jù)庫文件不存在,則自動創(chuàng)建兩個(gè)名為DB_filename.dir和DB_filename.pag的文件,除非把讀寫模式設(shè)為undef值。
缺省的,只有64個(gè)記錄被讀進(jìn)內(nèi)存,可以通過給%array_name分配大小來改變此缺省值。如果你只是給自己的網(wǎng)頁做計(jì)數(shù),缺省值已經(jīng)足夠了,但如果是給整個(gè)服務(wù)器建立計(jì)數(shù)器,一般需要更大的值。
現(xiàn)在看看這三個(gè)參數(shù)。當(dāng)調(diào)用dbmopen時(shí),%array_name原有的值都被清除(如果有的話),用DBM文件中的值替換掉,給之賦予新值很簡單:$array_name{'new_key'} = value; 當(dāng)調(diào)用dbmclose (%array_name);語句時(shí)綁定被解除,關(guān)聯(lián)數(shù)組中的內(nèi)容被寫如DBM文件,也可以不關(guān)閉文件而將內(nèi)容寫入,方法是調(diào)用reset (%array_name);語句,注意此語句并不是重置DBM文件,而是將內(nèi)存中的數(shù)據(jù)寫入文件。第二個(gè)參數(shù)DB_filename是不包含擴(kuò)展名的,至于讀寫模式詳見本教程的語言部分。
下面是個(gè)使用DBM文件的計(jì)數(shù)器的簡單例子:
1: dbmopen(%COUNTERS, $DOCUMENT_ROOT/DBM_FILES/counters,0666);
2: if(!(defined($counters{'my_counter'})){
3: $counters{'my_counter'}=0;}
4: $counters{'my_counter'})++;
5: $count=$counters{'my_counter'};
6: dbmclose (counters);
2、文本文件
如果不用DBM文件而用文本文件,除了打開、關(guān)閉文件外,還要涉及到數(shù)據(jù)的讀寫問題,必須確定合適的數(shù)據(jù)格式,基本步驟如下: 1)打開文件
2)讀取計(jì)數(shù)
3)自增
4)寫入新值
5)關(guān)閉文件
3、文件鎖定
當(dāng)更新文件內(nèi)容時(shí),該文件可能同時(shí)被另一個(gè)進(jìn)程修改。對計(jì)數(shù)器程序而言,如果兩個(gè)或多個(gè)人同時(shí)訪問頁面調(diào)用了計(jì)數(shù)器程序,就會出現(xiàn)多個(gè)進(jìn)程同時(shí)修改同一文件的情況,這樣有的進(jìn)程的修改就會失效。當(dāng)然這并不是太大的問題,只是失去一些計(jì)數(shù)而已,不過計(jì)數(shù)器就不準(zhǔn)確了,訪問的人越多,這個(gè)問題就越大。解決辦法就是修改時(shí)通知其它試圖打開該文件的進(jìn)程等待,或叫文件鎖定,修改完再釋放,允許其它進(jìn)程打開文件并修改。有兩種方法,一是創(chuàng)建自己的鎖定機(jī)制,一種是使用系統(tǒng)函數(shù)flock()。
1)創(chuàng)建自己的文件鎖
這種方法具體實(shí)現(xiàn)是創(chuàng)建和刪除一個(gè)特定名稱的文件,這在資源共享機(jī)制中通常稱作semaphore。下面是個(gè)例子:
01: While(-f counter.lock){
02: select(undef,undef,undef,0.1);}
03: open(LOCKFILE,">counter.lock);
04: dbmopen(%COUNTERS, $DOCUMENT_ROOT/DBM_FILES/counters,0666);
05: if(!(defined($counters{'my_counter'})){
06: $counters{'my_counter'}=0;}
07: $counters{'my_counter'})++;
08: $count=$counters{'my_counter'};
09: dbmclose (counters);
10: close(LOCKFILE);
11: unlink(counter.lock);
首先檢查鎖定標(biāo)志文件是否存在,如果存在,就說明另一個(gè)進(jìn)程正在使用該文件,于是等待直到該文件(此處命名為counter.lock)不存在為止。此處用select()的特殊形式循環(huán)等待,此語句使程序進(jìn)入休眠狀態(tài)一段時(shí)間,該時(shí)間段由最后一個(gè)參數(shù)定義。之所以不用sleep()函數(shù)是因?yàn)槠浠締挝粸槊?,對這種文件鎖定而言太長了,幾個(gè)微秒就足夠了。
當(dāng)鎖定標(biāo)志文件不再存在,就創(chuàng)建自己的鎖定標(biāo)志文件并開始修改計(jì)數(shù),完成后關(guān)閉該文件并用unlink函數(shù)將之刪除,這樣其它的進(jìn)程又被允許修改計(jì)數(shù)。鎖定標(biāo)志文件并不是特殊的文件,其文件名也可以由你自己隨意選擇。
2)使用flock()
其實(shí)鎖定文件是很普通的編程步驟,系統(tǒng)函數(shù)flock()提供了這一功能,如果在你的系統(tǒng)上不提供的話,可以使用前面介紹的方法自己實(shí)現(xiàn)。
flock()的語法為:
flock (filehandle, lock_type);
參數(shù)filehandle為用open()函數(shù)打開的文件句柄,lock_type可以為下面四個(gè)值之一:
1:定義共享鎖。對計(jì)數(shù)器而言不適用。
2:定義排他鎖。
3:定義非阻止鎖。此處亦不用。
4:解除鎖定。
使用flock()實(shí)現(xiàn)的文件鎖定例子如下:
1a: dbmopen(%counters,"filename", 0666);
or
1b: OPEN(counters,"<filename")'
2: flock(counters,2);
3: if(!(defined($counters{'my_counter'})){
4: $counters{'my_counter'}=0;}
5: $counters{'my_counter'})++;
6: $count=$counters{'my_counter'};
7: dbmclose (counters);
8: flock(counters,8);
4、輸出計(jì)數(shù)結(jié)果
現(xiàn)在一切就緒,只剩下輸出我們的計(jì)數(shù)結(jié)果了,有三種輸出方法:
1)用上面談到的SSI方法輸出。
2)創(chuàng)建各種文本格式輸出。
3)生成各種漂亮的圖形結(jié)果輸出,本教程的《動態(tài)創(chuàng)建圖像》一章講述了基本原理并提供了一個(gè)x-bitmap格式的小例子,下面介紹兩個(gè)更完善和漂亮的程序/庫,這兩個(gè)例子均需要C編譯器。
5、www Homepage Access Counter
這是一個(gè)廣為應(yīng)用的網(wǎng)頁計(jì)數(shù)程序,它利用已有的GIF圖象連接起來生成一個(gè)GIF圖象,此程序是用C語言寫的,有適用于各種操作系統(tǒng)的版本,可以在http://www.nease.net/tppmsgs/msgs0.htm#37下載。它提供了很多參數(shù),功能比較齊全,生成的圖象結(jié)果也很漂亮,可以選擇圖像格式,其自帶了一些數(shù)字樣式,但你可以增加自己的數(shù)字圖像生成各種想要的圖像,cervantes.comptons.com/digits/digits.htm提供了很多GIF數(shù)字圖象。其參數(shù)通過QUERY_STRING傳遞,且必須是小寫字母,下面是個(gè)較復(fù)雜的調(diào)用例子:
<img src="/cgi-bin/Count.cgi?ft=9|frgb=69;139;50|tr=0|trgb=0;0;0|wxh=15;20|md=6|dd=A|st=5|sh=1|df=count.dat" align=absmiddle>;
其參數(shù)詳細(xì)說明和使用方法詳見上述下載網(wǎng)址。如果有必要的話,研究并修改一下其源程序可以使你生成更適合于自己需要的圖象。
6、使用GD圖形庫
www Homepage Access Counter利用現(xiàn)有的數(shù)字圖象簡化了一部分的工作,其目的就是用于圖形計(jì)數(shù)器。GD圖形庫的功能更加強(qiáng)大,不僅可以用于創(chuàng)建圖形計(jì)數(shù)器,還可以生成各種統(tǒng)計(jì)圖表,還提供了Perl接口庫。GD及其衍生的程序詳見本教程《動態(tài)創(chuàng)建圖像》一章。
在下載的程序中有一個(gè)名為gddemo.c的程序演示了其使用方法,在sparke.cs.nyu.edu:8086/cgi.htm有其用于計(jì)數(shù)器的例子。下面是一個(gè)通過GD.pm調(diào)用GD圖形庫生成圖像的Perl程序例:
#!/usr/bin/perl
use GD;
# create a new image
$im = new GD:Image(100,100);
# allocate some colors
$white = $im->colorAllocate(255,255,255);
$black = $im->colorAllocate(0,0,0);
$red = $im->colorAllocate(255,0,0);
$blue = $im->colorAllocate(0,0,255);
# make the background transparent and interlaced
$im->transparent($white);
$im->interlaced('true');
# Put a black frame around the picture
$im->rectangle(0,0,99,99,$black);
# Draw a blue oval
$im->arc(50,50,95,75,0,360,$blue);
# And fill it with red
$im->fill(50,50,$red);
# Convert the image to GIF and print it on standard output
print $im->gif;