深入 Hyperf:HTTP 服务启动时发生了什么?

这篇具有很好参考价值的文章主要介绍了深入 Hyperf:HTTP 服务启动时发生了什么?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

当我们创建 Hyperf 项目之后,只需要在终端执行 php bin/hyperf.php start 启动命令,等上几秒钟,就可以看到终端输出的 Worker 进程已启动,HTTP 服务监听在 9501 端口的日志信息。

[INFO] Worker#3 started.  
[INFO] Worker#1 started.  
[INFO] Worker#2 started.  
[INFO] Worker#0 started.  
[INFO] HTTP Server listening at 0.0.0.0:9501  

打开浏览器访问 http://127.0.0.1:9501,不出意外的话,页面会显示 Hello Hyperf,说明 HTTP 服务已经在工作了。那么这是怎么做到的呢?当我们执行启动命令后,Hyperf 是如何让 HTTP 服务启动的?

所以今天这篇文章我会从启动命令开始,给你介绍下 HTTP 服务是如何完成初始化并启动的。通过阅读这篇文章,你可以了解到以下内容:

  • Hyperf 启动时会做哪些初始化操作?
  • HTTP 服务启动时会做哪些初始化操作?
  • HTTP 服务初始化时有哪些关键配置项?

接下来,我们就从 Hyperf 的入口文件开始,了解启动 HTTP 服务的实现思路。

文章会持续修订,转载请注明来源地址:https://her-cat.com/posts/2023/05/15/what-happens-when-hyperf-http-server-starts/

bin/hyperf.php 文件:Hyperf 的入口

在启动命令中,除了 PHP 可执行文件以外,有两个是我们要关注的重点:

  • bin/hyperf.php:Hyperf 的入口文件
  • start:启动命令的参数

先来看一下 bin/hyperf.php 文件,我将该文件的执行逻辑分成了四个阶段。

初始化项目配置信息

在这个阶段中,主要是通过调用一些 PHP 内置函数,完成 PHP 相关的配置初始化,比如运行内存大小限制、错误级别、时区等等。

我们需要注意下在这一阶段定义的两个常量: BASE_PATH 和 SWOOLE_HOOK_FLAGS。

  • BASE_PATH:保存的是 Hyperf 项目所在目录的完整路径,Hyperf 中很多操作都是基于该常量来定位目录和文件的路径。
  • SWOOLE_HOOK_FLAGS:Swoole 采用 Hook 原生 PHP 函数的方式实现协程客户端,该常量保存的是 Hook 的函数的范围,比如套接字、文件、curl 等等。SWOOLE_HOOK_ALL 表示 Hook 所有函数。

我们经常会在 Swoole 相关的资料文档中看到「一键协程化」技术,实际上指的就是在启用协程时传入 SWOOLE_HOOK_ALL 配置项,通过 Hook 所有函数,让项目中会发生 IO 阻塞的代码变成可以协程调度的异步 IO,即一键协程化。

初始化类加载器

在 Hyperf 中,我们可以使用注解减少一些繁琐的配置,还可以基于注解实现很多强大的功能。比如注解注入、AOP 面向切面编程、路由定义、权限控制等等。这些功能能够正常运行,其实都离不开类加载器在初始化过程中的准备工作。

在初始化类加载器过程中,主要会进行以下操作:

  • 收集注解使用信息并完成注解收集器的初始化。
  • 生成代理类,为实现 AOP 及 Inject 注解注入功能做准备工作。
  • 生成运行时缓存,提高框架启动速度。

初始化依赖注入容器

在这个阶段, Hyperf 会先读取预先定义好的依赖关系的配置信息,包括 config/autoload/dependencies.php 配置文件中用户自定义的依赖关系,以及各组件中通过 ConfigProvider 机制定义的依赖关系。将这些初始的依赖关系保存到依赖注入容器中,完成对容器的初始化。

初始化命令行应用

我们回过头来看一下启动命令,你会发现,实际上 Hyperf 本身就是一个命令行应用,而启动命令中的 start 不过是命令行应用的参数,也就是要执行的命令的名字。

