Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
php / lab4.doc
Скачиваний:
49
Добавлен:
07.02.2016
Размер:
119.3 Кб
Скачать

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<?

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

// . . .

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

flock($f,LOCK_EX); // ждем, пока мы не станем единственными

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

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

// . . .

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().

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

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

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

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

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

Модель процесса с разделяемой блокировкой

<?

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

// . . .

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

flock($f,LOCK_SH); // ждем, когда процессы-писатели угомонятся

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

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

// . . .

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

fclose($f);

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

// . . .

?>

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

Пример

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

Новый вариант формы представлен на рис.

Поле формы для ввода адреса доставки имеет имя address. В результате мы располагаем переменной, к которой при обработке формы в РНР можно обращаться как к $address при условии, что установлен флаг register_globals.

Каждый из поступающих заказов записывается в один файл.

Скрипт processorder.php для обработки формы приведен ниже:

<?php

// Создать короткие имена переменных

$tireqty = $HTTP_POST_VARS['tireqty'];

$oilqty = $HTTP_POST_VARS['oilqty'];

$sparkqty = $HTTP_POST_VARS['sparkqty'];

$address = $HTTP_POST_VARS['address'];

$DOCUMENT_ROOT = $HTTP_SERVER_VARS['DOCUMENT_ROOT'];

?>

<html>

<head>

<title>Автозапчасти от Боба - Результаты заказа</title>

</head>

<body>

<h1>Автозапчасти от Боба</h1>

<h2>Результаты заказа</h2>

<?php

$totalqty = 0;

$totalqty += $tireqty;

$totalqty += $oilqty;

$totalqty += $sparkqty;

$totalamount = 0.00;

define('TIREPRICE', 100);

define('OILPRICE', 10);

define('SPARKPRICE', 4);

$date = date('H:i, jS F');

echo '<p>Заказ обработан в ';

echo $date;

echo '<br />';

echo '<p>Список вашего заказа:';

echo '<br />';

if( $totalqty == 0 )

{

echo 'Вы ничего не заказали на предыдущей странице!<br />';

}

else

{

if ( $tireqty>0 )

echo $tireqty.' автопокрышек<br />';

if ( $oilqty>0 )

echo $oilqty.' бутылок с маслом<br />';

if ( $sparkqty>0 )

echo $sparkqty.' свечей зажигания<br />';

}

$total = $tireqty * TIREPRICE + $oilqty * OILPRICE + $sparkqty * SPARKPRICE;

$total=number_format($total, 2, '.', ' ');

echo '<P>Итого по заказу: '.$total.'</p>';

echo '<P>Адрес доставки: '.$address.'<br />';

$outputstring = $date."\t".$tireqty." автопокрышек \t".$oilqty." масла\t"

.$sparkqty." свечей\t\$".$total

."\t". $address."\n";

// Открыть файл для добавления

$fp = fopen("orders.txt", 'a');

flock($fp, LOCK_EX);

if (!$fp)

{

echo '<p><strong>В настоящий момент ваш запрос не может быть обработан. '

.'Пожалуйста, попытайтесь позже.</strong></p></body></html>';

exit;

}

fwrite($fp, $outputstring);

flock($fp, LOCK_UN);

fclose($fp);

echo '<p>Заказ сохранен.</p>';

?>

</body>

</html>

Ниже расположен РНР-скрипт для считывания файла

<?php

// Создать короткие имена переменных

$DOCUMENT_ROOT = $HTTP_SERVER_VARS['DOCUMENT_ROOT'];

?>

<html>

<head>

<title>Автозапчасти - Заказы клиентов</title>

</head>

<body>

<h1>Автозапчасти</h1>

<h2>Заказы клиентов</h2>

<?php

@ $fp = fopen("orders.txt", 'r');

flock($fp, LOCK_SH);

if (!$fp)

{

echo '<p><strong>Нет ожидающих заказов.'

.'Пожалуйста, попытайтесь позже.</strong></p>';

exit;

}

while (!feof($fp))

{

$order= fgets($fp, 999);

echo $order.'<br />';

}

flock($fp, LOCK_UN);

fclose($fp);

?>

</body>

</html>

Задания.

  1. Создать «гостевую книгу» в виде формы, на которой расположены несколько полей для ввода информации о посетителе а также таблица, в которой отображены сведения о всех посетителях. Сведения о каждом посетителе сохранять в текстовом файле.

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

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

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

  5. Создать форму для ввода имени и содержимого текстового файла с кнопкой, при нажатии которой этот файл создается. Отдельно создать форму с полем ввода имени файла и кнопкой, при нажатии на которую этот файл удаляется.

Соседние файлы в папке php