Swoft源码之Swoole和Swoft的分析

这篇文章给大家分享的内容是关于Swoft 源码剖析之Swoole和Swoft的一些介绍(Task投递/定时任务篇),有一定的参考价值,有需要的朋友可以参考一下。

前言

Swoft的任务功能基于SwooleTask机制,或者说SwoftTask机制本质就是对SwooleTask机制的封装和加强。

我的官方群点击此处

任务投递

//Swoft\Task\Task.php

class Task

{

    /**

     * Deliver coroutine or async task

     *

     * @param string $taskName

     * @param string $methodName

     * @param array  $params

     * @param string $type

     * @param int    $timeout

     *

     * @return bool|array

     * @throws TaskException

     */

    public static function deliver(string $taskName,string $methodName,array $params = [],string $type = self::TYPE_CO,$timeout = 3)

    {

        $data   = TaskHelper::pack($taskName,$methodName,$params,$type);

 

        if(!App::isWorkerStatus() && !App::isCoContext()){

            return self::deliverByQueue($data);//见下文Command章节

        }

 

        if(!App::isWorkerStatus() && App::isCoContext()){

            throw new TaskException('Please deliver task by http!');

        }

 

 

        $server = App::$server->getServer();

        // Delier coroutine task

        if ($type == self::TYPE_CO) {

            $tasks[0]  = $data;

            $prifleKey = 'task' . '.' . $taskName . '.' . $methodName;

 

            App::profileStart($prifleKey);

            $result = $server->taskCo($tasks,$timeout);

            App::profileEnd($prifleKey);

 

            return $result;

        }

 

        // Deliver async task

        return $server->task($data);

    }

}

 

任务投递Task::deliver()将调用参数打包后根据$type参数通过Swoole$server->taskCo()$server->task()接口投递到Task进程
Task本身始终是同步执行的,$type仅仅影响投递这一操作的行为,Task::TYPE_ASYNC对应的$server->task()是异步投递,Task::deliver()调用后马上返回;Task::TYPE_CO对应的$server->taskCo()是协程投递,投递后让出协程控制,任务完成或执行超时后Task::deliver()才从协程返回。

任务执行

//Swoft\Task\Bootstrap\Listeners\TaskEventListener 

/**

 * The listener of swoole task

 * @SwooleListener({

 *     SwooleEvent::ON_TASK,*     SwooleEvent::ON_FINISH,* })

 */

class TaskEventListener implements TaskInterface,FinishInterface

{

    /**

     * @param \Swoole\Server $server

     * @param int            $taskId

     * @param int            $workerId

     * @param mixed          $data

     * @return mixed

     * @throws \InvalidArgumentException

     */

    public function onTask(Server $server,int $taskId,int $workerId,$data)

    {

        try {

            /* @var TaskExecutor $taskExecutor*/

            $taskExecutor = App::getBean(TaskExecutor::class);

            $result = $taskExecutor->run($data);

        } catch (\Throwable $throwable) {

            App::error(sprintf('TaskExecutor->run %s file=%s line=%d ',$throwable->getMessage(),$throwable->getFile(),$throwable->getLine()));

            $result = false;

 

            // Release system resources

            App::trigger(AppEvent::RESOURCE_RELEASE);

 

            App::trigger(TaskEvent::AFTER_TASK);

        }

        return $result;

    }

}

 

此处是swoole.onTask的事件回调,其职责仅仅是将将Worker进程投递来的打包后的数据转发给TaskExecutor

SwooleTask机制的本质是Worker进程将耗时任务投递给同步的Task进程(又名TaskWorker)处理,所以swoole.onTask的事件回调是在Task进程中执行的。上文说过,Worker进程是你大部分HTTP服务代码执行的环境,但是从TaskEventListener.onTask()方法开始,代码的执行环境都是Task进程,也就是说,TaskExecutor和具体的TaskBean都是执行在Task进程中的。

//Swoft\Task\TaskExecutor

/**

 * The task executor

 *

 * @Bean()

 */

class TaskExecutor

{

    /**

     * @param string $data

     * @return mixed

    */

    public function run(string $data)

