Примеры

Пример #1 Простой клиент HTTP

<?php
// Функция обратного вызова обработки чтения
function readcb($bev$base) {
    
//$input = $bev->input; //$bev->getInput();

    //$pos = $input->search("TTP");
    
$pos $bev->input->search("TTP");

    while ((
$n $bev->input->remove($buf1024)) > 0) {
        echo 
$buf;
    }
}

// Функция обратного вызова обработки события
function eventcb($bev$events$base) {
    if (
$events EventBufferEvent::CONNECTED) {
        echo 
"Соединение установлено.\n";
    } elseif (
$events & (EventBufferEvent::ERROR EventBufferEvent::EOF)) {
        if (
$events EventBufferEvent::ERROR) {
            echo 
"Ошибка DNS: "$bev->getDnsErrorString(), PHP_EOL;
        }

        echo 
"Закрываем соединение\n";
        
$base->exit();
        exit(
"Готово\n");
    }
}

if (
$argc != 3) {
    echo <<<EOS
Trivial HTTP 0.x client
Syntax: php 
{$argv[0]} [hostname] [resource]
Example: php 
{$argv[0]} www.google.com /

EOS;
    exit();
}

$base = new EventBase();

$dns_base = new EventDnsBase($baseTRUE); // Используем асинхронный запрос к DNS
if (!$dns_base) {
    exit(
"Failed to init DNS Base\n");
}

$bev = new EventBufferEvent($base/* используем внутренний сокет */ NULL,
    
EventBufferEvent::OPT_CLOSE_ON_FREE EventBufferEvent::OPT_DEFER_CALLBACKS,
    
"readcb"/* writecb */ NULL"eventcb"
);
if (!
$bev) {
    exit(
"Ошибка создания сокета bufferevent\n");
}

//$bev->setCallbacks("readcb", /* writecb */ NULL, "eventcb", $base);
$bev->enable(Event::READ Event::WRITE);

$output $bev->output//$bev->getOutput();
if (!$output->add(
    
"GET {$argv[2]} HTTP/1.0\r\n".
    
"Host: {$argv[1]}\r\n".
    
"Connection: Close\r\n\r\n"
)) {
    exit(
"Ошибка добавления запроса в буфер вывода\n");
}

if (!
$bev->connectHost($dns_base$argv[1], 80EventUtil::AF_UNSPEC)) {
    exit(
"Невозможно установить соединение с {$argv[1]}\n");
}

$base->dispatch();
?>

Результатом выполнения данного примера будет что-то подобное:

Соединение установлено.
HTTP/1.1 301 Moved Permanently
Date: Fri, 01 Mar 2013 18:47:48 GMT
Location: http://www.google.co.uk/
Content-Type: text/html; charset=UTF-8
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 221
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Age: 133438
Expires: Sat, 30 Mar 2013 05:39:28 GMT
Connection: close

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.co.uk/">here</A>.
</BODY></HTML>
Закрываем соединение
Готово

Пример #2 HTTP клиент с асинхронным запросом к DNS

<?php
/*
 * 1. Соединяемся с 127.0.0.1 на порту 80
 * посредством EventBufferEvent::connect().
 *
 * 2. Запрашиваем /index.cphp по протоколу HTTP/1.0
 * используя буфер вывода.
 *
 * 3. Асинхорнно читаем ответ и выводим его в stdout.
 */

// Функция обратного вызова обработки чтения
function readcb($bev$base) {
    
$input $bev->getInput();

    while ((
$n $input->remove($buf1024)) > 0) {
        echo 
$buf;
    }
}

// Функция обратного вызова обработки события
function eventcb($bev$events$base) {
    if (
$events EventBufferEvent::CONNECTED) {
        echo 
"Соединение установлено.\n";
    } elseif (
$events & (EventBufferEvent::ERROR EventBufferEvent::EOF)) {
        if (
$events EventBufferEvent::ERROR) {
            echo 
"Ошибка DNS: "$bev->getDnsErrorString(), PHP_EOL;
        }

        echo 
"Закрываем соединение\n";
        
$base->exit();
        exit(
"Готово\n");
    }
}

$base = new EventBase();

echo 
"step 1\n";
$bev = new EventBufferEvent($base/* используем внутренний сокет*/ NULL,
    
EventBufferEvent::OPT_CLOSE_ON_FREE EventBufferEvent::OPT_DEFER_CALLBACKS);
if (!
$bev) {
    exit(
"Ошибка создания сокета bufferevent\n");
}

echo 
"step 2\n";
$bev->setCallbacks("readcb"/* writecb */ NULL"eventcb"$base);
$bev->enable(Event::READ Event::WRITE);

echo 
"step 3\n";
// Посылаем запрос
$output $bev->getOutput();
if (!
$output->add(
    
"GET /index.cphp HTTP/1.0\r\n".
    
"Connection: Close\r\n\r\n"
)) {
    exit(
"Ошибка добавления запроса в буфер вывода\n");
}

