最近公司接入了几个h5小游戏,后端用的是SwooleDistributed 框架(以下简称SD),于是阅读了一下源码,给大家分享一下

SD启动入口如下图, require的 define文件 主要工作就是加载了composer autoload文件,实例化依赖

SwooleDistributed 源码分析 1 -- 启动

run方法内容如下:

SwooleDistributed 源码分析 1 -- 启动

这边使用到了symfony console 这个组件,这是一个提供完善交互功能的cli模式,需要按照Symfony的规范事先定义Command

可以看到run方法调用了addDirCommand ,这边把Server_DIR目录下所有command都实例化注册到cmdlist数组中,每个command都有自己不重复的名字,等待匹配

比如 我输入了 php bin/start_swoole_server.php start  那么程序会寻找一个名字叫start的 命令,然后执行他的excute方法

SwooleDistributed 源码分析 1 -- 启动

excute 方法中除了打印出配置文件中的信息以外,会new AppServer(), 然后调用start方法

我们重点看一下AppServer 做了哪些事

SwooleDistributed 源码分析 1 -- 启动

可以看到构造函数中 实例化了一个loader,他的作用 1  初始化 mysql redis 等的连接池 2 用于加载Model层的文件,model

层的文件都是业务处理逻辑

然后一路调用父类的构造方法,这里 SwooleDistributedServer 继承于 SwooleWebSocketServer ,SwooleWebSocketServer

继承于SwooleHttpServer,SwooleHttpServer 继承于 SwooleServer, 直到调用他的构造方法

/**
 * SwooleServer constructor.
 */
public function __construct()
{
    // 设置错误handle
    $this->onErrorHandel = [$this, 'onErrorHandel'];
    // 加载用户config文件夹下所有的文件
    $this->setConfig();
    // 加载中间件
    $this->middlewareManager = new MiddlewareManager();
    $this->user = $this->config->get('server.set.user', '');
    // 初始化logger handle, SD的logger 使用的是monolog,需要在config/log 里进行配置
    $this->setLogHandler();
    // 这边注册了error_handler, exception handler
    register_shutdown_function(array($this, 'checkErrors'));
    set_error_handler(array($this, 'displayErrorHandler'), E_ALL | E_STRICT);
    set_exception_handler(array($this, 'displayExceptionHandler'));
    // portsManager比较重要
    $this->portManager = new PortManager($this->config['ports']);
    if ($this->loader == null) {
        $this->loader = new Loader();
    }
}
看一下portmanager, swoole 官方提供了多端口监听的方法,如果你的应用需要监听多个端口,官方wiki给出了一些方案

SwooleDistributed 源码分析 1 -- 启动

class PortManager 
public function __construct($portConfig)
{
    foreach ($portConfig as $key => $value) {
        $this->portConfig[$value['socket_port']] = $value;
        if ($value['socket_type'] == self::SOCK_WS) {
            $this->websocket_enable = true;
        } else if ($value['socket_type'] == self::SOCK_HTTP) {
            $this->http_enable = true;
        } else {
            $this->tcp_enable = true;
        }
        $this->addPort($value);
    }
}

可以看到如果config/ports 文件配置了socket_type为ws, websocket_enable 就会 变成true 后面会讲到这个值的用途

接下来看一下start 方法做了什么

SwooleDistributiedServer

public function start()
{
    // 如果设置了这个参数为真,则起一个redis 连接池, 默认是自动启
    if ($this->config->get('redis.enable', true)) {
        //加载redis的lua脚本
        $redis_pool = new RedisAsynPool($this->config, $this->config->get('redis.active'));
        $redisLuaManager = new RedisLuaManager($redis_pool->getSync());
        $redisLuaManager->registerFile(LUA_DIR);
        $redis_pool->getSync()->close();
        $redis_pool = null;
    }
    //非集群默认是leader
    if (!$this->isCluster()) {
        Start::setLeader(true);
    } // 调用父类SwooleWsServer的start方法
    parent::start();
}

SwooleWsServer的 start方法

