Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель PHP 4 - Котеров Д. В

..pdf
Скачиваний:
93
Добавлен:
24.05.2014
Размер:
4.38 Mб
Скачать

258

Часть IV. Стандартные функции PHP

string realpath(string $path)

Эта функция очень часто оказывается чрезвычайно полезной. На нее возложена довольно непростая задача: преобразовать относительный путь в $path в абсолютный, т. е. начинающийся от корня. Например:

echo realpath("../t.php"); // абсолютный путь — например, /home/test.php

echo realpath(".");

// выводит имя текущего каталога

К сожалению, последний оператор в некоторых версиях PHP выводит не тот же самый результат, что и функция getcwd(). А именно, к имени текущего ката- лога "прицепляются" слэши, а иногда даже и /./. Так что, если без определе- ния текущего каталога вам не обойтись, используйте getcwd().

Файл, который указывается в параметре $path, должен существовать, иначе функция возвращает false.

Функция realpath() также "расширяет" имена всех символических ссылок, которые могут встретиться в строке, задающей путь к файлу. Она всегда воз- вращает абсолютное каноническое имя, состоящее только из имен файлов но не имен ссылок.

Функции манипулирования целыми файлами

На самом деле всех перечисленных выше функций достаточно для реализации обмена с файлами любой сложности. Однако часто бывает нужно работать с файлами не построчно (или поблочно), а целиком. Функции, описанные в этом разделе, как раз для этого и предназначены.

bool copy(string $src, string $dst)

Копирует файл с именем $src в файл с именем $dst. При этом, если файл $dst на момент вызова существовал, осуществляется его перезапись. Функция возвращает true, если копирование прошло успешно, а в случае провала — false.

bool rename(string $oldname, string $newname)

Переименовывает (или перемещает, что одно и то же) файл с именем $oldname в файл с именем $newname. Если файл $newname уже существует, регистрируется ошибка, и функция возвращает false. То же происходит и при всех прочих неудачах. Если же все прошло успешно, возвращается true.

Глава 15. Работа с файлами

259

Функция не выполняет переименование файла, если его новое имя располо- жено в другой файловой системе (на другой смонтированной системе в Unix или на другом диске в Windows). Так что никогда не используйте rename() для получения загруженного по HTTP файла (о загрузке подробно рассказано в пятой части книги) — ведь временный каталог /tmp вашего хостинг- провайдера скорее всего располагается на отдельном разделе диска.

bool unlink(string $filename)

Удаляет файл с именем $filename. В случае неудачи возвращает false, иначе — true.

На самом-то деле файл удаляется только в том случае, если число "жестких" ссылок на него стало равным 0. Правда, эта схема специфична для Unix- систем.

list File(string $filename)

Считывает файл с именем $filename целиком и возвращает массив-список, каждый элемент которого соответствует строке в прочитанном файле. Функция работает очень быстро — гораздо быстрее, чем если бы мы использовали fopen() и читали файл по одной строке. Неудобство этой функции состоит в том, что символы конца строки (обычно \n), не вырезаются из строк файла, а также не транслируются, как это делается для текстовых файлов.

array get_meta_tags(string $filename, int $use_include_path=false);

Функция открывает файл и ищет в нем все тэги <meta> до тех пор, пока не встретится закрывающий тэг </head>. Если очередной тэг <meta> имеет вид:

<meta name="название" content="содержимое">

то пара название=>содержимое добавляется в результирующий массив, который под конец и возвращается. Функцию удобно использовать для быстрого получения всех метатегов из указанного файла (что работает гораздо быстрее, чем соответствующее использование fopen() и затем чтение и разбор файла по строкам). Если необязательный параметр $use_include_path установлен, то поиск файла осуществляется не только в текущем каталоге, но и во всех тех, которые назначены для поиска инструкциями include и require.

Другие функции

bool ftruncate(int $f, int $newsize)

260

Часть IV. Стандартные функции PHP

Эта функция усекает открытый файл $f до размера $newsize. Разумеется, файл должен быть открыт в режиме, разрешающем запись. Например, следующий код просто очищает весь файл:

ftruncate($f,0); // очистить содержимое файла

void fflush(int $f)

Заставляет PHP немедленно записать на диск все изменения, которые производились до этого с открытым файлом $f. Что это за изменения? Дело в том, что для повышения производительности все операции записи в файл буферизируются: например, вызов fputs($f,"Это строка!") не приводит к непосредственной записи данных на диск — сначала они попадают во внутренний буфер (обычно размером 8K). Как только буфер заполняется, его содержимое отправляется на диск, а сам он очищается, и все повторяется вновь. Особенный выигрыш от буферизации чувствуется в сетевых операциях, когда просто глупо отправлять данные маленькими порциями. Конечно, функция fflush() вызывается неявно и при закрытии файла.

