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

Открытие файла. Работа с файлами в языке РНР-кодов разделяется на три этапа. Сперва файл открывается в нужном режиме - возвращается некое целое число, являющееся идентификатором открытого файла: дескриптор файла. Потом идут команды работы с файлом - чтение или запись, или и то и другое. Команды работы с файлом относятся далее к дескриптору файла, а не к его имени. После всего файл лучше всего закрыть, хотя можно и не закрывать, потому что РНР автоматически закрывает все файлы по завершении сценария:

                      int fopen(string $filename, string $mode, bool $use_include_path=false)

Открывает файл с именем $filename в режиме $mode и возвращает дескриптор открытого файла. Если файл не удалось открыть, то fopen() возвращает false. Вы можете не беспокоиться, проверяя выходное значение на ложность — вполне подойдет и проверка на ноль, потому что дескриптор 0 соответствует стандартному потоку ввода. Пока не будет закрыт нулевой дескриптор. Нулевой дескриптор редко закрывается. Необязательный параметр $use_inciude_path сообщает что, если задано относительное имя файла, то его следует искать также и в списке путей, используемом инструкциями include и require. Обычно параметр $use_inciude_path не используют. Параметр $mode может принимать следующие значения:

- r — существующий файл открывается только для чтения. Если файл не существует, вызов регистрирует ошибку. После удачного открытия указатель файла устанавливается на его первый байт, т.е. на начало.

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

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

- w+ — открывает существующий файл и тут же стирает все его содержимое. Далее режим аналогичен r+: с файлом можно работать как в режиме чтения, так и записи.

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

- а+ — открывает файл в режиме чтения и записи, указатель файла устанавливается на конец файла, при этом содержимое файла не уничтожается. Отличается от режима а тем, что если файл сначала же не существовал, то он создается. Этот режим полезен при условии необходимости что-то дописать в файл, но не знаете, создан ли такой файл.

Так выглядят параметра $mode. И еще. В конце любой из строк r, w, a, r+, w+ и а+ может находиться еще один необязательный символ — b или t. Если указан b, то файл открывается в режиме бинарного чтения/записи. Если же это t, то для файла устанавливается режим трансляции символа перевода строки, т.е. он воспринимается как текстовый. Если не указано ни t, ни b, то сказать с полной достоверностью, в каком режиме откроется файл невозможно. Рекомендуется всегда явно указывать бинарный или текстовый режим.

Конструкция or die(). Вот примеры:

                     $f = fopen("/home/user/file.txt", "rt") or die("Ошибка!");
                      // открывает файл  на чтение
                      
                     $f = fopen("http://www.php.ru/", "rt") or die("Ошибка!");
                      //открывает HTTP-соединение
                      
                     $f = fopen("ftp://user:pass@example.com/a.txt", "wt") or die("Ошибка!");
                      //открывает FTP-соединение с указанием имени входа и пароля для записи

Обратите внимание на конструкцию or die(). Конструкцию or die() удобно применять при работе с файлами. Оператор or аналогичен ||, но имеет очень низкий приоритет (даже ниже, чем у =), поэтому всегда выполняется уже после присваивания. Поэтому первый пример с точки зрения РНР выглядит так:

                     ($f = fopen("/home/user/file.txt", "rt")) or die("Ошибка!");