在 Hyperf 中有很多内置的命令,比如 start、migrate、gen 等等,当然我们也可以根据自己的需求自定义命令。初始化命令行应用的过程,就是将这些 Hyperf 内置的命令、自定义的命令,注册到命令行应用中的过程。

初始化并启动 HTTP 服务

到了这里,Hyperf 的初始化工作就已经结束了,命令行应用就会开始对启动命令中的参数进行解析,通过参数找到在命令行应用中注册的命令并执行。参数 start 对应的命令类是 StartServer,你可以在 hyperf/server 组件中找到它。

在 StartServer 中,完成了对 HTTP 服务的初始化以及启动操作,包含检查运行环境、读取服务配置文件、初始化 HTTP 服务、启动 HTTP 服务四个步骤,下面我们来了解一下这些步骤中分别做了哪些事情。

检查运行环境

我们知道,Hyperf 目前使用 Swoole 作为底层框架,所以在启动的时候,会先检查是否安装了 Swoole 的扩展,然后再检查是否禁用了 Swoole 的函数短名(short function name),如果没有禁用,就会输出提示信息并终止程序的运行。

读取服务配置

在 Hyperf 中,我们使用 config/autoload/server.php 文件来配置服务信息,详细的字段说明可以查看 Hyperf 官方文档。

其中有两个字段需要注意,分别是 server.typeserver.servers.type,很多人不太清楚这两个配置项的作用和区别,下面我们来了解一下。

Swoole 提供了异步和协程两种风格的服务端,下面是两者的不同之处。

  • 协程风格的服务端可以在运行时动态创建、销毁,而异步风格的服务端在启动后就不能再对它进行操作了。
  • 协程风格的服务端对连接的处理是在单独的协程中完成,与客户端的交互是顺序性的,而异步风格的服务端无法保证顺序性。

Hyperf 作为上层框架,当然要支持这两种风格的服务端,同时还要考虑到扩展性,方便后续接入其它风格的服务端。

所以 Hyperf 在设计之初做了一层抽象,定义了一个 ServerInterface 接口,在接口中定义了三个常量,作为服务类型的枚举值。用于在配置文件中通过 server.servers.type 配置项设置服务的类型。同时,还定义了构造函数、初始化、启动三个方法。

interface ServerInterface
{    
	// HTTP 服务  
    public const SERVER_HTTP = 1;    
    // Websocket 服务  
    public const SERVER_WEBSOCKET = 2;    
   // TCP 服务  
    public const SERVER_BASE = 3;    
   // 构造函数  
    public function __construct(ContainerInterface $container, LoggerInterface $logger, EventDispatcherInterface $dispatcher);    
   // 初始化  
    public function init(ServerConfig $config): ServerInterface;    
   // 启动  
    public function start(): void;
}  

Hyperf 不仅实现了基于 Swoole 的两种风格的服务端,还实现了基于 Swow 的服务端。

  • Hyperf\Server\Server:异步风格的服务端,由 Swoole 提供底层支持。
  • Hyperf\Server\CoroutineServer:协程风格的服务端,由 Swoole 提供底层支持。
  • Hyperf\Server\SwowServer:协程风格的服务端,由 Swow 提供底层支持。

我们可以通过 server.type 配置项,来决定使用哪种风格的服务端用于运行各种类型的服务。当然,你也可以通过实现 ServerInterface 接口,自定义其它类型的服务端。

初始化 HTTP 服务

通过上面的内容你可以知道,在运行 Hyperf 的时候,只能使用一种服务端,但是可以运行多个不同类型的服务,比如 HTTP 服务、Websocket 服务等等。为了便于说明,我会使用异步风格服务端给你介绍初始化 HTTP 服务的过程。

初始化 HTTP 服务的操作,是在 ServerFactory::configure 方法中完成的,主要可以分为两个步骤。

  • 第一步,将配置信息解析成 ServerConfig 对象。

在这一步骤中,主要是将配置文件中数组形式的配置信息,解析成 ServerConfig 对象。

