PHP 事件中的循环行为
Posted
技术标签:
【中文标题】PHP 事件中的循环行为【英文标题】:Looping behaviour in PHP Event 【发布时间】:2016-11-30 07:27:37 【问题描述】:我正在使用 libevent 的 php Event 类包装器来读取串行端口。我正在使用它来避免缓冲区溢出 - 想法是使用 Event 定期检查端口,以免丢失数据。
我希望事件在设置时会触发,但得出的结论是事件仅在调用 EventBase::loop()
后触发。在这种情况下,当我调用loop()
时,控制流会从我的代码转到 libevent 中的调度程序。最终控制流返回到我的代码在调用循环之后的位置。
我从这种行为中假设我实际上是在调度事件的调度,并且应该定期调用 loop() 以避免我的事件被 CPU 耗尽。
但是在这种情况下,我应该永远无法在之前的 loop()
调用运行时调用 loop()
,因为根据上面的解释,控制流要么在我的代码中,要么在 libevent 中,不能同时在两者中.
所以我通过我的代码调用loop()
(总共四个 - 我正在摸索中),其中两个产生 libevent 重入警告。
我显然不明白这一点。有人可以帮忙吗?
干杯保罗
<?php
// serial comms defines
define("PORT", "/dev/serial0");
const PORTSETTINGS = array(
'baud' => 9600,
'bits' => 8,
'stop' => 1,
'parity' => 0
);
define("SYN", 170);
define("MSB", 127);
const POLL = 0.1;
/*
** Class Scanner
**
** Manages low level serial comms with the vbus master
**
*/
class Scanner
private $fd;
private $pkt;
private $state;
private $base;
private $timer;
/*
** __construct()
**
** setup the serial port for reading using dio
** setup a timer to read anything on the serial port frequently
**
*/
function __construct()
// set up port and state machine
$this->fd = dio_open(PORT, O_RDONLY | O_NOCTTY | O_NONBLOCK);
dio_tcsetattr($this->fd, PORTSETTINGS);
$this->pkt = array();
$this->state = "discard";
// set up timer handler
$this->base = new EventBase();
$this->timer = new Event($this->base, -1, Event::TIMEOUT | Event::PERSIST, array($this, "Tickle"));
$this->timer->addTimer(POLL);
$this->base->loop(EventBase::LOOP_NONBLOCK);
function PrintPkt($pkt)
echo "\n\n".date("H:i:s");
foreach($pkt as $i)
echo " ".dechex($i);
/*
** Tickle()
**
** read the serial port, if MSB set discard the packet, else save the packet and then pass for processing
** called by the event timer on a regular basis ie POLL seconds
*/
function Tickle()
do
// read the next one and convert to int
$ch = dio_read($this->fd, 1);
$i = ord($ch);
// check for MSB, if set discard to the next packet
if (($i > MSB) && ($i != SYN))
$state="discard";
// if there is nothing on the port it returns 0x0 ie null/false
if ($i)
if ($i == SYN)
// we are at the start of a new packet
if (count($this->pkt) > 0)
if ($this->state === "save")
// this is where we would save the packet but for now we are printing it.
$this->PrintPkt($this->pkt);
// reset for the next packet
$this->pkt = array();
$this->state = "save";
// save this number
$this->pkt[] = $i;
while ($ch);
// restart the timer
$this->timer->addTimer(POLL);
/*
** spin()
**
** call the base loop so that the timer event is serviced
*/
function spin()
$this->base->loop(EventBase::LOOP_NONBLOCK);
$c = new Scanner();
echo "setup";
while(1);
// $c->spin();
?>
【问题讨论】:
我已经添加了我正在使用的测试代码。我已经采纳了俄语的建议。唯一的变化是底座上的非阻塞标志。这导致没有输出。没有非阻塞标志,它输出正常。目前底部有一个无限循环,最终将被编码以处理已保存在事件回调中的数据包。目的是在端口上没有数据时给予串行处理优先级并执行其他所有操作。 【参考方案1】:我希望事件在设置时会触发,但得出的结论是事件仅在调用 EventBase::loop() 后触发。
Event::__construct()
注册一个事件并将其与EventBase
关联。此时,Event
对象代表一组特定事件的条件和回调。在这种状态下,事件没有被触发。
Event::add()
使事件待处理。当事件处于待处理状态时,当满足相应条件时,准备触发。
EventBase::loop()
运行EventBase
直到其中没有更多事件待处理。一个事件只有在对应的base运行时才能触发。
当一个事件被触发时,它变为active,并且它的回调被运行。如果事件配置为persistent,则在运行回调后仍处于挂起状态。否则,它将停止挂起。考虑一下:
$base = new EventBase();
$e = new Event($base, -1, Event::TIMEOUT, function()
// The event is not pending, since it is not persistent:
printf("1 sec elapsed\n");
);
printf("1) Event is pending: %d\n", $e->pending);
// Make $e pending
$e->add(1);
printf("2) Event is pending: %d\n", $e->pending);
// Dispatch all pending events
$base->loop();
printf("3) Event is pending: %d\n", $e->pending);
输出
1) Event is pending: 0
2) Event is pending: 1
1 sec elapsed
3) Event is pending: 0
带有Event::PERSIST
标志:
$e = new Event($base, -1, Event::TIMEOUT | Event::PERSIST, function()
回调将每秒调用一次,因为该事件仍处于未决状态。
最终控制流返回到我的代码在调用循环之后的位置。
进程被阻塞直到循环结束。我们需要等待事件被处理。否则,流程可能会在处理完所有事件之前到达程序的末尾。这就是所有异步程序实际工作的方式。
我从这种行为中假设我实际上是在调度事件的调度,并且应该定期调用 loop() 以避免我的事件被 CPU 耗尽。
是的,您是在运行基地之前安排活动。不,你不应该经常调用EventBase::loop()
,也不需要考虑CPU被“饿死”,因为底层实现基于有效的平台特定后端,例如epoll
、poll
、kqueue
、等等。在空闲状态下(当一个正在运行的基只等待事件发生时),进程消耗的系统资源可以忽略不计。
您可以通过计时器事件来控制流程,例如添加/删除事件或修改其回调中的事件属性。
DIO
事件扩展目前无法识别 DIO 流。没有干净的方法来获取封装到 DIO 资源中的文件描述符。但是有一个解决方法:
为带有fopen()
的端口打开流;
使用 [stream_set_blocking()
][3] 使流非阻塞;
使用 [EventUtil::getSocketFd()
][3] 从流中获取数字文件描述符;
将数字文件描述符传递给dio_fdopen()
(目前未记录)并获取DIO资源;
添加带有回调的Event
,用于监听文件描述符上的读取事件;
在回调中耗尽可用数据并根据应用程序的逻辑对其进行处理。
替代方案:修补/贡献给 DIO
当然,您可以添加一个函数,将底层文件描述符导出为整数。这很容易。签出项目:
svn checkout https://svn.php.net/repository/pecl/dio/trunk dio
cd dio
为php7/dio.c
添加新功能:
/* proto int dio_get_fd(resource fd)
Returns numeric file descriptor for the given DIO resource */
PHP_FUNCTION(dio_get_fd)
zval *r_fd;
php_fd_t *f;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &r_fd) == FAILURE)
return;
if ((f = (php_fd_t *) zend_fetch_resource(Z_RES_P(r_fd), le_fd_name, le_fd)) == NULL)
RETURN_FALSE;
RETURN_LONG(f->fd);
/* */
/* ... */
PHP_FE(dio_get_fd, dio_close_args)
及其原型为php7/php_dio.h
:
PHP_FUNCTION(dio_get_fd);
重建扩展,你就可以使用dio_get_fd()
:
$this->dio = dio_open($this->port, O_RDONLY | O_NOCTTY | O_NONBLOCK);
$this->fd = dio_get_fd($this->dio);
$this->e_read = new Event($this->base, $this->fd, Event::READ | Event::PERSIST,
[$this, '_onRead']);
$this->e_read->add();
$this->base->dispatch();
【讨论】:
谢谢俄语。一个非常完整的答案。但是,如果端口上没有三个数据,我将使用非阻塞标志专门将控制权返回给我的其他代码。您是否建议我应该根据事件回调运行该代码? 再次感谢俄语。我即将重写我的代码。在您的帮助下,我解决了其中一个障碍,也解决了另一个障碍。总是有更多的东西要学。带有事件的 DIO 描述符 - 我尝试了相反的方法。使用 uri "php://fd/".(int)fd 获取路径,然后通过将其插入 fopen 来创建流指针。这似乎不起作用,但这可能是我的代码其他地方的问题。以上是关于PHP 事件中的循环行为的主要内容,如果未能解决你的问题,请参考以下文章