猫耳山在天边 2019-06-27
本文主要讲解EasySwoole 服务的启动过程,会通过源码片段讲解主体的设计流程
当我们通过php easyswoole start启动EasySwoole 服务时,命令真正到达的文件是 easyswoole项目\vendor\easyswoole\easyswoole\bin\easyswoole,命令start执行的整体流程如下图:

主要方法为:serverStart($options);,其重要执行代码如下:
$conf = Conf::getInstance(); $inst = Core::getInstance()->initialize(); $inst->run();
Config.php内容如下:return [
    'SERVER_NAME'=>"EasySwoole",
    'MAIN_SERVER'=>[
        'HOST'=>'0.0.0.0',
        'PORT'=>9501,
        'SERVER_TYPE'=>\EasySwoole\Core\Swoole\ServerManager::TYPE_WEB_SERVER,
        'SOCK_TYPE'=>SWOOLE_TCP,//该配置项当为SERVER_TYPE值为TYPE_SERVER时有效
        'RUN_MODEL'=>SWOOLE_PROCESS,
        'SETTING'=>[
            'task_worker_num' => 8, //异步任务进程
            'task_max_request'=>10,
            'max_request'=>5000,//强烈建议设置此配置项
            'worker_num'=>8
        ],
    ],
    'DEBUG'=>true,
    'TEMP_DIR'=>null,//若不配置,则默认框架初始化
    'LOG_DIR'=>null,//若不配置,则默认框架初始化
    'EASY_CACHE'=>[
        'PROCESS_NUM'=>1,//若不希望开启,则设置为0
        'PERSISTENT_TIME'=>0//如果需要定时数据落地,请设置对应的时间周期,单位为秒
    ],
    'CLUSTER'=>[
        'enable'=>false,
        'token'=>null,
        'broadcastAddress'=>['255.255.255.255:9556'],
        'listenAddress'=>'0.0.0.0',
        'listenPort'=>'9556',
        'broadcastTTL'=>5,
        'nodeTimeout'=>10,
        'nodeName'=>'easySwoole',
        'nodeId'=>null
    ]
];initialize()方法run()方法ps:此处插入说明一个点,EasySwoole中单例类都复用同一个trait
trait Singleton
{
    private static $instance;
    static function getInstance(...$args)
    {
        if(!isset(self::$instance)){
            self::$instance = new static(...$args);
        }
        return self::$instance;
    }
}Easywechat 真实入口文件为EasySwoole\Core\Core,上述已经说到命令启动时,执行了以下代码:
Core::getInstance(); $inst = Core::getInstance()->initialize(); $inst->run();
在整个EasySwoole生命周期中,Core对象只会被实例化一次,Code的初始化做了如下操作:
public function __construct()
    {
        defined('SWOOLE_VERSION') or define('SWOOLE_VERSION',intval(phpversion('swoole')));
        defined('EASYSWOOLE_ROOT') or define('EASYSWOOLE_ROOT',realpath(getcwd()));
        if(file_exists(EASYSWOOLE_ROOT.'/EasySwooleEvent.php')){
            require_once EASYSWOOLE_ROOT.'/EasySwooleEvent.php'; //引入全局初始化事件类
        }
        $this->sysDirectoryInit(); //设置temp目录和log目录,路径可配置化
    }SWOOLE_VERSION 和 EASYSWOOLE_ROOTEasySwooleEvent.php$this->sysDirectoryInit(); 设置temp目录和log目录,路径可配置化Core类中的initialize方法:
public function initialize():Core
    {
        Di::getInstance()->set(SysConst::VERSION,'2.1.2');
        Di::getInstance()->set(SysConst::HTTP_CONTROLLER_MAX_DEPTH,3);
        EasySwooleEvent::frameInitialize();
        $this->errorHandle();
        return $this;
    }SysConst::VERSION 和 SysConst::HTTP_CONTROLLER_MAX_DEPTH 的值EasySwooleEvent::frameInitialize();事件$this->errorHandle();注册系统中的set_error_handler、register_shutdown_functionCore类的run方法为核心功能:
