Циклическое поведение в событии PHP

Я использую оболочку класса PHP Event для libevent для чтения последовательного порта. Я использую это, чтобы избежать переполнения буфера - идея состоит в том, чтобы использовать Event для регулярной проверки порта, чтобы данные не были потеряны.

Я надеялся, что события будут срабатывать только тогда, когда он установлен, но пришел к выводу, что события срабатывают только после вызова EventBase::loop(). В этом случае поток управления переходит от моего кода к диспетчеру в libevent, когда я вызываю loop(). В конце концов поток управления возвращается к моему коду в позиции после вызова цикла.

Исходя из этого поведения, я предположил, что я, по сути, планирую отправку событий и должен регулярно вызывать loop(), чтобы мои события не испытывали нехватки ресурсов ЦП.

Однако в этом сценарии я никогда не смогу вызвать 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();




?>

person betchern0t    schedule 30.11.2016    source источник
comment
Я добавил тестовый код, который использую. Я включил предложения от русского. Единственное изменение — неблокирующий флаг на базе. Это приводит к отсутствию вывода. Без неблокирующего флага он выводится нормально. В настоящее время в нижней части находится бесконечный цикл, который в конечном итоге будет закодирован для обработки пакетов, сохраненных в обратном вызове события. Цель состояла в том, чтобы отдать приоритет последовательной обработке и делать все остальное, пока на порту нет данных.   -  person betchern0t    schedule 01.12.2016


Ответы (1)


Я надеялся, что события будут срабатывать только тогда, когда он установлен, но пришел к выводу, что события срабатывают только после вызова EventBase::loop().

Event::__construct() регистрирует событие и связывает его с EventBase. На данный момент объект Event представляет собой набор условий и обратных вызовов для определенных событий. В этом состоянии событие не запускается.

Event::add() делает событие ожидающим. Когда событие находится в состоянии ожидания, оно готово к запуску при выполнении соответствующих условий.

EventBase::loop() запускает EventBase до тех пор, пока в нем не закончатся ожидающие события. Событие может быть вызвано только тогда, когда соответствующая база работает.

Когда событие запускается, оно становится активным, и выполняется его обратный вызов. Если событие настроено как постоянное, оно остается в ожидании после запуска обратного вызова. В противном случае он перестает быть ожидающим. Учти это:

$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(), чтобы мои события не испытывали нехватки ресурсов ЦП.

Да, вы планируете события перед запуском базы. Нет, вам не следует регулярно вызывать EventBase::loop(), и вам не нужно думать о том, что ЦП «голодает», поскольку базовая реализация основана на эффективных механизмах обработки для конкретных платформ, таких как epoll, poll, kqueue и т. д. В состоянии простоя (когда работающая база только и ждет, когда произойдут события), процесс потребляет пренебрежимо малое количество системных ресурсов.

Вы можете управлять потоком с помощью событий таймера, добавляя/удаляя события или изменяя свойства событий в их обратных вызовах, например.

ДИО

Потоки DIO в настоящее время не распознаются расширением Event. Не существует простого способа получить дескриптор файла, инкапсулированный в ресурс 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();
person Ruslan Osmanov    schedule 30.11.2016
comment
Спасибо русский. Очень полный ответ. Однако я использую неблокирующий флаг, чтобы специально вернуть управление моему другому коду, если три - это отсутствие данных на порту. Вы предлагаете мне запустить этот код на основе обратного вызова события? - person betchern0t; 01.12.2016
comment
Еще раз спасибо, русский. Я собираюсь переписать свой код. С вашей помощью я преодолел один из барьеров и преодолел другой. Всегда есть чему поучиться. Дескриптор DIO с событием — я пробовал противоположный подход. Использовал uri php://fd/.(int)fd для получения пути, а затем создал указатель потока, подключив его к fopen. Кажется, это не работает, но, возможно, это проблемы в другом месте моего кода. - person betchern0t; 01.12.2016
comment
Что касается исправлений и т. Д., Моя единственная реальная проблема заключается в том, что все это происходит на Raspberry Pi, и я немного боюсь делать что-либо сверхмощное из-за крошечного процессора. - person betchern0t; 01.12.2016