    {

        $data = TaskHelper::unpack($data);

 

        $name   = $data['name'];

        $type   = $data['type'];

        $method = $data['method'];

        $params = $data['params'];

        $logid  = $data['logid'] ?? uniqid('',true);

        $spanid = $data['spanid'] ?? 0;

 

 

        $collector = TaskCollector::getCollector();

        if (!isset($collector['task'][$name])) {

            return false;

        }

 

        list(,$coroutine) = $collector['task'][$name];

        $task = App::getBean($name);

        if ($coroutine) {

            $result = $this->runCoTask($task,$method,$logid,$spanid,$name,$type);

        } else {

            $result = $this->runSyncTask($task,$type);

        }

 

        return $result;

    }

}

 

任务执行思路很简单,将Worker进程发过来的数据解包还原成原来的调用参数,根据$name参数找到对应的TaskBean并调用其对应的task()方法。其中TaskBean使用类级别注解@Task(name="TaskName")或者@Task("TaskName")声明。

值得一提的一点是,@Task注解除了name属性,还有一个coroutine属性,上述代码会根据该参数选择使用协程的runCoTask()或者同步的runSyncTask()执行Task。但是由于而且由于SwooleTask进程的执行是完全同步的,不支持协程,所以目前版本请该参数不要配置为true。同样的在TaskBean中编写的任务代码必须的同步阻塞的或者是要能根据环境自动将异步非阻塞和协程降级为同步阻塞的

从Process中投递任务

前面我们提到:

SwooleTask机制的本质是Worker进程将耗时任务投递给同步的Task进程(又名TaskWorker)处理。

换句话说,Swoole$server->taskCo()$server->task()都只能在Worker进程中使用。

这个限制大大的限制了使用场景。 如何能够为了能够在Process中投递任务呢?Swoft为了绕过这个限制提供了Task::deliverByProcess()方法。其实现原理也很简单,通过Swoole$server->sendMessage()方法将调用信息从Process中投递到Worker进程中,然后由Worker进程替其投递到Task进程当中,相关代码如下:

//Swoft\Task\Task.php

/**

 * Deliver task by process

 *

 * @param string $taskName

 * @param string $methodName

 * @param array  $params

 * @param string $type

 * @param int    $timeout

 * @param int    $workId

 *

 * @return bool

 */

public static function deliverByProcess(string $taskName,int $timeout = 3,int $workId = 0,string $type = self::TYPE_ASYNC): bool

{

    /* @var PipeMessageInterface $pipeMessage */

    $server      = App::$server->getServer();

    $pipeMessage = App::getBean(PipeMessage::class);

    $data = [

        'name'    => $taskName,'method'  => $methodName,'params'  => $params,'timeout' => $timeout,'type'    => $type,];

 

    $message = $pipeMessage->pack(PipeMessage::MESSAGE_TYPE_TASK,$data);

    return $server->sendMessage($message,$workId);

}

 

数据打包后使用$server->sendMessage()投递给Worker:

//Swoft\Bootstrap\Server\ServerTrait.php

/**

 * onPipeMessage event callback

 *

 * @param \Swoole\Server $server

 * @param int            $srcWorkerId

 * @param string         $message

 * @return void

 * @throws \InvalidArgumentException

 */

public function onPipeMessage(Server $server,int $srcWorkerId,string $message)

{

    /* @var PipeMessageInterface $pipeMessage */

    $pipeMessage = App::getBean(PipeMessage::class);

    list($type,$data) = $pipeMessage->unpack($message);

 

    App::trigger(AppEvent::PIPE_MESSAGE,null,$type,$data,$srcWorkerId);

}

 

$server->sendMessage后,Worker进程收到数据时会触发一个swoole.pipeMessage事件的回调,Swoft会将其转换成自己的swoft.pipeMessage事件并触发.

//Swoft\Task\Event\Listeners\PipeMessageListener.php

/**

 * The pipe message listener

 *

 * @Listener(event=AppEvent::PIPE_MESSAGE)

 */

class PipeMessageListener implements EventHandlerInterface

{

    /**

     * @param \Swoft\Event\EventInterface $event

     */

