PHP物联网开发利器之Actor并发模型

PHP不适合作物联网服务端吗?

在传统的思惟中,常常会有人告诉你,php不适合用来作物联网服务端,让你换java,node,go等其余语言,是的,没错传统意义上的php,确实很难作物联网服务器,由于它实在太蹩脚了,固然,这也不是意味着完全就不能作。举个例子,当你想实现一个TCP服务器的时候,你可能须要写出原理大约以下的代码:php

for ($i = 0;$i <= 1;$i++){
    $pid = pcntl_fork();
    if($pid){
        if($i == 0){
            $server = stream_socket_server("tcp://127.0.0.1:9501", $errno, $errstr, STREAM_SERVER_BIND);
        }else if($i == 1){
            $tickTime = time()+3600;
            while (1){
                usleep(1);
                if($tickTime == time()){
                    //do my tick func
                }
            }
        }
    }
}

以上代码的意义等于在一个进程中建立一个TCP 服务端,另一个进程中死循环来作时间检测,从而实现定时器逻辑。这样看起来,确实很蹩脚,并且对于编程基础广泛比较薄弱的PHPer来讲,这真的很难维护。固然这个时候,就会有人说,这不是还有Workerman吗,是的,确实还有Workerman,Workerman就是高度封装了上述代码原理,帮助你专心于实现代码逻辑的一个PHP多进程框架,所以说PHP不时候作物联网,其实这是谬论。固然这个时候可能又会有人说,go语言有协程,你用Workerman当出现阻塞数据库调用的时候,那效率就很是的差,很难出现高并发,这么说没错,可是实际上,咱们能够尽量的用多进程去弥补这个不足,也就是堆机器。固然,若是你真的想锱铢必较,不要紧,这个时候咱们就能够拿出咱们的杀器,那就是Swoole4.x的协程。java

Swoole作TCP服务器

举个例子,以下代码:node

$server = new swoole_server("127.0.0.1", 9501);
$server->on('workerstart',function ($ser,$workerId){
    if($workerId == 0){
        swoole_timer_tick(1000,function (){
            
        });
    }
});
$server->on('connect', function ($server, $fd){
    echo "connection open: {$fd}\n";
});
$server->on('receive', function ($server, $fd, $reactor_id, $data) {
    $server->send($fd, "Swoole: {$data}");
    $server->close($fd);
});
$server->on('close', function ($server, $fd) {
    echo "connection close: {$fd}\n";
});
$server->start();

咱们就能够很快的建立出一个多进程的协程TCP服务器,并且在各个回调函数内,均自动建立协程环境,咱们能够在协程回调内,去调用协程的数据库API,这样就避免了由于阻塞数据库调用而致使没法处理其余客户端请求的问题。然而尽管如此,不少人可能都没有思考过,如何优雅的写出本身的物联网服务器。举个例子,咱们常见的互联网设备管理服务中,大约可能出现以下代码:react

swoole_timer_tick(5000,function (){
    $deviceList = $db->getAll();
    foreach ($deviceList as $device){
        //do your check
        /*
         * 例如设备状态处于1,那么须要处理流程1
         * 例如设备状态处于2,那么须要处理流程2
         * 例如设备状态处于3,那么须要处理流程3
         */
    }
});
定时遍历检查设备状态以及广播
这样乍一看好像无伤大雅,可是当出现多种设备,且每种设备逻辑都不一致的时候,那么这样的编写模式就很容易写出一大坨代码出来,并且在协程下,若是不注意变量访问安全与协程上下文隔离,那么就很容易出现bug,致使很难维护。

Actor模型

什么是Actor,简单来讲,Actor就是一种高度抽象化的并发模型,每一个Actor实例的内存空间都是互相隔离的,用于下降用户编程与维护难度。关于Swoole4.x如何实现协程版本的Actor,咱们以前已经在文章 https://segmentfault.com/a/11... 中讲解了如何用Swoole实现协程的原理。git

Actor模型库实战

咱们依旧用easyswoole/actor库来说解,例如,咱们有一种型号的设备,那么咱们能够定义一个设备Actor,并把该设备的所有逻辑,写在该actor模型内,例子代码以下:github

namespace App\Device;


