Skip to content

启动服务

这节分析入口文件最后两行代码,

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();
})();

提示

简单来说,这段代码是从依赖注入容器中获取ApplicationInterface接口对应的实例,然后调用run方法启动服务。

生成Application

从上一篇实例化container中可知,其中ApplicationInterface对应的是Hyperf\Framework\ApplicationFactory类。

警告

具体get取出过程,请参考 附录1

通过分析get方法可知,最终调用ApplicationFactory类的__invoke方法,获得解析后的对象。

php
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命令数组如下所示,

点我查看
php
 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对象。

上面代码做了三件事:

  1. 首先从容器中取出事件调度器,并触发BootApplication事件。
  2. 创建Application应用对象,从注解和配置文件中获取所有命令。

提示

Symfony\Component\Console\Application 类是 Symfony 控制台组件的核心部分,用于创建和管理 CLI 应用程序。它提供了一系列功能,使开发者可以轻松地定义和运行命令行工具

  1. 将命令注册到应用中。

警告

从命令注册到Application的具体实现,请参考附录3

php
foreach ($commands as $command) { 
   $application->add(
       $this->pendingCommand($container->get($command))
   );
}
  1. 返回Application实例
php
return $application;

run启动

启动服务,

文件位置:/bin/hyperf.php

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方法。 先看源码,

php
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方法后,会由HyperfStartServer的命令类接管,执行该类的execute方法。

提示

run方法的具体实现请参考附录4

StartServer类

文件位置:/vendor/hyperf/server/src/Command/StartServer.php

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服务。

总结

  1. 获取ServerFactory实例,设置事件调度器,设置日志
  2. 获取config目录下server.php中的server配置,没有则抛异常
  3. 根据config创建对应的服务
  4. 设置协程的 hook 标志,将所有相关的阻塞操作转换为非阻塞操作
  5. 启动服务

配置服务

查看服务配置实现,调用ServerFactory类的configure方法,

php
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();
    }
}
  1. 首先将配置文件中的server配置数组包装成ServerConfig类,传递给成员变量config
点我查看ServerConfig
php
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',
        ]);
    }
}
php
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;
    }
}
  1. 根据Server配置的服务类型,实例化对应服务,调用服务的init方法

提示

(以默认配置为例,server.php文件中未配置type值,使用默认Hyperf\Server\Server::class)

  1. getServer方法返回Hyperf\Server\Server实例
  2. 调用Hyperf\Server\Server类的init方法

实例化Server

php
// $this->getServer()返回的是一个Hyperf/Server/Server类实例
$this->getServer()->init($this->config);

文件位置:/vendor/hyperf/server/src/Server.php

php
public function init(ServerConfig $config): ServerInterface
{
  $this->initServers($config);

  return $this;
}
php
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));
        }
    }
}
php
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;
}
php
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 服务器按特定顺序排列

php
$servers = $this->sortServers($config->getServers());

这里返回排好序的Port类对象数组。

之后循环遍历$servers数组,每个value是一个Port对象,走if

php
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));
    }
} 
php
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;
    }
}
php
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对象实例。

php
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
php
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、默认回调的配置。

php
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
php
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);
        },
    ];
}

根据服务名注册回调,

默认回调列表
php
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
        )

)
php
$this->registerSwooleEvents($this->server, $callbacks, $name);
php
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\ServeronRequest方法。

参考

初始化中间件操作,请参考附录5

接下来设置运行时的一些参数,列表如下,

运行参数
php
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
)

同样的,合并settingserver配置会替换 settings下的某些配置。

setting的具体配置可参考swoole官方文档

php
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));

最后,添加服务信息到ServerManager,触发BeforeMainServerStart事件

php
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

php
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方法执行完成,返回自身

php
public function init(ServerConfig $config): ServerInterface
{
    $this->initServers($config);

    return $this; 
}
startServer

至此,Hyperf\Server\Serverinit方法走完,返回自身实例。 Hyperf\Server\ServerFactory工厂类configure方法完成。 回到StartServer类的execute方法。

php

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

php

public function start(): void
{
  $this->getServer()->start();
}

$this->getServer()前面已经生成,这里直接返回Hyperf\Server\Server实例。

文件位置:/vendor/hyperf/server/src/Server.php

php
public function start(): void
{
  $this->server->start();
}

提示

其中$this->server对应的是Swoole\Server实例。调用start方法,实际就是启动Swoole的服务。 此时server启动,等待请求。