/* Синхронно соединяемся с хостом.
Мы знаем IP и не нуждаемся в запросе к DNS. */
if (!$bev->connect("127.0.0.1:80")) {
    exit(
"Не удалось установить соединение\n");
}

// Обрабатываем ожидающие события
$base->dispatch();
?>

Пример #3 Эхо-сервер

<?php
/*
 * Простой эхо-сервер на базе слушателя соединений libevent
 *
 * Использование:
 * 1) В первом терминальном окне запускаем:
 *
 * $ php listener.php 9881
 *
 * 2) Во втором терминальном окне открываем соединение:
 *
 * $ nc 127.0.0.1 9881
 *
 * 3) Начинаем печатать. Сервер должен повторять наш ввод.
 */

class MyListenerConnection {
    private 
$bev$base;

    public function 
__destruct() {
        
$this->bev->free();
    }

    public function 
__construct($base$fd) {
        
$this->base $base;

        
$this->bev = new EventBufferEvent($base$fdEventBufferEvent::OPT_CLOSE_ON_FREE);

        
$this->bev->setCallbacks(array($this"echoReadCallback"), NULL,
            array(
$this"echoEventCallback"), NULL);

        if (!
$this->bev->enable(Event::READ)) {
            echo 
"Не удалось разрешить чтение (READ)\n";
            return;
        }
    }

    public function 
echoReadCallback($bev$ctx) {
        
// Копируем все данные из буфера ввода в буфер вывода

        // Вариант #1
        
$bev->output->addBuffer($bev->input);

        
/* Вариант #2 */
        /*
        $input    = $bev->getInput();
        $output = $bev->getOutput();
        $output->addBuffer($input);
        */
    
}

    public function 
echoEventCallback($bev$events$ctx) {
        if (
$events EventBufferEvent::ERROR) {
            echo 
"Ошибка bufferevent\n";
        }

        if (
$events & (EventBufferEvent::EOF EventBufferEvent::ERROR)) {
            
//$bev->free();
            
$this->__destruct();
        }
    }
}

