Реализиране на прост брояч на посетителите с PHP. Функции за заключване на файлове и тяхната употреба
Има случаи в които е нужно да се отчита поредния номер на текущия посетител на сайта. Дали ще за надписа "Вие сте посетител номер 5" или за някакво по-специално представяне за всеки десети посетител "Вие сте десетия посетител, печелите достъп до специалното ни съдържание". По-долу е показано как да се реализира такава функционалност без използването на база данни и със сравнително малко използване на системните ресурси. Ще запазваме поредния номер във текстов файл и ще ползваме cookie за запазване на номера между заявките на един и същи потребител.
Ето първата версия на класа:
<?php
/**
* This class implements simple visitor counter using plain text file for storage.
*
*/
class VisitorCounter {
var $storage_file;
var $counter_cookie = 'MYSITE_VISITOR_NUMBER';
var $cookie_lifetime = 86400;
/**
* Constructs a new counter instance.
*
* @param $filename the storage filename. Current visitor number is kept there.
* @param $cookie_name the cookie name to assign visitor number
* @param $cookie_lifetime the cookie lifetime. Determines for how long the visitor keeps his number until it's reset
*
*/
function __construct($filename, $cookie_name = null, $cookie_lifetime = null) {
$this->storage_file = $filename;
if($cookie_name) {
$this->counter_cookie = $cookie_name;
}
if($cookie_lifetime) {
$this->cookie_lifetime = $cookie_lifetime;
}
}
/**
* Gets the visitor number of the current visitor. First check if the cookie contains valid previously assigned number to reuse and
* if not - generate the next number
*
* @return int current visitor number
*/
function get_visitor_number() {
if(isset($_COOKIE[$this->counter_cookie]) && intval($_COOKIE[$this->counter_cookie]) > 0) {
return (int) $_COOKIE[$this->counter_cookie];
}
$current_number = $this->read_and_increment_counter_file();
setcookie($this->counter_cookie, (string) $current_number, time() + $this->cookie_lifetime);
return $current_number;
}
/**
* Reads the current contents of the storage file, increments it by 1 and writes the new value.
*
* @return int current content of the storage file
*/
private function read_and_increment_counter_file() {
if(!file_exists($this->storage_file)) {
return 1;
}
$fp = fopen($this->storage_file, "r+");
$num_string = fread($fp, 1024);
$last_number = intval($num_string);
if($last_number < 1) {
$last_number = 0;
}
$current_number = $last_number + 1;
rewind($fp);
fwrite($fp, (string) $current_number);
fclose($fp);
return intval($num_string);
}
}
?>
И как да се използва:
<?php
$counter = new VisitorCounter($file, 'mysite.com_visitor', 3600);
$number = $counter->get_visitor_number();
echo "You are visitor number " . $number . ".";
?>
Какво точно става, когато се извика $counter->get_visitor_number()? Първо проверяваме cookie-то за вече генериран пореден номер. Ако го има, то това не е първата заявка от този потребител и използваме запазената стойност. Ако не - прочитаме файла, добавяме единица към полученото и запазваме новата стойност. Съответно задаваме нова стойност на cookie-то.
Дотук добре, но какво става ако два процеса или две нишки едновременно изпълнят read_and_increment_counter_file(). Нека разгледаме случая:
$fp = fopen($this->storage_file, "r+");
$num_string = fread($fp, 1024);
//нишка 1 прочита "7" и контекста превключва към друга нишка
//нишка 2 прочита "7" и контекста превключва към друга нишка
$last_number = intval($num_string);
if($last_number < 1) {
$last_number = 0;
}
$current_number = $last_number + 1;
rewind($fp);
fwrite($fp, (string) $current_number);
//нишка 1 записва "8" и контекста превключва към друга нишка
//нишка 2 записва "8" и контекста превключва към друга нишка
fclose($fp);
//и двете нишки връщат 8 и вече имаме двама посетители с номер 8
return intval($num_string);
Не е точно очаквания резултат. След малко преглеждане на документацията откриваме функцията flock. Двата типа заключване, LOCK_EX за писане и LOCK_SH за четене, са това, което ни трябва. Поправяме кога и пак проиграваме проблема:
$fp = fopen($this->storage_file, "r+");
flock($fp, LOCK_SH);
$num_string = fread($fp, 1024);
//нишка 1 заключва файла за четене, прочита "7" и контекста превключва към друга нишка
//тъй като нишка едно е заключила файла за четене, то и нишка 2 го заключва за четене и прочита "7" и контекста превключва към друга нишка
$last_number = intval($num_string);
if($last_number < 1) {
$last_number = 0;
}
$current_number = $last_number + 1;
flock($fp, LOCK_EX);
//двете нишки заключват файла за писане една по една, но отново записват еднакви стойности
rewind($fp);
fwrite($fp, (string) $current_number);
flock($fp, LOCK_UN);
fclose($fp);
Изненада! Изглежда, че въпреки всичкото заключване, поведението не се променя. Това е защото заключването за четене не изключва повече от една нишка да четат едновременно, откъдето идва и нашия проблем. Сега поправяме това:
$fp = fopen($this->storage_file, "r+");
flock($fp, LOCK_EX);
$num_string = fread($fp, 1024);
$last_number = intval($num_string);
if($last_number < 1) {
$last_number = 0;
}
$current_number = $last_number + 1;
rewind($fp);
fwrite($fp, (string) $current_number);
flock($fp, LOCK_UN);
fclose($fp);
Сега всички нишки влизат в блока с четенето и писането един по един, така че се постига нещо като критична секция. Ето я и финалната версия на класа:
/**
* This class implements simple visitor counter using plain text file for storage.
*
*/
class VisitorCounter {
var $storage_file;
var $counter_cookie = 'MYSITE_VISITOR_NUMBER';
var $cookie_lifetime = 86400;
/**
* Constructs a new counter instance.
*
* @param $filename the storage filename. Current visitor number is kept there.
* @param $cookie_name the cookie name to assign visitor number
* @param $cookie_lifetime the cookie lifetime. Determines for how long the visitor keeps his number until it's reset
*
*/
function __construct($filename, $cookie_name = null, $cookie_lifetime = null) {
$this->storage_file = $filename;
if($cookie_name) {
$this->counter_cookie = $cookie_name;
}
if($cookie_lifetime) {
$this->cookie_lifetime = $cookie_lifetime;
}
}
/**
* Gets the visitor number of the current visitor. First check if the cookie contains valid previously assigned number to reuse and
* if not - generate the next number
*
* @return int current visitor number
*/
function get_visitor_number() {
if(isset($_COOKIE[$this->counter_cookie]) && intval($_COOKIE[$this->counter_cookie]) > 0) {
return (int) $_COOKIE[$this->counter_cookie];
}
$current_number = $this->read_and_increment_counter_file();
setcookie($this->counter_cookie, (string) $current_number, time() + $this->cookie_lifetime);
return $current_number;
}
/**
* Reads the current contents of the storage file, increments it by 1 and writes the new value.
*
* @return int current content of the storage file
*/
private function read_and_increment_counter_file() {
if(!file_exists($this->storage_file)) {
return 0;
}
$fp = fopen($this->storage_file, "r+");
flock($fp, LOCK_EX);
$num_string = fread($fp, 1024);
$last_number = intval($num_string);
if($last_number < 1) {
$last_number = 0;
}
$current_number = $last_number + 1;
rewind($fp);
fwrite($fp, (string) $current_number);
flock($fp, LOCK_UN);
fclose($fp);
return intval($num_string);
}
}
Няма коментари
Обратно към списъка със статиите
Тази страница последно е променяна на 2025-04-30 16:22:43