public function start()
{
    // 如果这个值为false会直接执行父类的方法, 大致类似,也是设置各个回调函数
    if (!$this->portManager->websocket_enable) {
        parent::start();
        return;
    }
    // 获取port 配置中的第一个配置
    $first_config = $this->portManager->getFirstTypePort();
    $set = $this->portManager->getProbufSet($first_config['socket_port']);
    // 看看是否需要配置ssl
    if (array_key_exists('ssl_cert_file', $first_config)) {
        $set['ssl_cert_file'] = $first_config['ssl_cert_file'];
    }
    if (array_key_exists('ssl_key_file', $first_config)) {
        $set['ssl_key_file'] = $first_config['ssl_key_file'];
    }
    $socket_ssl = $first_config['socket_ssl'] ?? false;
    //开启一个websocket服务器
    if ($socket_ssl) {
        $this->server = new \swoole_websocket_server($first_config['socket_name'], $first_config['socket_port'], SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
    } else {
        $this->server = new \swoole_websocket_server($first_config['socket_name'], $first_config['socket_port']);
    }
    $this->setServerSet($set);
    // 配置ws的各个回调函数,根据业务可以自己重写
    // 只需要自己extends SwooleDistributedServer 就可以override各个callback
    $this->server->on('Start', [$this, 'onSwooleStart']);
    $this->server->on('WorkerStart', [$this, 'onSwooleWorkerStart']);
    $this->server->on('WorkerStop', [$this, 'onSwooleWorkerStop']);
    $this->server->on('Task', [$this, 'onSwooleTask']);
    $this->server->on('Finish', [$this, 'onSwooleFinish']);
    $this->server->on('PipeMessage', [$this, 'onSwoolePipeMessage']);
    $this->server->on('WorkerError', [$this, 'onSwooleWorkerError']);
    $this->server->on('ManagerStart', [$this, 'onSwooleManagerStart']);
    $this->server->on('ManagerStop', [$this, 'onSwooleManagerStop']);
    $this->server->on('request', [$this, 'onSwooleRequest']);
    $this->server->on('open', [$this, 'onSwooleWSOpen']);
    $this->server->on('message', [$this, 'onSwooleWSMessage']);
    $this->server->on('close', [$this, 'onSwooleWSClose']);
    $this->server->on('Shutdown', [$this, 'onSwooleShutdown']);
    if ($this->custom_handshake) {
        $this->server->on('handshake', [$this, 'onSwooleWSHandShake']);
    }
    // 这边会展开说下
    $this->portManager->buildPort($this, $first_config['socket_port']);
    
    $this->beforeSwooleStart();
    // 服务正式启动, ,start 方法会一直阻塞
    $this->server->start();
}

其中buildPort 的功能为:

/**
 * 构架端口
 * @param SwooleServer $swoole_server
 * @param $first_port
 * @throws \Exception
 */
public function buildPort(SwooleServer $swoole_server, $first_port)
{
    /*
     * buildPort方法从portConfig数组中取出所有port的配置
     * 如果是已经设置的配置则跳过
     * 如果是新的配置,根据类型 http or tcp
     * 相应监听各自的端口,并且分别设置各自的回调函数
     */
    foreach ($this->portConfig as $key => $value) {
        if ($value['socket_port'] == $first_port) continue;
        //获得set
        $set = $this->getProbufSet($value['socket_port']);
        if (array_key_exists('ssl_cert_file', $value)) {
            $set['ssl_cert_file'] = $value['ssl_cert_file'];
        }
        if (array_key_exists('ssl_key_file', $value)) {
            $set['ssl_key_file'] = $value['ssl_key_file'];
        }
        $socket_ssl = $value['socket_ssl'] ?? false;
        if ($value['socket_type'] == self::SOCK_HTTP || $value['socket_type'] == self::SOCK_WS) {
            if ($socket_ssl) {
                $port = $swoole_server->server->listen($value['socket_name'], $value['socket_port'], self::SOCK_TCP | self::SWOOLE_SSL);
            } else {
                $port = $swoole_server->server->listen($value['socket_name'], $value['socket_port'], self::SOCK_TCP);
            }
            if ($port == false) {
                throw new \Exception("{$value['socket_port']}端口创建失败");
            }
            if ($value['socket_type'] == self::SOCK_HTTP) {
                $set['open_http_protocol'] = true;
                $port->set($set);
                $port->on('request', [$swoole_server, $value['request'] ?? 'onSwooleRequest']);
                $port->on('handshake', function () {
                    return false;
                });
            } else {
                $set['open_http_protocol'] = true;
                $set['open_websocket_protocol'] = true;
                $port->set($set);
                $port->on('open', [$swoole_server, $value['open'] ?? 'onSwooleWSOpen']);
                $port->on('message', [$swoole_server, $value['message'] ?? 'onSwooleWSMessage']);
                $port->on('close', [$swoole_server, $value['close'] ?? 'onSwooleWSClose']);
                $port->on('handshake', [$swoole_server, $value['handshake'] ?? 'onSwooleWSHandShake']);
            }
        } else {
            if ($socket_ssl) {
                $port = $swoole_server->server->listen($value['socket_name'], $value['socket_port'], $value['socket_type'] | self::SWOOLE_SSL);
            } else {
                $port = $swoole_server->server->listen($value['socket_name'], $value['socket_port'], $value['socket_type']);
            }
            if ($port == false) {
                throw new \Exception("{$value['socket_port']}端口创建失败");
            }
            $port->set($set);
            $port->on('connect', [$swoole_server, $value['connect'] ?? 'onSwooleConnect']);
            $port->on('receive', [$swoole_server, $value['receive'] ?? 'onSwooleReceive']);
            $port->on('close', [$swoole_server, $value['close'] ?? 'onSwooleClose']);
            $port->on('packet', [$swoole_server, $value['packet'] ?? 'onSwoolePacket']);
        }

    }
}

所以按照官方wiki给出的demo,如果您的应用需要提供tcp 服务的同时,又要提供http , ws 服务

那么ports文件应该是这样写

<?php
/**
 * Created by PhpStorm.
 * User: zhangjincheng
 * Date: 16-7-14
 * Time: 下午1:58
 */

use Server\CoreBase\PortManager;


$config['ports'][] = [
    'socket_type' => PortManager::SOCK_WS,
    'socket_name' => '0.0.0.0',
    'socket_port' => 8083,
    'route_tool' => 'NormalRoute',
    'pack_tool' => 'NonJsonPack',
    'opcode' => PortManager::WEBSOCKET_OPCODE_TEXT,
    'middlewares' => ['MonitorMiddleware', 'NormalHttpMiddleware']
];

$config['ports'][] = [
    'socket_type' => PortManager::SOCK_TCP,
    'socket_name' => '0.0.0.0',
    'socket_port' => 9091,
    'pack_tool' => 'LenJsonPack',
    'route_tool' => 'NormalRoute',
    'middlewares' => ['MonitorMiddleware']
];

return $config;

否则会出问题, 主server 不能是tcpserver!

至此sd的服务就启动了 , 下次会讲一下SD 收发消息的整个过程,包括收到的消息如何路由到controller的 具体 function 谢谢

相关文章: