最近公司接入了几个h5小游戏,后端用的是SwooleDistributed 框架(以下简称SD),于是阅读了一下源码,给大家分享一下
SD启动入口如下图, require的 define文件 主要工作就是加载了composer autoload文件,实例化依赖
run方法内容如下:
这边使用到了symfony console 这个组件,这是一个提供完善交互功能的cli模式,需要按照Symfony的规范事先定义Command
可以看到run方法调用了addDirCommand ,这边把Server_DIR目录下所有command都实例化注册到cmdlist数组中,每个command都有自己不重复的名字,等待匹配
比如 我输入了 php bin/start_swoole_server.php start 那么程序会寻找一个名字叫start的 命令,然后执行他的excute方法
excute 方法中除了打印出配置文件中的信息以外,会new AppServer(), 然后调用start方法
我们重点看一下AppServer 做了哪些事
可以看到构造函数中 实例化了一个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给出了一些方案
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 谢谢