int set_file_buffer(int $f, int $size)

Эта функция устанавливает размер буфера, о котором мы только что говорили, для указанного открытого файла $f. Чаще всего она используется так:

set_file_buffer($f,0);

Приведенный код отключает буферизацию для указанного файла, так что теперь все данные, записываемые в файл, немедленно отправляются на диск или в сеть.

Буферизированный ввод/вывод придуман не зря. Не отключайте его без край- ней надобности это может нанести серьезный ущерб производительности. В крайнем случае используйте fflush().

Блокирование файла

При интенсивном обмене данными с файлами в мультизадачных операционных системах встает вопрос синхронизации операций чтения/записи между процессами. Например, пусть у нас есть несколько "процессов-писателей" и один "процесс-читатель". Необходимо, чтобы в единицу времени к файлу имел доступ лишь один процессписатель, а остальные на этот момент времени как бы "подвисали", ожидая своей очереди. Это нужно, например, чтобы данные от нескольких процессов не перемешивались в файле, а следовали блок за блоком. Как мы можем этого достигнуть?

Здесь на помощь приходит функция flock(), которая устанавливает так называемую "рекомендательную блокировку" для файла. Это означает, что блокирование доступа

Глава 15. Работа с файлами

261

осуществляется не на уровне ядра системы, а на уровне программы. Поясню на примере.

Однажды я прочитал одно замечательное сравнение рекомендательной блокировки с перекрестком, на котором установилось довольно оживленное движение, регулируемое светофором. Когда горит красный, одни машины стоят, а другие проезжают. В принципе, любая машина может, так сказать, проехать наперекор правилам дорожного движения, не дожидаясь зеленого сигнала, но в таком случае возможны аварии. Рекомендательная блокировка работает точно таким же образом. А именно, процессы, которые ей пользуются, будут работать с разделяемым файлом правильно, а остальные… как-нибудь да будут, пока не произойдет "столкновение".

С другой стороны, "жесткая блокировка" (которая в PHP не поддерживается) подобна шлагбауму: никто не сможет проехать, пока его не поднимут. О жесткой блокировке мы в этой книге говорить не будем.

Единственная функция, которая занимается управлением блокировками в PHP, назы-

вается flock().

bool flock(int $f, int $operation [, int& $wouldblock])

Функция устанавливает для указанного открытого дескриптора файла $f режим блокировки, который бы хотел получить текущий процесс. Этот режим задается аргументом $operation и может быть одной из следующих констант:

rLOCK_SH (или 1) — разделяемая блокировка;

rLOCK_EX (или 2) — исключительная блокировка;

rLOCK_UN (или 3) — снять блокировку;

rLOCK_NB (или 4) — эту константу нужно прибавить к одной из предыдущих, если вы не хотите, чтобы программа "подвисала" на flock() в ожидании своей очереди, а сразу возвращала управление.

Вслучае, если был затребован режим без ожидания, и блокировка не была успешно установлена, в необязательный параметр-переменную $wouldblock будет записано значение истина true.

Вслучае ошибки функция, как всегда, возвращает false, а в случае успешного завершения — true.

Хотя в документации PHP и сказано, что flock() работает во всех операционных системах (в том числе и под Windows), мои тесты показали, что как раз для Windows это не так. А именно, в этой системе функция всегда возвращает индикатор провала, независимо от того, правильно она вызывается, или нет. Возможно, в бу- дущих версиях PHP это досадное недоразумение будет исправлено.

262

Часть IV. Стандартные функции PHP

Типы блокировок

Вспоминаю, когда я впервые столкнулся с описанием возможностей рекомендательных блокировок Си в одном из электронных справочников (кажется, это был man операционной системы FreeBSD), я был неприятно удивлен, — настолько все выглядело сложным и нелогичным. Слава Богу, на самом деле ситуация далеко не так плачевна. Сейчас я постараюсь понятным языком разъяснить все, что касается блокировок в языке PHP. Что же из себя представляют понятия исключительная блокировка и разделяемая блокировка?

Исключительная блокировка

Вернемся к нашему примеру с процессами-писателями. Каждый такой процесс страстно желает, чтобы в некоторый момент (точнее, когда он уже почти готов начать писать) он был единственным, кому разрешена запись в файл.

Он хочет стать исключительным.

Отсюда и название блокировки, которую процесс должен для себя установить. Вызвав функцию flock($f,LOCK_EX), он может быть абсолютно уверен, что все остальные процессы не начнут без разрешения писать в файл, пока он не выполнит все свои действия и не вызовет flock($f, LOCK_UN) или не закроет файл.