    public function handle(EventInterface $event)

    {

        $params = $event->getParams();

        if (count($params) < 3) {

            return;

        }

 

        list($type,$srcWorkerId) = $params;

 

        if ($type != PipeMessage::MESSAGE_TYPE_TASK) {

            return;

        }

 

        $type       = $data['type'];

        $taskName   = $data['name'];

        $params     = $data['params'];

        $timeout    = $data['timeout'];

        $methodName = $data['method'];

 

        // delever task

        Task::deliver($taskName,$params,$timeout);

    }

}

 

swoft.pipeMessage事件最终由PipeMessageListener处理。在相关的监听其中,如果发现swoft.pipeMessage事件由Task::deliverByProcess()产生的,Worker进程会替其执行一次Task::deliver(),最终将任务数据投递到TaskWorker进程中。

一道简单的回顾练习:从Task::deliverByProcess()到某TaskBean 最终执行任务,经历了哪些进程,而调用链的哪些部分又分别是在哪些进程中执行?

从Command进程或其子进程中投递任务

//Swoft\Task\QueueTask.php

/**

 * @param string $data

 * @param int    $taskWorkerId

 * @param int    $srcWorkerId

 *

 * @return bool

 */

public function deliver(string $data,int $taskWorkerId = null,$srcWorkerId = null)

{

    if ($taskWorkerId === null) {

        $taskWorkerId = mt_rand($this->workerNum + 1,$this->workerNum + $this->taskNum);

    }

 

    if ($srcWorkerId === null) {

        $srcWorkerId = mt_rand(0,$this->workerNum - 1);

    }

 

    $this->check();

    $data   = $this->pack($data,$srcWorkerId);

    $result = \msg_send($this->queueId,$taskWorkerId,false);

    if (!$result) {

        return false;

    }

 

    return true;

}

 

对于Command进程的任务投递,情况会更复杂一点。
上文提到的Process,其往往衍生于Http/Rpc服务,作为同一个Manager的子孙进程,他们能够拿到Swoole\Server的句柄变量,从而通过$server->sendMessage(),$server->task()等方法进行任务投递。

但在Swoft的体系中,还有一个十分路人的角色: Command
Command的进程从shellcronb独立启动,和Http/Rpc服务相关的进程没有亲缘关系。因此Command进程以及从Command中启动的Process进程是没有办法拿到Swoole\Server的调用句柄直接通过UnixSocket进行任务投递的。
为了为这种进程提供任务投递支持,Swoft利用了SwooleTask进程的一个特殊功能----消息队列

 

 

同一个项目中CommandHttp\RpcServer 通过约定一个message_queue_key获取到系统内核中的同一条消息队列,然后Comand进程就可以通过该消息队列向Task进程投递任务了。
该机制没有提供对外的公开方法,仅仅被包含在Task::deliver()方法中,Swoft会根据当前环境隐式切换投递方式。但该消息队列的实现依赖Semaphore拓展,如果你想使用,需要在编译PHP时加上--enable-sysvmsg参数。

定时任务

除了手动执行的普通任务,Swoft还提供了精度为秒的定时任务功能用来在项目中替代Linux的Crontab功能.

Swoft用两个前置Process---任务计划进程:CronTimerProcess和任务执行进程CronExecProcess
,和两张内存数据表-----RunTimeTable(任务(配置)表)OriginTable((任务)执行表)用于定时任务的管理调度。
两张表的每行记录的结构如下:

\\Swoft\Task\Crontab\TableCrontab.php

/**

 * 任务表,记录用户配置的任务信息

 * 表每行记录包含的字段如下,其中`rule`,`taskClass`,`taskMethod`生成key唯一确定一条记录

 * @var array $originStruct 

 */

