背景:
想利用 PHP的shmop、ThinkPHP框架下的自定义命令行 实现一个常驻进程的自动任务,解决原本用crontab实现秒级别的麻烦,同时可以快速查看自动任务的状态,但要求确保当前的自动任务只起了一个进程。
问题:
在shmop创建一个共享内存,并存入值之后,在当前进程或其他进程都无法shmop_open?
代码:
<?php
namespace app\autotask;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\console\input\Argument;
use think\console\input\Option;
use think\Log;
class Demo extends Command
{
const SHARE_SIZE = 1024;
protected function configure()
{
//设置参数
$this->addArgument('act', Argument::REQUIRED); //必传参数
//帮助文档中的命令备注
$this->setName('demo')->setDescription('Here is the remark of autotask-demo');
}
/**
* 可以这样执行命令 php think demo start|stop|status
* @param Input $input
* @param Output $output
*/
protected function execute(Input $input, Output $output)
{
//获取参数值
$args = $input->getArguments();
if(!in_array($args['act'],['start','stop','status'])){
$output->writeln('Unknow args!');
}
switch ($args['act']){
case 'start':
$res = $this->setRuningStatus(true);
if($res){
$msg ='Demo start success!';
}else{
$msg ='Demo start fail!';
}
break;
case 'stop':
$res = $this->setRuningStatus(false);
if($res){
$msg ='Demo stop success!';
}else{
$msg ='Demo stop fail!';
}
break;
case 'status':
$population = $this->getRuningStatus();
if($population===false){
$status = 'notfound';
}elseif($population===1){
$status = 'start';
}elseif($population===2){
$status = 'stop';
}else{
$status = 'unknow';
}
$msg = "The Demo's status is ".$status;
break;
}
$output->writeln($msg);
if($args['act']=='start' && $res){
$this->keepStart();
}
}
protected function getRuningStatus()
{
$shmop_key = $this->getShmopKey();
if($shmop_key==-1){
return false;
}
$shmop_id = shmop_open($shmop_key,'w',0,self::SHARE_SIZE);
$shmop_size = shmop_size($shmop_id);
$population = shmop_read($shmop_id,0, $shmop_size);
shmop_close($shmop_id);
return $population;
}
protected function setRuningStatus($start)
{
$shmop_key = $this->getShmopKey();
if($shmop_key==-1){
return false;
}
$shmop_id = shmop_open($shmop_key,'c',0644,self::SHARE_SIZE);
if($start===true){
$population = 1;
}else{
$population = 2;
}
$shmop_bytes_written = shmop_write($shmop_id, $population,0);
shmop_close($shmop_id);
if ($shmop_bytes_written != strlen($population)) {
return false;
} else {
return true;
}
}
protected function getShmopKey()
{
if(!function_exists('ftok')) {
$shmop_key = myftok(__FILE__, 23);
}else{
$shmop_key = ftok(__FILE__,'p');
}
return $shmop_key;
}
protected function keepStart()
{
$output = new Output;
while (true){
if($this->getRuningStatus()!==1) {
$res = $this->setRuningStatus(true);
if($res){
$msg ='Demo auto start success!';
}else{
$msg ='Demo auto start fail!';
}
$output->writeln($msg);
}else{
sleep(3);
}
}
}
}
//$shmop_key = ftok(__FILE__, 'p'); 在win环境下,不支持这样的功能,故重写
function myftok($pathname, $proj_id)
{
$st = @stat($pathname);
if (!$st) {
return -1;
}
$key = sprintf("%u", (($st['ino'] & 0xffff) | (($st['dev'] & 0xff) << 16) | (($proj_id & 0xff) << 24)));
return $key;
}
报错信息:
shmop_open(): unable to attach to shared memory segment 'No error'
备注:
1.查找了很多资料,暂时没找到可解决的。
2.官方说shmop_open读取已有内存时,第3和第4个参数为0,但调试后发现这样会报另一个错误
shmop_open(): Shared memory segment size must be greater than zero
3.本人新手勿喷,请直戳重点,求大神解答,十分感谢!
看了背景之后觉得共享内存并不是很适合这种场景,而且 PHP shmop 共享内存这种通信方式确实有一些小坑。建议可以使用本地 socket ,方便且适应性强。
排查一下是否是打开的 flags 不对或者调用顺序错误。