Статус online в социальной сети

02.09.2011

Суть проблемы такова, что сервер по http протоколу никаким образом не может отслеживать, находится ли пользователь ещё на сайте или он уже давно с него ушёл, если пользователь не выполняет никаких активных действий на сайте. Следовательно информировать сервер о том, что пользователь ещё находится в сети, должен клиент пользователя (браузер). И данная проблема решается следующим способом.

  1. Браузер AJAX-ом при обновлении страницы и после обновления в определённый интервал времени отсылает "мусор" на сервер, например через POST передаёт переменную online со значением true каждые 45 секунд после обновления страницы, при обновлении которой он отсылает его сразу. Это конечно плохо, что мы вынуждены подобным образом засорять сервер, вынуждая его постоянно соединяться с БД, но деваться некуда, и при грамотном выборе времени, я выбрал 45 секунд, и способе обновления, а выбрал через AJAX, с этим жить можно :) Почему именно AJAX ? А вы представьте, смотрите вы на сайте какие то страницы, сайт довольно громоздкий с фотографиями и тут вам браузер, принудительно обновляет каждые 45 сек страницу со всем содержимым, и далеко не факт что после обновления вы перейдёте на ту самую страницу на которой находились. Глупо не так ли ? Поэтому тут вариант 1 - Использоватьь AJAX.
  2. Сервер, точнее, в модели mvc, - контроллер, при обнаружении существования сессии, корректного id пользователя в этой сессии и ключа суперглобалного массива $_POST['online'] равному логическому true, посылает его id $_SESSION['id'] и текущее время на сервере time() в модель для перезаписи в БД поле `status` этим временем у пользователя с этим id.
  3. При выводе списка пользователей, или друзей или очереди на добавления в друзья или просто при просмотре чего то профиля в представлении по мимо данных пользователей, выводиться из БД и время его последнего присутствия и вычитается из текущего времени сервера. И если эта разница менее 60 (секунд), то пользователь считается в сети и выводиться соответствующая надпись или иконка, если более или ровно, то пользователь считается не доступным и выводиться соответствующая надпись или иконка.

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

На последок привиду код AJAX-а первой части, так как код остальных частей примитивен и не должен вызывать ниукого затруднений.

Библиотека работы с AJAX-ом

ajax = {
	obj: false,
	url: '/',
	init: function(){
			try {
			        this.obj = new XMLHttpRequest();
			}
			catch (trymicrosoft) {
				try {
				        this.obj = new ActiveXObject("Msxml2.XMLHTTP");
				}
				catch (othermicrosoft) {
				        try {
					        this.obj = new ActiveXObject("Microsoft.XMLHTTP");
					}
					catch (failed) {
					        ajax.obj = false;
					}
				}
			}
		},
	get: function(url_u){
		       if(!this.obj){
		                alert('Error initializing XMLHttpRequest!');
			        return false;
		        }
		        url = ajax.url;
		        this.obj.open('GET',url,true);
		        this.obj.onreadystatechange = this.answer;
		        this.obj.send(null);
	},
	post: function(_form){
		       if(!this.obj){
		                alert('Error initializing XMLHttpRequest!');
			        return false;
		        }
		        data = this.splitForm(_form);
		        this.obj.open('POST',this.url,true);
		        this.obj.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		        this.obj.onreadystatechange = this.answer;
		        this.obj.send(data);
	},
	answer: function()
				{
					if (ajax.obj.readyState == 4)
					{
						if (ajax.obj.status == 200)
						{
							if(ajax.obj.getResponseHeader('Content-Type') == 'text/xml')
							{
								ajax.parseXML();
							}
							//else{ajax.parseText();}
						}
						else if (ajax.obj.status == 404){}
						else
						{
							alert("Error: status code is " + ajax.obj.status);
						}
					}
				},
	parseXML: function(){
			    if(this.obj.responseXML == null || this.obj.responseXML.documentElement == null){
			        try {
				        this.obj.responseXML.loadXML(this.obj.responseText)
				}
				catch(e){alert("Can't load");}
			    }
			    _xml = this.obj.responseXML.documentElement;
		            node_list = _xml.getElementsByTagName('error');
		            for(i=0; i<node_list.length; i++){
		                node = node_list.item(i);
				_text = node.text || node.textContent;
				id = node.getAttribute('id');
				try{
				document.getElementById(id).innerHTML = _text;
				}catch(e){}
			    }
			    _eval_list = _xml.getElementsByTagName('eval');
			    if(_eval_list.length > 0){
			        script = _eval_list.item(0).text || _eval_list.item(0).textContent;
			        try{eval(script);}catch(error){alert('AJAX: произошла ошибка eval');}
			    }
	},
	parseText: function(){
		             text = this.obj.responseText;
			     alert(text);
	},
	splitForm: function(_form){
	                var _param = new Array();
	                _input = _form.getElementsByTagName('input');
	                _textarea = _form.getElementsByTagName('textarea');
	                _select = _form.getElementsByTagName('select');
	                _li = _input.length;
	                _lt = _textarea.length;
	                _ls = _select.length;
	                for(i=0; i<_li; i++){
	                        if(_input.item(i).type != 'checkbox'){
	                                try{_el = encodeURIComponent(_input.item(i).name)+'='+encodeURIComponent(_input.item(i).value);}
	                                catch(e){}
	                                _param.push(_el);
	                        }
	                        else if(_input.item(i).type == 'checkbox' && _input.item(i).checked){
	                                try{_el = (_input.item(i).name)+'='+encodeURIComponent(_input.item(i).value);}
	                                catch(e){}
	                                _param.push(_el);
	                        }
	                }
	                for(i=0; i<_lt; i++){
	                        if(_textarea.item(i).value.length > 0){
	                                try{_el = encodeURIComponent(_textarea.item(i).name)+'='+encodeURIComponent(_textarea.item(i).value);}
	                                catch(_e){}
	                                _param.push(_el);
	                        }
	                }
	                for(i=0; i<_ls; i++){
	                        if(_select.item(i).value.length > 0){
	                                try{_el = encodeURIComponent(_select.item(i).name)+'='+encodeURIComponent(_select.item(i).value);}
	                                catch(_e){}
	                                _param.push(_el);
	                        }
	                }
	                return _param.join('&');

	}
}
ajax.init();

Код отправки статуса на сервер

<?php
	echo '<script type="text/javascript" src="/media/ajax.js"></script>
			<script type="text/javascript">
				function online()
				{
					ajax.post(document.onlineForm);
				}
				setInterval(function(){online();},45000);
				function addHandler(object,event,handler)
				{
					if (typeof object.addEventListener != \'undefined\')
						object.addEventListener(event, handler, false);
					else if (typeof object.attachEvent != \'undefined\')
						object.attachEvent(\'on\' + event, handler);
					else
						throw \'Incompatible browser\';
				}
				addHandler(window,\'load\',online);
			</script>';
?>

В этом коде мы подключаем ранее приведенную библиотеку, которая у нас находится в папке /media/ajax.js и определяем функцию online(), которая будет вызывать метод post класса ajax передавая ему в качестве параметра форму с именем onlineForm, которая будет отправлять на сервер статус.

Затем мы устанавливаем циклический вызов этой функции каждые 45 секунд, и определяем кросс-браузерный обработчик события DOM уровня 2, который при загрузке документа, так же вызывает функцию update()

Ну и собственно на ша форма с именем onlineForm с единственным скрытым полем с именем online и постоянным пораметром логического true, которая передают его на сервер методом post который будет обрабатываться главным контроллером сайта.

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

Последнее в нашем блоге