libevent — циклическое поведение в PHP Event

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

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

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

Однако в этом случае я никогда не смогу позвонить loop() в то время как предыдущий loop() call выполнялся, потому что, как объяснялось выше, поток управления находится либо в моем коде, либо в libevent и не может быть в обоих.

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

Я явно не понимаю этого. Кто-нибудь может помочь?

Ура Пол

<?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 = 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();
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]