"Это так просто, что даже ребенок сможет сделать"
Справка к любому конструктору "Сделай сам"
Практически на каждом сайте можно увидеть счетчики. На немного навороченных сайтах даже существуют специальные счетчики для каждой страницы с логотипом сайта. Могут встретиться счетчики, регистрирующие количество заходов на сайт, счётчики лиц, помещенных в бан-лист и пр. Я расскажу в этой статье, как написать не слишком навороченный счетчик. Все, что для этого может понадобиться - PHP, библиотека для работы с изображениями (обычно поставляется с php), рисунок счетчика и один файл под данные.
Для начала, нам нужно подумать, как счетчик будет действовать. На мой взгляд, самый простой вариант (он же наилучший) - добавлять данные по IP адресам в файл в байтовом виде, а потом выводить счётчик на экран. Вот простой примерчик с комментариями:
$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); //Уничтожаем рисунок
Наверное, многие заметили, что я не стал использовать для подсчета общего количества посещений функцию Count($data), а заново открывал файл (конечно, есть и более элегантные способы - но это ведь всего лишь простенький пример). Все дело в том, что PHP 4 автоматом выполняет функцию Array_Unique (ее можно даже не писать там), а так как после каждой записи ставится перенос строки, то самый надежный путь - подсчитать эти переносы. В этом примере есть и еще один недостаток - может получиться так, что один из байтов в записи IP будет равен 10, т. е. переносу строки.
Протестировав, мы увидим, что файл с данными все равно раздувается до невероятных размеров. А что, если вместо фиксирования каждого посещения, мы будем записывать данные о посещении с этого IP-адреса в отдельные 4 байта - ведь в результате уменьшится размер. К тому же вряд ли кто-то сможет посетить один сайт более 4 миллиардов раз. Улучшим наш пример:
$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); //Уничтожаем рисунок
Это, на мой взгляд - идеальный пример. Здесь можно определять количество посещений по IP-адресам (причем они записываются в импровизированную базу данных, которая размером получается меньше, чем используя уже существующие форматы без сжатия) и переводить значение из байтовой формы в числовую (DWORD) - это может пригодиться не только при написании счетчиков. Теоретически максимальный размер этой базы может быть равен 34359738360 байт (~ 32 гигабайт) - если страница будет посещена со всех возможных по записи IP-адресов. Практически размер такого файла обычно не превышает нескольких десятков килобайт (да и то, если сайт раскрученный). Этот пример можно еще улучшать и улучшать - например, уменьшить размер файла за счет удаления нулей: если вы посмотрите на файл с данными, то вы можете увидеть практически везде нулевые байты. Конечно, данные по IP-адресам мы никак оптимизировать не будем - это просто невозможно сделать без помощи архиватора, и то размер может получиться больше. Для оптимизации данных по посещениям нам нужно будет после записи IP-адреса добавить 1 байт, указывающий на размер записи о посещении. Приведу записи по неоптимизированной и оптимизированной базам (в десятичном счислении):
Неоптимизированная
Итого
Оптимизированная
Итого
127 0 0 1 0 0 0 17
8
127 0 0 1 1 17
6
127 0 0 1 127 63 31 15
8
127 0 0 1 4 127 63 31 15
9
Как видно из таблицы, подобная оптимизация имеет и обратную сторону медали: если кто-то умудрится посетить страницу с подобным счетчиком более 16777215 раз, то вместо 8 неоптимизированных байт он получит 9 оптимизированных. С другой стороны, выигрывая на двух байтах, мы проиграем всего лишь на одном. Так что решать вам. Пример приводить не буду по ряду причин: во-первых, каждый может сделать свою оптимизацию, во-вторых, на одних примерах научиться нельзя ничему - надо что-то думать и своим умом (как говорится, "кто не рискует, тот не пьет шампанское"), и, в-третьих, для большинства задач подойдет и слегка видоизмененный последний пример. Все вопросы можете присылать мне на e-mail - обязательно отвечу.