public function run():void
{
    ServerManager::getInstance()->start();
}ServerManager类,并执行start() 启动整个服务ServerManager是一个单例对象,在整个EasySwoole生命周期中,ServeManager对象只会被实例化一次.ServeManager 的 run 方法干了下面几件事:
public function start():void
    {
        $this->createMainServer();
        Cache::getInstance();
        Cluster::getInstance()->run();
        CronTab::getInstance()->run();
        $this->attachListener();
        $this->isStart = true;
        $this->getServer()->start();
    }createMainServer() 创建主服务cache,添加对应的CacheProcess。(Easyswoole的缓存服务是基于swoole_process的管道通信,后续会专门解析下系统组件cache的源码)Cluster集群模式的注册 (有兴趣的可以通过链接看看)CronTab 服务开启,后面说下crontab的使用attachListener事件监听,子服务多端口监听$this->getServer()->start(); 调用swoole_server的start方法,正式启动Easyswoole服务ServerManager 类的createMainServer()方法:
(1)读取配置,创建对应的swoole_server服务
case self::TYPE_SERVER:{
                $this->mainServer = new \swoole_server($host,$port,$runModel,$sockType);
                break;
            }
            case self::TYPE_WEB_SERVER:{
                $this->mainServer = new \swoole_http_server($host,$port,$runModel,$sockType);
                break;
            }
            case self::TYPE_WEB_SOCKET_SERVER:{
                $this->mainServer = new \swoole_websocket_server($host,$port,$runModel,$sockType);
                break;
            }
            default:{
                Trigger::throwable(new \Exception("unknown server type :{$conf['SERVER_TYPE']}"));
            }
        }(2)注册事件,onWorker、onTask、onFinish、onRequest等;还有Easyswoole事件mainServerCreate,开发者可以在mainServerCreate事件设置CronTab 服务等
$register = new EventRegister();//事件容器
        $this->finalHook($register);
        EasySwooleEvent::mainServerCreate($this,$register);
        $events = $register->all();(3)事件注册的过程中,还做了如下操作:实例化对象池。开发者可以通过配置,在此实例化mysql连接池等:
//实例化对象池管理
        PoolManager::getInstance();
        PoolManager::getInstance()->__workerStartHook($workerId);(4)在onRequest事件中,还执行了EasySwooleEvent的onRequest和afterAction,开发者可以在此自定义处理代码,如日志统一刷出等
EasySwooleEvent::onRequest($request_psr,$response_psr);
         $dispatcher->dispatch($request_psr,$response_psr);
         EasySwooleEvent::afterAction($request_psr,$response_psr);ServerManager 中 Cache::getInstance() 全局跨进程Cache的注册:
function __construct()
    {
        $num = intval(Config::getInstance()->getConf("EASY_CACHE.PROCESS_NUM"));
        if($num <= 0){
           return;
        }
        $this->cliTemp = new SplArray();
        //若是在主服务创建,而非单元测试调用
        if(ServerManager::getInstance()->getServer()){
            //创建table用于数据传递
            TableManager::getInstance()->add(self::EXCHANGE_TABLE_NAME,[
                'data'=>[
                    'type'=>Table::TYPE_STRING,
                    'size'=>10*1024
                ],
                'microTime'=>[
                    'type'=>Table::TYPE_STRING,
                    'size'=>15
                ]
            ],2048);
            $this->processNum = $num;
            for ($i=0;$i < $num;$i++){
                ProcessManager::getInstance()->addProcess($this->generateProcessName($i),CacheProcess::class);
            }
        }
    }cache 服务基于 swoole_table 实现全局数据共享和传递。
以上是Easyswoole服务启动过程中的主体设计,其中包括了各种组件的实例化,如PoolManager(对象池)、cache、CronTab等。