Практически на каждом сайте можно увидеть счетчики. На немного навороченных сайтах даже существуют специальные счетчики для каждой страницы с логотипом сайта. Могут встретиться счетчики, регистрирующие количество заходов на сайт, счётчики лиц, помещенных в бан-лист и пр. Я расскажу в этой статье, как написать не слишком навороченный счетчик. Все, что для этого может понадобиться - PHP, библиотека для работы с изображениями (обычно поставляется с php), рисунок счетчика и один файл под данные.
Для начала, нам нужно подумать, как счетчик будет действовать. На мой взгляд, самый простой вариант (он же наилучший) - добавлять данные по IP адресам в файл в байтовом виде, а потом выводить счётчик на экран. Вот простой примерчик с комментариями:
Code
$datafile = "counter.dat"; //Имя файла с данными $imagefile = "image.png"; //Имя файла с фоновым рисунком счетчика $font = 3; //Шрифт while (!($data = fOpen($datafile, "a+"))); //Открываем файл для добавления (если занят, то ждем) $IP = Explode(".",GetEnv('REMOTE_ADDR')); //Получаем IP-адрес в массиве из 4-х чисел (т. е. уже без точек) for ($i = 0; $i < Count($IP); $i++) fWrite($data, Chr($IP[$i])); //Записываем IP-адрес в виде 4-х байт безо всякого разделения fwrite($data,"\n"); fClose($data); //Закрываем файл с данными
$data = File($datafile); //Считываем данные из файла $IPs = Count(Array_Unique($data)); //Считаем кол-во разных IP-адресов $Records = 0; //Пока записей нет ни одной while (!($data = fOpen($datafile, "r+"))); //Открываем файл для добавления (если занят, то ждем) while (!fEOF($data)) { //Выполняем до тех пор, пока файл не кончится $s = fRead($data, 1); //Читаем один байт if ($s == "\n") $Records += 1; //Если это перенос строки, то увеличиваем значение $Records } fClose($data); //Закрываем файл
$img = ImageCreateFromPNG($imagefile); //Создаем рисунок. При необходимости можно заменить PNG на другое расширение (напр. JPEG) $TextColor = ImageColorAllocate($img, 255, 0, 0); //Цвет текста - красный ImageString($img, $font, 10, 10, "All: " . $Records, $TextColor); //Вывод строк на изображение ImageString($img, $font, 10, 30, "IPs: " . $IPs, $TextColor); //Теперь выводим рисунок, проверяя поддержку различных форматов if (Function_Exists("ImageJPEG")) { Header("Content-type: image/jpeg"); ImageJPEG($img, "", 100); } elseif (Function_Exists("ImagePNG")) { Header("Content-type: image/png"); ImagePNG($img); } elseif (Function_Exists("ImageGIF")) { Header("Content-type: image/gif"); ImageGIF($img); } elseif (Function_Exists("ImageWBMP")) { Header("Content-type: image/vnd.wap.wbmp"); ImageWBMP($img); } ImageDestroy($img); //Уничтожаем рисунок
Quote
Наверное, многие заметили, что я не стал использовать для подсчета общего количества посещений функцию Count($data), а заново открывал файл (конечно, есть и более элегантные способы - но это ведь всего лишь простенький пример). Все дело в том, что PHP 4 автоматом выполняет функцию Array_Unique (ее можно даже не писать там), а так как после каждой записи ставится перенос строки, то самый надежный путь - подсчитать эти переносы. В этом примере есть и еще один недостаток - может получиться так, что один из байтов в записи IP будет равен 10, т. е. переносу строки.
Протестировав, мы увидим, что файл с данными все равно раздувается до невероятных размеров. А что, если вместо фиксирования каждого посещения, мы будем записывать данные о посещении с этого IP-адреса в отдельные 4 байта - ведь в результате уменьшится размер. К тому же вряд ли кто-то сможет посетить один сайт более 4 миллиардов раз. Улучшим наш пример:
Code
$datafile = "counter.dat"; //Имя файла с данными $imagefile = "image.png"; //Имя файла с фоновым рисунком счетчика $font = 3; //Шрифт $IP = Explode(".",GetEnv('REMOTE_ADDR')); //Получаем IP-адрес в массиве из 4-х чисел (т. е. уже без точек) $sIP = ""; //Строка, в которую будет записываться IP-адрес уже в байтовой форме for ($i = 0; $i < Count($IP); $i++) $sIP .= Chr($IP[$i]); //Сохраняем IP-адрес в виде 4-х байт безо всякого разделения while (!($data = fOpen($datafile, "r+b"))); //Открываем файл для чтения и записи (если занят, то ждем) $recCount = FileSize($datafile) / 8; //Смотрим количество записей в файле $work = 1; //С помощью этой переменной мы будем проверять, занесен ли IP-адрес в список $Count = 0; //Здесь будет храниться общее количество посещений (если это не надо, то я написал где и что поправить) $IPs = 0; //Счетчик IP-адресов в списке. Применяется также для подсчета IP-адресов по базе while ($IPs < $size) { //Смотрим по всем IP-адресам в базе. Если Count не нужен, то добавьте к условию " & $work" $recIP = fRead($data, 4); //Читаем IP-адрес в байтовой форме $recCountS = fRead($data, 4); //Читаем число посещений с этого адреса в байтовой форме if ($recIP == $sIP) { //Если IP-адреса совпадают $recCountS{3} = Chr(Ord($recCountS{3}) + 1); //Прибавляем к крайнему байту единицу (если было 255, то станет 0) if (Ord($recCountS{3}) == 0) { //Если стало 0, то повторяем все это со следующим байтом и т. д. $recCountS{2} = Chr(Ord($recCountS{2}) + 1); if (Ord($recCountS{2}) == 0) { $recCountS{1} = Chr(Ord($recCountS{1}) + 1); if (Ord($recCountS{1}) == 0) $recCountS{0} = Chr(Ord($recCountS{0}) + 1); } } $work = 2; //Показываем, что IP был найден fSeek($data, -4, SEEK_CUR); //Перемещаемся обратно fWrite($data, $recCountS); //Записываем новое значение счетчика fSeek($data, 4, SEEK_CUR); //Хоть убейте, а не пойму, зачем тут эта строка - но без нее работает неправильно } //В следующей строке производится перевод значения счетчика на текущей записи из байтовой формы в числовую. //Если это не надо, то можно стереть $Count += Ord($recCountS{0})*0x1000000 + Ord($recCountS{1})*0x10000 + Ord($recCountS{2})*0x100 + Ord($recCountS{3}); $IPs += 1; //Прибавляем единицу к счетчику } if ($work == 1) { //Если IP не найден в базе, то добавляем его fSeek($data, 0, SEEK_END); //Перемещаемся к концу файла. Это обычно требуется, только если не использовался Count fWrite($data, $sIP.Chr(0).Chr(0).Chr(0).Chr(1)); //Добавляем запись $Count += 1; //Увеличиваем счетчики $IPs += 1; } fClose($data); //Закрываем файл $img = ImageCreateFromPNG($imagefile); //Создаем рисунок. При необходимости можно заменить PNG на другое расширение (напр. JPEG) $TextColor = ImageColorAllocate($img, 255, 0, 0); //Цвет текста - красный ImageString($img, $font, 10, 10, "All: " . $Count, $TextColor); //Вывод строк на изображение ImageString($img, $font, 10, 30, "IPs: " . $IPs, $TextColor); //Теперь выводим рисунок, проверяя поддержку различных форматов if (Function_Exists("ImageJPEG")) { Header("Content-type: image/jpeg"); ImageJPEG($img, "", 100); } elseif (Function_Exists("ImagePNG")) { Header("Content-type: image/png"); ImagePNG($img); } elseif (Function_Exists("ImageGIF")) { Header("Content-type: image/gif"); ImageGIF($img); } elseif (Function_Exists("ImageWBMP")) { Header("Content-type: image/vnd.wap.wbmp"); ImageWBMP($img); } ImageDestroy($img); //Уничтожаем рисунок
Quote
Это, на мой взгляд - идеальный пример. Здесь можно определять количество посещений по IP-адресам (причем они записываются в импровизированную базу данных, которая размером получается меньше, чем используя уже существующие форматы без сжатия) и переводить значение из байтовой формы в числовую (DWORD) - это может пригодиться не только при написании счетчиков. Теоретически максимальный размер этой базы может быть равен 34359738360 байт (~ 32 гигабайт) - если страница будет посещена со всех возможных по записи IP-адресов. Практически размер такого файла обычно не превышает нескольких десятков килобайт (да и то, если сайт раскрученный). Этот пример можно еще улучшать и улучшать - например, уменьшить размер файла за счет удаления нулей: если вы посмотрите на файл с данными, то вы можете увидеть практически везде нулевые байты. Конечно, данные по IP-адресам мы никак оптимизировать не будем - это просто невозможно сделать без помощи архиватора, и то размер может получиться больше. Для оптимизации данных по посещениям нам нужно будет после записи IP-адреса добавить 1 байт, указывающий на размер записи о посещении: