示例

示例 #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($buf, 1024)) > 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
简单 HTTP 0.x 客户端
语法: php
{$argv[0]} [主机名] [资源]
示例: php
{$argv[0]} www.google.com /

EOS;
exit();
}

$base = new EventBase();

$dns_base = new EventDnsBase($base, TRUE); // 我们将使用异步 DNS 解析
if (!$dns_base) {
exit(
"初始化 DNS 基站失败\n");
}

$bev = new EventBufferEvent($base, /* 使用内部套接字 */ NULL,
EventBufferEvent::OPT_CLOSE_ON_FREE | EventBufferEvent::OPT_DEFER_CALLBACKS,
"readcb", /* writecb */ NULL, "eventcb"
);
if (!
$bev) {
exit(
"创建缓冲事件套接字失败\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], 80, EventUtil::AF_UNSPEC)) {
exit(
"无法连接到主机 {$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() 连接到端口 80 上的 127.0.0.1。
*
* 2. 使用输出缓冲区通过 HTTP/1.0 请求 /index.cphp。
*
* 3. 异步读取响应并将其打印到标准输出。
*/

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

while ((
$n = $input->remove($buf, 1024)) > 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
"步骤 1\n";
$bev = new EventBufferEvent($base, /* 使用内部套接字 */ NULL,
EventBufferEvent::OPT_CLOSE_ON_FREE | EventBufferEvent::OPT_DEFER_CALLBACKS);
if (!
$bev) {
exit(
"创建缓冲区事件套接字失败\n");
}

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

echo
"步骤 3\n";
// 发送请求
$output = $bev->getOutput();
if (!
$output->add(
"GET /index.cphp HTTP/1.0\r\n".
"Connection: Close\r\n\r\n"
)) {
exit(span style="color: #DD0000">"将请求添加到输出缓冲区失败\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, $fd, EventBufferEvent::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
"缓冲事件错误\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
"无法打开事件基础";
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) {
// 我们获得了一个新的连接!为它设置一个缓冲事件。 */
$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 <= 0 || $port > 65535) {
exit(
"无效端口");
}

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

示例 #4 SSL 回声服务器

<?php
/*
* SSL echo server
*
* To test:
* 1) Run:
* $ php examples/ssl-echo-server/server.php 9998
*
* 2) in another terminal window run:
* $ 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();
}

// This callback is invoked when there is data to read on $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);
}

// This callback is invoked when some even occurs on the event listener,
// e.g. connection closed, or an error occurred
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();
}
}

// This callback is invoked when a client accepts new connection
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);
}

// This callback is invoked when we failed to setup new connection for a client
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);
}

// Initialize SSL structures, create an EventSslContext
// Optionally create self-signed certificates
function init_ssl() {
// We *must* have entropy. Otherwise there's no point to crypto.
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;
}
}

// Allow to override the port
$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
/* {{{ 配置和支持的内容 */
echo "支持的方法:\n";
foreach (
Event::getSupportedMethods() as $m) {
echo
$m, PHP_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 - 边缘触发 I/O\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 = new EventBase();
$event = new Event($base, STDIN, Event::READ | Event::PERSIST, function ($fd, $events, $arg) {
static
$max_iterations = 0;

if (++
$max_iterations >= 5) {
/* 使用 2.33 秒的超时时间在 5 次迭代后退出 */
echo "正在停止...\n";
$arg[0]->exit(2.33);
}

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

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

示例 #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 键)
*
* 它将输出:
* 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===== 转储 =====\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
$s, PHP_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 <= 0 || $port > 65535) {
exit(
"无效的端口");
}

$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", "自定义数据值");

$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
"计数器达到最大请求数 $max_requests. 退出\n";
exit();
}

echo
__METHOD__, " 被调用\n";
echo
"请求:"; var_dump($req);
echo
"数据:"; var_dump($data);

echo
"\n===== 转储 =====\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 <= 0 || $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(4, 8));
$http->setCallback("/about", "_http_about");
$http->setCallback("/err400", "_http_400");
$http->setDefaultCallback("_http_default", "自定义数据值");

$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
"主体:\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(
"无效端口\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($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
"结束\n";
?>

示例 #11 EventHttpConnection::makeRequest() 示例

<?php
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
"主体:\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 连接监听器的简单回显服务器。
*
* 使用方法:
* 1) 在一个终端窗口中运行:
*
* $ php unix-domain-listener.php [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) Prepare cert.pem certificate and privkey.pem private key files.
* 2) Launch the server script
* 3) Open TLS connection, e.g.:
* $ openssl s_client -connect localhost:25 -starttls smtp -crlf
* 4) Start testing the commands listed in `cmd` method below.
*/

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, // change to true with authentic cert
EventSslContext::OPT_ALLOW_SELF_SIGNED => true // change to false with authentic cert
]);

$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();
添加注释

用户贡献的注释 1 个注释

4
Bas Vijfwinkel
9 年前
为了使用上面示例中使用的某些功能,您需要一个非常新的 libevent 版本(>= 2.1)。
虽然“pecl install event”不会显示任何错误,但某些功能已禁用,某些函数调用可能使用不同的参数数量。
例如,EventHttp 会抛出警告,指出参数数量应为 1 而不是 2(当与 SSL 上下文一起使用时),作为奖励还会导致段错误。

在某些 Linux 发行版上,稳定的 libevent 库可能不够新,无法启用所有功能,您可能需要使用 alpha 版本。

您可以在机器上已经存在的稳定版本旁边安装 libevent 的 alpha 版本。
基本步骤如下:
- 将 alpha 版本的压缩包下载到某个文件夹,例如 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

现在,libevent 的 alpha 版本已创建在 /opt/libevent-alpha 中。

在运行 pecl 之前,首先导出 libevent 的库文件夹,以便 pecl 知道您的最新 libevent 文件夹在哪里
export LD_LIBRARY_PATH=/opt/libevent-2.1.3-alpha/lib
没有这个导出,我无法让 pecl 使用正确的 libevent

现在运行 'pecl install event'
当询问 libevent 安装前缀 [/usr] 时
输入:/opt/libevent-2.1.3-alpha
现在 pecl 将使用您的 alpha 版本的 libevent 而不是默认版本。

如果一切顺利,您应该能够享受这个奇妙扩展的所有荣耀。

如果您尝试在具有旧版本 openssl (0.9) 的系统上安装最新的 libevent 版本,您还需要更新 openssl,因为 libevent 中存在一些依赖项,这些依赖项与 0.9 不兼容。
To Top