本篇文章关键词:Workerman事件类,Workerman定时类
一、事件类
1、事件类实现:
workerman实现了5种方式的事件类,在Events文件夹,分别是Ev,libevent,event,swoole,select。前四种需要安装对应扩展,最后一种是用队列与信号实现的,就是使用PHP原生函数实现。他们都集成EventInterface接口,这五种事件类表现出的方法是一样的。所以我们只看一种的内部实现,select类。
在上一章worker代码中,引入event的方法
protected static $_availableEventLoops = array( 'libevent' => '\Workerman\Events\Libevent', 'event' => '\Workerman\Events\Event', 'swoole' => '\Workerman\Events\Swoole' ); protected static function getEventLoopName() { if (static::$eventLoopClass) { return static::$eventLoopClass; } $loop_name = ''; foreach (static::$_availableEventLoops as $name=>$class) { if (extension_loaded($name)) { $loop_name = $name; break; } } if ($loop_name) { if (interface_exists('\React\EventLoop\LoopInterface')) { switch ($loop_name) { case 'libevent': static::$eventLoopClass = '\Workerman\Events\React\ExtLibEventLoop'; break; case 'event': static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop'; break; default : static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop'; break; } } else { static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; } } else { static::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select'; } return static::$eventLoopClass; }
这个方法是判断有没有安装相应的扩展,没安装就使用select事件类
2、new select,首先看select的构造方法,创建一个socket跟一个队列
public function __construct() { // 创建一个socket $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); if($this->channel) { stream_set_blocking($this->channel[0], 0); $this->_readFds[0] = $this->channel[0]; } // 新建优先队列.信号使用 $this->_scheduler = new \SplPriorityQueue(); $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); }
3、add(),注册一个事件。当发生此事件时,回调相应方法
调用的时候这样调用:static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, ‘acceptConnection’));
//$fd:文件描述符 //$flag:事件类型 //$func:事件回调函数 //$args:参数 public function add($fd, $flag, $func, $args = array()) { switch ($flag) { case self::EV_READ: $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_readFds[$fd_key] = $fd; break; case self::EV_WRITE: $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_writeFds[$fd_key] = $fd; break; case self::EV_EXCEPT: $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_exceptFds[$fd_key] = $fd; break; //注册的是信号 case self::EV_SIGNAL: // Windows not support signal. if(DIRECTORY_SEPARATOR !== '/') { return false; } $fd_key = (int)$fd; $this->_signalEvents[$fd_key][$flag] = array($func, $fd); pcntl_signal($fd, array($this, 'signalHandler')); break; case self::EV_TIMER: case self::EV_TIMER_ONCE: $timer_id = $this->_timerId++; $run_time = microtime(true) + $fd; //定时任务存在队列里 $this->_scheduler->insert($timer_id, -$run_time); //_eventTimer存此任务其他信息 $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); $select_timeout = ($run_time - microtime(true)) * 1000000; if( $this->_selectTimeout > $select_timeout ){ $this->_selectTimeout = $select_timeout; } return $timer_id; } return true; } public function signalHandler($signal) { call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); }
4、del(),删除事件,就是在队列里或者_allEvents数组里删除对应的事件标识
内网穿透– 用我最直白的语言解读优秀源码
public function del($fd, $flag) { $fd_key = (int)$fd; switch ($flag) { case self::EV_READ: unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_WRITE: unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_EXCEPT: unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); if(empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_SIGNAL: if(DIRECTORY_SEPARATOR !== '/') { return false; } unset($this->_signalEvents[$fd_key]); pcntl_signal($fd, SIG_IGN); break; case self::EV_TIMER: case self::EV_TIMER_ONCE; unset($this->_eventTimer[$fd_key]); return true; } return false; }
4、loop(),事件循环监听代码
public function loop() { $e = null; while (1) { if(DIRECTORY_SEPARATOR === '/') { // 如果有事件发生就执行下回调 pcntl_signal_dispatch(); } $read = $this->_readFds; $write = $this->_writeFds; $except = $this->_exceptFds; // 去掉错误 set_error_handler(function(){}); //stream_select是等待 read/write/signal/timeout 事件,在_selectTimeout内有对应事件发生返回发生的数量 $ret = stream_select($read, $write, $except, 0, $this->_selectTimeout); restore_error_handler(); // if (!$this->_scheduler->isEmpty()) { $this->tick(); } if (!$ret) { continue; } //如果有读事件发生,就执行add注册的事件的方法,并传入注册时的socket,如在worker.php中的static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));,就会执行acceptConnection if ($read) { foreach ($read as $fd) { $fd_key = (int)$fd; if (isset($this->_allEvents[$fd_key][self::EV_READ])) { call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], array($this->_allEvents[$fd_key][self::EV_READ][1])); } } } //同上 if ($write) { foreach ($write as $fd) { $fd_key = (int)$fd; if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], array($this->_allEvents[$fd_key][self::EV_WRITE][1])); } } } //同上 if($except) { foreach($except as $fd) { $fd_key = (int) $fd; if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); } } } } }
5、tick(),loop中执行此函数,在队列中循环寻找可以执行的事件
protected function tick() { while (!$this->_scheduler->isEmpty()) { $scheduler_data = $this->_scheduler->top(); $timer_id = $scheduler_data['data']; $next_run_time = -$scheduler_data['priority']; $time_now = microtime(true); $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; //此事件到时间可以执行了 if ($this->_selectTimeout <= 0) { $this->_scheduler->extract(); if (!isset($this->_eventTimer[$timer_id])) { continue; } // [func, args, flag, timer_interval] $task_data = $this->_eventTimer[$timer_id]; if ($task_data[2] === self::EV_TIMER) { $next_run_time = $time_now + $task_data[3]; $this->_scheduler->insert($timer_id, -$next_run_time); } //执行 call_user_func_array($task_data[0], $task_data[1]); //是否需要循环执行 if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { $this->del($timer_id, self::EV_TIMER_ONCE); } continue; } return; } $this->_selectTimeout = 100000000; }
贴上完整的select类源代码
内网穿透– 用我最直白的语言解读优秀源码
<?php /** * This file is part of workerman. * * Licensed under The MIT License * For full copyright and license information, please see the MIT-LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @author walkor<walkor@workerman.net> * @copyright walkor<walkor@workerman.net> * @link http://www.workerman.net/ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Workerman\Events; /** * select eventloop */ class Select implements EventInterface { /** * All listeners for read/write event. * * @var array */ public $_allEvents = array(); /** * Event listeners of signal. * * @var array */ public $_signalEvents = array(); /** * Fds waiting for read event. * * @var array */ protected $_readFds = array(); /** * Fds waiting for write event. * * @var array */ protected $_writeFds = array(); /** * Fds waiting for except event. * * @var array */ protected $_exceptFds = array(); /** * Timer scheduler. * {['data':timer_id, 'priority':run_timestamp], ..} * * @var \SplPriorityQueue */ protected $_scheduler = null; /** * All timer event listeners. * [[func, args, flag, timer_interval], ..] * * @var array */ protected $_eventTimer = array(); /** * Timer id. * * @var int */ protected $_timerId = 1; /** * Select timeout. * * @var int */ protected $_selectTimeout = 100000000; /** * Paired socket channels * * @var array */ protected $channel = array(); /** * Construct. */ public function __construct() { // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); if($this->channel) { stream_set_blocking($this->channel[0], 0); $this->_readFds[0] = $this->channel[0]; } // Init SplPriorityQueue. $this->_scheduler = new \SplPriorityQueue(); $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); } /** * {@inheritdoc} */ public function add($fd, $flag, $func, $args = array()) { switch ($flag) { case self::EV_READ: $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_readFds[$fd_key] = $fd; break; case self::EV_WRITE: $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_writeFds[$fd_key] = $fd; break; case self::EV_EXCEPT: $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_exceptFds[$fd_key] = $fd; break; case self::EV_SIGNAL: // Windows not support signal. if(DIRECTORY_SEPARATOR !== '/') { return false; } $fd_key = (int)$fd; $this->_signalEvents[$fd_key][$flag] = array($func, $fd); pcntl_signal($fd, array($this, 'signalHandler')); break; case self::EV_TIMER: case self::EV_TIMER_ONCE: $timer_id = $this->_timerId++; $run_time = microtime(true) + $fd; $this->_scheduler->insert($timer_id, -$run_time); $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); $select_timeout = ($run_time - microtime(true)) * 1000000; if( $this->_selectTimeout > $select_timeout ){ $this->_selectTimeout = $select_timeout; } return $timer_id; } return true; } /** * Signal handler. * * @param int $signal */ public function signalHandler($signal) { call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); } /** * {@inheritdoc} */ public function del($fd, $flag) { $fd_key = (int)$fd; switch ($flag) { case self::EV_READ: unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_WRITE: unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_EXCEPT: unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); if(empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_SIGNAL: if(DIRECTORY_SEPARATOR !== '/') { return false; } unset($this->_signalEvents[$fd_key]); pcntl_signal($fd, SIG_IGN); break; case self::EV_TIMER: case self::EV_TIMER_ONCE; unset($this->_eventTimer[$fd_key]); return true; } return false; } /** * Tick for timer. * * @return void */ protected function tick() { while (!$this->_scheduler->isEmpty()) { $scheduler_data = $this->_scheduler->top(); $timer_id = $scheduler_data['data']; $next_run_time = -$scheduler_data['priority']; $time_now = microtime(true); $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; if ($this->_selectTimeout <= 0) { $this->_scheduler->extract(); if (!isset($this->_eventTimer[$timer_id])) { continue; } // [func, args, flag, timer_interval] $task_data = $this->_eventTimer[$timer_id]; if ($task_data[2] === self::EV_TIMER) { $next_run_time = $time_now + $task_data[3]; $this->_scheduler->insert($timer_id, -$next_run_time); } call_user_func_array($task_data[0], $task_data[1]); if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { $this->del($timer_id, self::EV_TIMER_ONCE); } continue; } return; } $this->_selectTimeout = 100000000; } /** * {@inheritdoc} */ public function clearAllTimer() { $this->_scheduler = new \SplPriorityQueue(); $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); $this->_eventTimer = array(); } /** * {@inheritdoc} */ public function loop() { $e = null; while (1) { if(DIRECTORY_SEPARATOR === '/') { // Calls signal handlers for pending signals pcntl_signal_dispatch(); } $read = $this->_readFds; $write = $this->_writeFds; $except = $this->_exceptFds; // Waiting read/write/signal/timeout events. set_error_handler(function(){}); $ret = stream_select($read, $write, $except, 0, $this->_selectTimeout); restore_error_handler(); if (!$this->_scheduler->isEmpty()) { $this->tick(); } if (!$ret) { continue; } if ($read) { foreach ($read as $fd) { $fd_key = (int)$fd; if (isset($this->_allEvents[$fd_key][self::EV_READ])) { call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], array($this->_allEvents[$fd_key][self::EV_READ][1])); } } } if ($write) { foreach ($write as $fd) { $fd_key = (int)$fd; if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], array($this->_allEvents[$fd_key][self::EV_WRITE][1])); } } } if($except) { foreach($except as $fd) { $fd_key = (int) $fd; if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); } } } } } /** * Destroy loop. *内网穿透– 用我最直白的语言解读优秀源码 * @return void */ public function destroy() { } /** * Get timer count. * * @return integer */ public function getTimerCount() { return count($this->_eventTimer); } }
二、定时任务类
定时任务类Timer使用两种定时任务方法,一种使用上面的事件,另一种是使用系统的时钟信号,精度一秒,系统每秒给Timer发送一次信号,Timer执行tick函数检查有没有执行的任务。
<?php /** * This file is part of workerman. * * Licensed under The MIT License * For full copyright and license information, please see the MIT-LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @author walkor<walkor@workerman.net> * @copyright walkor<walkor@workerman.net> * @link http://www.workerman.net/ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Workerman\Lib; use Workerman\Events\EventInterface; use Exception; /** * Timer. * * example: * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); */ class Timer { /** * Tasks that based on ALARM signal. * [ * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], * .. * ] * * @var array */ protected static $_tasks = array(); /** * event * * @var \Workerman\Events\EventInterface */ protected static $_event = null; /** * 初始化,如果不传则使用时钟信号,否则使用对应的事件函数 * * @param \Workerman\Events\EventInterface $event * @return void */ public static function init($event = null) { if ($event) { self::$_event = $event; } else { if (function_exists('pcntl_signal')) { pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); } } } /** * 时钟信号回调,每秒回调一次 * * @return void */ public static function signalHandle() { if (!self::$_event) { pcntl_alarm(1); self::tick(); } } /** * 增加一个定时任务 * * @param float $time_interval * @param callable $func * @param mixed $args * @param bool $persistent * @return int/false */ public static function add($time_interval, $func, $args = array(), $persistent = true) { if ($time_interval <= 0) { Worker::safeEcho(new Exception("bad time_interval")); return false; } //使用事件类的话 if (self::$_event) { return self::$_event->add($time_interval, $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); } if (!is_callable($func)) { Worker::safeEcho(new Exception("not callable")); return false; } if (empty(self::$_tasks)) { pcntl_alarm(1); } //不使用事件类,加到自己的self::$_tasks中 $time_now = time(); $run_time = $time_now + $time_interval; if (!isset(self::$_tasks[$run_time])) { self::$_tasks[$run_time] = array(); } self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); return 1; } /** * 不使用事件类执行 * * @return void */ public static function tick() { if (empty(self::$_tasks)) { pcntl_alarm(0); return; } $time_now = time(); foreach (self::$_tasks as $run_time => $task_data) { if ($time_now >= $run_time) { foreach ($task_data as $index => $one_task) { $task_func = $one_task[0]; $task_args = $one_task[1]; $persistent = $one_task[2]; $time_interval = $one_task[3]; try { call_user_func_array($task_func, $task_args); } catch (\Exception $e) { Worker::safeEcho($e); } if ($persistent) { self::add($time_interval, $task_func, $task_args); } } unset(self::$_tasks[$run_time]); } } } /** * 删除一个定时 * * @param mixed $timer_id * @return bool */ public static function del($timer_id) { if (self::$_event) { return self::$_event->del($timer_id, EventInterface::EV_TIMER); } return false; } /** * 删除所有 * * @return void */ public static function delAll() { self::$_tasks = array(); pcntl_alarm(0); if (self::$_event) { self::$_event->clearAllTimer(); } } }
内网穿透– 用我最直白的语言解读优秀源码