class ServerConfig implements Arrayable
{
    public function __construct(protected array $config = [])
    {
	    // 将各种类型的服务解析成 Port 对象
        $servers = [];
        foreach ($config['servers'] as $item) {
            $servers[] = Port::build($item);
        }

		// 将其它类型的配置都保存到对象中
        $this->setType($config['type'] ?? Server::class)
            ->setMode($config['mode'] ?? 0)
            ->setServers($servers)
            ->setProcesses($config['processes'] ?? [])
            ->setSettings($config['settings'] ?? [])
            ->setCallbacks($config['callbacks'] ?? []);
    }
}

当没有设置服务端的类型时,默认使用 Hyperf\Server\Server,即异步类型的服务端。

  • 第二步,调用 Hyperf\Server\Server::init 方法完成对 HTTP 服务的初始化。

在这一步骤中,会调用 ServerFactory::getServer 方法,根据 ServerConfig 对象中的 type 属性实例化出对应的服务端对象,即 Hyperf\Server\Server 对象。在 Hyperf\Server\Server 对象中,定义了一个 server 属性,用于保存 Swoole 异步风格服务器对象。在 Swoole 异步风格的服务端中,有以下三种类型的服务器:

  • Swoole\Server:TCP 服务器,是所有异步风格服务器的基类。
  • Swoole\Http\Server:HTTP 服务器。
  • Swoole\WebSocket\Server:WebSocket 服务器。

在 init 方法中,会根据 server.servers.type 配置项的值(即 ServerInterface 接口中的常量),实例化出相应的服务器对象,并保存到 server 属性中。

这里会有一个问题,在 Hyperf\Server\Server 对象中只有一个 server 属性,但是,在 server.servers 配置项中,我们可以配置多个不同类型的服务,那么是如何支持运行多个服务的呢?

这里就跟 Swoole 的服务器实现有关,Swoole 的异步风格服务器可以通过调用 addListener 方法监听多个端口,每个端口都可以设置不同的协议处理方式。这样就实现了一个服务器对象,同时运行多个不同类型的服务。

下面我们来看一下 init 方法的主要逻辑。

首先,在 init 方法中会先调用 ServerFactory::sortServers 方法,对需要启动的服务按照类型 Websocket、HTTP、TCP 的顺序进行排序。

然后,依次遍历这些服务,完成对每个服务的初始化。循环中包括两个分支:

  • 第一个分支对应了 server 属性未初始化的情况。 此时,会调用 makeServer 方法实例化出相应的服务器对象,然后为服务器对象注册事件回调函数,最后初始化服务器对象的配置信息。
  • 第二个分支对应了 server 属性已初始化的情况。 此时,会调用服务器对象的 addListener 方法,增加一个端口并返回子服务器对象,然后为子服务器对象注册事件回调函数,最后初始化子服务器对象的配置信息。

在 makeServer 方法中,会根据服务类型实例化出相应的服务器对象,下面代码展示了这部分的逻辑,你可以看下。

switch ($type) {  
    case ServerInterface::SERVER_HTTP:  
        return new Swoole\Http\Server($host, $port, $mode, $sockType);  
    case ServerInterface::SERVER_WEBSOCKET:  
        return new Swoole\WebSocket\Server($host, $port, $mode, $sockType);  
    case ServerInterface::SERVER_BASE:  
        return new Swoole\Server($host, $port, $mode, $sockType);  
}

Swoole 提供了很多事件,比如 workerStart 工作进程启动后的事件、request 收到请求后的事件,这些事件在 Hyperf\Server\Event 中都有相应的常量。

在 Hyperf 中,有三种事件回调函数的配置,分别是全局事件、服务事件、默认事件。

  • 全局事件:使用 server.callbacks 配置项设置全局的事件的回调函数。
  • 服务事件:使用 server.servers.callbacks 配置项为每一个服务单独设置事件的回调函数。
  • 默认事件:在 Hyperf\Server\Server 对象的 defaultCallbacks 方法中配置了一些默认的事件的回调函数。

这些配置优先级是:服务事件 > 全局事件 > 默认事件。下面的代码展示了注册事件的回调函数的核心逻辑。

