Простой comet
Всю неделю готовил эту статью, и вот наконец публикую.
Что же первое приходит в голову, когда видишь слово "comet", правильно - чистящее средство, потом комета, а оно почему-то означает технологию (хотя это, наверное, громко сказано, скорее паттерн) постоянного соединения с сервером. Все же в толстых книжках читали, что после того как сервер получил запрос и отдал ответ браузеру, он забывает, что к нему вообще кто-то обращался. А тут поседели умные люди и стали седыми. Нет, посидели и придумали идею, как заставить передавать браузеру информацию, изменившуюся на сервере.
Вот собственно об этом мы с вами сегодня и поговорим, а собственно я как обычно буду высказывать свои ламерские мысли, а вы, как обычно, будете комментировать...
И так с чего же начать, начать надо с того что выберем серверным языком php а клиентским как это не странно — javascript. Для того чтобы соединение не разрывалось, php скрипт должен все время работать, но тут мы встречаем два ограничения. Первое вполне логичное у нас нет бесконечного скрипта который бы все время что-то делал, да это и не надо, достаточно включить в код бесконечный цикл. А вот собственно второе ограничение уже куда более неприятное: по умолчанию php скрипт может выполняться только 30 секунд, после чего сервер выдает ошибку о зависании скрипта. С этим мы будем бороться, но об этом потом.
Теперь когда мы имеем серверную часть нужно придумать как она будет контактировать с клиентской. Для этого нужно написать еще какую-то функцию на javascript, которую бы мог вызвать сервер, причем это должно работать параллельным потоком относительно основной страницы. Для реализации параллельного запроса идеально подходит AJAX, но, к сожалению IE и Opera, не умеют корректно обрабатывать событие onreadystatechange==3 , поэтому для реализации параллельного потока в них используют iframe, а я буду использовать его и для остальных браузеров.
В общем пора переходить к делу. Для реализации порционного отдавания контента на php нужно использовать буферизацию, а то php будет хранить все у себя, аж пока не закончит работу скрипта, чего мы собственно пытаемся избежать. Дальше я приведу пример скрипта, который каждую секунду отдает браузеру команду обновить часы и текущее время.
// устанавливаем неограниченное время выполнения скрипта
ini_set('max_execution_time', 0);
// начинаем буферизацию
ob_start();
// ставим флаг отдавать буфер каждый раз как он больше не нужен
ob_implicit_flush(true);
// отдаем заголовки, чтобы избежать кэширования
header('Expires: Sut, 01 Jan 2000 00:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
// выбрасываем в поток килобайт пробелов,
// это нужно из-за того, что php первый раз меньше отдавать не хочет
echo str_repeat(chr(32), 1024);
ob_get_clean();
// в бесконечном цикле описываем то передачу информации в браузер
while(true){
echo ""
."<script type='text/javascript'>\n"
."parent.$(parent.document).trigger('comet".$_GET['sid']."',['".date("D, d M Y H:i:s O")."']);\n"
."</script>\n";
ob_get_clean();
// ждем 1 секунду до следующего обновления
sleep(1);
}
А вот и пример работы этого скрипта находиться сразу под кодом.
Теперь давайте разберем, клиентскую часть, она тоже весьма не большая. Как я уже говорил, ее задача в основном создать iframe и приаттачить нужные события. Событий может быть всего два: обновление с новыми данными и разрыв соединения с сервером. Оба события необязательные, потому что сервер и сам вполне может отдавать готовый javascript код, а разорванное соединение можно восстанавливать, а можно предположить, что все обновления с сервера уже пришли и новых больше не будет.
(function($) {
// конструктор объекта
var comets = function(options){
this.init(options);
}
// собственно сам объект
comets.prototype = {
comets : [], // итератор соединений
options : { // массив функций по умолчанию
connect:function(sid){
// функция, выполняющаяся при разрыве соединения с сервером
},
update:function(data){
// функция, вызываемая сервером с новыми данными
},
url:"", // адрес бэкэнда
},
init : function(options){
var self = this,
comet = {},
sid = self.comets.push([]); // i++
// создаем реальный массив параметров
$.extend(comet,self.options,options)
// создаем iframe и соединение
$("<iframe/>")
.appendTo("body")
.attr({src:comet.url+sid})
.bind("load", function(){comet.connect.apply(this,[sid]);})
// аттачим события, к ифрейму аттачить нельзя, так как он еще не загрузился
$(document).bind("comet"+sid, function(event,data){comet.update.apply(this,[data])})
}
}
$.fn.comets = function(options){
// создаем новый comet
new comets(options);
// не нарушаем цепочку
return this;
}
})(jQuery);
Теперь надо передать в этот плагин (да я как всегда все делаю плагином под jQuery) наши функции.
$().comets({
// функция которая обновит такст часов
update:function(data){
$("#clock").text(data);
},
// функция которая востановит соединение с сервером
connect:function(sid){
$(this).attr({src:"clock.php?sid="+sid});
},
// адресс бэкэнда
url:"clock.php?sid="
});
Как я говорил в начале я расскажу как бороться с временем выполнения скрипта если вам недоступна функция ini_set(); Для этого надо узнать сколько времени может выполняться скрипт и прерывать его работу незадолго до этого времени. Если передать браузеру команду восстановить соединение, то этого можно не указывать в параметрах в javascript. Вносим правки в алгоритм работы скрипта:
// узнаем время рабы и отнимаем 5 секунд запаса
$metime = ini_get('max_execution_time') - 5;
// узнаем время начала и конца работы скрипта
$start = $stop = array_sum(explode(" ",microtime()));
ob_start();
ob_implicit_flush(true);
header('Expires: Sut, 01 Jan 2000 00:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
echo str_repeat(chr(32), 1024);
ob_get_clean();
// по условию, что время начала больше чем время конца минус время исполнения прерываем процедуру
while($start > $stop-$metime){
echo ""
."<script type='text/javascript'>\n"
."parent.$(parent.document).trigger('comet".$_GET['sid']."',['".date("D, d M Y H:i:s O")."']);\n"
."</script>\n";
ob_get_clean();
$stop = array_sum(explode(" ",microtime()));
sleep(1);
}
// echo "<script type='text/javascript'>window.location='".$_SERVER['PHP_SELF']."?sid=".$_GET['sid']."';</script>n";
Вот пример работы этого кода, только для наглядности я поставил обрыв соединения каждые 10 секунд.
Вот такие вот у меня получились простые комёты ;) , возможно, я их немного доделаю и опубликую, свежую версию, но это не сейчас.
Свежие комментарии