问题
I have a single webpage and i would like to track how many times it's visited without using a database.
I thought about XML, updating a file every time a user visits the page:
<?xml version='1.0' encoding='utf-8'?>
<counter>8</counter>
Then i thought it could have been a better idea to declare a PHP counter in a separate file and then update it everytime a user visits the page.
counter.php
<?php
$counter = 0;
?>
update_counter.php:
<?php
include "counter.php";
$counter += 1;
$var = "<?php\n\t\$counter = $counter;\n?>";
file_put_contents('counter.php', $var);
?>
With this, everytime update_counter.php
is visited, the variable in the counter.php
file is incremented.
Anyway, i noticed that if the counter.php
file has $counter = 5
and the update_counter.php
file is visited by i.e. 1000 users at the exact same time, the file gets read 1000 times at the same time (so the the value 5
gets read in all requests) the counter.php
file will be updated with value 5+1 (=6)
instead of 1005
.
Is there a way to make it work without using database?
回答1:
You can use flock()
which will lock the file so that other processes are not writing to the file.
Edit: updated to use fread()
instead of include()
$fp = fopen("counter.txt", "r+");
while(!flock($fp, LOCK_EX)) { // acquire an exclusive lock
// waiting to lock the file
}
$counter = intval(fread($fp, filesize("counter.txt")));
$counter++;
ftruncate($fp, 0); // truncate file
fwrite($fp, $counter); // set your data
fflush($fp); // flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
fclose($fp);
回答2:
<?php
/**
* Create an empty text file called counterlog.txt and
* upload to the same directory as the page you want to
* count hits for.
*
* Add this line of code on your page:
* <?php include "text_file_hit_counter.php"; ?>
*/
// Open the file for reading
$fp = fopen("counterlog.txt", "r");
// Get the existing count
$count = fread($fp, 1024);
// Close the file
fclose($fp);
// Add 1 to the existing count
$count = $count + 1;
// Display the number of hits
// If you don't want to display it, comment out this line
echo "<p>Page views:" . $count . "</p>";
// Reopen the file and erase the contents
$fp = fopen("counterlog.txt", "w");
fwrite($fp, $count);
// Close the file
fclose($fp);
?>
回答3:
It sounds easy, but its really hard to solve. The reason are race-conditions.
What are race-conditions?
If you open a counter file, read the content, increment the hits and write the hits to the file content, many things can happen between all these steps through other visitors opening the same script on your website simultaneously. Think about the situation when the first visitors request (thread) writes "484049" hits to the counter file char by char and in the millisecond while "484" is written the second thread reads that value and increments it to "485" loosing most of your nice hits.
Do not use global locks!
Maybe you think about solving this issue by using LOCK_EX
. By that the second thread needs to wait until the first one has finished writing to the file. But "waiting" is nothing you really want. This means every thread and I really mean every thread needs to wait for other threads. You only need some raging bots on your website, many visitors or a temporary i/o problem on your drive and nobody is able to load your website until all writes have been finished... and what happens if a visitor can not open your website... he will refresh it, causing new waiting/locking threads... bottleneck!
Use thread based locks
The only secure solution is to create instantly a new counter file for simultaneously running threads:
<?php
// settings
$count_path = 'count/';
$count_file = $count_path . 'count';
$count_lock = $count_path . 'count_lock';
// aquire non-blocking exlusive lock for this thread
// thread 1 creates count/count_lock0/
// thread 2 creates count/count_lock1/
$i = 0;
while (file_exists($count_lock . $i) || !@mkdir($count_lock . $i)) {
$i++;
if ($i > 100) {
exit($count_lock . $i . ' writable?');
}
}
// set count per thread
// thread 1 updates count/count.0
// thread 2 updates count/count.1
$count = intval(@file_get_contents($count_file . $i));
$count++;
//sleep(3);
file_put_contents($count_file . $i, $count);
// remove lock
rmdir($count_lock . $i);
?>
Now you have count/count.1
, count/count.2
, etc in your counter folder while count.1
will catch most of the hits. The reason for that is that race-conditions do not happen all the time. They happen only if two threads were simultaneously.
Note: If you see (much) more than 2 files this means your server is really slow compared to the amount of visitors you have.
If you now want the total hits, you need to tidy them up (in this example randomly):
<?php
// tidy up all counts (only one thread is able to do that)
if (mt_rand(0, 100) == 0) {
if (!file_exists($count_lock) && @mkdir($count_lock)) {
$count = intval(@file_get_contents($count_file . 'txt'));
$count_files = glob($count_path . '*.*');
foreach ($count_files as $file) {
$i = pathinfo($file, PATHINFO_EXTENSION);
if ($i == 'txt') {
continue;
}
// do not read thread counts as long they are locked
if (!file_exists($count_lock . $i) && @mkdir($count_lock . $i)) {
$count += intval(@file_get_contents($count_file . $i));
file_put_contents($count_file . $i, 0);
rmdir($count_lock . $i);
}
}
file_put_contents($count_file . 'txt', $count);
rmdir($count_lock);
}
}
// print counter
echo intval(@file_get_contents($count_file . 'txt'));
?>
P.S. enable sleep(3)
and look into the counter folder to simulate a slow server and you see how fast the multiple count files are growing.
回答4:
<?php
$File = "counter.txt";
//This is the text file we keep our count in, that we just made
$handle = fopen($File, 'r+') ;
//Here we set the file, and the permissions to read plus write
$data = fread($handle, 512) ;
//Actully get the count from the file
$count = $data + 1;
//Add the new visitor to the count
print "You are visitor number ".$count;
//Prints the count on the page
?>
回答5:
The following works beautifully except for the large file size for too many visits.
file_put_contents('counter.txt', '1', FILE_APPEND);
echo '<h1>Hi, Page served ' . filesize('counter.txt') . ' times!</h1>';
However, after the file reaches 1000 or 1000000, simply create another file which counts that unit as well. Inelegance of the large size is matched by the performance that does not require locking.
来源:https://stackoverflow.com/questions/18236599/visits-counter-without-database-with-php