class 
MyListener {
    public 
$base,
        
$listener,
        
$socket;
    private 
$conn = array();

    public function 
__destruct() {
        foreach (
$this->conn as &$c$c NULL;
    }

    public function 
__construct($port) {
        
$this->base = new EventBase();
        if (!
$this->base) {
            echo 
"Не удаётся создать EventBase";
            exit(
1);
        }

        
// Вариант #1
        /*
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (!socket_bind($this->socket, '0.0.0.0', $port)) {
            echo "Невозможно назначить сокет\n";
            exit(1);
        }
        $this->listener = new EventListener($this->base,
            array($this, "acceptConnCallback"), $this->base,
            EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE,
            -1, $this->socket);
         */

        // Вариант #2
         
$this->listener = new EventListener($this->base,
             array(
$this"acceptConnCallback"), $this->base,
             
EventListener::OPT_CLOSE_ON_FREE EventListener::OPT_REUSEABLE, -1,
             
"0.0.0.0:$port");

        if (!
$this->listener) {
            echo 
"Невозможно создать слушателя";
            exit(
1);
        }

        
$this->listener->setErrorCallback(array($this"accept_error_cb"));
    }

    public function 
dispatch() {
        
$this->base->dispatch();
    }

    
// Эта функция обратного вызова будет вызвана, если в $bev есть данные для чтения
    
public function acceptConnCallback($listener$fd$address$ctx) {
        
// У нас новое соединение! Настроим bufferevent для него. */
        
$base $this->base;
        
$this->conn[] = new MyListenerConnection($base$fd);
    }

    public function 
accept_error_cb($listener$ctx) {
        
$base $this->base;

        
fprintf(STDERR"Ошибка слушателя: %d (%s). "
            
."Аварийная остановка.\n",
            
EventUtil::getLastSocketErrno(),
            
EventUtil::getLastSocketError());

        
$base->exit(NULL);
    }
}

$port 9808;

if (
$argc 1) {
    
$port = (int) $argv[1];
}
if (
$port <= || $port 65535) {
    exit(
"Некорректный порт");
}

$l = new MyListener($port);
$l->dispatch();
?>

Пример #4 SSL эхо-сервер

<?php
/*
 * SSL эхо-сервер
 *
 * Для тестирования:
 * 1) Запустите:
 * $ php examples/ssl-echo-server/server.php 9998
 *
 * 2) В другом окне:
 * $ socat - SSL:127.0.0.1:9998,verify=1,cafile=examples/ssl-echo-server/cert.pem
 */

class MySslEchoServer {
    public 
$port,
        
$base,
        
$bev,
        
$listener,
        
$ctx;

    function 
__construct ($port$host "127.0.0.1") {
        
$this->port $port;
        
$this->ctx $this->init_ssl();
        if (!
$this->ctx) {
            exit(
"Невозможно создать контекст SSL\n");
        }

        
$this->base = new EventBase();
        if (!
$this->base) {
            exit(
"Невозможно создать EventBase\n");
        }

        
$this->listener = new EventListener($this->base,
            array(
$this"ssl_accept_cb"), $this->ctx,
            
EventListener::OPT_CLOSE_ON_FREE EventListener::OPT_REUSEABLE,
            -
1"$host:$port");
        if (!
$this->listener) {
            exit(
"невозможно создать слушателя\n");
        }

        
$this->listener->setErrorCallback(array($this"accept_error_cb"));
    }
    function 
dispatch() {
        
$this->base->dispatch();
    }

    
// Эта функция обратного вызова будет вызвана, если в $bev есть данные для чтения
    
function ssl_read_cb($bev$ctx) {
        
$in $bev->input//$bev->getInput();

        
printf("Received %zu bytes\n"$in->length);
        
printf("----- data ----\n");
        
printf("%ld:\t%s\n", (int) $in->length$in->pullup(-1));

        
$bev->writeBuffer($in);
    }

    
// Эта функция обратного вызова будет вызвана, если на слушателя придёт событие,
    // например если закроется соединение или произойдёт ошибка
    
function ssl_event_cb($bev$events$ctx) {
        if (
$events EventBufferEvent::ERROR) {
            
// Извлекаем ошибку из стека ошибок SSL
            
while ($err $bev->sslError()) {
                
fprintf(STDERR"Ошибка bufferevent: %s.\n"$err);
            }
        }

        if (
$events & (EventBufferEvent::EOF EventBufferEvent::ERROR)) {
            
$bev->free();
        }
    }

    
// Эта функция обратного вызова будет вызвана, когда клиент примет новое соединение
    
function ssl_accept_cb($listener$fd$address$ctx) {
        
// У нас новое соединение! Настроим bufferevent для него.
        
$this->bev EventBufferEvent::sslSocket($this->base$fd$this->ctx,
            
EventBufferEvent::SSL_ACCEPTINGEventBufferEvent::OPT_CLOSE_ON_FREE);

        if (!
$this->bev) {
            echo 
"Failed creating ssl buffer\n";
            
$this->base->exit(NULL);
            exit(
1);
        }

        
$this->bev->enable(Event::READ);
        
$this->bev->setCallbacks(array($this"ssl_read_cb"), NULL,
            array(
$this"ssl_event_cb"), NULL);
    }

    
// Эта функция обратного вызова будет вызвана, если не удастся создать новое соединение
    
function accept_error_cb($listener$ctx) {
        
fprintf(STDERR"Ошибка слушателя: %d (%s). "
            
."Shutting down.\n",
            
EventUtil::getLastSocketErrno(),
            
EventUtil::getLastSocketError());

        
$this->base->exit(NULL);
    }

    
// Инициализируем структуры SSL, создаём EventSslContext
    // Опционально создаём самоподписанный сертификат
    
function init_ssl() {
        
// Нам *необходима* энтропия. Иначе в криптографии нет смысла.
        
if (!EventUtil::sslRandPoll()) {
            exit(
"EventUtil::sslRandPoll failed\n");
        }

        
$local_cert __DIR__."/cert.pem";
        
$local_pk   __DIR__."/privkey.pem";

        if (!
file_exists($local_cert) || !file_exists($local_pk)) {
            echo 
"Невозможно прочитать $local_cert или $local_pk file.  Для генерации ключа\n",
                
"и самоподписанного сертификата, запустите:\n",
                
"  openssl genrsa -out $local_pk 2048\n",
                
"  openssl req -new -key $local_pk -out cert.req\n",
                
"  openssl x509 -req -days 365 -in cert.req -signkey $local_pk -out $local_cert\n";

            return 
FALSE;
        }

        
$ctx = new EventSslContext(EventSslContext::SSLv3_SERVER_METHOD, array (
             
EventSslContext::OPT_LOCAL_CERT  => $local_cert,
             
EventSslContext::OPT_LOCAL_PK    => $local_pk,
             
//EventSslContext::OPT_PASSPHRASE  => "echo server",
             
EventSslContext::OPT_VERIFY_PEER => true,
             
EventSslContext::OPT_ALLOW_SELF_SIGNED => false,
        ));

        return 
$ctx;
    }
}

// Разрешаем переопределение порта
$port 9999;
if (
$argc 1) {
    
$port = (int) $argv[1];
}
if (
$port <= || $port 65535) {
    exit(
"Некорректный порт\n");
}


$l = new MySslEchoServer($port);
$l->dispatch();
?>

Пример #5 Обработчик сигналов

<?php
/*
Запустите в терминальном окне:

$ php examples/signal.php

В другом терминальном окне найдите pid этого процесса и пошлите ему сигнал SIGTERM:

$ ps aux | grep examp
ruslan    3976  0.2  0.0 139896 11256 pts/1    S+   10:25   0:00 php examples/signal.php
ruslan    3978  0.0  0.0   9572   864 pts/2    S+   10:26   0:00 grep --color=auto examp
$ kill -TERM 3976

В первом окне вы должны увидить следующее::

Пойман сигнал 15
*/

class MyEventSignal {
    private 
$base;

    function 
__construct($base) {
        
$this->base $base;
    }

    function 
eventSighandler($no$c) {
        echo 
"Пойман сигнал $no\n";
        
event_base_loopexit($c->base);
    }
}

$base event_base_new();
$c    = new MyEventSignal($base);
$no   SIGTERM;
$ev   evsignal_new($base$no, array($c,'eventSighandler'), $c);

evsignal_add($ev);

event_base_loop($base);
?>

Пример #6 Использование цикла libevent для обработки запросов расширения `eio'

<?php
// Функция обратного вызова для eio_nop()
function my_nop_cb($d$r) {
    echo 
"step 6\n";
}

$dir "/tmp/abc-eio-temp";
if (
file_exists($dir)) {
    
rmdir($dir);
}

echo 
"step 1\n";

$base = new EventBase();

echo 
"step 2\n";

eio_init();

eio_mkdir($dir0750EIO_PRI_DEFAULT"my_nop_cb");

$event = new Event($baseeio_get_event_stream(),
    
Event::READ Event::PERSIST, function ($fd$events$base) {
    echo 
"step 5\n";

    while (
eio_nreqs()) {
        
eio_poll();
    }

    
$base->stop();
}, 
$base);

echo 
"step 3\n";

$event->add();

echo 
"step 4\n";

$base->dispatch();

echo 
"Готово\n";
?>

Пример #7 Разное

<?php
/* {{{ Конфигурация и поддерживаемые методы */
echo "Поддерживаемые методы:\n";
foreach (
Event::getSupportedMethods() as $m) {
    echo 
$mPHP_EOL;
}

// Избегаем метода "select"
$cfg = new EventConfig();
if (
$cfg->avoidMethod("select")) {
    echo 
"Метод `select' исключён\n";
}

// Создаём event_base связанный с конфигурацией
$base = new EventBase($cfg);
echo 
"Используется событийный метод: "$base->getMethod(), PHP_EOL;

echo 
"Способы:\n";
$features $base->getFeatures();
(
$features EventConfig::FEATURE_ET) and print("ET - одноразовое срабатывание при пересечении порога (edge-triggered IO)\n");
(
$features EventConfig::FEATURE_O1) and print("O1 - операции добавления/удаления событий со сложностью O(1)\n");
(
$features EventConfig::FEATURE_FDS) and print("FDS - обычные дескрипторы файлов, а не только сокеты\n");

// Запрашиваем способ FDS
if ($cfg->requireFeatures(EventConfig::FEATURE_FDS)) {
    echo 
"Запрошен способ FDS\n";

    
$base = new EventBase($cfg);
    (
$base->getFeatures() & EventConfig::FEATURE_FDS)
        and print(
"FDS - обычные дескрипторы файлов, а не только сокеты\n");
}
/* }}} */

/* {{{ Base */
$base = new EventBase();
$event = new Event($baseSTDINEvent::READ Event::PERSIST, function ($fd$events$arg) {
    static 
$max_iterations 0;

    if (++
$max_iterations >= 5) {
        
/* выход после 5 итераций с паузами в 2.33 секунды */
        
echo "Останавливаемся...\n";
        
$arg[0]->exit(2.33);
    }

    echo 
fgets($fd);
}, array (&
$base));

$event->add();
$base->loop();
/* Base }}} */
?>

Пример #8 Простой HTTP-сервер

<?php
/*
 * Простой HTTP-сервер.
 *
 * Для проверки:
 * 1) Запускаете на любом порту, например:
 * $ php examples/http.php 8010
 * 2) В другом терминальном окне устанавливаете к нему соединение
 * и делаете GET или POST запрос(другие запросы не реализованы):
 * $ nc -t 127.0.0.1 8010
 * POST /about HTTP/1.0
 * Content-Type: text/plain
 * Content-Length: 4
 * Connection: close
 * (нажимаете Enter)
 *
 * Должно вывести:
 * a=12
 * HTTP/1.0 200 OK
 * Content-Type: text/html; charset=ISO-8859-1
 * Connection: close
 *
 * $ nc -t 127.0.0.1 8010
 * GET /dump HTTP/1.0
 * Content-Type: text/plain
 * Content-Encoding: UTF-8
 * Connection: close
 * (нажимаете Enter)
 *
 * IДолжно вывести:
 * HTTP/1.0 200 OK
 * Content-Type: text/html; charset=ISO-8859-1
 * Connection: close
 * (нажимаете Enter)
 *
 * $ nc -t 127.0.0.1 8010
 * GET /unknown HTTP/1.0
 * Connection: close
 *
 * Должно вывести:
 * HTTP/1.0 200 OK
 * Content-Type: text/html; charset=ISO-8859-1
 * Connection: close
 *
 * 3) Смотрите, что выводится в окне, в котором вы запустили сервер.
 */

function _http_dump($req$data) {
    static 
$counter      0;
    static 
$max_requests 2;

    if (++
$counter >= $max_requests)  {
        echo 
"Счётчик запросов достиг максимума $max_requests. Выходим\n";
        exit();
    }

    echo 
__METHOD__" метод\n";
    echo 
"запрос:"var_dump($req);
    echo 
"данные:"var_dump($data);

    echo 
"\n===== DUMP =====\n";
    echo 
"Команда:"$req->getCommand(), PHP_EOL;
    echo 
"URI:"$req->getUri(), PHP_EOL;
    echo 
"Входящие заголовки:"var_dump($req->getInputHeaders());
    echo 
"Исходящие заголовки:"var_dump($req->getOutputHeaders());

    echo 
"\n >> Посылаем ответ ...";
    
$req->sendReply(200"OK");
    echo 
"OK\n";

    echo 
"\n >> Читаем входной буфер ...\n";
    
$buf $req->getInputBuffer();
    while (
$s $buf->readLine(EventBuffer::EOL_ANY)) {
        echo 
$sPHP_EOL;
    }
    echo 
"В буфере больше нет данных\n";
}

function 
_http_about($req) {
    echo 
__METHOD__PHP_EOL;
    echo 
"URI: "$req->getUri(), PHP_EOL;
    echo 
"\n >> Посылаем ответ ...";
    
$req->sendReply(200"OK");
    echo 
"OK\n";
}

function 
_http_default($req$data) {
    echo 
__METHOD__PHP_EOL;
    echo 
"URI: "$req->getUri(), PHP_EOL;
    echo 
"\n >> Посылаем ответ ...";
    
$req->sendReply(200"OK");
    echo 
"OK\n";
}

$port 8010;
if (
$argc 1) {
    
$port = (int) $argv[1];
}
if (
$port <= || $port 65535) {
    exit(
"Некорректный порт");
}

$base = new EventBase();
$http = new EventHttp($base);
$http->setAllowedMethods(EventHttpRequest::CMD_GET EventHttpRequest::CMD_POST);

$http->setCallback("/dump""_http_dump", array(48));
$http->setCallback("/about""_http_about");
$http->setDefaultCallback("_http_default""custom data value");

$http->bind("0.0.0.0"8010);
$base->loop();
?>

Результатом выполнения данного примера будет что-то подобное:

a=12
HTTP/1.0 200 OK
Content-Type: text/html; charset=ISO-8859-1
Connection: close

HTTP/1.0 200 OK
Content-Type: text/html; charset=ISO-8859-1
Connection: close
(жмём Enter)

HTTP/1.0 200 OK
Content-Type: text/html; charset=ISO-8859-1
Connection: close

Пример #9 Простой HTTPS-сервер

<?php
/*
 * Простой HTTPS-сервер.
 *
 * 1) Запустите сервер: `php examples/https.php 9999`
 * 2) Протестируйте его: `php examples/ssl-connection.php 9999`
 */

function _http_dump($req$data) {
    static 
$counter      0;
    static 
$max_requests 200;

    if (++
$counter >= $max_requests)  {
        echo 
"Счётчик запросов достиг максимума $max_requests. Выходим\n";
        exit();
    }

    echo 
__METHOD__" called\n";
    echo 
"запрос:"var_dump($req);
    echo 
"данные:"var_dump($data);

    echo 
"\n===== DUMP =====\n";
    echo 
"Команда:"$req->getCommand(), PHP_EOL;
    echo 
"URI:"$req->getUri(), PHP_EOL;
    echo 
"Входящие заголовки:"var_dump($req->getInputHeaders());
    echo 
"Исходящие заголовки:"var_dump($req->getOutputHeaders());

    echo 
"\n >> Отправляем ответ ...";
    
$req->sendReply(200"OK");
    echo 
"OK\n";

    
$buf $req->getInputBuffer();
    echo 
"\n >> Читаем входящий буфер ("$buf->length") ...\n";
    while (
$s $buf->read(1024)) {
        echo 
$s;
    }
    echo 
"\nВ буфере больше нет данных\n";
}

function 
_http_about($req) {
    echo 
__METHOD__PHP_EOL;
    echo 
"URI: "$req->getUri(), PHP_EOL;
    echo 
"\n >> Отправляем ответ ...";
    
$req->sendReply(200"OK");
    echo 
"OK\n";
}

function 
_http_default($req$data) {
    echo 
__METHOD__PHP_EOL;
    echo 
"URI: "$req->getUri(), PHP_EOL;
    echo 
"\n >> Отправляем ответ ...";
    
$req->sendReply(200"OK");
    echo 
"OK\n";
}

function 
_http_400($req) {
    
$req->sendError(400);
}

function 
_init_ssl() {
    
$local_cert __DIR__."/ssl-echo-server/cert.pem";
    
$local_pk   __DIR__."/ssl-echo-server/privkey.pem";

    
$ctx = new EventSslContext(EventSslContext::SSLv3_SERVER_METHOD, array (
        
EventSslContext::OPT_LOCAL_CERT  => $local_cert,
        
EventSslContext::OPT_LOCAL_PK    => $local_pk,
        
//EventSslContext::OPT_PASSPHRASE  => "test",
        
EventSslContext::OPT_ALLOW_SELF_SIGNED => true,
    ));

    return 
$ctx;
}

$port 9999;
if (
$argc 1) {
    
$port = (int) $argv[1];
}
if (
$port <= || $port 65535) {
    exit(
"Некорректный порт");
}
$ip '0.0.0.0';

$base = new EventBase();
$ctx  _init_ssl();
$http = new EventHttp($base$ctx);
$http->setAllowedMethods(EventHttpRequest::CMD_GET EventHttpRequest::CMD_POST);

$http->setCallback("/dump""_http_dump", array(48));
$http->setCallback("/about""_http_about");
$http->setCallback("/err400""_http_400");
$http->setDefaultCallback("_http_default""custom data value");

$http->bind($ip$port);
$base->dispatch();

Пример #10 OpenSSL соединение

<?php
/*
 * Простой OpenSSL клиент.
 *
 * Использование:
 * 1) Запускаем сервер, например так:
 * $ php examples/https.php 9999
 *
 * 2) Запускаем клиента в другом окне:
 * $ php examples/ssl-connection.php 9999
 */

function _request_handler($req$base) {
    echo 
__FUNCTION__PHP_EOL;

    if (
is_null($req)) {
        echo 
"Превышен интервал ожидания\n";
    } else {
        
$response_code $req->getResponseCode();

        if (
$response_code == 0) {
            echo 
"В соединении отказано\n";
        } elseif (
$response_code != 200) {
            echo 
"Неожиданный ответ: $response_code\n";
        } else {
            echo 
"Соединение успешно: $response_code\n";
            
$buf $req->getInputBuffer();
            echo 
"Body:\n";
            while (
$s $buf->readLine(EventBuffer::EOL_ANY)) {
                echo 
$sPHP_EOL;
            }
        }
    }

    
$base->exit(NULL);
}

function 
_init_ssl() {
    
$ctx = new EventSslContext(EventSslContext::SSLv3_CLIENT_METHOD, array ());

    return 
$ctx;
}


// Разрешаем переопределять порт
$port 9999;
if (
$argc 1) {
    
$port = (int) $argv[1];
}
if (
$port <= || $port 65535) {
    exit(
"Некорректный порт\n");
}
$host '127.0.0.1';

$ctx _init_ssl();
if (!
$ctx) {
    
trigger_error("Не удалось создать контекст SSL"E_USER_ERROR);
}

$base = new EventBase();
if (!
$base) {
    
trigger_error("Не удалось инициализировать обработчик событий"E_USER_ERROR);
}

$conn = new EventHttpConnection($baseNULL$host$port$ctx);
$conn->setTimeout(50);

$req = new EventHttpRequest("_request_handler"$base);
$req->addHeader("Host"$hostEventHttpRequest::OUTPUT_HEADER);
$buf $req->getOutputBuffer();
$buf->add("<html>HTML TEST</html>");
//$req->addHeader("Content-Length", $buf->length, EventHttpRequest::OUTPUT_HEADER);
//$req->addHeader("Connection", "close", EventHttpRequest::OUTPUT_HEADER);
$conn->makeRequest($reqEventHttpRequest::CMD_POST"/dump");

$base->dispatch();
echo 
"END\n";
?>

Пример #11 Пример использования EventHttpConnection::makeRequest()

<?php
function _request_handler($req$base) {
    echo 
__FUNCTION__PHP_EOL;

    if (
is_null($req)) {
        echo 
"Timed out\n";
    } else {
        
$response_code $req->getResponseCode();

        if (
$response_code == 0) {
            echo 
"В соединении отказано\n";
        } elseif (
$response_code != 200) {
            echo 
"Неожиданный ответ: $response_code\n";
        } else {
            echo 
"Успешное соединение: $response_code\n";
            
$buf $req->getInputBuffer();
            echo 
"Данные:\n";
            while (
$s $buf->readLine(EventBuffer::EOL_ANY)) {
                echo 
$sPHP_EOL;
            }
        }
    }

    
$base->exit(NULL);
}

$address "127.0.0.1";
$port 80;

$base = new EventBase();
$conn = new EventHttpConnection($baseNULL$address$port);
$conn->setTimeout(5);
$req = new EventHttpRequest("_request_handler"$base);

$req->addHeader("Host"$addressEventHttpRequest::OUTPUT_HEADER);
$req->addHeader("Content-Length""0"EventHttpRequest::OUTPUT_HEADER);
$conn->makeRequest($reqEventHttpRequest::CMD_GET"/index.cphp");

$base->loop();
?>

Результатом выполнения данного примера будет что-то подобное:

_request_handler
Success: 200
Body:
PHP, date:
2013-03-13T20:27:52+05:00

Пример #12 Слушатель соединений на базе сокетов UNIX

<?php
/*
 * Простой echo-сервер на базе слушателя соединений libevent.
 *
 * Использование:
 * 1) В первом окне запустите слушатель:
 *
 * $ php unix-domain-listener.php [путь к сокету]
 *
 * 2) Во втором окне откройте соединение к сокету:
 *
 * $ socat - GOPEN:/tmp/1.sock
 *
 * 3) Начните печататью Сервер должен повторять ввод.
 */

class MyListenerConnection {
    private 
$bev$base;

    public function 
__destruct() {
        if (
$this->bev) {
            
$this->bev->free();
        }
    }

    public function 
__construct($base$fd) {
        
$this->base $base;

        
$this->bev = new EventBufferEvent($base$fdEventBufferEvent::OPT_CLOSE_ON_FREE);

        
$this->bev->setCallbacks(array($this"echoReadCallback"), NULL,
            array(
$this"echoEventCallback"), NULL);

        if (!
$this->bev->enable(Event::READ)) {
            echo 
"Не удалось разрешить чтение (READ)\n";
            return;
        }
    }

    public function 
echoReadCallback($bev$ctx) {
        
// Копируем все данные из входящего буфера в исходящий буфер
        
$bev->output->addBuffer($bev->input);
    }

    public function 
echoEventCallback($bev$events$ctx) {
        if (
$events EventBufferEvent::ERROR) {
            echo 
"Ошибка в bufferevent\n";
        }

        if (
$events & (EventBufferEvent::EOF EventBufferEvent::ERROR)) {
            
$bev->free();
            
$bev NULL;
        }
    }
}

class 
MyListener {
    public 
$base,
        
$listener,
        
$socket;
    private 
$conn = array();

    public function 
__destruct() {
        foreach (
$this->conn as &$c$c NULL;
    }

    public function 
__construct($sock_path) {
        
$this->base = new EventBase();
        if (!
$this->base) {
            echo 
"Не удаётся открыть обработчик ошибок";
            exit(
1);
        }

        if (
file_exists($sock_path)) {
            
unlink($sock_path);
        }

         
$this->listener = new EventListener($this->base,
             array(
$this"acceptConnCallback"), $this->base,
             
EventListener::OPT_CLOSE_ON_FREE EventListener::OPT_REUSEABLE, -1,
             
"unix:$sock_path");

        if (!
$this->listener) {
            
trigger_error("Невозможно создать слушатель"E_USER_ERROR);
        }

        
$this->listener->setErrorCallback(array($this"accept_error_cb"));
    }

    public function 
dispatch() {
        
$this->base->dispatch();
    }

    
// Эта callback-функция будет запущена, когда в $bev появятся данные для чтения
    
public function acceptConnCallback($listener$fd$address$ctx) {
        
// Появилось новое соединение! Настроем для него bufferevent. */
        
$base $this->base;
        
$this->conn[] = new MyListenerConnection($base$fd);
    }

    public function 
accept_error_cb($listener$ctx) {
        
$base $this->base;

        
fprintf(STDERR"Ошибка слушателя: %d (%s). "
            
."Shutting down.\n",
            
EventUtil::getLastSocketErrno(),
            
EventUtil::getLastSocketError());

        
$base->exit(NULL);
    }
}

if (
$argc <= 1) {
    exit(
"Не указан сокет\n");
}
$sock_path $argv[1];

$l = new MyListener($sock_path);
$l->dispatch();
?>

Пример #13 Простой SMTP-сервер

<?php
 
/*
 * Автор: Andrew Rose <hello at andrewrose dot co dot uk>
 *
 * Usage:
 * 1) Подготовим файлы сертификата cert.pem и приватного ключа privkey.pem.
 * 2) Запустим скрипт сервера
 * 3) Откроем TLS-соединение, например:
 *      $ openssl s_client -connect localhost:25 -starttls smtp -crlf
 * 4) Протестируем команды, описанные в метода `cmd`.
 */

class Handler {
    public 
$domainName FALSE;
    public 
$connections = [];
    public 
$buffers = [];
    public 
$maxRead 256000;

    public function 
__construct() {
        
$this->ctx = new EventSslContext(EventSslContext::SSLv3_SERVER_METHOD, [
            
EventSslContext::OPT_LOCAL_CERT  => 'cert.pem',
            
EventSslContext::OPT_LOCAL_PK    => 'privkey.pem',
            
//EventSslContext::OPT_PASSPHRASE  => '',
            
EventSslContext::OPT_VERIFY_PEER => false// для корректного сертификата укажите true
            
EventSslContext::OPT_ALLOW_SELF_SIGNED => true // для корректного сертификата укажите false
        
]);

        
$this->base = new EventBase();
        if (!
$this->base) {
            exit(
"Не удалось открыть обработчик событий\n");
        }

        if (!
$this->listener = new EventListener($this->base,
            [
$this'ev_accept'],
            
$this->ctx,
            
EventListener::OPT_CLOSE_ON_FREE EventListener::OPT_REUSEABLE,
            -
1,
            
'0.0.0.0:25'))
        {
            exit(
"Невозможно создать слушателя\n");
        }

        
$this->listener->setErrorCallback([$this'ev_error']);
        
$this->base->dispatch();
    }

    public function 
ev_accept($listener$fd$address$ctx) {
        static 
$id 0;
        
$id += 1;

        
$this->connections[$id]['clientData'] = '';
        
$this->connections[$id]['cnx'] = new EventBufferEvent($this->base$fd,
            
EventBufferEvent::OPT_CLOSE_ON_FREE);

        if (!
$this->connections[$id]['cnx']) {
            echo 
"Failed creating buffer\n";
            
$this->base->exit(NULL);
            exit(
1);
        }

        
$this->connections[$id]['cnx']->setCallbacks([$this"ev_read"], NULL,
            [
$this'ev_error'], $id);
        
$this->connections[$id]['cnx']->enable(Event::READ Event::WRITE);

        
$this->ev_write($id'220 '.$this->domainName." wazzzap?\r\n");
    }

    function 
ev_error($listener$ctx) {
        
$errno EventUtil::getLastSocketErrno();

        
fprintf(STDERR"Ошибка слушателя: %d (%s). Аварийное завершение работы.\n",
            
$errnoEventUtil::getLastSocketError());

        if (
$errno != 0) {
            
$this->base->exit(NULL);
            exit();
        }
    }

    public function 
ev_close($id) {
        
$this->connections[$id]['cnx']->disable(Event::READ Event::WRITE);
        unset(
$this->connections[$id]);
    }

    protected function 
ev_write($id$string) {
        echo 
'S('.$id.'): '.$string;
        
$this->connections[$id]['cnx']->write($string);
    }

    public function 
ev_read($buffer$id) {
        while(
$buffer->input->length 0) {
            
$this->connections[$id]['clientData'] .= $buffer->input->read($this->maxRead);
            
$clientDataLen strlen($this->connections[$id]['clientData']);

            if(
$this->connections[$id]['clientData'][$clientDataLen-1] == "\n"
                
&& $this->connections[$id]['clientData'][$clientDataLen-2] == "\r")
            {
                
// удаляем все завершающие \r\n
                
$line substr($this->connections[$id]['clientData'], 0,
                    
strlen($this->connections[$id]['clientData']) - 2);

                
$this->connections[$id]['clientData'] = '';
                
$this->cmd($buffer$id$line);
            }
        }
    }

    protected function 
cmd($buffer$id$line) {
        switch (
$line) {
            case 
strncmp('EHLO '$line4):
                
$this->ev_write($id"250-STARTTLS\r\n");
                
$this->ev_write($id"250 OK ehlo\r\n");
                break;

            case 
strncmp('HELO '$line4):
                
$this->ev_write($id"250-STARTTLS\r\n");
                
$this->ev_write($id"250 OK helo\r\n");
                break;

            case 
strncmp('QUIT'$line3):
                
$this->ev_write($id"250 OK quit\r\n");
                
$this->ev_close($id);
                break;

            case 
strncmp('STARTTLS'$line3):
                
$this->ev_write($id"220 Ready to start TLS\r\n");
                
$this->connections[$id]['cnx'] = EventBufferEvent::sslFilter($this->base,
                    
$this->connections[$id]['cnx'], $this->ctx,
                    
EventBufferEvent::SSL_ACCEPTING,
                    
EventBufferEvent::OPT_CLOSE_ON_FREE);
                
$this->connections[$id]['cnx']->setCallbacks([$this"ev_read"], NULL, [$this'ev_error'], $id);
                
$this->connections[$id]['cnx']->enable(Event::READ Event::WRITE);
                break;

            default:
                echo 
'неизвестная команда: '.$line."\n";
                break;
        }
    }
}

new 
Handler();
add a note add a note

User Contributed Notes 1 note

up
3
Bas Vijfwinkel
8 years ago
In order to use certain features used in the examples above here you need a very recent version of libevent (>= 2.1).
Although 'pecl install event' will not show any errors, certain features are disabled and certain function calls might use a different number of parameters.
For example EventHttp will throw a warning that the number of parameters should be 1 instead of 2 (when using it with a SSL context) and as a bonus cause a segmentation fault.

On some distributions of Linux, the stable libevent library might not be recent enough for all features to be enabled and you might need to use an alpha version.

You can install an alpha version of libevent next to the stable version that might already be on your machine.
The basic steps are:
- download the alpha tarball to a folder e.g libevent-2.1.3-alpha.tar.gz
- tar zxvf libevent-2.1.3-alpha.tar.gz
- cd libevent-2.1.3-alpha
- ./configure --prefix=/opt/libevent-alpha
- make
- make install

Now the alpha version of libevent is created in /opt/libevent-alpha.

Before running pecl, first export the library folder of libevent so that pecl knows where your most recent libevent stuff is:
export LD_LIBRARY_PATH=/opt/libevent-2.1.3-alpha/lib
Without this export I couldn't get pecl to use the correct libevent

Now run 'pecl install event'
and when asked libevent installation prefix [/usr] :
enter :  /opt/libevent-2.1.3-alpha
Now pecl will use your alpha version of libevent instead of the default version.

If everything goes well then you should be able to enjoy the full glory of this wonderfull extension.

If you try to install a very recent version of libevent on a system with a  old version of openssl (0.9), you also need to update that because there are some dependencies in libevent that do not work with 0.9
To Top