Если файл открыть не удалось, fopen() возвращает false, и осуществляется вызов die(). Тут нельзя просто так заменить or на, казалось бы, равнозначный ему оператор ||, потому что последний имеет гораздо более высокий приоритет — выше, чем у =. Поэтому в результате вызова функции:

                      $f = fopen("/home/user/file.txt", "rt") || die("Ошибка!"); 
                           в действительности будет выполнено:
                           
                      $f = (fopen('/home/user/file.txt", "rt") || die("Ошибка!")); 
                           не совсем тоже самое.

Надо запомнить, что есть различия текстового и бинарного режимов чтения и записи файлов.

Сетевые соединения. Можно записать имя файла строкой http:// или ftp://, при этом будет осуществляться доступ к файлу с удаленного хоста.

В случае HTTP-доступа РНР открывает соединение с указанным сервером, а также посылает нужные заголовки протокола HTTP 1.1: GET И Host. Потом при помощи файлового дескриптора из файла можно читать обычным образом - посредством функции fgets().

При открывании FTP-файла можно производить либо запись, либо читать из него, но никак то и другое одновременно. Кроме того, FTP-сервер должен поддерживать пассивный режим передачи. Не забывайте указывать имя пользователя и пароль.

Прямые и обратные слэши. Не используйте обратные слэши (\) в именах файлов, как это принято в DOS и Windows. Просто забудьте про такой архаизм. Поможет в этом РНР, который незаметно в нужный момент переводит прямые слэши (/) в обратные, когда Вы работаете под Windows. Если Вы не можете обойтись без привычного Вам обратного слэша, то не забывайте удвоить обратный слэш, потому что в строках он воспринимается как спецсимвол.

Иногда приходится работать с временными файлами, которые при завершении программы надо бы удалить. При этом интересен лишь файловый дескриптор, а не имя временного файла. Для создания безымянных временных файлов в РНР предусмотрена специальная функция:

                      int tmpfile ()

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

                      int fclose(int $fp)

Закрывает файл, открытый предварительно функцией fopen() или рореn(), или fsockopen(). Возвращает false, если файл закрыть не удалось( что-то с ним случилось или же разорвалась связь с удаленным хостом).В противном случае возвращает значение "истина". Вы должны всегда закрывать FTP- и HTTP-соединения, потому что в противном случае "ничьей" файл приведет к неоправданному простою канала и излишней загрузке сервера. Тем более успешно закрыв соединение, будете уверены в том, что все данные были доставлены без ошибок.

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

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

Блочные чтение/запись:

                       string fread(int $f, int $numbytes)

Функция string fread(int $f, int $numbytes) читает из файла $f блок из $numbytes символов и возвращает строку этих символов. После чтения указатель файла продвигается к следующим после прочитанного блока позициям и это происходит для всех остальных функций. Если $numbytes больше, чем можно прочитать из файла, возвращается что удалось считать. Этот прием можно использовать, если нужно считать в строку файл целиком и задайте в $numbytes очень большое число - пусть сто тысяч. Но если Вам надо сэкономить память в системе, то так поступать не стоит, потому что в некоторых версиях РНР передача большой длины строки во втором параметре fread() вызывает первоначальное выделение памяти в соответствии с запросом:

                       int fwrite (int $f, string $st)

Записывает в файл $f все содержимое строки $st. Эта функция составляет пару для fread(), действуя в противоположном напрвлении.

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

С помощью описанных двух функций можно копировать файлы: считывать файл целиком посредством fread() и затем записывать данные в новое место при помощи fwrite(). В языке РНР-кодов есть отдельная функция — сору().

Построчные чтение/запись:

                       string fgets (int $f, int $ length)

Читает из файла одну строку, заканчивающуюся символом новой строки \n. Этот символ считывается и включается в результат функции. Если строка в файле занимает больше $length-l байтов, то возвращаются только ее $length-1 символов. Функция string fgets (int $f, int $ length) полезна тогда. когда открыли файл и хотите проверить все его строки. Однако даже в этом случае лучше (и быстрее) будет воспользоваться функцией file(). Функция fgets(), как и функция fread(), в случае текстового режима в Windows заботится о преобразовании пар \r\n в один символ \n, так что будьте внимательны при работе с текстовыми файлами в операционной системе Windows:

                      int fputs (int $f, string $st)

Эта функция — синоним для fwritet().

Чтение CSV-файла. Программа Excel Microsoft Office стала настолько популярной, что в РНР встроили функцию для работы с одним из форматов файлов, в которых может сохранять данные Excel. Функция довольно удобна и экономит пару несколько строк дополнительного кода:

                      list fgetcsv(int $f, int $length, char $delim=',', char $quote='"')

Функция читает одну строку из файла, заданного дескриптором $f, и разбивает ее по символу $delim. Поля CSV-файла могут быть ограничены кавычками — символ кавычки задается в четвертом параметре $quote. Параметры $delim и $quote должны обязательно быть строкой из одного символа, в противном случае принимается во внимание только первый символ этой строки. Параметр $length задает максимальную длину строки как это делается в функции fgets(). Функция возвращает получившийся список полей или false, если строки кончились. Хотя в документации сказано, что пустые строки в файле не игнорируются, а возвращаются как список из одного элемента NULL, хотя это не совсем так.

Функция fgetcsv() гораздо более универсальна, чем просто пара fgets()/explode(). Функция fgetcsv() может работать с CSV-файлами, в чьих записях встречается перевод новой строки.

Положение указателя текущей позиции:

                      int feof (int $f)

Возвращает true, если достигнут конец файла, т.е. если указатель файла установлен за концом файла. Функция int feof (int $f) используется так:

                      $f = fopen("myfile.txt","r"); 
                      while (!feof($f)) {$st=fgets($f);
                  //теперь можно обработать очередную строку $st 
                            }
                      fclose($f);

При создании больших файлов такую конструкцию лучше не создавать.Желательно читать файл целиком при помощи file() или fread() — если нужен доступ к каждой строке этого файла, а не только к нескольким первым строкам файла:

                      int fseek (int $f, in $offset, int $whence=SEEK_SET)

Устанавливает указатель файла на байт со смещением $offset от начала файла, от его конца или от текущей позиции, в зависимости от параметра $whence. Это выражение может не сработать, если дескриптор $f соединен не с обычным локальным файлом, а с соединением HTTP или FTP.

Параметр $whence задает смещение $offset. В языке РНР для задания смещение $offset существуют три константы, равные, соответственно, 0, 1 и 2:

- SEEK_SET — устанавливает позицию, начиная с начала файла;

- SEEK_CUR — отсчитывает позицию относительно текущей позиции;

- SEEK END — отсчитывает позицию относительно конца файла.

В случае использования последних двух констант параметр $offset вполне может быть отрицательным. Будьте внимательны при использовании SEEK_END параметр $offset в большинстве случаев должен быть отрицательным, если только Вы не собираетесь писать за концом файла, при этом размер файла будет автоматически увеличен. При успешном завершения функция возвращает 0, а в случае неудачи -1.

                       Функция int ftell(int $f)

Возвращает позицию указателя файла. Вы можете сохранить текущее положение в переменной, выполнить с файлом какие-то операции, а потом вернуться к старой позиции при помощи функции fseek().

                       Функция bool ftruncate(int $f, int $nevsize)

усекает открытый файл $f до размера Snewsize. Файл должен быть открыт в режиме, разрешающем запись. Следующий код очищает весь файл:

                       $f = fopen("file.txt", "r+");
                       ftruncate($f, 0);	//очистить содержимое
                       fseek($f, 0, SEEK_SET); // перейти в начало файла

Будьте осторожны при применении функции ftruncate(). Можете очистить файл, в то время как текущая позиция дескриптора останется указывать на уже удаленные данные. При попытке записи по такой позиции ничего страшного не произойдет, просто образовавшаяся "пустота" будет заполнена байтами с нулевыми значениями. Не забывайте сразу же после ftruncate() вызывать fseek(), чтобы передвинуть файловый указатель внутрь файла.

Работа с путями. Иногда приходится манипулировать именами файлов. Допустим, прицепить к имени слева путь к какому-то каталогу или, наоборот, из полной спецификации файла выделить его непосредственное имя. В РНР для работы с именами файлов введены несколько функций.

                       Функция string basename(string $path)
                      выделяет имя файла из полного пути $path:
                      
                       echo basename ("/home/somebody/somefile.txt"); 
                                 // выводит "somefile.txt"
                       echo basename ("/");
                                 // ничего не выводит
                       echo basename("/.");
                                 // выводит "."
                       echo basename ("/./"); 
                                 // также выводит "."

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

                      string dimame (string $path)
                     возвращает имя каталога, выделенное из пути Spath:
                     
                       echo dirname("/home/file.txt"); 
                                  //выводит "/home" 
                       echo dirname("../file.txt"); 
                                  // выводит ".."
                       echo dirname("/file.txt"); 
                                  // выводит "/" под Unix 
         под Windows   echo dirname("/");	
                                  // то же самое
                       echo dirname("file.txt"); 
                                  // выводит "."

если функции dirname() передать "чистое" имя файла, она вернет ".", что означает "текущий каталог".

                        Функция string tenpnam(string $dir, string $pref±x)

генерирует имя файла в каталоге $dir с префиксом Sprefix в имени, чтобы созданный под этим именем в будущем файл был уникален. Для этого к строке $prefix присоединяется некое случайное число. Например, вызов tempnam("/tmp", "temp") может возвратить эдакое /tmp/temp3a6b243c. Если такое имя нужно создать в текущем каталоге, передайте $dir=".". Если каталог $dir не указан или не существует, то функция возьмет вместо него имя временного каталога из настроек пользователя - обычно хранится в переменной окружения ТМР или TMPDIR. Помимо генерации имени функция создает пустой файл с этим именем. Вот, использовать tempnam() в следующем случае опасно:

                       $fname = tempnam();
                       $f = fopen($fname, "w");
                              // работаем с временным файлом

хотя функция и возвращает уникальное имя, все-таки существует вероятность, что между tempnam(0 и fopen() "вклинится" какой-нибудь другой процесс, в котором функция tempnam() сгенерировала идентичное имя файла. Такая вероятность очень мала, но все-таки она существует.

Для решения проблемы можете использовать идентификатор текущего процесса РНР, доступный через вызов функции getmypid(), в качестве суффикса имени файла:

                       $fname = tempnam().getmypid(); 
                       $f = fopen($fname, "w");

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

                       На функцию string realpath(string $path)

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

                       echo realpath("../t.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.

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

                        Функция bool unlink(string $filename)

удаляет файл с именем $filename. В случае неудачи возвращает false, иначе — true. На самом деле файл удаляется только, если число "жестких" ссылок на него стало равным 0.

Чтение и запись целого файла.

                        Функция list file(string $filename, bool $use_include_path=false)

Считывает файл с именем $filename целиком в бинарном режиме и возвращает массив-список, каждый элемент которого соответствует строке в прочитанном файле. Функция list file(string $filename, bool $use_include_path=false) работает очень быстро — гораздо быстрее, чем если бы использовали fopen() и читали файл по одной строке. Если параметр $use_inciude_path содержит истинное значение, а переданное имя файла — относительное, функция также проводит поиск в каталогах библиотек РНР. Пути к таким каталогам содержатся в переменной inciude_path файла plip.ini И могут быть получены В программе при помощи ini_get("include_path"). Неудобство этой функции в том, что символы конца строки (обычно \n) не вырезаются из строк файла, а также не транслируются, как это делается для текстовых файлов, так как используется бинарный режим чтения. Конечно, можно потом пройтись по массиву и вручную выполнить trim() для каждого его элемента. Оказывается, есть значительно более быстрый способ добиться того же результата:

                       $f = fopen($fname="file.txt", "rt");
                       $lines = explode("\n", fread($f, filesize($fname))); ,

эти две строчки не только поместят в массив $lines все строки файла, но еще и:

- удалят лишние символы перевода строки (\n);

- удалят символы \r (за счет режима открытия файла "rt");

- сделают это быстрее, чем сработала бы функция file().

Пара fread()+explode() работает быстрее, чем один вызов функции file():

                       string file get contents (string $filename, bool $use_include_path=false)
                      cчитывает целиком файл $filename и возвращает все содержимое в виде одной строки
                      
                       string file_put_contents(string $filename, string $data[, bool $flags])

Данная функция позволяет в одно действие записать данные $data в файл, имя которого передано в параметре $filename. При этом данные записываются, как есть — трансляция переводов строк не производится. Можете воспользоваться следующими операторами для копирования файла:

                       $data = file_get_contents("image.gif"); 
                       file_put_contents("newimage.gif", $data);

Параметр $flags может содержать значение, полученное как сумма следующих констант:

- FILE APPEND — произвести дописывание в конец файла;

- FILE_USE_INCLUDE_PATH — найти файл в путях поиска библиотек, используемых функциями include и require.Применяйте аккуратно, чтобы не стереть важные файлы!.

                       Функция array get_meta_tags(string $filename, 
                               int $use_±nclude_path=£alse); 

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

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

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

В языке РНР-кодов существует удобная функция, которая позволяет использовать в программах стандартный формат ini-файлов, пришедший из Windows. INI-файл — текстовый файл, с расширением .ini, состоящий из нескольких секций. В каждой секции может быть определено 0 или более пар ключ=>значение.

В файле с расширением .ini можно задавать комментарии двух видов: предваренные символом ; или символами //. При чтении ini-файла они будут проигнорированы.

                       Функция array parse_ini_file(string $filename, bool $useSections=false)

читает ini-файл, имя которого передано в параметре $filename, и возвращает ассоциативный массив, содержащий ключи и значения. Если аргумент $useSections имеет значение false, тогда все секции в файле игнорируются, и возвращается просто массив ключей и значений. Если же он равен true, тогда функция вернет двумерный массив. Ключи первого измерения — имена секций, а второго измерения — имена параметров внутри секций. Доступ к значению некоторого параметра нужно организовывать так:

                       $array[$sectionName] [$paramName]
                       Функция void fflush(int $f)

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

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

                       Функция int set_file_buffer(int $f, int $size)

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

                       set_file_buffer($f, 0);

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

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

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

Рекомендательная и жесткая блокировки. На помощь приходит функция fiock(), которая устанавливает так называемую "рекомендательную блокировку" (advisory locking) для файла. Это означает, что блокирование доступа осуществляется не на уровне ядра системы, а на уровне программы. Процессы, которые ею пользуются, будут работать с разделяемым файлом правильно, а остальные... как-нибудь да будут, пока что-нибудь да не случится( как при работе с светофором). С другой стороны, "жесткая блокировка" (mandatory locking; точный перевод — "принудительная блокировка") подобна шлагбауму: никто не сможет проехать, пока его не поднимут. Windows-версия РНР поддерживает только жесткую блокировку.

Функция flock() ведет себя так, как будто бы устанавливается не рекомендательная, а жесткая блокировка. Всегда старайтесь действовать так, чтобы скрипт работал и в условиях рекомендательных, и в условиях жестких блокировок. Единственная функция, которая занимается управлением блокировками в РНР, называется flock():

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

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

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

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

- LOCK UN (или з) — снять блокировку;

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

В случае если был затребован режим без ожидания, и блокировка не была успешно установлена, в необязательный параметр-переменную $wouldblock будет записано значение true. При ошибке функция, как всегда, возвращает false, а в случае успешного завершения — true. При работе с файловой системой FAT32, используемой иногда в Windows, функция всегда возвращает индикатор провала, независимо от того, правильно она вызывается или нет.

Исключительная блокировка. Вспомним о процессах-писателей. Каждый такой процесс страстно желает, чтобы в некоторый момент, когда он уже почти готов начать писать, был бы единственным, кому разрешена запись в файл. Он хочет стать исключительным. Отсюда и название блокировки, которую процесс должен для себя установить. Вызвав функцию flock($f, LOCK EX), он может быть абсолютно уверен, что все остальные процессы не начнут без разрешения писать в файл, соответствующий дескриптору $f, пока не выполнит все свои действия и не вызовет flock($f, LOCK UN) или не закроет файл. Если в данный момент процесс — не единственный претендент на запись, операционная система просто не выпустит его из "внутренностей" функции flock(), т.е. не допустит его продолжения, пока процесс-писатель не станет единственным. Момент, когда процесс, использующий исключительную блокировку, становится активным, знаменателен еще и тем, что все остальные процессы-писатели ожидают функцию flock(). Когда закончит свою работу с файлом операционная система выберет следующий исключительный процесс, и т.д..

Как должен быть устроен процесс-писатель, желающий установить для себя исключительную блокировку:

                      < ?php 
                         ## Модель процесса-писателя. 
                      $file = "file.txt";
                         // Вначале создаем пустой файл
                         // Если же файл существует, это его не разрушит. 
                      fclose(fopen($file, "a+b"));
                         // Блокируем файл.
                      $f = fopen($file, "r+b") or die("He могу открыть файл!"); 
                      flock($f, LOCK_EX); 
                         // ждет, пока не станет единственным
                         //здесь можем быть уверены, что только 
                         //эта программа работает с файлом.
                         // Все сделано. Снимаем блокировку.
                      fclose($f); 
                      ? >

Главное коварство блокировок в том, что с ними очень легко ошибиться. Вот и данный вариант кода является чуть ли не единственным по-настоящему рабочим. Шаг влево, шаг вправо — и все, блокировка окажется неправильной. При открытии файла написали не деструктивный режим w, который удаляет файл, если он существовал, но более терпимый режим — r+, потому что удаление файла есть изменение его содержимого. Что не должны делать до получения исключительной блокировки.

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

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

Не применяйте режимамы w и w+. Режимы w и w+ не совместимы с функцией fiock().

К сожалению, режим r+ требует обязательного существования файла. Если файла нет, произойдет ошибка. Важно ясно понимать, что использовать код наподобие идущего далее ни в коем случае нельзя. Никогда так не делайте:

                      $f = fopen($file, file_exists($file)? "r+b" : "w+b");

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

Почему сразу не использовать режим а+? Ведь он, с одной стороны, открывает файл на чтение и запись, с другой — не уничтожает уже существующие файлы, а с третьей — создает пустой файл, если его не было на момент вызова. К сожалению, в некоторых версиях операционной системы FreeBSD с режимом а+ наблюдаются проблемы: при его использовании РНР позволяет писать данные только в конец файла, а любая попытка передвинуть указатель через fseek() оказывается неуспешной. Даже вызов ftruncate() возвращает ошибку. Так что не рекомендуется применять режим а+, если только не записываете данные исключительно в конец файла.

Функция fclose() перед закрытием файла всегда снимает с него блокировку, так что чаще всего не приходится делать это вручную. Тем не менее иногда бывает удобно долго держать файл открытым, блокируя его лишь изредка, во время выполнения операций записи. Чтобы не задерживать другие процессы, после окончания изменения файла в текущей итерации (и до начала нового сеанса изменений) файл можно разблокировать при вызове flock(Sf, LOCK UN). И вот тут поджидает другая западня. Все дело в буферизации файлового ввода/вывода. Разблокировав файл, должны быть уверены, что данные к этому моменту уже "сброшены" в файл. Иначе возможна ситуация записи в файл уже после снятия блокировки, что недопустимо. Всегда следите за тем, чтобы перед операцией разблокирования был вызов fflush().

Делаем выводы:

- устанавливайте исключительную блокировку, когда хотите изменять файл;

- всегда используйте при этом режим открытия r, r+ или а+;

- никогда и ни при каких условиях не применяйте режимы w и w+, как бы этого ни хотелось.

- снимайте блокировку так рано, как только сможете, и не забывайте перед этим вызвать fflush().

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

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

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

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

Дело в том, что, если почти всегда активна разделяемая блокировка, операционная система все равно так распределяет кванты времени, что в некоторые из них можно "включить" исключительную блокировку.

Модель процесса, использующего разделяемую блокировку:

                      < ?php 
                      ## Модель процесса-читателя. 
                      $file = "file.txt";
                                // Вначале создает пустой файл.
                                // Если же файл существует, это его не разрушит, 
                      fclose(fopen($file, "a+b"));
                                // Блокирует файл.
                      $f = fopen($file, "r+b") or die("He могу открыть файл!"); 
                      flock($f, LOCK_SH); 
                                // ждет, пока не завершится процесс-писатель
                                //В этой точке можно быть уверенным, 
                                // что в файл никто не пишет.
                                // Все сделано. Снимает блокировку.
                      fclose($f); 
                      ? >

Предыдущий код программи блокировки отличается лишь комментариями да константой LOCK_SH вместо LOCK_EX.

- Делаем выводы:

- устанавливайте разделяемую блокировку, когда собираетесь только читать из файла, не изменяя его;

- всегда используйте при этом режим открытия r или r+, и никакой другой;

- снимайте блокировку так рано, как только сможете.

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

Пример использования исключительной блокировки в совокупности с LOCK_NB:

                       $f=fopen("file.txt", "r+b"); 
                       while (!flock($f, LOCK_EX+LOCK_NB)) {
                       echo "Пытаемся получить доступ к файлу 
"; sleep(1); // ждет 1 секунду } //Работает, файл заблокирован.

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

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

                      < ?php 
                      ## Скрипт-счетчик с блокировкой.
                      $file = "counter.dat";
                      fclose(fopen($file, "a+b")); 
                             // создается первоначально пустой файл
                      $f = fopen($file, "r+t");
                             // открывается файл счетчика
                      flock($f, LOCK_EX);	
                             // дальше будет работать только счетчик
                      $count = fread($f, 100);	
                             // читает значение, сохраненное в файле
                      $count = $count+l;	
                             //увеличивает его на 1 (пустая строка = 0)
                      ftruncate($f, 0);	
                             // очищается файл
                      fseek($f, 0, SEEK_SET);	
                             // переходит в начало файла
                      fwrite($f, $count);	
                             // записывает новое значение
                      fclose($f);	
                             // закрывает файл
                      echo $count;	
                             // печатает величину счетчика
                      ? >

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


seosait21.ru
HTML

seosait21.ru
CSS

seosait21.ru
Web-диз.
HTML ссылка CSS ссылка ...

seosait21.ru
JavaScript

seosait21.ru
PHP

seosait21.ru
JQuery
JavaScript ссылка PHP ссылка JQuery ссылка

seosait21.ru
SEO.

seosait21.ru
MySQL

seosait21.ru
XML
... ... ...

обратно на главную     назад    дальше     вперед