digitalstars / daemon
用于轻松工作和创建PHP守护进程的库
Requires
- php: >=7.1
- ext-pcntl: *
- ext-posix: *
- ext-sysvmsg: *
README
社区
为什么选择SimpleDaemon?
- 轻量级 - Daemon不依赖大量重型库,仅依赖PHP运行所需的模块
- 通用性和简单性 - 尽管轻量级且简单,Daemon仍然是通用且灵活的解决方案,提供大多数情况下所需的功能
- 多线程 - 同时支持多线程数据处理(仅适用于Linux)
功能
库中包含2个模块
- 服务器 - 处理客户端传输的数据,也支持多线程模式(仅适用于Linux系统)
- 客户端 - 可以连接到任何PHP文件以快速向服务器发送数据并调用服务器方法
它如何工作以及为什么需要它
服务器上可以实现需要大量计算且不返回任何内容的函数。客户端可以快速调用这些函数并向它们传递数据(如果所有守护进程进程都忙碌,则调用将积聚在队列中)。但是,一旦函数在服务器上完成,就无法将其返回给客户端(由于PHP缺乏异步性)。
一个典型的例子是日志记录或复杂计算。假设你有一个处理请求的Web服务器,在这些请求中需要执行复杂函数的计算,并将结果记录到数据库中(不作为响应返回)。在这种情况下,可以将计算值并将其记录到数据库的操作移到守护进程服务器脚本中,然后在处理Web服务器的脚本中初始化守护进程客户端,该客户端只需调用带有必要参数的函数,然后无需等待其执行即可向用户返回所需信息。以下是一个示意图
目录
- 连接
- 服务器
- 初始化服务器
- 服务器设置方法
- isLog($is = true) - 将输出重定向到文件或忽略输出
- isMultiThread() - 启用多线程
- maxThreads(int $count) - 最大子进程数
- maxSize(int $size) - 设置最大消息大小
- 服务器的基本方法
- init($func) - 初始化服务器
- errorHandler($func) - 错误处理
- module($id, $func) - 注册客户端可以调用的函数(守护进程服务器的主要功能在这里)
- 服务器辅助方法 - 是一些极端情况,所有这些方法都有类似的控制台命令
- bool clear() - 清除消息队列
- stop() - 停止服务器
- kill() - 强制停止服务器
- int isActive() - 检查守护进程是否正在运行
- run() - 运行守护进程
- 服务器工作管理 - 通过控制台命令管理守护进程服务器的工作
- 客户端
- 客户端初始化
- 客户端方法
- errorHandler($func) - 客户端错误处理
- send($module_id, $msg = []) - 调用服务器函数并发送参数 (客户端的主要功能在这里)
连接
使用composer
- 安装
composer require digitalstars/daemon
- 连接
autoload.php
require_once "vendor/autoload.php";
手动
- 从github下载最新版本
- 连接
autoload.php
.
如果您的脚本与
daemon-master
文件夹在同一目录下,连接方式如下:
require_once "daemon-master/autoload.php";
服务器
服务器是一个独立的PHP脚本,其中实现了数据处理的方法。此脚本在后台运行,独立于其他脚本。为了管理服务器状态,提供了控制台命令。可以同时运行多个服务器。
客户端在初始化时通过ID连接到服务器。它们必须匹配!
初始化服务器
require_once "vendor/autoload.php"; // Подключаем файлы use DigitalStars\Daemon\Server; // Подключаем класс Server $s = new Server(9); // Инициализируем демона с ID = 9
同时支持通过Create
方法进行初始化,以方便使用。
$s = Server::create(9);
服务器设置方法
所有配置方法都返回 $this。因此,可以在初始化后立即通过 Server::create() 方法调用它们进行链式调用。
示例:$s = Server::create(9)->isLog(false)->isMultiThread()->maxThreads(4);
isLog($is = true)
用于管理日志的方法。如果指定true
,则在脚本目录中创建新的目录daemon_logs
,其中包含3个日志文件
- error.log - 包含运行时发生的错误
- application.log - 成为标准输出(所有应输出到正常工作脚本控制台的消息都将在此文件中)
- daemon.log - 也包含运行时发生的错误,但类型不同
如果关闭日志记录,则错误和其他消息的输出将被忽略。
isMultiThread()
方法激活数据的多线程处理,只有当php存在ext-pcntl
模块时才能激活多线程。如果没有该模块,则将引发异常
maxThreads(int $count)
设置多线程时允许的最大子进程数量。
maxSize(int $size)
设置最大消息大小的方法。有关详细信息,请参阅此处(参数 max_message_size)。
如果不确定,最好不要动
服务器的基本方法
init($func)
设置初始化变量的函数,这些变量应在数据处理期间可用。在此函数内部,可以初始化,例如,用于数据库操作的对象
示例
use PDO; use DigitalStars\Daemon\Server; $s = Server::create(9); $db = null; $s->init(function () use (&$db) { $db = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'DB_USER', 'DB_PASSWORD'); });
errorHandler($func)
设置用于处理错误的函数。在这种情况下,如果指定了函数,则不会抛出异常。错误信息以以下形式传递给函数
$func($error, $message, $file, $line, $e)
其中
- $error - 错误代码,可能是
ERROR_DAEMON
- 守护进程内部错误(如果找不到某个php模块或无法执行某事)ERROR_MODULE
- 模块内部错误- PHP错误类型(
E_ERROR
,E_WARNING
,E_PARSE
,E_NOTICE
,E_CORE_ERROR
,E_CORE_WARNING
,E_COMPILE_ERROR
,E_COMPILE_WARNING
,E_USER_ERROR
,E_USER_WARNING
,E_USER_NOTICE
,E_STRICT
,E_RECOVERABLE_ERROR
,E_DEPRECATED
,E_USER_DEPRECATED
)
- $message - 错误消息
- $file - 发生错误的文件
- $line - 行号
- $e - Throwable(Exception的父类)实例,描述错误
注意! $file,$line,$e 只在错误类型不是
ERROR_DAEMON
的情况下存在,否则将只显示错误文本描述。
示例
use PDO; use DigitalStars\Daemon\Server; $s = Server::create(9); $db = null; $s->init(function () use (&$db) { $db = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'DB_USER', 'DB_PASSWORD'); }); $s->errorHandler(function ($error, $error_str, $error_file, $error_line, $e) use ($db) { // Составляем текст ошибки $text = "Error: $error\n" . "Str: $error_str" . "File: $error_file ($error_line)\n" . (($e instanceof Throwable) ? "Back Trace:" . $e->getTraceAsString() : ""); // Записываем ошибку в таблицу логов в БД $db->prepare("INSERT INTO error_log (text_error) VALUE (?)")->execute([$text]); });
module($id, $func)
方法注册新的函数,可以从客户端通过ID(第一个参数)调用,并且如果需要,可以将其数据传递给它
实际上,这个方法就是守护进程的主要功能。
示例
服务器代码
use PDO; use DigitalStars\Daemon\Server; $s = Server::create(9); $db = null; $s->init(function () use (&$db) { $db = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'DB_USER', 'DB_PASSWORD'); }); $s->module('testFunction', function ($text) use (&$db) { $db->prepare("INSERT INTO log_action (text_value) VALUE (?)")->execute([$text]); });
客户端代码
use DigitalStars\Daemon\Client; $c = Client::create(9); $c->testFunction('Какое-то тестовое сообщение, которое передастся серверу');
在这个例子中,客户端向服务器发送消息,服务器接收并记录到数据库。
服务器服务方法
这些方法通常不需要使用。它们在库内部自动调用,但在处理某些特殊情况时可能有用。
bool clear()
用于清除消息队列的方法。默认情况下,即使服务器未启动,客户端的消息也会积累在队列中。该方法清除队列,并在成功时返回 true,在错误时返回 false。
stop()
该方法正常停止工作。这意味着,在调用方法时,守护进程将等待所有子进程完成,然后才会结束。
kill()
该方法异常终止守护进程。即使某些子进程尚未完成,它们也会被强制终止。
int isActive()
检查守护进程是否已启动。如果是,则返回主(父)进程的 PID。如果没有,则返回 false。
run()
启动守护进程的工作。
服务器工作管理
通常不需要使用“服务方法”部分的方法,可以直接从控制台管理守护进程的状态。守护进程支持以下控制台命令:
start
- 启动守护进程stop
- 停止守护进程kill
- 强制停止守护进程(未完成的进程将被杀死)status
- 获取状态(守护进程是否正在运行)restart
- 重启守护进程(这会使用stop
来停止)。在服务器代码中进行修改时可能很有用clear
- 清除消息队列
示例
创建一个名为 test_daemon.php
的守护进程服务器脚本,内容如下:
use PDO; use DigitalStars\Daemon\Server; $s = Server::create(9)->isMultiThread()->maxThreads(2); $db = null; $s->init(function () use (&$db) { $db = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'DB_USER', 'DB_PASSWORD'); }); $s->module('testFunction', function ($text) use (&$db) { $db->prepare("INSERT INTO log_action (text_value) VALUE (?)")->execute([$text]); });
要使其工作,需要执行命令: php test_daemon.php start
。
要停止工作,需要调用命令: php test_daemon.php stop
客户端
客户端可以在任何 PHP 脚本中初始化,可以使用客户端实例调用模块中实现的服务器函数,并传递参数。参数可以是任何原子变量、数组和一些对象(更多关于这一点可以在这里阅读这里,参数 message
和 serialize
设置为 true
)
客户端在初始化时通过ID连接到服务器。它们必须匹配!
客户端初始化
require_once "vendor/autoload.php"; // Подключаем файлы use DigitalStars\Daemon\Client; // Подключаем класс Server $c = new Client(9); // Инициализируем клиент демона с ID = 9 (ID должен совпадать у клиента и сервера)
同时支持通过Create
方法进行初始化,以方便使用。
$c = Client::create(9);
客户端方法
errorHandler($func)
客户端错误处理方法。与服务器端类似的方法相比,它更简单。将 Exception
实例传递给 $func
函数。如果函数已定义,则异常不会被抛出。
send($module_id, $msg = [])
该方法调用具有 ID = $module_id 的服务器函数。此时,将从数组 $msg 中传递参数到函数。
该方法也实现为魔术方法。也就是说,可以调用客户端上不存在的、在服务器上实现的函数,然后客户端会调用相应的方法,并将参数传递给它。
示例
服务器代码
use PDO; use DigitalStars\Daemon\Server; $s = Server::create(9); $db = null; $s->init(function () use (&$db) { $db = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'DB_USER', 'DB_PASSWORD'); }); $s->module('testFunctionTwoArguments', function ($val1, $val2) use (&$db) { $db->prepare("INSERT INTO log_action_two_field (field_1, field_2) VALUE (?, ?)")->execute([$val1, $val2]); }); $s->module('testFunctionOneArguments', function ($text) use (&$db) { $db->prepare("INSERT INTO log_action (text_value) VALUE (?)")->execute([$text]); });
客户端代码
use DigitalStars\Daemon\Client; $c = Client::create(9); // Следующие 2 строки равносильны. Они вызовут функцию testFunctionTwoArguments на сервере и передадут в неё 2 параметра $c->testFunctionTwoArguments('Значение 1', 'Значение 2'); $c->send('testFunctionTwoArguments', ['Значение 1', 'Значение 2']); // Следующие 3 строки равносильны. Они вызовут функцию testFunctionOneArguments на сервере и передадут в неё 1 параметр $c->testFunctionOneArguments('Одно значение'); $c->send('testFunctionOneArguments', ['Одно значение']); $c->send('testFunctionOneArguments', 'Одно значение');
在这个例子中,客户端向服务器发送消息,服务器接收并记录到数据库。