use EasySwoole\Actor\AbstractActor;
use EasySwoole\Actor\ActorConfig;
use EasySwoole\EasySwoole\Logger;
use EasySwoole\EasySwoole\ServerManager;
use EasySwoole\EasySwoole\Trigger;

class DeviceActor extends AbstractActor
{
    private $fd;
    private $deviceId;
    private $lastHeartBeat;
    public static function configure(ActorConfig $actorConfig)
    {
       $actorConfig->setActorName('Device');
    }

    protected function onStart()
    {
        $this->lastHeartBeat = time();
        /*
         * 该参数是建立的时候传递的
         */
        $this->fd = $this->getArg()['fd'];
        $this->deviceId = $this->getArg()['deviceId'];
        //记录到table manager中
        DeviceManager::addDevice(new DeviceBean([
            'deviceId'=>$this->deviceId,
            'actorId'=>$this->actorId(),
            'fd'=>$this->fd
        ]));
        //推送消息
        ServerManager::getInstance()->getSwooleServer()->push($this->fd,"connect to server success,your actorId is {$this->actorId()}");
        //建立一个定时器,若是一个设备20s没有收到消息,自动下线
        $this->tick(20*2000,function (){
            if(time() - $this->lastHeartBeat > 20){
                $this->exit(-1);
            }
        });
    }

    protected function onMessage($msg)
    {
        if($msg instanceof Command){
            switch ($msg->getCommand()){
                case $msg::RECONNECT:{
                    DeviceManager::updateDeviceInfo($this->deviceId,[
                        'fd'=>$msg->getArg()
                    ]);
                    $this->fd = $msg->getArg();
                    Logger::getInstance()->console("deviceId {$this->deviceId}  at actorId {$this->actorId()} reconnect success");
                    ServerManager::getInstance()->getSwooleServer()->push($this->fd,"deviceId {$this->deviceId}  at actorId {$this->actorId()} reconnect success");
                    break;
                }
                case $msg::WS_MSG:{
                    $recv = $msg->getArg();
                    Logger::getInstance()->console("deviceId {$this->deviceId}  at actorId {$this->actorId()} recv ws msg: {$recv}");
                    ServerManager::getInstance()->getSwooleServer()->push($this->fd,'actor recv msg for hash '.md5($recv));
                    break;
                }
                case $msg::REPLY_MSG:{
                    $recv = $msg->getArg();
                    Logger::getInstance()->console("deviceId {$this->deviceId}  at actorId {$this->actorId()} recv reply msg: {$recv}");
                    ServerManager::getInstance()->getSwooleServer()->push($this->fd,'actor recv reply msg '.$recv);
                    //此处return 一个数据,会返回给客户端
                    return "actorId {$this->actorId()} recv {$recv}";
                    break;
                }
            }
        }
    }

    protected function onExit($arg)
    {
        if($arg == -1){
            if(ServerManager::getInstance()->getSwooleServer()->exist($this->fd)){
                ServerManager::getInstance()->getSwooleServer()->push($this->fd,"heartbeat lost,actor exit");
                ServerManager::getInstance()->getSwooleServer()->close($this->fd);
            }
        }
        DeviceManager::deleteDevice($this->deviceId);
        Logger::getInstance()->console("deviceId {$this->deviceId} at actorId {$this->actorId()} exit");
    }

    protected function onException(\Throwable $throwable)
    {
        Trigger::getInstance()->throwable($throwable);
    }
}

在该Actor内,咱们定义了这个设备的生命周期行为。数据库

  • 设备上线,记录设备id与fd信息,并建立心跳周期检查
  • 收到消息,能够对该Actor投递数据,处理对应的消息行为
  • 设备下线,当设备下线,能够自动的清理定时器与其余的一些通知与清理逻辑

咱们能够很清楚的看到,Actor模型下,容许咱们对一种设备模型进行高度自治的管理。固然,咱们本章节主要在讲解如何优雅的利用Swoole协程来实现Actor模型,从而更好的开发管理咱们的设备,所以我再也不贴过多的代码,有兴趣的同窗能够在Easyswoole框架demo中查看完整的示例代码https://github.com/easy-swool...编程

Easyswoole项目主页:http://easyswoole.com/
Easyswoole github 主仓库https://github.com/easy-swool... ,若是你以为咱们的努力有对你起到帮助做用,记得给个starsegmentfault

相关文章
相关标签/搜索