// 按照优先级获取配置的所有事件及其回调函数
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
foreach ($callbacks as $event => $callback) {
	// 非 Swoole 事件,直接跳过
    if (! Event::isSwooleEvent($event)) {
        continue;
    }
    ...
    // 为服务器对象注册该事件的回调函数
    $server->on($event, $callback);
}

启动 HTTP 服务

在启动 HTTP 服务之前,会执行以下代码设置一键协程化 Hook 的函数范围,swoole_hook_flags 函数的返回值就是 SWOOLE_HOOK_FLAGS 常量的值,即 SWOOLE_HOOK_ALL。

Coroutine::set(['hook_flags' => swoole_hook_flags()]);

接着会调用 ServerFactory::start 方法启动服务,在该方法中,直接调用 Hyperf\Server\Server 的 start 方法启动 Swoole 服务器。

当 Swoole 服务器启动后,会执行注册在服务器对象的 Event::ON_WORKER_START 事件的回调函数 WorkerStartCallback::onWorkerStart。

在 onWorkerStart 方法中,输出 Worker#{$workerId} started. 日志信息,并通过事件分发器分发 AfterWorkerStart 事件,在该事件的监听器 AfterWorkerStartListener 中,输出 HTTP Server listening at 0.0.0.0:9501 日志信息。

到这里,HTTP 服务就已经启动了。

总结

在这篇文章中,我们通过 bin/hyperf.php 文件,了解了 Hyperf 在初始化框架时会执行哪些操作。接着,又通过 StartServer 了解了 HTTP 服务在启动过程中的四个步骤。其中,HTTP 服务的初始化是整个启动过程中的关键步骤,你可以配合源码进一步了解 Hyperf 的设计和实现思路。

尽管本文的主题是 HTTP 服务,但实际上,无论是 WebSocket服务、TCP服务还是其他类型的服务,这些服务的启动过程与 HTTP 服务的启动过程大同小异。

因此,掌握 HTTP 服务的启动过程,不仅有助于你了解 HTTP 服务的运行细节,还有助于你了解 Hyperf 以及其它类型服务的运行细节。当你遇到问题时,可以按照启动过程中的步骤逐步检查,从而帮助你更快地解决问题。文章来源地址https://www.toymoban.com/news/detail-450395.html

