# 示例

示例 #1 简单的 HTTP 客户端

    <?php
    // Read 回调
    function readcb($bev, $base) {
        //$input = $bev->input; //$bev->getInput();

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

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

    // Event 回调
    function eventcb($bev, $events, $base) {
        if ($events & EventBufferEvent::CONNECTED) {
            echo "Connected.\n";
        } elseif ($events & (EventBufferEvent::ERROR | EventBufferEvent::EOF)) {
            if ($events & EventBufferEvent::ERROR) {
                echo "DNS error: ", $bev->getDnsErrorString(), PHP_EOL;
            }

            echo "Closing\n";
            $base->exit();
            exit("Done\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($base, TRUE); // We'll use async DNS resolving
    if (!$dns_base) {
        exit("Failed to init DNS Base\n");
    }

    $bev = new EventBufferEvent($base, /* use internal socket */ NULL,
        EventBufferEvent::OPT_CLOSE_ON_FREE | EventBufferEvent::OPT_DEFER_CALLBACKS,
        "readcb", /* writecb */ NULL, "eventcb"
    );
    if (!$bev) {
        exit("Failed creating bufferevent socket\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("Failed adding request to output buffer\n");
    }

    if (!$bev->connectHost($dns_base, $argv[1], 80, EventUtil::AF_UNSPEC)) {
        exit("Can't connect to host {$argv[1]}\n");
    }

    $base->dispatch();
    ?>

以上示例的输出类似于：

    Connected.
    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>
    Closing
    Done

示例 #2 使用异步 DNS 解析器的 HTTP 客户端

    <?php
    /*
     * 1. 借助 EventBufferEvent::connect()
     * 连接到 127.0.0.1 的 80 端口。
     *
     * 2. 使用输出缓冲通过 HTTP/1.0 请求 /index.cphp。
     *
     * 3. 异步读取响应并将其打印到标准输出。
     */

    // Read 回调
    function readcb($bev, $base) {
        $input = $bev->getInput();

        while (($n = $input->remove($buf, 1024)) > 0) {
            echo $buf;
        }
    }

    // Event 回调
    function eventcb($bev, $events, $base) {
        if ($events & EventBufferEvent::CONNECTED) {
            echo "Connected.\n";
        } elseif ($events & (EventBufferEvent::ERROR | EventBufferEvent::EOF)) {
            if ($events & EventBufferEvent::ERROR) {
                echo "DNS error: ", $bev->getDnsErrorString(), PHP_EOL;
            }

            echo "Closing\n";
            $base->exit();
            exit("Done\n");
        }
    }

    $base = new EventBase();

    echo "step 1\n";
    $bev = new EventBufferEvent($base, /* use internal socket */ NULL,
        EventBufferEvent::OPT_CLOSE_ON_FREE | EventBufferEvent::OPT_DEFER_CALLBACKS);
    if (!$bev) {
        exit("Failed creating bufferevent socket\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("Failed adding request to output buffer\n");
    }

    /* 同步连接到主机。
    知道 IP，所以不需要解析 DNS。 */
    if (!$bev->connect("127.0.0.1:80")) {
        exit("Can't connect to host\n");
    }

    // Dispatch pending events
    $base->dispatch();
    ?>

示例 #3 Echo 服务器

    <?php
    /*
     * 基于 libevent 连接监听器的简单 echo 服务器。
     *
     * 用法：
     * 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, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);

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

            if (!$this->bev->enable(Event::READ)) {
                echo "Failed to enable 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 "Error from 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 "Couldn't open event base";
                exit(1);
            }

            // 方式 #1
            /*
            $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            if (!socket_bind($this->socket, '0.0.0.0', $port)) {
                echo "Unable to bind socket\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 "Couldn't create listener";
                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, "Got an error %d (%s) on the listener. "
                ."Shutting down.\n",
                EventUtil::getLastSocketErrno(),
                EventUtil::getLastSocketError());

            $base->exit(NULL);
        }
    }

    $port = 9808;

    if ($argc > 1) {
        $port = (int) $argv[1];
    }
    if ($port <= 0 || $port > 65535) {
        exit("Invalid port");
    }

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

示例 #4 SSL echo 服务器

    <?php
    /*
     * SSL echo 服务器
     *
     * 测试：
     * 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("Failed creating SSL context\n");
            }

            $this->base = new EventBase();
            if (!$this->base) {
                exit("Couldn't open event base\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("Couldn't create listener\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) {
                // Fetch errors from the SSL error stack
                while ($err = $bev->sslError()) {
                    fprintf(STDERR, "Bufferevent error %s.\n", $err);
                }
            }

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

        // 当客户端接受新连接时调用此回调
        function ssl_accept_cb($listener, $fd, $address, $ctx) {
            // We got a new connection! Set up a bufferevent for it.
            $this->bev = EventBufferEvent::sslSocket($this->base, $fd, $this->ctx,
                EventBufferEvent::SSL_ACCEPTING, EventBufferEvent::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, "Got an error %d (%s) on the listener. "
                ."Shutting down.\n",
                EventUtil::getLastSocketErrno(),
                EventUtil::getLastSocketError());

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

        // 初始化 SSL 结构，创建 EventSslContext
        // 视需要使用自签名证书
        function init_ssl() {
            // *必须有* entropy。否则加密没有意义。
            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 "Couldn't read $local_cert or $local_pk file.  To generate a key\n",
                    "and self-signed certificate, run:\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 <= 0 || $port > 65535) {
        exit("Invalid port\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

    在第一个终端窗口中，应该捕获以下内容：

    Caught signal 15
    */
    class MyEventSignal {
        private $base;

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

        function eventSighandler($no, $c) {
            echo "Caught signal $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($dir, 0750, EIO_PRI_DEFAULT, "my_nop_cb");

    $event = new Event($base, eio_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 "Done\n";
    ?>

示例 #7 杂项

    <?php
    /* {{{ Config & supported stuff */
    echo "Supported methods:\n";
    foreach (Event::getSupportedMethods() as $m) {
        echo $m, PHP_EOL;
    }

    // 不要使用“select”方法
    $cfg = new EventConfig();
    if ($cfg->avoidMethod("select")) {
        echo "'select' method avoided\n";
    }

    // 创建跟配置相关的 event_base
    $base = new EventBase($cfg);
    echo "Event method used: ", $base->getMethod(), PHP_EOL;

    echo "Features:\n";
    $features = $base->getFeatures();
    ($features & EventConfig::FEATURE_ET) and print "ET - edge-triggered IO\n";
    ($features & EventConfig::FEATURE_O1) and print "O1 - O(1) operation for adding/deleting events\n";
    ($features & EventConfig::FEATURE_FDS) and print "FDS - arbitrary file descriptor types, and not just sockets\n";

    // 需要 FDS 功能
    if ($cfg->requireFeatures(EventConfig::FEATURE_FDS)) {
        echo "FDS feature is now required\n";

        $base = new EventBase($cfg);
        ($base->getFeatures() & EventConfig::FEATURE_FDS)
            and print "FDS - arbitrary file descriptor types, and not just sockets\n";
    }
    /* }}} */

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

        if (++$max_iterations >= 5) {
            /* exit after 5 iterations with timeout of 2.33 seconds */
            echo "Stopping...\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
     * (press 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
     * (press Enter)
     *
     * 将会输出：
     * HTTP/1.0 200 OK
     * Content-Type: text/html; charset=ISO-8859-1
     * Connection: close
     * (press 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 "Counter reached max requests $max_requests. Exiting\n";
            exit();
        }

        echo __METHOD__, " called\n";
        echo "request:"; var_dump($req);
        echo "data:"; var_dump($data);

        echo "\n===== DUMP =====\n";
        echo "Command:", $req->getCommand(), PHP_EOL;
        echo "URI:", $req->getUri(), PHP_EOL;
        echo "Input headers:"; var_dump($req->getInputHeaders());
        echo "Output headers:"; var_dump($req->getOutputHeaders());

        echo "\n >> Sending reply ...";
        $req->sendReply(200, "OK");
        echo "OK\n";

        echo "\n >> Reading input buffer ...\n";
        $buf = $req->getInputBuffer();
        while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
            echo $s, PHP_EOL;
        }
        echo "No more data in the buffer\n";
    }

    function _http_about($req) {
        echo __METHOD__, PHP_EOL;
        echo "URI: ", $req->getUri(), PHP_EOL;
        echo "\n >> Sending reply ...";
        $req->sendReply(200, "OK");
        echo "OK\n";
    }

    function _http_default($req, $data) {
        echo __METHOD__, PHP_EOL;
        echo "URI: ", $req->getUri(), PHP_EOL;
        echo "\n >> Sending reply ...";
        $req->sendReply(200, "OK");
        echo "OK\n";
    }

    $port = 8010;
    if ($argc > 1) {
        $port = (int) $argv[1];
    }
    if ($port <= 0 || $port > 65535) {
        exit("Invalid port");
    }

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

    $http->setCallback("/dump", "_http_dump", array(4, 8));
    $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
    (press 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 "Counter reached max requests $max_requests. Exiting\n";
            exit();
        }

        echo __METHOD__, " called\n";
        echo "request:"; var_dump($req);
        echo "data:"; var_dump($data);

        echo "\n===== DUMP =====\n";
        echo "Command:", $req->getCommand(), PHP_EOL;
        echo "URI:", $req->getUri(), PHP_EOL;
        echo "Input headers:"; var_dump($req->getInputHeaders());
        echo "Output headers:"; var_dump($req->getOutputHeaders());

        echo "\n >> Sending reply ...";
        $req->sendReply(200, "OK");
        echo "OK\n";

        $buf = $req->getInputBuffer();
        echo "\n >> Reading input buffer (", $buf->length, ") ...\n";
        while ($s = $buf->read(1024)) {
            echo $s;
        }
        echo "\nNo more data in the buffer\n";
    }

    function _http_about($req) {
        echo __METHOD__, PHP_EOL;
        echo "URI: ", $req->getUri(), PHP_EOL;
        echo "\n >> Sending reply ...";
        $req->sendReply(200, "OK");
        echo "OK\n";
    }

    function _http_default($req, $data) {
        echo __METHOD__, PHP_EOL;
        echo "URI: ", $req->getUri(), PHP_EOL;
        echo "\n >> Sending reply ...";
        $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 <= 0 || $port > 65535) {
        exit("Invalid port");
    }
    $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(4, 8));
    $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 "Timed out\n";
        } else {
            $response_code = $req->getResponseCode();

            if ($response_code == 0) {
                echo "Connection refused\n";
            } elseif ($response_code != 200) {
                echo "Unexpected response: $response_code\n";
            } else {
                echo "Success: $response_code\n";
                $buf = $req->getInputBuffer();
                echo "Body:\n";
                while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
                    echo $s, PHP_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 <= 0 || $port > 65535) {
        exit("Invalid port\n");
    }
    $host = '127.0.0.1';

    $ctx = _init_ssl();
    if (!$ctx) {
        trigger_error("Failed creating SSL context", E_USER_ERROR);
    }

    $base = new EventBase();
    if (!$base) {
        trigger_error("Failed to initialize event base", E_USER_ERROR);
    }

    $conn = new EventHttpConnection($base, NULL, $host, $port, $ctx);
    $conn->setTimeout(50);

    $req = new EventHttpRequest("_request_handler", $base);
    $req->addHeader("Host", $host, EventHttpRequest::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($req, EventHttpRequest::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 "Connection refused\n";
            } elseif ($response_code != 200) {
                echo "Unexpected response: $response_code\n";
            } else {
                echo "Success: $response_code\n";
                $buf = $req->getInputBuffer();
                echo "Body:\n";
                while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
                    echo $s, PHP_EOL;
                }
            }
        }

        $base->exit(NULL);
    }

    $address = "127.0.0.1";
    $port = 80;

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

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

    $base->loop();
    ?>

以上示例的输出类似于：

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

示例 #12 基于 UNIX 域套接字的连接监听器

    <?php
    /*
     * 基于 libevent 的连接监听器的简单 echo 服务器。
     *
     * 用法：
     * 1) 在终端粗昂口中运行：
     *
     * $ php unix-domain-listener.php [path-to-socket]
     *
     * 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, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);

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

            if (!$this->bev->enable(Event::READ)) {
                echo "Failed to enable READ\n";
                return;
            }
        }

        public function echoReadCallback($bev, $ctx) {
            // 将输入缓冲区中的所有数据复制到输出缓冲区
            $bev->output->addBuffer($bev->input);
        }

        public function echoEventCallback($bev, $events, $ctx) {
            if ($events & EventBufferEvent::ERROR) {
                echo "Error from 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 "Couldn't open event base";
                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("Couldn't create listener", E_USER_ERROR);
            }

            $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, "Got an error %d (%s) on the listener. "
                ."Shutting down.\n",
                EventUtil::getLastSocketErrno(),
                EventUtil::getLastSocketError());

            $base->exit(NULL);
        }
    }

    if ($argc <= 1) {
        exit("Socket path is not provided\n");
    }
    $sock_path = $argv[1];

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

示例 #13 简单 SMTP 服务器

    <?php
     /*
     * Author: 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("Couldn't open event base\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("Couldn't create listener\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, "Got an error %d (%s) on the listener. Shutting down.\n",
                $errno, EventUtil::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")
                {
                    // remove the trailing \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 ', $line, 4):
                    $this->ev_write($id, "250-STARTTLS\r\n");
                    $this->ev_write($id, "250 OK ehlo\r\n");
                    break;

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

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

                case strncmp('STARTTLS', $line, 3):
                    $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 'unknown command: '.$line."\n";
                    break;
            }
        }
    }

    new Handler();

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open