private $originStruct = [

    'rule'       => [\Swoole\Table::TYPE_STRING,100],//定时任务执行规则,对应@Scheduled注解的cron属性

    'taskClass'  => [\Swoole\Table::TYPE_STRING,255],//任务名 对应@Task的name属性(默认为类名)

    'taskMethod' => [\Swoole\Table::TYPE_STRING,//Task方法,对应@Scheduled注解所在方法

    'add_time'   => [\Swoole\Table::TYPE_STRING,11],//初始化该表内容时的10位时间戳

];

 

/**

 * 执行表,记录短时间内要执行的任务列表及其执行状态

 * 表每行记录包含的字段如下,其中`taskClass`,`taskMethod`,`minute`,`sec`生成key唯一确定一条记录

 * @var array $runTimeStruct 

 */

private $runTimeStruct = [

    'taskClass'  => [\Swoole\Table::TYPE_STRING,//同上

    'taskMethod' => [\Swoole\Table::TYPE_STRING,//同上

    'minute'      => [\Swoole\Table::TYPE_STRING,20],//需要执行任务的时间,精确到分钟 格式date('YmdHi')

    'sec'        => [\Swoole\Table::TYPE_STRING,//需要执行任务的时间,精确到分钟 10位时间戳

    'runStatus'  => [\Swoole\TABLE::TYPE_INT,4],//任务状态,有 0(未执行)  1(已执行)  2(执行中) 三种。 

    //注意:这里的执行是一个容易误解的地方,此处的执行并不是指任务本身的执行,而是值`任务投递`这一操作的执行,从宏观上看换成 _未投递_,_已投递_,_投递中_描述会更准确。

];

 

此处为何要使用Swoole的内存Table?

Swoft的的定时任务管理是分别由 任务计划进程 和 任务执行进程 进程负责的。两个进程的运行共同管理定时任务,如果使用进程间独立的array()等结构,两个进程必然需要频繁的进程间通信。而使用跨进程的Table(本文的Table,除非特别说明,都指SwooleSwoole\Table结构)直接进行进程间数据共享,不仅性能高,操作简单 还解耦了两个进程。

为了Table能够在两个进程间共同使用,Table必须在Swoole Server启动前创建并分配内存。具体代码在Swoft\Task\Bootstrap\Listeners->onBeforeStart()中,比较简单,有兴趣的可以自行阅读。

背景介绍完了,我们来看看这两个定时任务进程的行为

//Swoft\Task\Bootstrap\Process\CronTimerProcess.php

/**

 * Crontab timer process

 *

 * @Process(name="cronTimer",boot=true)

 */

class CronTimerProcess implements ProcessInterface

{

    /**

     * @param \Swoft\Process\Process $process

     */

    public function run(SwoftProcess $process)

    {

        //code....

        /* @var \Swoft\Task\Crontab\Crontab $cron*/

        $cron = App::getBean('crontab');

 

        // Swoole/HttpServer

        $server = App::$server->getServer();

 

        $time = (60 - date('s')) * 1000;

        $server->after($time,function () use ($server,$cron) {

            // Every minute check all tasks,and prepare the tasks that next execution point needs

            $cron->checkTask();

            $server->tick(60 * 1000,function () use ($cron) {

                $cron->checkTask();

            });

        });

    }

}
 

//Swoft\Task\Crontab\Crontab.php

/**

 * 初始化runTimeTable数据

 *

 * @param array $task        任务

 * @param array $parseResult 解析crontab命令规则结果,即Task需要在当前分钟内的哪些秒执行

 * @return bool

 */

private function initRunTimeTableData(array $task,array $parseResult): bool

{

    $runTimeTableTasks = $this->getRunTimeTable()->table;

 

    $min = date('YmdHi');

    $sec = strtotime(date('Y-m-d H:i'));

    foreach ($parseResult as $time) {

        $this->checkTaskQueue(false);

        $key = $this->getKey($task['rule'],$task['taskClass'],$task['taskMethod'],$min,$time + $sec);

        $runTimeTableTasks->set($key,[

            'taskClass'  => $task['taskClass'],'taskMethod' => $task['taskMethod'],'minute'     => $min,'sec'        => $time + $sec,'runStatus'  => self::NORMAL

        ]);

    }

 

    return true;

}

 

CronTimerProcessSwoft的定时任务调度进程,其核心方法是Crontab->initRunTimeTableData()

该进程使用了Swoole的定时器功能,通过Swoole\Timer在每分钟首秒时执行的回调,CronTimerProcess每次被唤醒后都会遍历任务表计算出当前这一分钟内的60秒分别需要执行的任务清单,写入执行表并标记为 未执行。

//Swoft\Task\Bootstrap\Process

/**

 * Crontab process

 *

 * @Process(name="cronExec",boot=true)

 */

class CronExecProcess implements ProcessInterface

{

    /**

     * @param \Swoft\Process\Process $process

     */

    public function run(SwoftProcess $process)

    {

        $pname = App::$server->getPname();

        $process->name(sprintf('%s cronexec process',$pname));

 

        /** @var \Swoft\Task\Crontab\Crontab $cron */

        $cron = App::getBean('crontab');

 

        // Swoole/HttpServer

        $server = App::$server->getServer();

 

        $server->tick(0.5 * 1000,function () use ($cron) {

            $tasks = $cron->getExecTasks();

            if (!empty($tasks)) {

                foreach ($tasks as $task) {

                    // Diliver task

                    Task::deliverByProcess($task['taskClass'],$task['taskMethod']);

                    $cron->finishTask($task['key']);

                }

            }

        });

    }

}

 

CronExecProcess作为定时任务的执行者,通过Swoole\Timer0.5s唤醒自身一次,然后把 执行表 遍历一次,挑选当下需要执行的任务,通过sendMessage()投递出去并更新该 任务执行表中的状态。
该执行进程只负责任务的投递,任务的实际实际执行仍然在Task进程中由TaskExecutor处理。

定时任务的宏观执行情况如下:

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的官方群点击此处

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.SW的HttpServerHttpServer的本质仍然是swoole_server,其协议加些部分固定使用Http协议解析,支持同步和异步2种模式完整的HTTP协议请求会被封装在swoole_http_request对象内,所有HTTP响应都会通过swoole_http_response对象进行封装和发送无论是同步模式还是异步模式,HttpServer都可
1、Swoole依赖安装hiredissudowgethttps://github.comedis/hiredis/archive/v0.14.0.tar.gzsudotarxfv0.14.0.tar.gzcdhiredis-0.14.0/sudomakesudomakeinstallsudoldconfig需要在编译时增加--enable-async-redis来开启此功能nghttp2sudowgethttps://gith
直接安装会提示找不到openssl/ssl.h文件即便通过Brew安装了OpenSSLbrewinstallopenssl他自己找不到我们手动让他找到就行了呗…当然OpenSSL一定是要安装的,命令就在上方…然后brewinfoopenssl可以看到:Forcompilerstofindopensslyoumayneedtoset:
1.安装依赖yum install -y php php-pear php-devel httpd gcc gcc-c++2.编译安装swoole上传并解压swoole 源码包至服务器 swoole-src-4.3.1.tar.gzcd  /usr/local/swoole-src-4.3.1输入  phpize执行 ./configure执行 make && make install3.配置PHP支持该
下载swoole地址:https://pan.baidu.com/s/1_N3RiFtT3iHLA5xt6oElqA下载后解压tar-zxvf......执行phpize(去php的安装目录执行这个)如果报以下错误:Cannotfindconfig.m4.Makesurethatyourun'/srv/php/bin/phpize'inthetoplevelsourcedirectoryofthem
昨晚我躺在床上,百无聊赖地翻阅 阿兰·德波顿《身份的焦虑》这本书,看到这么一段话,让我想起网络上做墙头草的键盘侠,他们喜欢贴标签,然后去简单粗暴地批评或讨好一类人,那么可以说公众的眼睛是雪亮的吗?我一直不喜欢太绝对太肯定的观点,对此我也保留怀疑,包括那些道听途说来的道理。  
本着开源为原则,为这个世界更美好作出一份共享,我就给大家做个指路人,如果实用,记得给提供开源的朋友一些鼓励。简单介绍一下实现思路,使用swoole扩展接管php运行,由于swoole只能在类UNIX上运行,所以win朋友需要安装cygwin运行的类UNIX模拟环境。为了照顾大多数用户,这里就以win为例:首先
1.文件锁子进程回复制父进程的IO句柄,但是不能让所有的子进程同时对同一个文件进行操作,所以需要文件锁。2.进程间的通讯方式--管道管道是一组(2个)特殊的描述符管道需要在fork函数调用前创建如果某一段主动关闭管道,另一端读取操作会直接返回0,之后会关闭管道在父进程中创建管道的时候
环境:gccyuminstallgcc第一步:下载swoole包wgethttp://pecl.php.net/package/swoole第二步:解压,并进入执行:/usr/local/php/bin/phpize可能会报错Cannotfindautoconf.Pleasecheckyourautoconfinstallationandthe?$PHP_AUTOCONF?environmentvariableissetcorrectly
1、安装swoolewgethttps://github.com/swoole/swoole-src/archive/v1.9.1-stable.tar.gztarzxvfv1.9.1-stable.tar.gzcdswoole-src-1.9.1-stable/usr/local/php/bin/phpize./configure--with-php-config=/usr/local/php/bin/php-configmakemakeinstall2、配置php支持swoolevi
由来环境:PHP7、Swoole、linux对聊天室有点感兴趣,对于网络协议有一点一知半解,所以决定借助swoole实现个简单的聊天室,来简单剖析下原理,知道原理以后就可以考虑用其他语言或者自己造轮子写个,当然这是后话。源码我放置github(https://github.com/WalkingSun/SwooleServer),有兴趣可
检查环境是否满足Swoole的环境依赖#查看linux内核uname-a#查看gcc版本gcc--versiong++--version升级gcc到4.8以上cd/usr/local/srcwgetwgethttp://ftp.gnu.org/gnu/gcc/gcc-4.8.5/gcc-4.8.5.tar.bz2tar-jxvfgcc-4.8.5.tar.bz2cdgcc-4.8.5/usr/local/sr
卸载现有phpapt-getautoremovephp7*find/etc-name"*php*"|xargsrm-rfaptpurge`dpkg-l|grepphp|awk'{print$2}'|tr"\n"""`编译安装phpaptinstallgccmakelibxml2libxml2-devwgethttp://cn2.php.net/distrib
目录概述代码小结扩展参考文档概述Swoole异步Task,主要实现调用异步任务的执行。常用的场景:异步支付处理、异步订单处理、异步日志处理、异步发送邮件/短信等。Swoole的实现方式是worker进程处理数据请求,分配给task进程执行。官方介绍:task底层使用UnixSoc
上一篇写到了如何在windows系统上面利用docker快速搭建swoole开发环境,接下来体验下swoole的使用使用swoole实现tcp服务<?php$serv=newSwoole\Server("0.0.0.0",9501);$serv->on('connect',function($serv,$fd){echo"客户端".$fd."连接成功\n";});$serv-&
一、同步与异步的区别:1、同步模式:后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的.2、异步模式:则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个
1.什么是RPCRPC是一种进程间的通讯方式,全称是“远程调用过程”。当客户端向服务器发出请求时,并不是至直接发送给目标服务器,而是发送给RPCServer,由RPCServer进行调度。RPC提供与调用本地方法无差别的远程调用方法。2.Hprose简介Hprose是一个轻量级的高性能跨语言RPC服务框架;提供
swoole中,接受数据流处理时,需要将数据流切分成小包才能进行功能的实现,以下为常见的几个协议1.EOF协议EOF协议是用一组固定的,不会出现在数据内的字符作为数据分割的标记,简称EOF协议EOF协议的格式如下:DATA|EOF|DATA|EOF|......2.固定包头协议在数
环境依赖:swoole-1.x需要php-5.3.10或更高版本,swoole-2.x需要php-7.0.0或更高版本。安装步骤:[root@dev~]#cd/usr/local/src[root@devsrc]#wgethttp://pecl.php.net//get/swoole-4.3.5.tgz[root@devsrc]#tar-zxvfswoole-4.3.5.tgz[root@devsrc]#cdsw
Swoole:面向生产环境的PHP异步网络通信引擎    使PHP开发人员可以编写高性能的异步并发TCP、UDP、UnixSocket、HTTP,WebSocket服务。Swoole可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。使用PHP+Swoole作