probleme avec compteur de visiste PHP

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : probleme avec compteur de visiste PHP

par ritpas » 26 juil. 2007, 15:20

Voilà qui est clair!
Merci à tous et en particuliers à delcedo galaxialord pour son excellent topic!
Je n'utilise pas de base de donnée car pour y stocker une seule valeur, j'estime que ça ne vaut pas le coup.

par delcedo galaxialord » 26 juil. 2007, 14:58

Salut !

Bienvenue dans le monde de la programmation concurrentielle avec tous les problèmes qui en découlent.

Le problème survient lorsque 2 ou plus personnes exécutent le script en quasi simultanné.

Le processeur de n'importe quel ordi (sauf les biprocesseurs, ou multi cores je crois) ne peut exécuter qu'une seule instruction machine à la fois. Suivant cette logique, si 2 programmes s'exécutent, le premier devra d'abord se terminer afin que le deuxième puisse s'exécuter.
Cette méthode était une méthode que l'on utilisait au début de l'informatique.
Actuellement, il est possible (aux yeux de l'utilisateur) d'exécuter plusieurs programmes (ou script PHP) en même temps (et heureusement car sinon on ne pourrait avoir qu'un seul programme qui tourne sur l'ordi ! soit par exemple juste 1 seul processus Apache et absolument rien d'autre).
Le "multi-tâche", c'est le système d'exploitation qui le gère de plusieurs façons suivant le système, mais je ne rentrerai pas dans les détails, c'est inutile pour le problème.

Toutes les méthodes arrivent à ce principe : pour faire du multi-tâche, le système d'exploitation suspend le processus qui utilise le processeur et en envoie un autre à la place. Ainsi de suite, les processus exécutent des morceaux de leur code en un certains laps de temps, et aux yeux de l'utilisateur cela parait transparent. Les long processus peuvent ainsi laisser la place aux petits processus et tout le monde est content.

Tout le monde, ou presque :-P

La programmation concurrentielle (comprendre plusieurs processus nécessitant les mêmes ressources (ressources = processeur, base de données, imprimante, fichier...)) impose de prendre quelques précautions lors de la programmation.

Ton script par exemple n'est pas compatible avec une utilisation concurrentielle (c'est d'ailleurs pourquoi tu rencontres un bug). En effet, comme je l'ai dit plus haut, le système d'exploitation peut suspendre l'exécution de ton script à n'importe quel moment.

Voici pourquoi le script te met un 0 :

Le premier utilisateur (que je nommerai U1), exécute ce morceau de code :
$filename="compteur.txt"; 
$fp=fopen($filename,"r"); 
$Old=fread($fp,100); 
fclose($fp); 

$Old=split("=",$Old,6); 
$NewCount=$Old[1]+'1'; 
$New="nombre=$NewCount"; 
$fp=fopen($filename,"w+");
Le système d'exploitation suspend le processus, et passe la main au deuxième utilisateur U2, qui exécute ce morceau de code :
$filename="compteur.txt"; 
$fp=fopen($filename,"r"); 
$Old=fread($fp,100); 
fclose($fp); 
La valeur de $Old est NULL, car le fichier vient juste d'être réécrit par U1.
Au tour de U1 de reprendre le processeur, et il finit d'exécuter son script.
if (flock($fp,2) ){ 
    fwrite($fp,$New,100);} 
fclose($fp);
La valeur enregistrée est bien la bonne, tout est correcte après la fin de U1.

U1 a fini d'exécuter son script, U2 est libre de terminer son exécution :
$Old=split("=",$Old,6); 
$NewCount=$Old[1]+'1'; 
$New="nombre=$NewCount"; 
$fp=fopen($filename,"w+"); 
if (flock($fp,2) ){ 
    fwrite($fp,$New,100);} 
fclose($fp);
U2 ouvre le fichier, et met la valeur NULL + '1' = 1, dans le fichier.

Le bon compteur de U1 est écrasé par celui de U2 qui n'a pas pu récupérer la bonne valeur du compteur.

Pour éviter ce problème, il est souvent très simple de passé par une base de données qui gère la programmation concurrentielle.
Sinon php gère aussi l'exclusion mutuelle sur les fichiers. Tu l'as utilisé sur la deuxième parti, c'est dommage de pas l'avoir fait sur le début :-).

Pour corriger, voici une solution qui devrait fonctionner, attention flock() ne fonctionne pas sur tous les types de partitions (notament les partitions réseaux ou FAT d'après la doc) :
<?php
$fp = @fopen('compteur.txt', 'r+') or die('compteur.txt n\'existe pas'); // Si le fichier n'existe pas on le crée

flock($fp, LOCK_EX) or die('Impossible de poser un verrou'); // Pose un verrou

$old = fread($fp,100); // Lecture

// Ton traitement de la châine
$old = split('=', $old, 6); 
$newCount = $old[1]+1; 
$new = 'nombre='.$newCount; 

fseek($fp, 0);
fwrite($fp,$new,100); // Ecrit le nouveau contenu dans le fichier qui est au moins de même longueur

flock($fp, LOCK_UN); // Ôte le verrou
fclose($fp);
?>

Re: probleme avec compteur de visiste PHP

par Hubert Roksor » 26 juil. 2007, 14:35

Quelqu'un a-t-il une idée pour éviter ce genre de maiuvaise surprise? Merci
Arrêter d'utiliser un fichier texte ?

À part ça, remplace ton mode "w" par un mode "a" avec ftruncate() pour rembobiner le pointer. Ça devrait aider pour ton problème de concurrence (parce que c'est de ça qu'il s'agit, ton flock() ne garantit pas que ton fichier ne sera pas lu pendant qu'il est tronqué, et donc égal à zéro. Pour cela il faudrait un lock également lors de la lecture). Sinon t'as qu'à utiliser file_put_contents(), ça devrait être atomique.

par Sékiltoyai » 26 juil. 2007, 14:34

Rho, bah il doit bien y avoir quelquefois quelqu'un pour appeler ton script juste au moment où ton fichier est vidé par ton script, c'est à dire que utiliser fopen() en mode w+ réduit le fichier à 0, et si quelqu'un essaye de lire le fichier juste avant que tu réécrives dedans, il ne lit rien, donc il va écrire 1 dans le fichier vu qu'il a lu 0.
J'ai quelques remarques à ce sujet :
-Tu ouvres 2 fois ton fichier, une fois en lecture, une fois en écriture, et c'est complètement inutile, et ca rallonge les accès de fichier, si tu peux lire et écrire dedans, tu l'ouvres en lecture et écriture.
-Ca ne sert à rien dans ton cas de réduire le fichier à 0, sauf à avoir des problèmes de ce genre.
-Entre la lecture et l'écriture, tu peux avoir pas mal de connexions, donc de scripts qui vont lire la même valeur dans ton fichier, utilise flock() si tu veux éviter ce genre de choses.
-Tu te galères un peu avec les fichiers, les bases de données peuvent faire la même chose en bien mieux.

probleme avec compteur de visiste PHP

par ritpas » 26 juil. 2007, 13:54

Bonjour,

Je développe des sites et pour les compteurs de visites, j'utilise toujours le même scripts uisvant qui met à jour un fichier compteur.txt :

Code : Tout sélectionner

<?php $filename="compteur.txt"; $fp=fopen($filename,"r"); $Old=fread($fp,100); fclose($fp); $Old=split("=",$Old,6); $NewCount=$Old[1]+'1'; $New="nombre=$NewCount"; $fp=fopen($filename,"w+"); if (flock($fp,2) ){ fwrite($fp,$New,100);} fclose($fp); ?>
Ce script fonctionne MAIS de temps à autre compteur se remet subitement à 0 et je ne vois vraiment pas qu'elle peut en être en cause à part modifier le fichier compteur.txt manuellement, ce qui me paraît peu vraisemblable.

Quelqu'un a-t-il une idée pour éviter ce genre de maiuvaise surprise? Merci

Pascal