PHP事件中的循环行为

PHP事件中的循环行为,php,libevent,pecl-event,pecl-dio,Php,Libevent,Pecl Event,Pecl Dio,我正在使用libevent的PHP事件类包装器来读取串行端口。我用它来避免缓冲区过度运行——其想法是使用事件来定期检查端口,这样就不会丢失任何数据 我希望事件在设置时会触发,但得出的结论是,事件只有在调用EventBase::loop()后才会触发。在这种情况下,当我调用loop()时,控制流从我的代码流向libevent中的调度器。最终,控制流在调用循环后的位置返回到我的代码 从这个行为中,我假设我实际上是在安排事件的调度,并且应该定期调用loop(),以避免事件缺少CPU 然而,在这种情况下

我正在使用libevent的PHP事件类包装器来读取串行端口。我用它来避免缓冲区过度运行——其想法是使用事件来定期检查端口,这样就不会丢失任何数据

我希望事件在设置时会触发,但得出的结论是,事件只有在调用
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();




?>

我希望事件在设置时会触发,但得出的结论是,事件只有在调用EventBase::loop()后才会触发

Event::\uu construct()
注册事件并将其与
EventBase
关联。此时,
事件
对象表示特定事件的一组条件和回调。在此状态下,不会触发事件

Event::add()
使事件挂起。当事件处于挂起状态时,当满足相应条件时,即可触发事件

EventBase::loop()
运行
EventBase
,直到其中不再有未决事件。只有在相应的base运行时才能触发事件

触发事件后,它将变为活动状态,并运行其回调。如果将事件配置为,则在运行回调后它仍处于挂起状态。否则,它将停止挂起。考虑这一点:

$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)事件挂起:0
2) 事件处于挂起状态:1
1秒钟过去了
3) 事件处于挂起状态:0
使用
Event::PERSIST
标志:

$e = new Event($base, -1, Event::TIMEOUT | Event::PERSIST, function() {
回调将每秒调用一次,因为事件仍处于挂起状态

最终,控制流在调用循环后的位置返回到我的代码

该进程将被阻止,直到循环完成。我们需要等待事件得到处理。否则,流可能在处理所有事件之前到达程序的末尾。这就是所有异步程序实际工作的方式

从这个行为中,我假设我实际上是在安排事件的调度,并且应该定期调用loop(),以避免事件缺少CPU

是的,您正在运行base之前安排事件。不,您不应该定期调用
EventBase::loop()
,也不需要考虑CPU“匮乏”,因为底层实现基于有效的平台特定后端,例如空闲状态下的
epoll
poll
kqueue
等(当运行的库只等待事件发生时),该进程消耗的系统资源很少,可以忽略不计

例如,您可以通过计时器事件、添加/删除事件或修改其回调中的事件属性来控制流

迪奥 事件扩展当前无法识别DIO流。没有干净的方法可以获得封装到DIO资源中的文件描述符。但有一个解决办法:

  • 使用
    fopen()
    打开端口的流
  • 使用[
    stream\u set\u blocking()
    ][3]使流无阻塞
  • 使用[
    EventUtil::getSocketFd()
    ][3]从流中获取数字文件描述符
  • 将数字文件描述符传递到
    dio_fdopen()
    (当前未记录)并获取dio资源
  • 添加一个带有回调的
    事件
    ,用于侦听文件描述符上的读取事件
  • 在回调函数中,耗尽可用数据,并根据应用程序的逻辑进行处理
备选方案:修补/协助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\u get\u fd()


我已经添加了我正在使用的测试代码。我采纳了俄罗斯的建议。唯一的变化是底座上的非阻塞标志。这导致没有输出。如果没有非阻塞标志,则输出良好。目前,底部有一个无限循环,最终将对其进行编码,以处理在事件回调中保存的数据包。其目的是在端口上没有数据的情况下,给予串行处理优先级并执行其他所有操作。谢谢Rusian。一个非常完整的答案。但是,如果端口上没有数据,我将使用非阻塞标志将控制权返回到其他代码。你是在建议我基于事件回调运行代码吗?再次感谢Rusian。我正要重写我的代码。在你的帮助下,我解决了其中一个障碍,也解决了另一个障碍。总是有更多的东西要学。带有事件的DIO描述符-我尝试了相反的方法。使用uri“php://fd/“(int)fd获取路径,然后通过将其插入fopen创建流指针。这似乎不起作用,但可能是我的代码在其他地方的问题。在补丁等方面,我唯一真正的问题是,这一切都是在一个树莓圆周率
$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();