启动服务
这节分析入口文件最后两行代码,
(function () {
Hyperf\Di\ClassLoader::init();
/** @var Psr\Container\ContainerInterface $container */
$container = require BASE_PATH . '/config/container.php';
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
$application->run();
})();
提示
简单来说,这段代码是从依赖注入容器中获取ApplicationInterface
接口对应的实例,然后调用run
方法启动服务。
生成Application
从上一篇实例化container
中可知,其中ApplicationInterface
对应的是Hyperf\Framework\ApplicationFactory
类。
警告
具体get
取出过程,请参考 附录1
通过分析get
方法可知,最终调用ApplicationFactory
类的__invoke
方法,获得解析后的对象。
namespace Hyperf\Framework;
use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Parser;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Framework\Event\BootApplication;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Exception\InvalidArgumentException as SymfonyInvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class ApplicationFactory
{
public function __invoke(ContainerInterface $container)
{
// 获取事件调度器,并触发BootApplication事件
if ($container->has(EventDispatcherInterface::class)) {
$eventDispatcher = $container->get(EventDispatcherInterface::class);
$eventDispatcher->dispatch(new BootApplication());
}
// 获取配置文件ConfigInterface的接口实现类
$config = $container->get(ConfigInterface::class);
$commands = $config->get('commands', []);
// 从注解中获取命令
$annotationCommands = [];
if (class_exists(AnnotationCollector::class) && class_exists(Command::class)) {
$annotationCommands = AnnotationCollector::getClassesByAnnotation(Command::class);
$annotationCommands = array_keys($annotationCommands);
}
// 合并命令
$commands = array_unique(array_merge($commands, $annotationCommands));
// 实例化Application类
$application = new Application();
// 如果启用 Symfony 事件,设置事件调度器
if ($config->get('symfony.event.enable', false) && isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) {
$application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher));
}
// 添加命令到应用Application中
foreach ($commands as $command) {
$application->add(
$this->pendingCommand($container->get($command))
);
}
return $application;
}
/**
* @throws InvalidArgumentException
* @throws SymfonyInvalidArgumentException
* @throws LogicException
*/
protected function pendingCommand(SymfonyCommand $command): SymfonyCommand
{
/** @var null|Command $annotation */
$annotation = AnnotationCollector::getClassAnnotation($command::class, Command::class) ?? null;
if (! $annotation) {
return $command;
}
if ($annotation->signature) {
[$name, $arguments, $options] = Parser::parse($annotation->signature);
if ($name) {
$annotation->name = $name;
}
if ($arguments) {
$annotation->arguments = array_merge($annotation->arguments, $arguments);
}
if ($options) {
$annotation->options = array_merge($annotation->options, $options);
}
}
if ($annotation->name) {
$command->setName($annotation->name);
}
if ($annotation->arguments) {
$annotation->arguments = array_map(static function ($argument): InputArgument {
if ($argument instanceof InputArgument) {
return $argument;
}
if (is_array($argument)) {
return new InputArgument(...$argument);
}
throw new LogicException(sprintf('Invalid argument type: %s.', gettype($argument)));
}, $annotation->arguments);
$command->getDefinition()->addArguments($annotation->arguments);
}
if ($annotation->options) {
$annotation->options = array_map(static function ($option): InputOption {
if ($option instanceof InputOption) {
return $option;
}
if (is_array($option)) {
return new InputOption(...$option);
}
throw new LogicException(sprintf('Invalid option type: %s.', gettype($option)));
}, $annotation->options);
$command->getDefinition()->addOptions($annotation->options);
}
if ($annotation->description) {
$command->setDescription($annotation->description);
}
if ($annotation->aliases) {
$command->setAliases($annotation->aliases);
}
return $command;
}
}
警告
从注解中获取命令的具体实现,请参考附录2
获取到的$commands
命令数组如下所示,
点我查看
Array
(
[0] => Hyperf\Server\Command\StartServer
[1] => Hyperf\Database\Commands\ModelCommand
[2] => Hyperf\Database\Commands\Migrations\GenMigrateCommand
[3] => Hyperf\Database\Commands\Seeders\GenSeederCommand
[4] => Hyperf\Database\Commands\Migrations\InstallCommand
[5] => Hyperf\Database\Commands\Migrations\MigrateCommand
[6] => Hyperf\Database\Commands\Migrations\FreshCommand
[7] => Hyperf\Database\Commands\Migrations\RefreshCommand
[8] => Hyperf\Database\Commands\Migrations\ResetCommand
[9] => Hyperf\Database\Commands\Migrations\RollbackCommand
[10] => Hyperf\Database\Commands\Migrations\StatusCommand
[11] => Hyperf\Database\Commands\Seeders\SeedCommand
[12] => Hyperf\Watcher\Command\WatchCommand
[13] => Qbhy\HyperfAuth\AuthCommand
[14] => Hyperf\Devtool\InfoCommand
[15] => Hyperf\Devtool\Describe\ListenersCommand
[16] => Hyperf\Devtool\Describe\RoutesCommand
[17] => Hyperf\Devtool\Describe\AspectsCommand
[18] => Hyperf\Devtool\Generator\CommandCommand
[19] => Hyperf\Devtool\Generator\NatsConsumerCommand
[20] => Hyperf\Devtool\Generator\ListenerCommand
[21] => Hyperf\Devtool\Generator\NsqConsumerCommand
[22] => Hyperf\Devtool\Generator\AspectCommand
[23] => Hyperf\Devtool\Generator\JobCommand
[24] => Hyperf\Devtool\Generator\ResourceCommand
[25] => Hyperf\Devtool\Generator\ClassCommand
[26] => Hyperf\Devtool\Generator\ControllerCommand
[27] => Hyperf\Devtool\Generator\AmqpProducerCommand
[28] => Hyperf\Devtool\Generator\AmqpConsumerCommand
[29] => Hyperf\Devtool\Generator\ProcessCommand
[30] => Hyperf\Devtool\Generator\KafkaConsumerCommand
[31] => Hyperf\Devtool\Generator\MiddlewareCommand
[32] => Hyperf\Devtool\Generator\ConstantCommand
[33] => Hyperf\Devtool\Generator\RequestCommand
[34] => Hyperf\Devtool\VendorPublishCommand
[35] => App\Command\InitCommand
)
从上面代码可以看出,返回的是一个Symfony\Component\Console\Application
对象。
上面代码做了三件事:
- 首先从容器中取出事件调度器,并触发
BootApplication
事件。 - 创建
Application
应用对象,从注解和配置文件中获取所有命令。
提示
Symfony\Component\Console\Application
类是 Symfony
控制台组件的核心部分,用于创建和管理 CLI
应用程序。它提供了一系列功能,使开发者可以轻松地定义和运行命令行工具
- 将命令注册到应用中。
警告
从命令注册到Application
的具体实现,请参考附录3
foreach ($commands as $command) {
$application->add(
$this->pendingCommand($container->get($command))
);
}
- 返回
Application
实例
return $application;
run启动
启动服务,
文件位置:/bin/hyperf.php
(function () {
Hyperf\Di\ClassLoader::init();
/** @var Psr\Container\ContainerInterface $container */
$container = require BASE_PATH . '/config/container.php';
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
$application->run();
})();
从上面分析可知,$application
是一个Symfony\Component\Console\Application
对象,调用run
方法。 先看源码,
public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
{
// 设置终端的高度和宽度
if (\function_exists('putenv')) {
@putenv('LINES='.$this->terminal->getHeight());
@putenv('COLUMNS='.$this->terminal->getWidth());
}
// 初始化输入和输出对象,如果没有提供输入和输出对象,则创建默认
$input ??= new ArgvInput();
$output ??= new ConsoleOutput();
// 渲染异常的闭包函数
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
$this->renderThrowable($e, $output->getErrorOutput());
} else {
$this->renderThrowable($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
$errorHandler = true;
} elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($errorHandler);
}
}
// 根据输入参数和环境变量配置输入和输出对象
$this->configureIO($input, $output);
try {
// 执行命令
$exitCode = $this->doRun($input, $output);
} catch (\Throwable $e) {
if ($e instanceof \Exception && !$this->catchExceptions) {
throw $e;
}
if (!$e instanceof \Exception && !$this->catchErrors) {
throw $e;
}
$renderException($e);
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if ($exitCode <= 0) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
} finally {
// if the exception handler changed, keep it
// otherwise, unregister $renderException
if (!$phpHandler) {
if (set_exception_handler($renderException) === $renderException) {
restore_exception_handler();
}
restore_exception_handler();
} elseif (!$errorHandler) {
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderException) {
$phpHandler[0]->setExceptionHandler($finalHandler);
}
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
return $exitCode;
}
调用run
方法后,会由Hyperf
的StartServer
的命令类接管,执行该类的execute
方法。
提示
run
方法的具体实现请参考附录4
StartServer类
文件位置:/vendor/hyperf/server/src/Command/StartServer.php
namespace Hyperf\Server\Command;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Engine\Coroutine;
use Hyperf\Server\ServerFactory;
use Hyperf\Support\Composer;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
//...
protected function execute(InputInterface $input, OutputInterface $output): int
{
// 检查环境
$this->checkEnvironment($output);
// 第一步,取得ServerFactory实例对象
$serverFactory = $this->container->get(ServerFactory::class)
// 设置事件调度器
->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
// 设置日志
->setLogger($this->container->get(StdoutLoggerInterface::class));
// 获取config目录下server.php中的server配置
$serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
if (! $serverConfig) {
throw new InvalidArgumentException('At least one server should be defined.');
}
// 根据给定的参数配置服务
$serverFactory->configure($serverConfig);
// 设置协程的hook标志,将所有相关的阻塞操作转换为非阻塞操作
Coroutine::set(['hook_flags' => swoole_hook_flags()]);
// 启动服务
$serverFactory->start();
return 0;
}
默认情况下,配置文件中只有一个
http
服务。
总结
- 获取
ServerFactory
实例,设置事件调度器,设置日志 - 获取
config
目录下server.php
中的server
配置,没有则抛异常 - 根据
config
创建对应的服务 - 设置协程的
hook
标志,将所有相关的阻塞操作转换为非阻塞操作 - 启动服务
配置服务
查看服务配置实现,调用ServerFactory
类的configure
方法,
namespace Hyperf\Server;
use Hyperf\Server\Entry\EventDispatcher;
use Hyperf\Server\Entry\Logger;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
class ServerFactory
{
protected ?LoggerInterface $logger = null;
protected ?EventDispatcherInterface $eventDispatcher = null;
protected ?ServerInterface $server = null;
protected ?ServerConfig $config = null;
public function __construct(protected ContainerInterface $container)
{
}
public function configure(array $config): void
{
// 获取服务配置信息,包装成ServerConfig类
$this->config = new ServerConfig($config);
$this->getServer()->init($this->config);
}
/**
* 启动Server
*/
public function start(): void
{
$this->getServer()->start();
}
/**
* 获取Server
*/
public function getServer(): ServerInterface
{
if (! $this->server instanceof ServerInterface) {
$serverName = $this->config->getType();
$this->server = new $serverName(
$this->container,
$this->getLogger(),
$this->getEventDispatcher()
);
}
return $this->server;
}
// 设置Server
public function setServer(Server $server): static
{
$this->server = $server;
return $this;
}
/**
* 获取事件调度器
*/
public function getEventDispatcher(): EventDispatcherInterface
{
if ($this->eventDispatcher instanceof EventDispatcherInterface) {
return $this->eventDispatcher;
}
return $this->getDefaultEventDispatcher();
}
/**
* 设置事件调度器
*/
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): static
{
$this->eventDispatcher = $eventDispatcher;
return $this;
}
/**
* 获取Loger
*/
public function getLogger(): LoggerInterface
{
if ($this->logger instanceof LoggerInterface) {
return $this->logger;
}
return $this->getDefaultLogger();
}
public function setLogger(LoggerInterface $logger): static
{
$this->logger = $logger;
return $this;
}
public function getConfig(): ?ServerConfig
{
return $this->config;
}
private function getDefaultEventDispatcher(): EventDispatcherInterface
{
return new EventDispatcher();
}
private function getDefaultLogger(): LoggerInterface
{
return new Logger();
}
}
- 首先将配置文件中的
server
配置数组包装成ServerConfig
类,传递给成员变量config
点我查看ServerConfig
namespace Hyperf\Server;
use Hyperf\Contract\Arrayable;
use Hyperf\Server\Exception\InvalidArgumentException;
/**
* @method ServerConfig setType(string $type)
* @method ServerConfig setMode(int $mode)
* @method ServerConfig setServers(array $servers)
* @method ServerConfig setProcesses(array $processes)
* @method ServerConfig setSettings(array $settings)
* @method ServerConfig setCallbacks(array $callbacks)
* @method string getType()
* @method int getMode()
* @method Port[] getServers()
* @method array getProcesses()
* @method array getSettings()
* @method array getCallbacks()
*/
class ServerConfig implements Arrayable
{
public function __construct(protected array $config = [])
{
if (empty($config['servers'] ?? [])) {
throw new InvalidArgumentException('Config server.servers not exist.');
}
$servers = [];
// 循环配置的server数组
foreach ($config['servers'] as $name => $item) {
if (! isset($item['name']) && ! is_numeric($name)) {
$item['name'] = $name;
}
$servers[] = Port::build($item);
}
$this->setType($config['type'] ?? Server::class) // 设置Server的类型,默认是Hyperf\Server\Server::class
->setMode($config['mode'] ?? 0)
->setServers($servers)
->setProcesses($config['processes'] ?? [])
->setSettings($config['settings'] ?? [])
->setCallbacks($config['callbacks'] ?? []);
}
public function __set($name, $value)
{
$this->set($name, $value);
}
public function __get($name)
{
if (! $this->isAvailableProperty($name)) {
throw new \InvalidArgumentException(sprintf('Invalid property %s', $name));
}
return $this->config[$name] ?? null;
}
/**
* 魔术方法,当调用不存在的方法的时候,会调用__call方法
*/
public function __call($name, $arguments)
{
$prefix = strtolower(substr($name, 0, 3));
if (in_array($prefix, ['set', 'get'])) {
$propertyName = strtolower(substr($name, 3));
if (! $this->isAvailableProperty($propertyName)) {
throw new \InvalidArgumentException(sprintf('Invalid property %s', $propertyName));
}
return $prefix === 'set' ? $this->set($propertyName, ...$arguments) : $this->__get($propertyName);
}
throw new \InvalidArgumentException(sprintf('Invalid method %s', $name));
}
public function addServer(Port $port): static
{
$this->config['servers'][] = $port;
return $this;
}
public function toArray(): array
{
return $this->config;
}
protected function set($name, $value): self
{
if (! $this->isAvailableProperty($name)) {
throw new \InvalidArgumentException(sprintf('Invalid property %s', $name));
}
$this->config[$name] = $value;
return $this;
}
private function isAvailableProperty(string $name)
{
return in_array($name, [
'type', 'mode', 'servers', 'processes', 'settings', 'callbacks',
]);
}
}
namespace Hyperf\Server;
class Port
{
protected string $name = 'http';
protected int $type = ServerInterface::SERVER_HTTP;
protected string $host = '0.0.0.0';
protected int $port = 9501;
protected int $sockType = 0;
protected array $callbacks = [];
protected array $settings = [];
protected ?Option $options = null;
public static function build(array $config): static
{
$config = self::filter($config);
$port = new static();
isset($config['name']) && $port->setName($config['name']);
isset($config['type']) && $port->setType($config['type']);
isset($config['host']) && $port->setHost($config['host']);
isset($config['port']) && $port->setPort($config['port']);
isset($config['sock_type']) && $port->setSockType($config['sock_type']);
isset($config['callbacks']) && $port->setCallbacks($config['callbacks']);
isset($config['settings']) && $port->setSettings($config['settings']);
isset($config['options']) && $port->setOptions(Option::make($config['options']));
return $port;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getType(): int
{
return $this->type;
}
public function setType(int $type): static
{
$this->type = $type;
return $this;
}
public function getHost(): string
{
return $this->host;
}
public function setHost(string $host): static
{
$this->host = $host;
return $this;
}
public function getPort(): int
{
return $this->port;
}
public function setPort(int $port): static
{
$this->port = $port;
return $this;
}
public function getSockType(): int
{
return $this->sockType;
}
public function setSockType(int $sockType): static
{
$this->sockType = $sockType;
return $this;
}
public function getCallbacks(): array
{
return $this->callbacks;
}
public function setCallbacks(array $callbacks): static
{
$this->callbacks = $callbacks;
return $this;
}
public function getSettings(): array
{
return $this->settings;
}
public function setSettings(array $settings): static
{
$this->settings = $settings;
return $this;
}
public function getOptions(): ?Option
{
return $this->options;
}
public function setOptions(Option $options): static
{
$this->options = $options;
return $this;
}
private static function filter(array $config): array
{
if ((int) $config['type'] === ServerInterface::SERVER_BASE) {
$default = [
'open_http2_protocol' => false,
'open_http_protocol' => false,
];
$config['settings'] = array_merge($default, $config['settings'] ?? []);
}
return $config;
}
}
- 根据
Server
配置的服务类型,实例化对应服务,调用服务的init
方法
提示
(以默认配置为例,server.php
文件中未配置type
值,使用默认Hyperf\Server\Server::class
)
getServer
方法返回Hyperf\Server\Server
实例- 调用
Hyperf\Server\Server
类的init
方法
实例化Server
// $this->getServer()返回的是一个Hyperf/Server/Server类实例
$this->getServer()->init($this->config);
文件位置:/vendor/hyperf/server/src/Server.php
public function init(ServerConfig $config): ServerInterface
{
$this->initServers($config);
return $this;
}
protected function initServers(ServerConfig $config)
{
// 对server进行排序
$servers = $this->sortServers($config->getServers());
foreach ($servers as $server) {
$name = $server->getName();
$type = $server->getType();
$host = $server->getHost();
$port = $server->getPort();
$sockType = $server->getSockType();
$callbacks = $server->getCallbacks();
if (! $this->server instanceof SwooleServer) {
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
$this->registerSwooleEvents($this->server, $callbacks, $name);
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
ServerManager::add($name, [$type, current($this->server->ports)]);
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
} else {
/** @var bool|SwoolePort $slaveServer */
$slaveServer = $this->server->addlistener($host, $port, $sockType);
if (! $slaveServer) {
throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");
}
$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
$this->registerSwooleEvents($slaveServer, $callbacks, $name);
ServerManager::add($name, [$type, $slaveServer]);
}
// Trigger beforeStart event.
if (isset($callbacks[Event::ON_BEFORE_START])) {
[$class, $method] = $callbacks[Event::ON_BEFORE_START];
if ($this->container->has($class)) {
$this->container->get($class)->{$method}();
}
}
if (class_exists(BeforeServerStart::class)) {
// Trigger BeforeServerStart event.
$this->eventDispatcher->dispatch(new BeforeServerStart($name));
}
}
}
protected function sortServers(array $servers): array
{
$sortServers = [];
foreach ($servers as $server) {
switch ($server->getType()) {
case ServerInterface::SERVER_HTTP:
$this->enableHttpServer = true;
if (! $this->enableWebsocketServer) {
array_unshift($sortServers, $server);
} else {
$sortServers[] = $server;
}
break;
case ServerInterface::SERVER_WEBSOCKET:
$this->enableWebsocketServer = true;
array_unshift($sortServers, $server);
break;
default:
$sortServers[] = $server;
break;
}
}
return $sortServers;
}
namespace Hyperf\Server;
use Hyperf\Contract\MiddlewareInitializerInterface;
use Hyperf\Framework\Bootstrap;
use Hyperf\Framework\Event\BeforeMainServerStart;
use Hyperf\Framework\Event\BeforeServerStart;
use Hyperf\Server\Exception\RuntimeException;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Swoole\Http\Server as SwooleHttpServer;
use Swoole\Server as SwooleServer;
use Swoole\Server\Port as SwoolePort;
use Swoole\WebSocket\Server as SwooleWebSocketServer;
class Server implements ServerInterface
{
protected bool $enableHttpServer = false;
protected bool $enableWebsocketServer = false;
protected ?SwooleServer $server = null;
protected array $onRequestCallbacks = [];
public function __construct(protected ContainerInterface $container, protected LoggerInterface $logger, protected EventDispatcherInterface $eventDispatcher)
{
}
public function init(ServerConfig $config): ServerInterface // [!code ]
{
$this->initServers($config);
return $this;
}
public function start(): void
{
$this->server->start();
}
public function getServer(): SwooleServer
{
return $this->server;
}
protected function initServers(ServerConfig $config)
{
$servers = $this->sortServers($config->getServers());
foreach ($servers as $server) {
$name = $server->getName();
$type = $server->getType();
$host = $server->getHost();
$port = $server->getPort();
$sockType = $server->getSockType();
$callbacks = $server->getCallbacks();
if (! $this->server instanceof SwooleServer) {
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
$this->registerSwooleEvents($this->server, $callbacks, $name);
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
ServerManager::add($name, [$type, current($this->server->ports)]);
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
} else {
/** @var bool|SwoolePort $slaveServer */
$slaveServer = $this->server->addlistener($host, $port, $sockType);
if (! $slaveServer) {
throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");
}
$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
$this->registerSwooleEvents($slaveServer, $callbacks, $name);
ServerManager::add($name, [$type, $slaveServer]);
}
// Trigger beforeStart event.
if (isset($callbacks[Event::ON_BEFORE_START])) {
[$class, $method] = $callbacks[Event::ON_BEFORE_START];
if ($this->container->has($class)) {
$this->container->get($class)->{$method}();
}
}
if (class_exists(BeforeServerStart::class)) {
// Trigger BeforeServerStart event.
$this->eventDispatcher->dispatch(new BeforeServerStart($name));
}
}
}
/**
* @param Port[] $servers
* @return Port[]
*/
protected function sortServers(array $servers): array
{
$sortServers = [];
foreach ($servers as $server) {
switch ($server->getType()) {
case ServerInterface::SERVER_HTTP:
$this->enableHttpServer = true;
if (! $this->enableWebsocketServer) {
array_unshift($sortServers, $server);
} else {
$sortServers[] = $server;
}
break;
case ServerInterface::SERVER_WEBSOCKET:
$this->enableWebsocketServer = true;
array_unshift($sortServers, $server);
break;
default:
$sortServers[] = $server;
break;
}
}
return $sortServers;
}
protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType): SwooleServer
{
switch ($type) {
case ServerInterface::SERVER_HTTP:
return new SwooleHttpServer($host, $port, $mode, $sockType);
case ServerInterface::SERVER_WEBSOCKET:
return new SwooleWebSocketServer($host, $port, $mode, $sockType);
case ServerInterface::SERVER_BASE:
return new SwooleServer($host, $port, $mode, $sockType);
}
throw new RuntimeException('Server type is invalid.');
}
protected function registerSwooleEvents(SwoolePort|SwooleServer $server, array $events, string $serverName): void
{
foreach ($events as $event => $callback) {
if (! Event::isSwooleEvent($event)) {
continue;
}
if (is_array($callback)) {
[$className, $method] = $callback;
if (array_key_exists($className . $method, $this->onRequestCallbacks)) {
$this->logger->warning(sprintf('%s will be replaced by %s. Each server should have its own onRequest callback. Please check your configs.', $this->onRequestCallbacks[$className . $method], $serverName));
}
$this->onRequestCallbacks[$className . $method] = $serverName;
$class = $this->container->get($className);
if (method_exists($class, 'setServerName')) {
// Override the server name.
$class->setServerName($serverName);
}
if ($class instanceof MiddlewareInitializerInterface) {
$class->initCoreMiddleware($serverName);
}
$callback = [$class, $method];
}
$server->on($event, $callback);
}
}
protected function defaultCallbacks()
{
$hasCallback = class_exists(Bootstrap\StartCallback::class)
&& class_exists(Bootstrap\ManagerStartCallback::class)
&& class_exists(Bootstrap\WorkerStartCallback::class);
if ($hasCallback) {
$callbacks = [
Event::ON_MANAGER_START => [Bootstrap\ManagerStartCallback::class, 'onManagerStart'],
Event::ON_WORKER_START => [Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
Event::ON_WORKER_STOP => [Bootstrap\WorkerStopCallback::class, 'onWorkerStop'],
Event::ON_WORKER_EXIT => [Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
];
if ($this->server->mode === SWOOLE_BASE) {
return $callbacks;
}
return array_merge([
Event::ON_START => [Bootstrap\StartCallback::class, 'onStart'],
], $callbacks);
}
return [
Event::ON_WORKER_START => function (SwooleServer $server, int $workerId) {
printf('Worker %d started.' . PHP_EOL, $workerId);
},
];
}
}
分析sortServicers
方法, 对Server
进行排序,确保 HTTP
服务器和 WebSocket
服务器按特定顺序排列
$servers = $this->sortServers($config->getServers());
这里返回排好序的Port
类对象数组。
之后循环遍历$servers
数组,每个value
是一个Port
对象,走if
foreach ($servers as $server) {
// 获取服务名
$name = $server->getName();
// 获取服务类型(http or websocket)
$type = $server->getType();
// 获取绑定的主机地址
$host = $server->getHost();
// 获取端口
$port = $server->getPort();
// 获取套接字类型
$sockType = $server->getSockType();
// 获取回调信息
$callbacks = $server->getCallbacks();
if (! $this->server instanceof SwooleServer) {
// 分析makeServer方法
// 这里返回的是一个Swoole\Server对象
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
// 获取所有回调事件,进行合并
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
// 注册回调事件
$this->registerSwooleEvents($this->server, $callbacks, $name);
// 设置运行参数
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
// 将服务以键值对的形式添加到ServerManager中
ServerManager::add($name, [$type, current($this->server->ports)]);
// 如果类存在,则触发BeforeMainServerStart事件
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
} else {
/** @var bool|\Swoole\Server\Port $slaveServer */
$slaveServer = $this->server->addlistener($host, $port, $sockType);
if (! $slaveServer) {
throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");
}
$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
$this->registerSwooleEvents($slaveServer, $callbacks, $name);
ServerManager::add($name, [$type, $slaveServer]);
}
// 如果设置了ON_BEFORE_START回调,则运行该回调
if (isset($callbacks[Event::ON_BEFORE_START])) {
[$class, $method] = $callbacks[Event::ON_BEFORE_START];
if ($this->container->has($class)) {
$this->container->get($class)->{$method}();
}
}
if (class_exists(BeforeServerStart::class)) {
// 触发BeforeServerStart事件
$this->eventDispatcher->dispatch(new BeforeServerStart($name));
}
}
namespace Hyperf\Server;
class Port
{
protected string $name = 'http';
protected int $type = ServerInterface::SERVER_HTTP;
protected string $host = '0.0.0.0';
protected int $port = 9501;
protected int $sockType = 0;
protected array $callbacks = [];
protected array $settings = [];
protected ?Option $options = null;
public static function build(array $config): static
{
$config = self::filter($config);
$port = new static();
isset($config['name']) && $port->setName($config['name']);
isset($config['type']) && $port->setType($config['type']);
isset($config['host']) && $port->setHost($config['host']);
isset($config['port']) && $port->setPort($config['port']);
isset($config['sock_type']) && $port->setSockType($config['sock_type']);
isset($config['callbacks']) && $port->setCallbacks($config['callbacks']);
isset($config['settings']) && $port->setSettings($config['settings']);
isset($config['options']) && $port->setOptions(Option::make($config['options']));
return $port;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getType(): int
{
return $this->type;
}
public function setType(int $type): static
{
$this->type = $type;
return $this;
}
public function getHost(): string
{
return $this->host;
}
public function setHost(string $host): static
{
$this->host = $host;
return $this;
}
public function getPort(): int
{
return $this->port;
}
public function setPort(int $port): static
{
$this->port = $port;
return $this;
}
public function getSockType(): int
{
return $this->sockType;
}
public function setSockType(int $sockType): static
{
$this->sockType = $sockType;
return $this;
}
public function getCallbacks(): array
{
return $this->callbacks;
}
public function setCallbacks(array $callbacks): static
{
$this->callbacks = $callbacks;
return $this;
}
public function getSettings(): array
{
return $this->settings;
}
public function setSettings(array $settings): static
{
$this->settings = $settings;
return $this;
}
public function getOptions(): ?Option
{
return $this->options;
}
public function setOptions(Option $options): static
{
$this->options = $options;
return $this;
}
private static function filter(array $config): array
{
if ((int) $config['type'] === ServerInterface::SERVER_BASE) {
$default = [
'open_http2_protocol' => false,
'open_http_protocol' => false,
];
$config['settings'] = array_merge($default, $config['settings'] ?? []);
}
return $config;
}
}
return [
'mode' => SWOOLE_PROCESS,
'servers' => [
[
'name' => 'http',
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 9501,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
],
'options' => [
// Whether to enable request lifecycle event
'enable_request_lifecycle' => false,
],
],
],
'settings' => [
Constant::OPTION_ENABLE_COROUTINE => true,
Constant::OPTION_WORKER_NUM => swoole_cpu_num(),
Constant::OPTION_PID_FILE => BASE_PATH . '/runtime/hyperf.pid',
Constant::OPTION_OPEN_TCP_NODELAY => true,
Constant::OPTION_MAX_COROUTINE => 100000,
Constant::OPTION_OPEN_HTTP2_PROTOCOL => true,
Constant::OPTION_MAX_REQUEST => 100000,
Constant::OPTION_SOCKET_BUFFER_SIZE => 2 * 1024 * 1024,
Constant::OPTION_BUFFER_OUTPUT_SIZE => 2 * 1024 * 1024,
// 将 public 替换为上传目录
'document_root' => BASE_PATH . '/public',
'enable_static_handler' => true,
],
'callbacks' => [
Event::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
Event::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
Event::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
],
];
initServers
分析可知,makeServer
方法的作用就是根据$type
参数,默认返回对应的Server
类型。 根据配置文件中的type
参数可知,返回的是一个Swoole\Http\Server
对象实例。
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
use Swoole\Http\Server as SwooleHttpServer;
use Swoole\Server as SwooleServer;
use Swoole\WebSocket\Server as SwooleWebSocketServer;
protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType): SwooleServer
{
switch ($type) {
// HTTP类型 (默认)
case ServerInterface::SERVER_HTTP:
return new SwooleHttpServer($host, $port, $mode, $sockType);
// WebSocket类型 (默认)
case ServerInterface::SERVER_WEBSOCKET:
return new SwooleWebSocketServer($host, $port, $mode, $sockType);
// 基础服务TCP类型
case ServerInterface::SERVER_BASE:
return new SwooleServer($host, $port, $mode, $sockType);
}
throw new RuntimeException('Server type is invalid.');
}
接下来将回调合并, 合并顺序: 默认回调 -> 配置回调 -> 传递的回调, server
数组中的配置最大,可以覆盖Server
、默认回调的配置。
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
protected function defaultCallbacks()
{
$hasCallback = class_exists(Bootstrap\StartCallback::class)
&& class_exists(Bootstrap\ManagerStartCallback::class)
&& class_exists(Bootstrap\WorkerStartCallback::class);
if ($hasCallback) {
$callbacks = [
Event::ON_MANAGER_START => [Bootstrap\ManagerStartCallback::class, 'onManagerStart'],
Event::ON_WORKER_START => [Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
Event::ON_WORKER_STOP => [Bootstrap\WorkerStopCallback::class, 'onWorkerStop'],
Event::ON_WORKER_EXIT => [Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
];
if ($this->server->mode === SWOOLE_BASE) {
return $callbacks;
}
return array_merge([
Event::ON_START => [Bootstrap\StartCallback::class, 'onStart'],
], $callbacks);
}
return [
Event::ON_WORKER_START => function (SwooleServer $server, int $workerId) {
printf('Worker %d started.' . PHP_EOL, $workerId);
},
];
}
根据服务名注册回调,
默认回调列表
Array
(
[start] => Array
(
[0] => Hyperf\Framework\Bootstrap\StartCallback
[1] => onStart
)
[managerStart] => Array
(
[0] => Hyperf\Framework\Bootstrap\ManagerStartCallback
[1] => onManagerStart
)
[workerStart] => Array
(
[0] => Hyperf\Framework\Bootstrap\WorkerStartCallback
[1] => onWorkerStart
)
[workerStop] => Array
(
[0] => Hyperf\Framework\Bootstrap\WorkerStopCallback
[1] => onWorkerStop
)
[workerExit] => Array
(
[0] => Hyperf\Framework\Bootstrap\WorkerExitCallback
[1] => onWorkerExit
)
[pipeMessage] => Array
(
[0] => Hyperf\Framework\Bootstrap\PipeMessageCallback
[1] => onPipeMessage
)
[request] => Array
(
[0] => Hyperf\HttpServer\Server
[1] => onRequest
)
)
$this->registerSwooleEvents($this->server, $callbacks, $name);
protected function registerSwooleEvents(SwoolePort|SwooleServer $server, array $events, string $serverName): void
{
foreach ($events as $event => $callback) {
if (! Event::isSwooleEvent($event)) {
continue;
}
if (is_array($callback)) {
// 获取事件对应class和回调方法
[$className, $method] = $callback;
if (array_key_exists($className . $method, $this->onRequestCallbacks)) {
$this->logger->warning(sprintf('%s will be replaced by %s. Each server should have its own onRequest callback. Please check your configs.', $this->onRequestCallbacks[$className . $method], $serverName));
}
// 记录回调
$this->onRequestCallbacks[$className . $method] = $serverName;
// 解析回调类
$class = $this->container->get($className);
// 设置服务名
if (method_exists($class, 'setServerName')) {
// Override the server name.
$class->setServerName($serverName);
}
// 如果类实现MiddlewareInitializerInterface接口,则调用初始化中间件方法
if ($class instanceof MiddlewareInitializerInterface) {
$class->initCoreMiddleware($serverName);
}
$callback = [$class, $method];
}
// 注册事件回调函数
$server->on($event, $callback);
}
}
提示
当http
请求过来时,会触发request
事件,从注册的事件列表中可知,会调用Hyperf\HttpServer\Server
的onRequest
方法。
参考
初始化中间件操作,请参考附录5
接下来设置运行时的一些参数,列表如下,
运行参数
Array
(
[enable_coroutine] => 1
[worker_num] => 4
[pid_file] => /opt/www/runtime/hyperf.pid
[open_tcp_nodelay] => 1
[max_coroutine] => 100000
[open_http2_protocol] => 1
[max_request] => 100000
[socket_buffer_size] => 2097152
[buffer_output_size] => 2097152
[document_root] => /opt/www/public
[enable_static_handler] => 1
)
同样的,合并setting
,server
配置会替换 settings
下的某些配置。
setting
的具体配置可参考swoole官方文档
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
最后,添加服务信息到ServerManager
,触发BeforeMainServerStart
事件
ServerManager::add($name, [$type, current($this->server->ports)]);
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
当存在多个server
时,后续server
启动,会通过else
启动,同样的,设置运行参数,注册事件回调,添加信息到ServerManager
if (! $this->server instanceof SwooleServer) {
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
$this->registerSwooleEvents($this->server, $callbacks, $name);
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
ServerManager::add($name, [$type, current($this->server->ports)]);
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
} else {
/** @var bool|SwoolePort $slaveServer */
// 添加监听端口
$slaveServer = $this->server->addlistener($host, $port, $sockType);
if (! $slaveServer) {
throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");
}
// 获取服务配置
$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
// 注册回调事件
$this->registerSwooleEvents($slaveServer, $callbacks, $name);
ServerManager::add($name, [$type, $slaveServer]);
}
initServers
方法执行完成,返回自身
public function init(ServerConfig $config): ServerInterface
{
$this->initServers($config);
return $this;
}
startServer
至此,Hyperf\Server\Server
的init
方法走完,返回自身实例。 Hyperf\Server\ServerFactory
工厂类configure
方法完成。 回到StartServer
类的execute
方法。
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->checkEnvironment($output);
$serverFactory = $this->container->get(ServerFactory::class)
->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
->setLogger($this->container->get(StdoutLoggerInterface::class));
$serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
if (! $serverConfig) {
throw new InvalidArgumentException('At least one server should be defined.');
}
$serverFactory->configure($serverConfig);
Coroutine::set(['hook_flags' => swoole_hook_flags()]);
$serverFactory->start();
return 0;
}
提示
该方法的$serverFactory->configure($serverConfig);
这一行执行完成,返回Hyperf\Server\Server
对象,然后调用start
方法,启动服务。
start方法
终于可以启动服务了,
文件位置:/vendor/hyperf/server/src/ServerFactory.php
public function start(): void
{
$this->getServer()->start();
}
$this->getServer()
前面已经生成,这里直接返回Hyperf\Server\Server
实例。
文件位置:/vendor/hyperf/server/src/Server.php
public function start(): void
{
$this->server->start();
}
提示
其中$this->server
对应的是Swoole\Server
实例。调用start
方法,实际就是启动Swoole
的服务。 此时server
启动,等待请求。