到了这里,关于深入 Hyperf:HTTP 服务启动时发生了什么?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • CSDN文章点赞、收藏、评论后到底发生了什么?简要分析HTTP交互机制

    CSDN文章点赞、收藏、评论后到底发生了什么?简要分析HTTP交互机制

    作者:Eason_LYC 悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。 一个人的价值,在于他拥有的,而不是他会的。所以可以不学无数,但不能一无所有! 技术领域:WEB安全、网络攻防 关注WEB安全、网络攻防。我的专栏文章知识点全面细致,逻辑清晰、结合实战,让

    2024年02月21日
    浏览(15)
  • 【RabbitMQ】RabbitMQ 服务无法启动。系统出错。发生系统错误 1067。进程意外终止。

    【RabbitMQ】RabbitMQ 服务无法启动。系统出错。发生系统错误 1067。进程意外终止。

    RabbitMQ 服务无法启动。 RabbitMQ和Erlang版本不匹配可能导致RabbitMQ服务无法启动。RabbitMQ需要特定版本的Erlang才能正常运行。 根据RabbitMQ官方文档中的说明,查询并安装RabbitMQ版本对应的Erlang版本。你可以在以下链接中找到相应的Erlang版本:RabbitMQ官方Erlang版本对应表

    2024年02月10日
    浏览(12)
  • 当黑客入侵了服务器后会发生什么

    当黑客入侵了服务器后会发生什么

    作为互联网服务的基础承载,服务器作用重大。但正是这种重要性,让它成为越来越多网络攻击的首选目标。目前,针对服务器的网络攻击层出不穷,从勒索软件、漏洞利用再到数据窃取以及加密货币挖矿等等,种种网络攻击让服务器随时随地处于危险之中。 网络罪犯会利用

    2024年01月23日
    浏览(14)
  • docker使用Dockerfile制做容器(以hyperf为列,开机启动)

    1、Dockerfile文件 1-1、执行命令生成hyperf:latest容器(文件名是Dockerfile可以省略,如果是其它文件名需要写上docker build –f dockerfile文件路径 –t 镜像名称:版本) 2、start.sh脚本 3、启动,重新启动也会执行脚本,后面加上/data/start.sh(启动执行脚本,重新启动也会执行脚本)

    2024年01月21日
    浏览(10)
  • 【腾讯云 Finops Crane集训营】Finops Crane究竟能为我们带来什么价值和思考?深入探究Crane

    【腾讯云 Finops Crane集训营】Finops Crane究竟能为我们带来什么价值和思考?深入探究Crane

    目录 前言 一、Crane目的是什么? 二、Crane有哪些功能? 1.成本可视化和优化评估  2.推荐框架  3.基于预测的水平弹性器 4.负载感知的调度器 5.拓扑感知的调度器 6.基于 QOS 的混部 三.Crane的整体架构及特性 1.Crane架构 Craned Fadvisor Metric Adapter Crane Agent 2.Crane特性 一键部署 简单易

    2024年02月05日
    浏览(13)
  • MySQL启动服务时发生系统错误 5,拒绝访问且管理员权限无效、net start mysql 服务名无效解决方法

    MySQL启动服务时发生系统错误 5,拒绝访问且管理员权限无效、net start mysql 服务名无效解决方法

    在重启MySQL服务后,报错 启动服务时发生系统错误 5,拒绝访问 网上查询解决办法都是使用管理员权限开启CMD运行 net start mysql 会报错 服务名无效 ,解决办法为修改为 net start mysqlXX ,XX为版本号,如我的8.0就是 net start mysql80 运行后仍然会报错 启动服务时发生系统错误 5,拒

    2024年02月01日
    浏览(13)
  • mac 安装 php 与 hyperf 框架依赖的扩展并启动 gptlink 项目

    mac 安装 php 与 hyperf 框架依赖的扩展并启动 gptlink 项目

    gptlink 项目是一个前后端一体化的 chatgpt 开源项目 gptlink 项目地址:https://github.com/gptlink/gptlink 安装完成后提示如下: 根据如上提示在 ~/.zshrc 文件中添加下面环境变量配置: 添加完成后重启命令行执行 php -v 如下说明 php 安装成功: (它是 php 的包管理工具用来安装项目的依

    2024年02月15日
    浏览(13)
  • 无法从命令行或调试器启动服务,必须首先安装Windows服务....。在“安装”阶段发生异常。 System.Security.SecurityException:未找到源

    无法从命令行或调试器启动服务,必须首先安装Windows服务....。在“安装”阶段发生异常。 System.Security.SecurityException:未找到源

    此处一共两个问题,第一个问题完整描述是: 无法从命令行或调试器启动服务,必须首先安装Windows服务(使用installutil.exe),然后用ServerExplorer、Windows服务器管理工具或NET START命令启动它。 第二个问题是: Windows Service服务 出现System.Security.SecurityException: 未找到源,但未能搜索

    2023年04月15日
    浏览(21)
  • 软件测试|深入解析Docker Run命令:创建和启动容器的完全指南

    软件测试|深入解析Docker Run命令:创建和启动容器的完全指南

    简介 Docker是一种流行的容器化平台,用于构建、分发和运行应用程序。其中一个最基本且重要的Docker命令是 docker run ,用于创建和启动容器。本文将详细解析 docker run 命令的用途、参数和示例,帮助您全面掌握创建和启动容器的过程。 docker run 在Docker中,容器是运行应用程序

    2024年02月09日
    浏览(37)
  • Hyperf 运行各种网络服务

    简单地运行起普通的 HTTP 服务之后,今天我们再来学习一下如何使用 Hyperf 运行 TCP/UDP 以及 WebSocket 服务。 之前我们通过普通的 Swoole 都已经搭建起过这些服务,其实和 HTTP 服务都差不多,只是修改一些参数或者监听的事件而已。在框架中,实现这些服务也是类似的,而且会更

    2024年02月03日
    浏览(8)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包