Решил попробовать вести на livestreet. Мой новый программистский блог. Тут просто надоело, хоть я и пишу редко
Мои программистские наработки
Обычный блог программиста. Что делаю, то и пишу
пятница, 18 января 2013 г.
среда, 16 января 2013 г.
Прокладка маршрута по всему миру Open Street Map
ВВедение в суть...
В прошлой своей статье я писал о том, как прокладывать маршруты на Google Maps, но, когда дошел до места, где понял, что по России google отказывается прокладывать маршруты, начал искать решение в Open Street Map. Сразу оговорюсь, что используется сервис cloudmade.com, стандартного способа не нашел.
Итак, приступим. Чтобы проложить маршрут, следует сделать запрос вида: http://routes.cloudmade.com/YOUR-API-KEY-GOES-HERE/api/0.3/start_point,[transit_point1,...,transit_pointN],end_point/route_type[/route_type_modifier].output_format[?lang=(Two letter ISO 3166-1 code)][&units=(km|miles)]
Тут следует пояснить формат запроса. Вот оригинал, а ниже перевод своими словами:
Примеры:
http://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/47.25976,9.58423,47.26117,9.59882/car/shortest.jshttp://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/47.25976,9.58423,47.26117,9.59882/bicycle.gpxhttp://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/51.22545,4.40730,[51.22,4.41,51.2,4.41],51.23,4.42/car.js?lang=de&units=miles
В прошлой своей статье я писал о том, как прокладывать маршруты на Google Maps, но, когда дошел до места, где понял, что по России google отказывается прокладывать маршруты, начал искать решение в Open Street Map. Сразу оговорюсь, что используется сервис cloudmade.com, стандартного способа не нашел.
Итак, приступим. Чтобы проложить маршрут, следует сделать запрос вида: http://routes.cloudmade.com/YOUR-API-KEY-GOES-HERE/api/0.3/start_point,[transit_point1,...,transit_pointN],end_point/route_type[/route_type_modifier].output_format[?lang=(Two letter ISO 3166-1 code)][&units=(km|miles)]
Тут следует пояснить формат запроса. Вот оригинал, а ниже перевод своими словами:
Общие параметры
start_point | Широта и долгота начального пункта, разделенные запятой (lat,lon) |
---|---|
end_point | Широта и долгота конечного пункта, разделенные запятой (lat,lon) |
route_type | Тип маршрута ( машина - "car", пешком - "foot", велосипед - "bicycle") |
output_format | Формат результата ( json - "js", xml - "gpx") |
Необязательные параметры
transit_points | Перечисление промежуточных точек. Также их широта и долгота, разделенные запятой. Сами точки тоже разделены запятой [51.2227,4.4120,51.2,4.41] |
---|---|
route_type_modifier | На момент написания статьи доступны следующие значения: Кратчайший путь - "shortest" Быстрейший путь - "fastest" Используется только для маршрута типа "car" |
lang | Язык ответа: de, en, es, fr, hu, it, nl, ro, ru, se, vi, zh. Добавить свой перевод тут: Routing_translation |
units | (km or miles). Default - km |
callback | optional parameter for JS response, if specified response will be wrapped into the function call. e.g. if callback=routeLoaded, response will be routeLoaded(JSON_object) |
http://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/47.25976,9.58423,47.26117,9.59882/car/shortest.jshttp://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/47.25976,9.58423,47.26117,9.59882/bicycle.gpxhttp://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/51.22545,4.40730,[51.22,4.41,51.2,4.41],51.23,4.42/car.js?lang=de&units=miles
На самом деле всё и без перевода ясно в документации. Вообщем перевод для людей, которые учили в школе немецкий =).
Далее лично у меня возникает вопрос этичности. Как я знаю, у гугла достаточно мочный фреймворк для работы с его картой и я уже имел некоторый опыт работы с их картой. Также координаты OSM и google Maps совпадают. Может брать маршруты у cloudmade и рисовать их на карте гугла?
Заметки по yii
Подключить js
Yii::app()->clientScript->registerScriptFile("/js/checkers.js");
Ошибка 404
Yii::app()->clientScript->registerScriptFile("/js/checkers.js");
Ошибка 404
throw new CHttpException(404,'The requested page does not exist.');
Является ли запрос ajax
if(Yii::app()->getRequest()->getIsAjaxRequest()) {
echo CActiveForm::validate( array( $model));
Yii::app()->end();
}
if(Yii::app()->request->isAjaxRequest) { $this->renderPartial('_ajaxContent',$data); } else { $this->render('index',$data); }
Простой способ настроить прокси на Ubuntu
проще всего делать форвард траффика через rinetd:
apt-get install rinetd
root@server:/etc# nano rinetd.conf
тут пишем в формате # bindadress bindport connectaddress connectport
root@server:/etc# echo "rinetd_enable=YES" >> /etc/rc.conf
root@server:/etc# /etc/init.d/rinetd restart
root@server:/etc# echo "rinetd_enable=YES" >> /etc/rc.conf
root@server:/etc# /etc/init.d/rinetd restart
Прокси готов, через iptables тоже можно, но там куча заморочек
четверг, 16 июня 2011 г.
PNG в IE fadeOut и fadeIn
Суть проблемы: при использовании функции fadeOut/fadeIn для png картинки в IE появляются вокруг картинки черные пятна.
Пример кода:
<html>
<head>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#all").click( function() {
$("#all").fadeOut(1000);
});
});
</script>
</head>
<body>
<img src="images/all.png" id="all" />
</body>
</html>
Очень долго рылся в интернете, но всё же нашел решение. Все пишут про фильтры, но лично у меня сработало только это решение (IE6 / IE7 / IE8 - по IETester)
<html>
<head>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var i;
for (i in document.images) {
if (document.images[i].src) {
var imgSrc = document.images[i].src;
if (imgSrc.substr(imgSrc.length-4) === '.png' || imgSrc.substr(imgSrc.length-4) === '.PNG') {
document.images[i].style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true',sizingMethod='crop',src='" + imgSrc + "')";
}
}
}
$("#all").click( function() {
$("#all").fadeOut(1000);
});
});
</script>
</head>
<body>
<div id="all"><img src="images/all.png" id="all" /></div>
</body>
</html>
Пример кода:
<html>
<head>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#all").click( function() {
$("#all").fadeOut(1000);
});
});
</script>
</head>
<body>
<img src="images/all.png" id="all" />
</body>
</html>
Очень долго рылся в интернете, но всё же нашел решение. Все пишут про фильтры, но лично у меня сработало только это решение (IE6 / IE7 / IE8 - по IETester)
<html>
<head>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var i;
for (i in document.images) {
if (document.images[i].src) {
var imgSrc = document.images[i].src;
if (imgSrc.substr(imgSrc.length-4) === '.png' || imgSrc.substr(imgSrc.length-4) === '.PNG') {
document.images[i].style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true',sizingMethod='crop',src='" + imgSrc + "')";
}
}
}
$("#all").click( function() {
$("#all").fadeOut(1000);
});
});
</script>
</head>
<body>
<div id="all"><img src="images/all.png" id="all" /></div>
</body>
</html>
среда, 15 июня 2011 г.
PNG в IE Элегантное решение без заморочек
script.js:
function fixPNG(element){
if(/MSIE (5\.5|6).+Win/.test(navigator.userAgent)){
var src;
if(element.tagName=='IMG'){
if (/\.png$/.test(element.src)){
src = element.src;
element.src = "/images/spacer.gif";
}
}
else {
src = element.currentStyle.backgroundImage.match(/url\("(.+\.png)"\)/i)
if(src){
src = src[1];
element.runtimeStyle.backgroundImage="none";
}
}
if(src){
element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "',sizingMethod='scale')";
}
}
}
html:
<!--[if lt IE 7]>
<script type="text/javascript" src="/fixpng.js"></script>
<style type="text/css">
.iePNG, img { filter:expression(fixPNG(this)); }
.iePNG a { position: relative; }
</style>
<![endif]-->
вторник, 14 июня 2011 г.
Безопасность БД Joomla
Замена mysql_real_escape_string:
string getEscaped ( $text ) - экранирует символы для добавления в базу данных
string Quote ( $text ) - тоже что и getEscaped, только ещё строка обрамляется одинарными кавычками
Это методы объекта класса JDatabase
используются так (в модели):
$db = &$this->getDBO();
$query = 'INSERT INTO blabla VALUES('.$db->Quote($name).','.$db->Quote($description).');';
Данные вставляются именно так. Перед выводом на экран не забываем htmlspecialchars();
Прокладка маршрута Google Maps на javascript
Добрый день, уважаемый читатель. Заказали у меня скрипт для прокладки маршрута на карте, расчёта расстояний между городами. Первое, на что наткнулся: http://habrahabr.ru/blogs/webdev/110460/ Тут рассказывается о том, как подключить карту на страницу, расставить пины на ней. Был у меня опыт работы с такой фичей на айфоне, а тут мы будем писать полностью рабочее JavaScript решение (без серверной стороны!). Итак, создаём файл map.html и пишем туда обычные для любого html документа строки, сразу подключаем jquery (оно на по-любому где-нибудь понадоибится).
Статья написана не до конца (т.к. маршрут на Google Maps по России не прокладывается)
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=ru"></script>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
<style type="text/css">
#map_canvas
{
width:400px;
height:300px;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
var myLatlng = new google.maps.LatLng(-34.397, 150.644);
var myOptions = {
zoom: 8,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
width:"200px"
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
});
</script>
</head>
<body>
<div id="map_canvas"></div>
</body>
</html>
Этот код выведет у вас на странице карту (на разные файлы стили и javascript не разношу для удобства написания статьи). Далее нам нужно понять как проложить маршрут между двумя точками. Хоть и достаточно сложно найти нужные статьи в интернете, но благо гуглу и его отличной документации: Directions Requests . Из документации мы берём функцию
function calcRoute()
Статья написана не до конца (т.к. маршрут на Google Maps по России не прокладывается)
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=ru"></script>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
<style type="text/css">
#map_canvas
{
width:400px;
height:300px;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
var myLatlng = new google.maps.LatLng(-34.397, 150.644);
var myOptions = {
zoom: 8,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
width:"200px"
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
});
</script>
</head>
<body>
<div id="map_canvas"></div>
</body>
</html>
Этот код выведет у вас на странице карту (на разные файлы стили и javascript не разношу для удобства написания статьи). Далее нам нужно понять как проложить маршрут между двумя точками. Хоть и достаточно сложно найти нужные статьи в интернете, но благо гуглу и его отличной документации: Directions Requests . Из документации мы берём функцию
function calcRoute()
var start = document.getElementById("start").value;
var end = document.getElementById("end").value;
var request = {
origin:start,
destination:end,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(result, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
}
});
}
Т.е. гугл даёт нам вполне удобный фреймворк для работы с его картой. Нам достаточно придать переменный start и end нужный вид (название города или координаты точки) и указать каким образом мы будем передвигаться (travelMode : DRIVING, WALKING, BICYCLING (велосипеды на момент написания статьи поддерживаются только в US)), после чего наш маршрут проложен и останется получить лишь необходимые нам данные: через какие города проходит маршрут, расстояние между каждым населёнными пунктами и полное расстояние между городами, а для этого нам нужно будет использовать Distance Matrix API. Но сначала разберёмся как нам правильно проложить маршрут между двумя нашими городами (например, Москва и Париж).
Используем Geocoding API. Для того, чтобы получить координаты города нам нужно послать обычный GET-запрос: http://maps.google.com/maps/geo?q=Москва&output=json&oe=utf8&sensor=false. Но, как мы знаем, москва есть и в Америке: http://maps.google.com/maps/geo?q=Moscow+US&output=json&oe=utf8&sensor=false . Это всё хорошо, но тут я наткнулся в документации, что это дедовские методы, а сейчас пора Google Maps V3. Вуаля, есть повод для размышлений. Открываю консоль в google chrome и начинаю писать запрос через классы.
Копаем Google Maps V3.4
http://maps.google.com/maps/api/js?sensor=false&v=3.4&language=ru
Все методы, которые были описаны выше работают, но теперь я задаюсь вопросом: как продвинутые пользователи работают с картами. Для начала мы начнём с геокодирования. Есть класс google.maps.Geocoder() который имеет один метод geocode(request:GeocoderRequest, callback:function(Array.<GeocoderResult>,GeocoderStatus)).
GeocoderRequest в свою очередь это обычная спецификация объекта (т.е. как объект должен выглядеть):
var Request = { address : "Адресс для геокодирования",
bounds : "Квадрат поиска, является объектом LatLngBounds",
language : "Желаемый язык результата",
location : "Координаты, вокруг которых искать, является объектом LatLng",
region : "Домен страны, по которой идёт поиск" } - вот и весь объект
Далее привожу описание классов из документации гугла:
google.maps.GeocoderStatus class
The status returned by the
Geocoder
on the completion of a call to geocode()
.Constant
Constant | Description |
---|---|
ERROR | There was a problem contacting the Google servers. |
INVALID_REQUEST | This GeocoderRequest was invalid. |
OK | The response contains a valid GeocoderResponse . |
OVER_QUERY_LIMIT | The webpage has gone over the requests limit in too short a period of time. |
REQUEST_DENIED | The webpage is not allowed to use the geocoder. |
UNKNOWN_ERROR | A geocoding request could not be processed due to a server error. The request may succeed if you try again. |
ZERO_RESULTS | No result was found for this GeocoderRequest . |
google.maps.GeocoderResult object specification
A single geocoder result retrieved from the geocode server. A geocode request may return multiple result objects. Note that though this result is "JSON-like," it is not strictly JSON, as it indirectly includes a
LatLng
object.Properties
Properties | Type | Description |
---|---|---|
address_components | Array.<GeocoderAddressComponent> | An array of GeocoderAddressComponent s |
geometry | GeocoderGeometry | A GeocoderGeometry object |
types | Array.<string> | An array of strings denoting the type of the returned geocoded element. A type consists of a unique string identifying the geocode result. (For example, "administrative_area_level_1", "country", etc.) |
google.maps.GeocoderAddressComponent object specification
A single address component within a
GeocoderResult
. A full address may consist of multiple address components.Properties
Properties | Type | Description |
---|---|---|
long_name | string | The full text of the address component |
short_name | string | The abbreviated, short text of the given address component |
types | Array.<string> | An array of strings denoting the type of this address component |
Теперь с помощью этих классов мы можем поставить карту на нужный адрес. Пример:
var geocoder = new google.maps.Geocoder(); geocoder.geocode( { 'address': "Moscow"}, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { map.setCenter(results[0].geometry.location); var marker = new google.maps.Marker({ map: map, position: results[0].geometry.location }); } else { alert("Geocode was not successful for the following reason: " + status); } });
Ну это не практический пример.. Нам надо получить полное название всех городов и вывести их на экран, где человек уже выберет нужный город
var geocoder = new google.maps.Geocoder(); geocoder.geocode( { 'address': "Odessa USA"}, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { for(var i = 0; i < results.length; i++) console.log(results[i].formatted_address, results[i].geometry.location.lat(), results[i].geometry.location.lng()); } else { alert("Geocode was not successful for the following reason: " + status); } });
Тут мы получили все необходимые точки, ввода данных будет происходить ниже. Сейчас надо разобраться, как получить маршрут. Вроде вверху и была функция, которая легко получала маршрут, но посмотрите на это:
Первый запрос отлично возвращает информацию о маршруте, а второй совсем не хочет.. пишет ZERO_RESULTS. В документации написано, что не может быть найден маршрут. Но на другом сайте, использующем google maps маршрут прокладывается. В чем дело, гугл? На этом пока ступор.. даже на maps.google.com не выводит маршрута.
JQuery UI и Вывод на экран
jquery тоже имеет очень хорошую документацию и прямо там я нашёл такой пример: http://jqueryui.com/demos/autocomplete/#remote-jsonp
Это как раз то, что нужно, полностью берём код примера и копируем себе на страницу
следующие файлы:
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/base/jquery-ui.css" type="text/css" media="all" />
<link rel="stylesheet" href="http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css" type="text/css" media="all" />
и картинку можно взять отсюда
вторник, 24 мая 2011 г.
Перенаправление на страницу авторизации со своего компонента Joomla 1.5
Мне понадобилось добиться от компонента того, чтобы если каким-то образом не авторизованный пользователь перейдёт на страницу компонента, то выскочит форма авторизации и после авторизации пользователь получит желанное содержимое. Не буду многословным, просто приведу код контроллера:
<?php
defined('_JEXEC') or die( 'Restricted access' );
jimport( 'joomla.application.component.controller' );
class HostOrderController extends JController
{
function show()
{
$user =& JFactory::getUser();
if ($user->guest) {
$redirecturl = base64_encode("index.php?option=com_hostorder");
$joomlaLoginUrl = 'index.php?option=com_user&view=login&return=';
$finalUrl = $joomlaLoginUrl.$redirecturl;
global $mainframe;
$mainframe->redirect($finalUrl);
}
echo "YEAP!";
}
}
На всякий случай код точки доступа:
<?php
defined('_JEXEC') or die('Restricted access');
require_once (JPATH_COMPONENT.DS.'controller.php');
$controller = new HostOrderController( array('default_task' => 'show') );
$controller->execute(JRequest::getVar('task', null, 'default', 'cmd'));
$controller->redirect();
<?php
defined('_JEXEC') or die( 'Restricted access' );
jimport( 'joomla.application.component.controller' );
class HostOrderController extends JController
{
function show()
{
$user =& JFactory::getUser();
if ($user->guest) {
$redirecturl = base64_encode("index.php?option=com_hostorder");
$joomlaLoginUrl = 'index.php?option=com_user&view=login&return=';
$finalUrl = $joomlaLoginUrl.$redirecturl;
global $mainframe;
$mainframe->redirect($finalUrl);
}
echo "YEAP!";
}
}
На всякий случай код точки доступа:
<?php
defined('_JEXEC') or die('Restricted access');
require_once (JPATH_COMPONENT.DS.'controller.php');
$controller = new HostOrderController( array('default_task' => 'show') );
$controller->execute(JRequest::getVar('task', null, 'default', 'cmd'));
$controller->redirect();
воскресенье, 5 декабря 2010 г.
Socket-соединения в Веб-приложениях
источник
Конечно же в JavaScript`е нету сокетов. Но зато есть такая чудесная вещь как AJAX, а также есть еще более привлекательная вещь как Comet. И с помощью этих двух технологий можно добиться от веб-страницы поразительного сходства с прикладными программами по части socket-соединений.
Хочу немного расказать о сокетах своими словами. Сокет — это что-то вроде радио-приемника, который появляется на сервере после соединения с ним клиента, для обратной связи с клиентским приложением. Как только в этот приемник попадают (записываются) данные, он сразу отправляет их своему приложению-слушателю. Приложение, услышав (прочитав) данные, выполняет какие-либо действия на их основе. Это может быть простое сообщение, которое следует напечатать в чате, или команда которую нужно выполнить итд. Если не углубляться в подробности работы TCP/IP, то примерно так все и происходит.
Ну а теперь давайте вернемся к нашим бара… сокетам. Итак, что нам нужно? Нам нужно сделать так, чтобы как только один соединенный с сервером клиент что-то отправит на сервер, это сразу же было отправлено всем другим соединенным в данный момент с сервером клиентам. Или другая ситуация. Допустим при определенных обстоятельствах послать с сервера сообщение всем соединенным с ним клиентам, и чтобы они его мгновенно (!) получили. Как этого добиться? Я раскажу ниже.
Итак, определим что нам нужно для этого:
Для начала я приведу листинги клиента и сервера, а потом раскажу как воспользоваться готовым примером.
Клиент (index.html):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"«http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd»>
<html xmlns=«http://www.w3.org/1999/xhtml»>
<head>
<title>Socket AppClient</title>
<meta http-equiv=«content-type»content=«text/html; charset=utf-8» />
<meta name=«author» content=«Melnaron»/>
<link rel=«stylesheet» type=«text/css»href=«css/style.css» />
<style type=«text/css»>
#log {
border: 1px solid #999999;
height: 250px;
overflow: auto;
margin: 10px 0;
padding: 10px;
}
</style>
<script type=«text/javascript»src=«lib/jquery.js»></script>
<script type=«text/javascript»>
$(document).ready(function() {
/*
* Настройка AJAX
*/
$.ajaxSetup({url: 'server.php', type: 'post', dataType: 'json'});
/*
* События кнопок и поля ввода
*/
$('#btnConnect').click(user.Connect);
$('#btnDisconnect').click(user.Disconnect);
$('#btnSend').click(user.Send);
$('#input')
.keydown(function(e){ if (e.keyCode == 13) { user.Send();return false; } })
.keypress(function(e){ if (e.keyCode == 13) { return false; }})
.keyup(function(e){ if (e.keyCode == 13) { return false; } })
;
});
</script>
<script type=«text/javascript»>
/*
* Лог
*/
var log = {
print: function(s) {
$('#log').append('<div>'+s+'</div>').get(0).scrollTop += 100;
}
};
/*
* Действияприсылаемые с сервера
*/
var actions = {
Connect: function(params) {
log.print('Connected.');
log.print('Sock: '+params.sock);
user.sock = params.sock;
user.conn = true;
user.Read();
},
Disconnect: function(params) {
log.print('Disconnected.');
},
Print: function(params) {
log.print(params.message);
}
};
/*
* Пользователь(клиент)
*/
var user = {
sock: null,
conn: false,
busy: false,
read: null,
/*
* Эта функция обрабатывает приходящие с сервера действия и выполняет их.
*/
onSuccess: function(data) {
if (typeof data.actions == 'object') {
for (var i = 0; i <data.actions.length; i++) {
if (typeofactions[data.actions[i].action] == 'function') {
actions[data.actions[i].action](data.actions[i].params);
}
}
}
},
/*
* Эта функция выполняется по завершении ajax-запроса.
*/
onComplete: function(xhr) {
if (xhr.status == 404) {
actions.Disconnect();
}
user.busy = false;
},
/*
* Эта функция выполняется по завершении запроса-слушания.
* При удачном завершении запроса (==200) моментальное возобновлениепрослушивания соккета.
* При неудачном (!=200) возобновление через 5 секунд.
*/
onCompleteRead: function(xhr) {
if (xhr.status == 200) {
user.Read();
} else {
setTimeout(user.Read, 5000);
}
},
/*
* Действие.
* Соединение с сервером.
*/
Connect: function() {
if (user.conn == false && user.busy == false) {
log.print('Connecting...');
user.busy = true;
$.ajax({
data: 'action=Connect',
success:user.onSuccess,
complete:user.onComplete
});
}
},
/*
* Действие.
* Отсоединение от сервера.
*/
Disconnect: function() {
if (user.conn && user.busy == false &&user.read) {
log.print('Disconnecting...');
user.busy = true;
$.ajax({
data:'action=Disconnect&sock='+user.sock,
success:user.onSuccess,
complete:user.onComplete
});
user.sock = null;
user.conn = false;
user.read.abort();
}
},
/*
* Действие.
* Отправка данных на сервер.
*/
Send: function() {
if (user.conn) {
var data = $.trim($('#input').val());
if (!data) {
return;
}
$.ajax({
data:'action=Send&sock='+user.sock+'&data='+data,
success:user.onSuccess,
complete:user.onComplete
});
$('#input').val('');
} else {
log.print('Please connect.');
}
},
/*
* Действие.
* Прослушивание соккета.
*/
Read: function() {
if (user.conn) {
user.read = $.ajax({
data:'action=Read&sock='+user.sock,
success:user.onSuccess,
complete:user.onCompleteRead
});
}
}
};
</script>
</head>
<body>
<input id=«btnConnect» type=«button»value=«Connect» />
<input id=«btnDisconnect» type=«button»value=«Disconnect» />
<div id=«log»></div>
<input id=«input» type=«text» />
<input id=«btnSend» type=«button»value=«Send» />
</body>
</html>
Сервер (server.php):
<?php
class Server {
/*
* Стек действий для отправки клиенту в текущем запросе.
*/
static private $actions;
/*
* Основная функция сервера.
* Запускает на выполнение переданное клиентом действие.
*/
static function Run() {
foreach (glob('sockets/*') as $sock) {
if (filesize($sock)> 2048) {
unlink($sock);
}
}
$action = 'action'.$_POST['action'];
if (is_callable('self::'.$action)) {
self::$action();
self::Send();
}
}
/*
* Эта функция пишет действие в сокеты.
* Если передан параметр $self, то исключает указанный в этом параметре сокет.
*/
static function AddToSock($action, $params = '', $self =null) {
foreach (glob('sockets/*') as $sock) {
if ($self &&strpos($sock, $self) !== false) {
continue;
}
$f = fopen($sock,'a+b') or die('socket not found');
flock($f, LOCK_EX);
fwrite($f, '{action:"'.$action.'", params: {'.$params.'}}'."\r\n");
fclose($f);
}
}
/*
* Эта функция добавляет действие в стек для отправки в текущем запросе.
*/
static function AddToSend($action, $params = '') {
self::$actions[] = '{action:"'.$action.'", params: {'.$params.'}}';
}
/*
* Отправка стека действий на выполнение клиенту.
*/
static function Send() {
if (self::$actions) {
exit('{actions:['.implode(', ', self::$actions).']}');
}
}
/*
* Действие.
* Соединение с сервером.
* Создает сокет и отправляет его идентификатор клиенту.
*/
static function actionConnect() {
$sock = md5(microtime().rand(1, 1000));
fclose(fopen('sockets/'.$sock, 'a+b'));
self::AddToSock('Print', 'message: «Clientconnected.»', $sock);
self::AddToSend('Connect', 'sock:"'.$sock.'"');
}
/*
* Действие.
* Отсоединение от сервера.
* Удаляет сокет.
*/
static function actionDisconnect() {
$sock = $_POST['sock'];
unlink('sockets/'.$sock);
self::AddToSock('Print', 'message: «Client disconnected.»');
self::AddToSend('Disconnect');
}
/*
* Действие.
* Отправляет введенные данные всем клиентам.
*/
static function actionSend() {
$sock = $_POST['sock'];
$data =htmlspecialchars(trim($_POST['data']), ENT_QUOTES);
if (strlen($data)) {
self::AddToSock('Print', 'message: "'.$data.'"', $sock);
self::AddToSend('Print', 'message: "'.$data.'"');
}
}
/*
* Действие.
* Слушает сокет до момента когда в нем появятся данные или же до истечения таймаута.
*/
static function actionRead() {
$sock = $_POST['sock'];
$time = time();
while ((time() — $time) < 30) {
if ($data =file_get_contents('sockets/'.$sock)) {
$f =fopen('sockets/'.$sock, 'r+b') or die('socket not found');
flock($f, LOCK_EX);
ftruncate($f, 0);
fwrite($f, '');
fclose($f);
$data = trim($data, "\r\n");
foreach (explode("\r\n", $data) as $action) {
self::$actions[] = $action;
}
self::Send();
}
usleep(250);
}
}
}
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
header('content-type: text/plain; charset=utf-8');
Server::Run();
}
?>
Для того чтобы увидеть это чудо в действии вам следует открыть приведенную ниже ссылку в 2х или более окнах браузера (или вообще в разных браузерах).
Итак, работающий пример находится тут.
Открываем, распологаем окна так чтобы работая в одном окне видить что происходит в другом. Соединяемся с сервером.
Соединив с сервером первое окно-приложение вы увидите идентификатор своего сокета отправленный с сервера. Соединив второе окно, в первом вы тут же увидите сообщение о соединении клиента. А теперь попробуйте наблюдая за первым окном что-то отправить из второго. Я надеюсь интернет у вас не очень медленный/загруженный, и вы увидите как быстро сообщение отобразится в обоих окнах — почти мгновенно.
Итак, это простой пример. В коде конечно можно еще покопаться. Для старта я думаю хватит.
Исходники полностью работающего примера можно скачать тут.
Хочу немного расказать о сокетах своими словами. Сокет — это что-то вроде радио-приемника, который появляется на сервере после соединения с ним клиента, для обратной связи с клиентским приложением. Как только в этот приемник попадают (записываются) данные, он сразу отправляет их своему приложению-слушателю. Приложение, услышав (прочитав) данные, выполняет какие-либо действия на их основе. Это может быть простое сообщение, которое следует напечатать в чате, или команда которую нужно выполнить итд. Если не углубляться в подробности работы TCP/IP, то примерно так все и происходит.
Ну а теперь давайте вернемся к нашим бара… сокетам. Итак, что нам нужно? Нам нужно сделать так, чтобы как только один соединенный с сервером клиент что-то отправит на сервер, это сразу же было отправлено всем другим соединенным в данный момент с сервером клиентам. Или другая ситуация. Допустим при определенных обстоятельствах послать с сервера сообщение всем соединенным с ним клиентам, и чтобы они его мгновенно (!) получили. Как этого добиться? Я раскажу ниже.
Итак, определим что нам нужно для этого:
- Один php-файл для сервера.
- Один html-файл для клиента.
- Библиотека jQuery.
- И папка sockets для хранения сокетов.
Для начала я приведу листинги клиента и сервера, а потом раскажу как воспользоваться готовым примером.
Клиент (index.html):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"«http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd»>
<html xmlns=«http://www.w3.org/1999/xhtml»>
<head>
<title>Socket AppClient</title>
<meta http-equiv=«content-type»content=«text/html; charset=utf-8» />
<meta name=«author» content=«Melnaron»/>
<link rel=«stylesheet» type=«text/css»href=«css/style.css» />
<style type=«text/css»>
#log {
border: 1px solid #999999;
height: 250px;
overflow: auto;
margin: 10px 0;
padding: 10px;
}
</style>
<script type=«text/javascript»src=«lib/jquery.js»></script>
<script type=«text/javascript»>
$(document).ready(function() {
/*
* Настройка AJAX
*/
$.ajaxSetup({url: 'server.php', type: 'post', dataType: 'json'});
/*
* События кнопок и поля ввода
*/
$('#btnConnect').click(user.Connect);
$('#btnDisconnect').click(user.Disconnect);
$('#btnSend').click(user.Send);
$('#input')
.keydown(function(e){ if (e.keyCode == 13) { user.Send();return false; } })
.keypress(function(e){ if (e.keyCode == 13) { return false; }})
.keyup(function(e){ if (e.keyCode == 13) { return false; } })
;
});
</script>
<script type=«text/javascript»>
/*
* Лог
*/
var log = {
print: function(s) {
$('#log').append('<div>'+s+'</div>').get(0).scrollTop += 100;
}
};
/*
* Действияприсылаемые с сервера
*/
var actions = {
Connect: function(params) {
log.print('Connected.');
log.print('Sock: '+params.sock);
user.sock = params.sock;
user.conn = true;
user.Read();
},
Disconnect: function(params) {
log.print('Disconnected.');
},
Print: function(params) {
log.print(params.message);
}
};
/*
* Пользователь(клиент)
*/
var user = {
sock: null,
conn: false,
busy: false,
read: null,
/*
* Эта функция обрабатывает приходящие с сервера действия и выполняет их.
*/
onSuccess: function(data) {
if (typeof data.actions == 'object') {
for (var i = 0; i <data.actions.length; i++) {
if (typeofactions[data.actions[i].action] == 'function') {
actions[data.actions[i].action](data.actions[i].params);
}
}
}
},
/*
* Эта функция выполняется по завершении ajax-запроса.
*/
onComplete: function(xhr) {
if (xhr.status == 404) {
actions.Disconnect();
}
user.busy = false;
},
/*
* Эта функция выполняется по завершении запроса-слушания.
* При удачном завершении запроса (==200) моментальное возобновлениепрослушивания соккета.
* При неудачном (!=200) возобновление через 5 секунд.
*/
onCompleteRead: function(xhr) {
if (xhr.status == 200) {
user.Read();
} else {
setTimeout(user.Read, 5000);
}
},
/*
* Действие.
* Соединение с сервером.
*/
Connect: function() {
if (user.conn == false && user.busy == false) {
log.print('Connecting...');
user.busy = true;
$.ajax({
data: 'action=Connect',
success:user.onSuccess,
complete:user.onComplete
});
}
},
/*
* Действие.
* Отсоединение от сервера.
*/
Disconnect: function() {
if (user.conn && user.busy == false &&user.read) {
log.print('Disconnecting...');
user.busy = true;
$.ajax({
data:'action=Disconnect&sock='+user.sock,
success:user.onSuccess,
complete:user.onComplete
});
user.sock = null;
user.conn = false;
user.read.abort();
}
},
/*
* Действие.
* Отправка данных на сервер.
*/
Send: function() {
if (user.conn) {
var data = $.trim($('#input').val());
if (!data) {
return;
}
$.ajax({
data:'action=Send&sock='+user.sock+'&data='+data,
success:user.onSuccess,
complete:user.onComplete
});
$('#input').val('');
} else {
log.print('Please connect.');
}
},
/*
* Действие.
* Прослушивание соккета.
*/
Read: function() {
if (user.conn) {
user.read = $.ajax({
data:'action=Read&sock='+user.sock,
success:user.onSuccess,
complete:user.onCompleteRead
});
}
}
};
</script>
</head>
<body>
<input id=«btnConnect» type=«button»value=«Connect» />
<input id=«btnDisconnect» type=«button»value=«Disconnect» />
<div id=«log»></div>
<input id=«input» type=«text» />
<input id=«btnSend» type=«button»value=«Send» />
</body>
</html>
Сервер (server.php):
<?php
class Server {
/*
* Стек действий для отправки клиенту в текущем запросе.
*/
static private $actions;
/*
* Основная функция сервера.
* Запускает на выполнение переданное клиентом действие.
*/
static function Run() {
foreach (glob('sockets/*') as $sock) {
if (filesize($sock)> 2048) {
unlink($sock);
}
}
$action = 'action'.$_POST['action'];
if (is_callable('self::'.$action)) {
self::$action();
self::Send();
}
}
/*
* Эта функция пишет действие в сокеты.
* Если передан параметр $self, то исключает указанный в этом параметре сокет.
*/
static function AddToSock($action, $params = '', $self =null) {
foreach (glob('sockets/*') as $sock) {
if ($self &&strpos($sock, $self) !== false) {
continue;
}
$f = fopen($sock,'a+b') or die('socket not found');
flock($f, LOCK_EX);
fwrite($f, '{action:"'.$action.'", params: {'.$params.'}}'."\r\n");
fclose($f);
}
}
/*
* Эта функция добавляет действие в стек для отправки в текущем запросе.
*/
static function AddToSend($action, $params = '') {
self::$actions[] = '{action:"'.$action.'", params: {'.$params.'}}';
}
/*
* Отправка стека действий на выполнение клиенту.
*/
static function Send() {
if (self::$actions) {
exit('{actions:['.implode(', ', self::$actions).']}');
}
}
/*
* Действие.
* Соединение с сервером.
* Создает сокет и отправляет его идентификатор клиенту.
*/
static function actionConnect() {
$sock = md5(microtime().rand(1, 1000));
fclose(fopen('sockets/'.$sock, 'a+b'));
self::AddToSock('Print', 'message: «Clientconnected.»', $sock);
self::AddToSend('Connect', 'sock:"'.$sock.'"');
}
/*
* Действие.
* Отсоединение от сервера.
* Удаляет сокет.
*/
static function actionDisconnect() {
$sock = $_POST['sock'];
unlink('sockets/'.$sock);
self::AddToSock('Print', 'message: «Client disconnected.»');
self::AddToSend('Disconnect');
}
/*
* Действие.
* Отправляет введенные данные всем клиентам.
*/
static function actionSend() {
$sock = $_POST['sock'];
$data =htmlspecialchars(trim($_POST['data']), ENT_QUOTES);
if (strlen($data)) {
self::AddToSock('Print', 'message: "'.$data.'"', $sock);
self::AddToSend('Print', 'message: "'.$data.'"');
}
}
/*
* Действие.
* Слушает сокет до момента когда в нем появятся данные или же до истечения таймаута.
*/
static function actionRead() {
$sock = $_POST['sock'];
$time = time();
while ((time() — $time) < 30) {
if ($data =file_get_contents('sockets/'.$sock)) {
$f =fopen('sockets/'.$sock, 'r+b') or die('socket not found');
flock($f, LOCK_EX);
ftruncate($f, 0);
fwrite($f, '');
fclose($f);
$data = trim($data, "\r\n");
foreach (explode("\r\n", $data) as $action) {
self::$actions[] = $action;
}
self::Send();
}
usleep(250);
}
}
}
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
header('content-type: text/plain; charset=utf-8');
Server::Run();
}
?>
Для того чтобы увидеть это чудо в действии вам следует открыть приведенную ниже ссылку в 2х или более окнах браузера (или вообще в разных браузерах).
Итак, работающий пример находится тут.
Открываем, распологаем окна так чтобы работая в одном окне видить что происходит в другом. Соединяемся с сервером.
Соединив с сервером первое окно-приложение вы увидите идентификатор своего сокета отправленный с сервера. Соединив второе окно, в первом вы тут же увидите сообщение о соединении клиента. А теперь попробуйте наблюдая за первым окном что-то отправить из второго. Я надеюсь интернет у вас не очень медленный/загруженный, и вы увидите как быстро сообщение отобразится в обоих окнах — почти мгновенно.
Итак, это простой пример. В коде конечно можно еще покопаться. Для старта я думаю хватит.
Исходники полностью работающего примера можно скачать тут.
Подписаться на:
Сообщения (Atom)
Разделы
- БД (3)
- Не по теме (4)
- Полезное (3)
- Практика (8)
- Разработка компонентов (14)
- Хостинг (2)
- Joomla (29)
- Joomla классы (6)
- linux (2)
- PHP (33)