Откуда такая уверенность? Дело в том, что если в данный момент наш процесс не единственный претендент на запись, операционная система просто не выпустит его из "внутренностей" функции flock(), т. е. не допустит его продолжения, пока про- цесс-писатель не станет единственным. Момент, когда процесс, использующий исключительную блокировку, становится активным, знаменателен еще и тем, что все остальные процессы-писатели ожидают (все в той же функции flock()), когда же он, наконец, закончит свою работу с файлом. Как только это произойдет, операционная система выберет следующий исключительный процесс, и т. д.

Что ж, давайте теперь рассмотрим, как в общем случае должен быть устроен процессписатель, желающий установить для себя исключительную блокировку (листинг

15.2).

Листинг 15.2. Модель процесса с исключительной блокировкой

<?

//инициализация

//. . .

$f=fopen($f,"a+") or die("Не могу открыть файл на запись!"); flock($f,LOCK_EX); // ждем, пока мы не станем единственными

//В этой точке мы можем быть уверены, что только эта

//программа работает с файлом

Глава 15. Работа с файлами

263

// . . .

 

 

fflush($f);

// записываем все изменения на диск

 

flock($f,LOCK_UN); // говорим, что мы больше не будем работать с файлом fclose($f);

//Завершение

//. . .

?>

Заметьте, что при открытии файла мы использовали не деструктивный режим w (который удаляет файл, если он существовал), а более "мягкий" — a+. Это неспроста. Посудите сами: удаление файла идеологически есть изменение его содержимого. Но мы не должны этого делать до получения исключительной блокировки (вспомните пример со светофором)! Поэтому, если вам нужно обязательно каждый раз стирать содержимое файла, ни в коем случае не используйте режим открытия w — применяйте a+ и функцию ftruncate(), описанную выше. Например:

$f=fopen($f,"a+") or die("Не могу открыть файл на запись!");

flock($f,LOCK_EX);

//

ждем, пока мы не станем единственными

ftruncate($f,0);

//

очищаем все содержимое файла

Зачем мы используем fflush() перед тем, как разблокировать файл? Все очень просто: отключение блокировки не ведет к сбросу внутреннего файлово- го буфера, т. е. некоторые изменения могут быть "сброшены" в файл уже после того, как блокировка будет снята. Мы, разумеется, этого не хотим, вот и за- ставляем PHP принудительно записать все изменения на диск.

Устанавливайте исключительную блокировку, когда вы собираетесь изменять файл. Всегда используйте при этом режим открытия r, r+ или a+. Никогда не применяйте режим w. Снимайте блокировку так рано, как только сможете, и не забывайте перед этим вызвать fflush().

Разделяемая блокировка

Мы решили ровно половину нашей задачи. Действительно, теперь данные из нескольких процессов-писателей не будут перемешиваться, но как быть с читателями? А вдруг процесс-читатель захочет прочитать как раз из того места, куда пишет про- цесс-писатель? В этом случае он, очевидно, получит "половинчатые" данные. То есть, данные неверные. Как же быть?

Существуют два метода обхода этой проблемы. Первый — это использовать все ту же исключительную блокировку. Действительно, кто сказал, что исключительную

264

Часть IV. Стандартные функции PHP

блокировку можно применять только в процессах, изменяющих файл? Ведь функция flock() не знает, что будет выполнено с файлом, для которого она вызвана. Однако этот метод довольно-таки неудачен, и вот по какой причине. Представьте, что про- цессов-читателей много, а писателей — мало, и к тому же писатели еще и вызываются, скажем, раз в пару минут, а не постоянно, как читатели. В случае использования исключительной блокировки для процессов-читателей, довольно интенсивно обращающихся к файлу, мы очень скоро получим целый их рой, висящий, недовольно гудя, в очереди, пока очередному процессу разрешат читать. Но ведь никакой "аварии" не случится, если один и тот же файл будут читать и сразу все процессы этого роя, правда? Ведь чтение из файла его не изменяет. Итак, предоставив исключительную блокировку для читателей, мы потенциально получаем проблемы с производительностью, перерастающие в катастрофу, когда процессов-читателей становится больше некоторого определенного порога.

Второй (и лучший) способ подразумевает использование разделяемой блокировки. Процесс, который устанавливает этот вид блокировки, будет приостановлен только в одном случае: когда активен другой процесс, установивший исключительную блокировку. В нашем примере процессы-читатели будут "поставлены в очередь" только тогда, когда активизируется процесс-писатель. И это правильно. Посудите сами: зачем зажигать красный свет на перекрестке, если поперечного движения заведомо нет?

