Кто хочет примеров работы. Этот пример был показан на devconf2010. тут асинхронная обработка данных + таймеры, коих совсем нет в документации, но они работают на ура.
<?php
/**
* Пример работы с сокетами с библиотекой libevent
* @author 440hz@php.ru
*/
class MySocket {
/**
* Ошибка чтения буфера
* @var int
*/
const EVBUFFER_READ = 0x01;
/**
* Ошибка буфера записи
* @var int
*/
const EVBUFFER_WRITE = 0x02;
/**
* Ошибка буфера конца файла
* @var int
*/
const EVBUFFER_EOF = 0x10;
/**
* Ошибка буфена
* @var int
*/
const EVBUFFER_ERROR = 0x20;
/**
* Ошибка буфера таймаут
* @var int
*/
const EVBUFFER_TIMEOUT = 0x40;
/**
* Пул в состоянии чтения данных
* @var int
*/
const POOL_READ = 0x01;
/**
* Пул в состоянии записи данных
* @var int
*/
const POOL_WRITE_CLOSE = 0x02;
/**
* Пул в состоянии записи данных и удержания коннекта
* @var int
*/
const POOL_WRITE_KEEP_ALIVE = 0x04;
/**
* Expect continie
* @var int
*/
const POOL_WRITE_EXPECT = 0x08;
/**
* Время в секундах ожидания ответа от клиента до закрытия коннекта
* @var int
*/
var $iTimeOutRead = 30;
/**
* Время в секундах ожидания ответа клиенту до закрытия коннекта
* @var int
*/
var $iTimeOutWrite = 30;
/**
* Общий счетчик кол-ва ошибок чтения
* @var int
*/
var $iErrorRead = 0;
/**
* Счетчик кол-ва ошибок чтения конца файла
* @var int
*/
var $iErrorReadEOF = 0;
/**
* Счетчик кол-ва ошибок чтения
* @var int
*/
var $iErrorReadError = 0;
/**
* Счетчик кол-ва ошибок таймаута
* @var int
*/
var $iErrorReadTimeOut = 0;
/**
* общий счетчик кол-ва ошибок записи
* @var int
*/
var $iErrorWrite = 0;
/**
* Счетчик кол-ва ошибок записи конца файла
* @var int
*/
var $iErrorWriteEOF = 0;
/**
* Счетчик кол-ва ошибок записи
* @var int
*/
var $iErrorWriteError = 0;
/**
* Счетчик кол-ва ошибок записи таймаута
* @var int
*/
var $iErrorWriteTimeOut = 0;
/**
* Длина буфера чтения
* @var int
*/
var $iBufferReadLenght = 4;
/**
* Счетчик коннектов
* @var int
*/
private $iConnection = 0;
/**
* массив коннектов
* @var array
*/
private $aConnections = array ();
var $rBaseEvent = null;
/**
* ЛОГ
* @param string $msg
*/
private function log($msg="\n`") {
print("{$msg}\n");
}
public function Start ( $sAddr = 'tcp://127.0.0.1:666' ) {
/**
* Открываем слушающий сокет
* @var $rSocket resource
*/
$this->rSocket = @stream_socket_server ( $sAddr, $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN );
if ( $this->rSocket === false ) {
die ( "Не могу открыть сокет по адресу [$sAddr].\n{$errno}:{$errstr}" );
} else {
$this->log("Открыт сокет по адресу: {$sAddr}");
}
/**
* сдалем его не блокирующим, что б позволить еще принимать коннекты
* @var $bRC bool
*/
$bRC = stream_set_blocking ( $this->rSocket, 0 );
if ( $bRC === false ) {
die ( "Не сделать сокет неблокирующим" );
} else {
$this->log('Сокет разблокирован');
}
/*
* Создаем базу событий
*/
$this->rBaseEvent = event_base_new ( );
if ( $this->rBaseEvent === false ) {
die ( 'Не создать базу событий' );
} else {
$this->log('Создана база событий');
}
/**
* новое событие для сокета
* @var $event resource
*/
$this->rSocketEvent = event_new ( );
/**
* ловим на чтение и после операции чтения возвращаем событие в базу
* EV_READ - чтение
* EV_PERSIST - вернуть событие в базу после выполнения
*
* @var $bRC bool
*/
$bRc = event_set ( $this->rSocketEvent, $this->rSocket, EV_READ | EV_PERSIST, array (
$this,
'onAcceptEvent'
) );
if ( $bRC === false ) {
die ( "Не установить событие сокета" );
} else {
$this->log('Создано событие сокета');
}
/*
* Кладем в базу событий
*/
$bRc = event_base_set ( $this->rSocketEvent, $this->rBaseEvent );
if ($bRc === false) {
die ( "Не установить событие сокета в базу");
} else {
$this->log('Событие сокета установлено в базу событий');
}
/*
* пускаем...
*/
$bRc = event_add ( $this->rSocketEvent );
if ($bRc === false) {
die ( "Не добавить событие сокета в базу");
} else {
$this->log('Событие сокета добавлено в базу событий');
}
}
/**
* Прием коннекта на сокете
* @param resource $rSocket
* @param resource $rEvent
* @param array $args
*/
function onAcceptEvent ( $rSocket, $rEvent, $args ) {
/**
* Номер нового коннекта
* @var $iConnect int
*/
$iConnect = $this->iConnection ++;
/**
* Примем коннект
* @var resource
*/
$rConnection = @stream_socket_accept ( $this->rSocket );
if ( $rConnection === false ) {
die ( "Ошибка соединения сокета" );
} else {
$this->log('Соединились ['.$iConnect.']');
}
/**
* сдалем его не блокирующим, что б позволить еще принимать коннекты
* @var bool
*/
$bRc = stream_set_blocking ( $rConnection, 0 );
if ( $bRc === false ) {
die ( "Не сделать коннект [{$iConnect}] на сокете не блокирующим" );
} else {
$this->log('Соединение ['.$iConnect.'] разблокировано');
}
/**
* запомним коннект
* @var array
*/
$this->aConnections [ $iConnect ] = $rConnection;
/*
* сформируем пулы обмена
*/
$this->aState [ $iConnect ] = self::POOL_READ;
$this->aReadPool [ $iConnect ] = '';
$this->aWritePool [ $iConnect ] = '';
/*
* создадим буфер обмена данными
*/
$buf = event_buffer_new ( $this->aConnections [ $iConnect ], array (
$this,
'onReadEvent'
), array (
$this,
'onWriteEvent'
), array (
$this,
'onFailureEvent'
), array (
$iConnect
) );
$this->log('Буфер для соединения ['.$iConnect.'] создан');
/* буфер в базовую наблюдалку */
event_buffer_base_set ( $buf, $this->rBaseEvent );
$this->log('Буфер для соединения ['.$iConnect.'] помещен в базу событий');
/* таймауты что б рубить задержки */
self::setTimeoutBuffer ( $buf, $this->iTimeOutRead, $this->iTimeOutWrite );
$this->log('Буферу для соединения ['.$iConnect.'] назначены таймеры ожидания');
/* начальные и концевые терминаторы */
event_buffer_watermark_set ( $buf, EV_READ | EV_WRITE , 0 , 0xffffff );
$this->log('Буферу для соединения ['.$iConnect.'] назначены концевые терминаторы');
/* приоритет буфера */
event_buffer_priority_set( $buf, 10 );
$this->log('Буферу для соединения ['.$iConnect.'] установлен приоритет');
/* включаем буфер на события и возвращаем события назад после выполнения */
event_buffer_enable ( $buf, EV_READ | EV_WRITE | EV_PERSIST );
$this->log('Буфер для соединения ['.$iConnect.'] включен на обработку чтения и записи');
/* сохраним буффер */
$this->aBuffers [$iConnect] = $buf;
}
/**
* Обработка ошибок буферов ввода-вывода
* Например по таймауту отвалить надо или сам отвалился кто
*
* @param $rStream Поток ввода-вывода
* @param $iError Ошибка
* @param $args Дополнительные аргументы
* @return void
*/
function onFailureEvent($rStream, $iError, $args) {
$iConnect = $args [0];
if ($iError & self::EVBUFFER_READ) {
$this->iErrorRead++;
if ($iError & self::EVBUFFER_EOF) {
$this->iErrorReadEOF++;
}
if ($iError & self::EVBUFFER_ERROR) {
$this->iErrorReadError++;
}
if ($iError & self::EVBUFFER_TIMEOUT) {
$this->iErrorReadTimeOut++;
}
}
if ($iError & self::EVBUFFER_WRITE) {
$this->iErrorWrite++;
if ($iError & self::EVBUFFER_EOF) {
$this->iErrorWriteEOF++;
}
if ($iError & self::EVBUFFER_ERROR) {
$this->iErrorWriteError++;
}
if ($iError & self::EVBUFFER_TIMEOUT) {
$this->iErrorWriteTimeOut++;
}
}
/* закроем коннект */
$this->CloseConnection ( $iConnect );
}
/**
* Закрыть коннект
*
* @param $c номер коннекта
* @return void
*/
function CloseConnection($iConnect) {
$this->log('Соединение ['.$iConnect.'] закрыто');
/* отрубим буфер */
event_buffer_disable ( $this->aBuffers [$iConnect], EV_READ | EV_WRITE );
/* освободим. */
event_buffer_free ( $this->aBuffers [$iConnect] );
unset ( $this->aBuffers [$iConnect] );
/* закроем коннект */
fclose ( $this->aConnections [$iConnect] );
/* освободим */
unset ( $this->aConnections [$iConnect] );
unset ( $this->aState [$iConnect] );
unset ( $this->aReadPool [$iConnect] );
}
/**
* Обнулить коннект и ждать опять запрос
*
* @param $c номер коннекта
* @return void
*/
function ZeroConnection($iConnect) {
$this->aState [$iConnect] = self::POOL_READ;
$this->aReadPool [$iConnect] = '';
}
/**
* Выдать данные и продолжить чтение. Для HTTP 101 кода
* @param $iConnect
*/
function ExpectConnection($iConnect) {
$this->aState [$iConnect] = self::POOL_READ;
}
/**
* Событие чтения данных
*
* @param $rStream
* @param $args
* @return void
*/
function onReadEvent($rStream, $args) {
$iConnect = $args [0];
if ($this->aState [$iConnect] !== self::POOL_READ) {
die ( "Состояние буфера[{$iConnect}] не ЧТЕНИЕ!");
$this->CloseConnection ( $iConnect );
return;
}
do {
do {
$tmp = self::readBuffer ( $this->aBuffers [$iConnect] );
/* не все клиент передал. обычно по telnet так. кусками плюет. */
if ($tmp === false) {
break;
}
if (0 == strlen($tmp)) {
return;
}
$this->log('На соединении ['.$iConnect.'] прочитано ['.strlen($tmp).'] байт');
$this->aReadPool [$iConnect] .= $tmp;
if( $this->iBufferReadLenght > strlen($tmp) ) {
break;
}
} while ( true );
} while (!$this->CheckRequest ( $iConnect ) );
}
/**
* Событие записи в буффер
*
* @param $rStream
* @param $args
* @return void
*/
function onWriteEvent($rStream, $args) {
$iConnect = $args [0];
if ($this->aState [$iConnect] === self::POOL_WRITE_KEEP_ALIVE) {
$this->log('На соединении ['.$iConnect.'] была запись с сохранением соединение');
$this->ZeroConnection ( $iConnect );
return;
}
if ($this->aState [$iConnect] === self::POOL_WRITE_CLOSE) {
$this->log('На соединении ['.$iConnect.'] была запись с закрытием соединение');
$this->CloseConnection ( $iConnect );
return;
}
if ($this->aState [$iConnect] === self::POOL_WRITE_EXPECT) {
$this->log('На соединении ['.$iConnect.'] была запись с сохранением соединение');
$this->ExpectConnection ( $iConnect );
return;
}
}
private function CheckRequest($iConnect) {
$buf = $this->aReadPool [$iConnect];
$this->log('На соединении ['.$iConnect.'] анализ полученных данных');
if( 0 < ($pos = strpos($buf,"\r\n")) ) {
$buf = substr($buf,0,$pos);
$this->aState [$iConnect] = self::POOL_WRITE_CLOSE;
self::writeBuffer ( $this->aBuffers [$iConnect], 'Hello, '.$buf."\n" );
return true;
} else {
return false;
}
}
/**
* Обвязка над функцией event_buffer_write
*
* @param $hBuffer
* @param $sData
* @param $iDataSize
* @return boolean
*/
protected static function writeBuffer ($hBuffer, $sData, $iDataSize=-1) {
if ($iDataSize > 0) {
$return = event_buffer_write($hBuffer, $sData, $iDataSize);
} else {
$return = event_buffer_write($hBuffer, $sData);
}
return $return;
}
/**
* Обвязка над функцией event_buffer_read
*
* @param $hBuffer Буфер
* @return string Вернет string или false
*/
protected function readBuffer($hBuffer) {
return event_buffer_read ( $hBuffer, $this->iBufferReadLenght );
}
/**
* Обвязка над функуцией event_buffer_timeout_set
*
* @param $hBuffer Буфер
* @param $iReadTimeout Таймаут в секундах на чтение
* @param $iWriteTimeout таймаут в секундах на запись
* @return boolean
*/
protected static function setTimeoutBuffer($hBuffer, $iReadTimeout, $iWriteTimeout) {
return event_buffer_timeout_set($hBuffer, $iReadTimeout, $iWriteTimeout);
}
/**
* запуск основного цикла обработки событий
* @return void
*/
function loop() {
$bRc = event_base_loop ( $this->rBaseEvent );
switch($bRc) {
case -1:
die ( 'Не могу создать очередь');
break;
case 1:
die ( "нет событий в базе");
break;
}
}
/* ТАЙМЕРЫ */
function addTimer($iTimer,$sTimerName,$aCallBack,$iInterval) {
$this->log ( "Создание таймера [{$sTimerName}]");
if( !is_callable($aCallBack,true,$sCallBack) OR !method_exists($aCallBack[0],$aCallBack[1])) {
die ( "Не определен CallBack [{$sCallBack}] для таймера [{$sTimerName}]");
} else {
$this->log ( "Установлен таймер [{$iTimer}/{$sTimerName}] [{$sCallBack}] с интервалом [".$iInterval."] сек." );
}
$this->aTimers[$iTimer] = array();
$this->aTimers[$iTimer]['name'] = $sTimerName;
$this->aTimers[$iTimer]['interval'] = $iInterval;
$this->aTimers[$iTimer]['callback'] = $aCallBack;
$hTmpFile = tmpfile();
if($hTmpFile === false) {
die ( "Не возможно создать временный файл для таймера");
} else {
$this->log('Создан временный файл для таймера');
}
$this->aTimers[$iTimer]['tmpfile'] = $hTmpFile;
$event = event_new();
$this->aTimers[$iTimer]['timer'] = $event;
$bRc = event_set(
$this->aTimers[$iTimer]['timer'],
$this->aTimers[$iTimer]['tmpfile'] ,
0,
array( $this , 'onTimer'),
array ( $iTimer ));
if ($bRc === false) {
die ( 'Не установить событие таймера ['.$iTimer.']' );
} else {
$this->log('Событие таймера ['.$iTimer.'] установлено');
}
$bRc = event_base_set(
$this->aTimers[$iTimer]['timer'],
$this->rBaseEvent );
if ($bRc === false) {
die ( "Не установить таймер в базу");
} else {
$this->log('Событие таймера ['.$iTimer.'] установлено в базу');
}
$this->setTimer($iTimer);
}
function setTimer($iTimer) {
if(!isset($this->aTimers[$iTimer])) return;
$bRc = event_add(
$this->aTimers[$iTimer]['timer'],
$this->aTimers[$iTimer]['interval'] * 1000000 );
if ($bRc === false) {
die ( "Не добавить таймер [{$iTimer}] в базу");
} else {
$this->log('Таймер добавлен ['.$iTimer.'] в базу');
}
}
function delTimer($iTimer,$aTimer){
$this->log ( "Таймер [".$iTimer."] [".$aTimer['name']."] удален");
event_free($aTimer['timer']);
}
/**
* Обработчик таймеров
* @param $rSocket
* @param $rEvent
* @param $a
*/
function onTimer( $rSocket, $rEvent, $a ) {
$iTimer = $a[0];
call_user_func(
$this->aTimers[$iTimer]['callback'],
$this,
$iTimer
);
$this->setTimer($iTimer);
}
static $iCount;
public static function onMyTimer($handler,$iTimer) {
print("Демон работает: ".(time() - MySocket::$iCount)." сек\n");
}
}
$MySocket = new MySocket();
$MySocket->Start();
MySocket::$iCount = time();
$MySocket->addTimer(0,'MYTIMER',array($MySocket,'onMyTimer'),10);
$MySocket->loop();
Основная проблема в том, что для написания программ в асинхронном стиле надо мыслить по другому.
Люди с трудом усваивают, что нельзя сделать var foo = ajax('example.com');, а уж целое асинхроноое приложение — это просто ужас.
Еще многие люди считают, что раз это (например) ДжаваСкрипт и несинхронно, то ничего кроме процедурщины уже не подходит. Немножко иначе выглядя все те же принципы остаются.
Бороться с вложеностью можно стандартными для синхронного программирования способами, например ООП. Если нам надо получить отдельным запросом топик и отдельным — комменты, то можно очень красиво сделать это так:
// обработка у нас будет выглядеть как-то так:
new Topic(id).get(function (data) {
render(data.article, data.comments);
});
// Сам класс прост:
var Topic = function (conn, id) {
this.conn = conn;
this.id = id;
};
Topic.prototype = {
// Получены и статья и комменты:
get : function (fn) {
var data = {};
var set = function (key, value) {
data[key] = value;
if ('article' in data && 'comments' in data) fn(data);
};
this.getArticle(function (article) {
set('article', article);
});
this.getComments(function (comments) {
set('comments', comments);
});
},
// получение статьи из БД:
getArticle(fn) {
this.conn.query(qArticles, function (err, res) {
if (err) throw err;
res.fetchAll(function (err, rows) {
if (err) throw err;
fn(rows.length ? rows[0] || null);
});
});
},
// Получение комментов из БД:
getComments(fn) {
this.conn.query(qComments, function (err, res) {
if (err) throw err;
res.fetchAll(function (err, rows) {
if (err) throw err;
fn(rows);
});
});
},
};
Методы разбиты на небольшие куски, красиво сгруппированы, вложенность — минимальная. Код читается не лучше, чем аналогичный синхронный.
Создание перекрашиваемой иконки для веба без векторного исходника:
По сути с небольшими доработками все это можно запихать в один экшн фотошопа который делал бы это автоматически.
Берем иконку (эта первая попавшаяся из того что было под рукой):
Чтобы получить базовый цвет — переводим из RGB в Lab. Канал Light заливаем 50% серым.
Как видно, иконка состоит из 2х цветов — основной голубой вверху и фиолетовый внизу. Так как голубого больше — берем пипеткой этот цвет и его используем как базовый.
Делаем наложение базового цвета на нашу иконку с режимами darken и lighten (слева и справа соответственно).
Переводим в ЧБ — desaturate
Автоуровни:
Инвертируем результат darken
По полученым яркостям делам маски прозрачности (darken для черного и для lighten белого)
белую маску не видно на светлом фоне.
Подкладываем наш базовый цвет и видим что все получилось.
Теперь совмещаем полученые результаты. Сначала кладем черный слой, потом белый.
Вот мы и получили клон нашей иконки.
И теперь мы можем подкладывать абсолютно любой цвет.
Для выучивания большого количества слов — главное, на мой взгляд:
1) регулярность упражнений,
2) спокойная подходящая обстановка,
3) чтобы не надоело.
Идеально отвечают этим критериям… ТУАЛЕТ.
Я отобрал и распечатал на бумаге незнакомые слова и разместил этот листок и ручку (чтобы вычёркивать выученные лова), извиняюсь, в держателе для освежителя.
Таким образам я _ежедневно_ изучаю слова уже больше месяца и при этом:
1) регулярность этого процесса гарантирована
2) обстановка тихая и спокойная,
3.1) это занимает мою скуку (а кто-то читает для этого газеты...)
3.2) мне это не надоедает, т. к., чтобы пробежать глазами очередную четвертинку листа (около 30 слов) — надо всего-то около минуты-двух.
Отсюда имеется очень хороший эффект — очередные 30 слов _сами_ запоминаются в _обе стороны_ максимум на 5-й день (в месяц «на халяву» получается, около 200 слов).
В очереди ждут 800 незнакомых слов из списка «Наиболее употребительные английские слова.» (2500 слов), взятого отсюда: www.alleng.ru/texts/mybook/TOP2500.htm
ага
Люди с трудом усваивают, что нельзя сделать
var foo = ajax('example.com');
, а уж целое асинхроноое приложение — это просто ужас.Еще многие люди считают, что раз это (например) ДжаваСкрипт и несинхронно, то ничего кроме процедурщины уже не подходит. Немножко иначе выглядя все те же принципы остаются.
Бороться с вложеностью можно стандартными для синхронного программирования способами, например ООП. Если нам надо получить отдельным запросом топик и отдельным — комменты, то можно очень красиво сделать это так:
Методы разбиты на небольшие куски, красиво сгруппированы, вложенность — минимальная. Код читается не лучше, чем аналогичный синхронный.
www.agence7seven.com/ (прямотаки на главной лежит, не забудьте возраст ввести)
тыц
тыц
тыц
Сча буду смотреть.
Кому тоже интересно, смотрим
rutube.ru/tracks/17662.html?v=c6c1211fd048452c141ff20385f4b4aa
Работает и в PHP4.
По сути с небольшими доработками все это можно запихать в один экшн фотошопа который делал бы это автоматически.
Берем иконку (эта первая попавшаяся из того что было под рукой):
Чтобы получить базовый цвет — переводим из RGB в Lab. Канал Light заливаем 50% серым.
Как видно, иконка состоит из 2х цветов — основной голубой вверху и фиолетовый внизу. Так как голубого больше — берем пипеткой этот цвет и его используем как базовый.
Делаем наложение базового цвета на нашу иконку с режимами darken и lighten (слева и справа соответственно).
Переводим в ЧБ — desaturate
Автоуровни:
Инвертируем результат darken
По полученым яркостям делам маски прозрачности (darken для черного и для lighten белого)
белую маску не видно на светлом фоне.
Подкладываем наш базовый цвет и видим что все получилось.
Теперь совмещаем полученые результаты. Сначала кладем черный слой, потом белый.
Вот мы и получили клон нашей иконки.
И теперь мы можем подкладывать абсолютно любой цвет.
Спасибо за внимание.
Для выучивания большого количества слов — главное, на мой взгляд:
1) регулярность упражнений,
2) спокойная подходящая обстановка,
3) чтобы не надоело.
Идеально отвечают этим критериям… ТУАЛЕТ.
Я отобрал и распечатал на бумаге незнакомые слова и разместил этот листок и ручку (чтобы вычёркивать выученные лова), извиняюсь, в держателе для освежителя.
Таким образам я _ежедневно_ изучаю слова уже больше месяца и при этом:
1) регулярность этого процесса гарантирована
2) обстановка тихая и спокойная,
3.1) это занимает мою скуку (а кто-то читает для этого газеты...)
3.2) мне это не надоедает, т. к., чтобы пробежать глазами очередную четвертинку листа (около 30 слов) — надо всего-то около минуты-двух.
Отсюда имеется очень хороший эффект — очередные 30 слов _сами_ запоминаются в _обе стороны_ максимум на 5-й день (в месяц «на халяву» получается, около 200 слов).
В очереди ждут 800 незнакомых слов из списка «Наиболее употребительные английские слова.» (2500 слов), взятого отсюда:
www.alleng.ru/texts/mybook/TOP2500.htm
Помимо этого, конечно, читаю и слушаю тексты.