Теперь давайте посмотрим на разделяемую блокировку читателей с точки зрения процесса-писателя. Что он должен делать, если кто-то читает из файла, в который он как раз собирается записывать? Очевидно, он должен дождаться, пока читатель не закончит работу. Иными словами, вызов flock($f,LOCK_EX) обязан подождать, пока активна хотя бы одна разделяемая блокировка. Это и происходит в действительности.

Возможно, вам на ум пришла аналогия с перекрестком, по одной дороге кото- рого движется почти непрерывный поток машин, и поперечное движение при этом блокируется навсегда, — так что у водителей нет никаких шансов про- биться через сплошной поток. В реальном мире это действительно иногда про- исходит (потому-то любой светофор всегда представляет собой исключитель- ную блокировку), но только не в мире PHP. Дело в том, что, если почти всегда активна разделяемая блокировка, операционная система все равно так рас- пределяет кванты времени, что в некоторые из них можно "включить" исключи- тельную блокировку. То есть "поток машин" становится не сплошным, а с "про- белами" — ровно такого размера, чтобы в них могли "прошмыгнуть" машины, едущие в перпендикулярном направлении.

В листинге 15.3 представлена модель процесса, использующего разделяемую блокировку.

Листинг 15.3. Модель процесса с разделяемой блокировкой

<?

Глава 15. Работа с файлами

265

//инициализация

//. . .

$f=fopen($f,"r") or die("Не могу открыть файл на чтение!"); flock($f,LOCK_SH); // ждем, когда процессы-писатели угомонятся

//В этой точке мы можем быть уверены, что эта программа работает

//с файлом, когда ни одна другая программа в него не пишет

//. . .

flock($f,LOCK_UN); // говорим, что мы больше не будем работать с файлом fclose($f);

//Завершение

//. . .

?>

Устанавливайте разделяемую блокировку, когда вы собираетесь только читать из файла, не изменяя его. Всегда используйте при этом режим открытия r, и никакой другой. Снимайте блокировку так рано, как только сможете.

Блокировки с запретом "подвисания"

Как следует из описания функции flock(), к ее второму параметру можно прибавить константу LOCK_NB для того, чтобы функция не ожидала, когда программа может "двинуться в путь", а сразу же возвращала управление в основную программу. Это может пригодиться, если вы не хотите, чтобы ваш сценарий бесполезно простаивал, ожидая, пока ему разрешат обратиться к файлу. В эти моменты, возможно, лучше будет заняться какой-нибудь полезной работой — например, почистить временные файлы, память, или же просто сообщить пользователю, что файл заблокирован, чтобы он подождал и не думал, что программа зависла. Вот пример использования исключительной блокировки в совокупности с LOCK_NB:

$f=fopen("file.txt","a+"); while(!flock($f,LOCK_EX+LOCK_NB)) {

echo "Пытаемся получить доступ к файлу <br>"; sleep(1); // ждем 1 секунду

}

// Работаем

Эта программа основывается на том факте, что выход из flock() может произойти либо в результате отказа блокировки, либо после того, как блокировка будет установлена — но не до того! Таким образом, когда мы наконец-то дождемся разрешения

266

Часть IV. Стандартные функции PHP

доступа к файлу и произойдет выход из цикла while, мы уже будем иметь исключительную блокировку, закрепленную за нашим файлом.

Пример счетчика

Давайте напоследок рассмотрим классический пример, когда без блокировки файла не обойтись. Если вы уже имели некоторый опыт в Web-программировании, то вы, наверное, уже догадываетесь, что речь пойдет о проблеме, возникающей при написании сценария счетчика.

Итак, нам нужен сценарий, который бы при каждом своем запуске увеличивал число, хранящееся в файле, и выводил его в браузер. Несложная, казалось бы, задача сильно осложняется тем, что при большой посещаемости сервера могут быть запущены сразу несколько процессов-счетчиков, которые попытаются обратиться к одному и тому же файлу. Если не принять мер, это приведет к тому, что счетчик рано или поздно "обнулится".

Далее следует сценарий, использующий блокировку для предотвращения указанной проблемы.

Листинг 15.4. Простейший текстовый счетчик

<? $f=fopen("counter.dat","a+");

flock($f,LOCK_EX);

// Говорим, что дальше будем работать только мы

$count=fread($f,100);

// Читаем значение, сохраненное в файле

@$count=$count+1;

// Увеличиваем его на 1 (пустая строка = 0)

ftruncate($f,0);

// Стираем файл

fwrite($f,$count);

// Записываем новое значение

fflush($f);

// Сбрасываем файловый буфер

flock($f,LOCK_UN);

// Отключаемся от блокировки

fclose($f);

// Закрываем файл

echo $count;

// Печатаем величину счетчика

?>

 

Здесь мы применяем только исключительную блокировку, потому что каждый раз, когда нам нужно вывести на экран счетчик, его также нужно и увеличить.