什么是协程

协程是用代码实现的一个非标准线程,可看作是轻量级的线程

协程与线程的区别

调度方式

  • 协程由用户代码来调度和管理
  • 线程由操作系统分配、管理、调度

运行

  • 进程内的多个协程,同一时间只能有一个在运行,其他协程是暂停状态
  • 多线程可以并发运行

运行机制

  • 在HTTP协程服务上,一个请求 = 一个协程。一个 worker 可以同时处理多个请求(协程),无需IO等待,可以同时发起/维护大量的请求(协程)。
  • 在每个worker内有一个协程调度器,在遇到 I/O 时底层自动的进行隐式协程切换,进程内以单线程的方式运行协程 —— 同时刻只有一个协程在运行
代码也可以显式的控制协程切换

安装运行框架

安装PHP扩展


按照文档来干,首先得有个装好的环境,pecl phpize啥的弄好,然后直接 pecl install 扩展 开搞。会有提示缺少其他扩展的,或者缺少系统软件的,按提示继续安装即可。

比如,在安装 redis 扩展时 pecl install redis 提示缺少 igbinary,继续 pecl install igbinary 即可

或者又报错,缺少 libzstd ,搜索一下 apt search libzstd,发现这个包是 libzstd-dev,继续 apt install libzstd-dev即可

创建项目

composer create-project hyperf/hyperf-skeleton newProject

之后会是一些功能模块的选择,按需添加,完成后会自动进行安装

如果加载模块的时候报错终止脚本了,不用慌,先把已经加载的模块安装上

composer install

如果之后发现缺少哪个模块,看文档再单独加载即可,到时候报错再处理也可以

运行

装好扩展,配置好php.ini,开始运行。注意每次变更配置,代码需要重新运行

php bin/hyperf.php start

简单配置

更改server 的端口,多 serverworker数量以及COROUTINE数量

    'servers' => [
        [
            ...
            'name' => 'http',        # 服务名字 多服务要区分
            'port' => 80,            # 监听端口
        ],
        [
            ...
            'name' => 'http_2',        # 第二服务器
            'port' => 8080,            # 可以设置不一样的端口
            'callbacks' => [           # 文档中有解释,使用一个新的Server类
                Event::ON_REQUEST => ['secondServer', 'onRequest'],
            ],
        ]
    ],
    'settings' => [
        # 是否开启协程
        Constant::OPTION_ENABLE_COROUTINE => true,
        # worker数量 (本地开发我选了两个,默认等于cpu核心数)
        Constant::OPTION_WORKER_NUM => 2,
        # 一个worker 最大的协程数量
        Constant::OPTION_MAX_COROUTINE => 100000,
        # https支持
        Constant::OPTION_OPEN_HTTP2_PROTOCOL => true,
        # 一个worker最大请求数
        Constant::OPTION_MAX_REQUEST => 100000,
    ],

config/autoload/server.php

namehttp_2server 需要的依赖关系

文档中有解释,使用一个新的Server类
return [
    'secondServer'  =>  Hyperf\HttpServer\Server::class
];

config/autoload/dependencies.php

使用热更新

不使用开发有点难受。生产环境不装。时间建议用1秒

安装

composer require hyperf/watcher --dev

配置

# 发布配置
php bin/hyperf.php vendor:publish hyperf/watcher
# 配置文件:config/autoload/watcher.php 可选几种驱动。更换详见文档

启动

跟项目直接启动方式不同

php bin/hyperf.php server:watch

用协程开发

注意点

  • 不写阻塞代码,数据库、请求、文件等。有协程客户端的用客户端,没有的用\Swoole\Runtime::enableCoroutine()。不行的再想办法
  • 协程内不直接使用全局变量。一个 worker 内的所有协程共享全局变量,从 请求中拿全局变量。
  • 底层中 协程以单线程模式运行,但一个主协程可以创建多个子协程

    • 协程间本质上是相互独立的没有父子关系
    • 一次创建了5个协程直接运行,就是该协程有5个子协程在同时运行
    • 拿到一个接口的数据后,再依次拿下一个,就是只有一个子协程在同时运行
    • 可以限制代码块同时运行的协程数量Concurrent
    • 子协程阻塞的是主协程,不会阻塞其他主协程

创建协程

co(callable $callable);
go(callable $callable);
Hyperf\Utils\Coroutine::create(callable $callable);

判断是否在协程内

Hyperf\Utils\Coroutine::inCoroutine(): bool

获取当前协程ID

Hyperf\Utils\Coroutine::id(): int        # 不在协程返回 -1

Channel

用于协程间通信,生产者和消费者模式

Defer

协程结束后执行的代码,先进后出

WaitGroup

主协程阻塞等待子协程执行完成

Parallel

基于WaitGroup,用于方便的创建需等待的子协程。可控制最大同时运行协程数量

Concurrent

基于Channel,用于控制代码块内 最大同时运行协程数量

协程上下文

获取/设置 仅限当前协程的上下文。在协程结束后会自动释放

Hyperf\Utils\Context::set('key','value');        # 设置值到当前协程
Hyperf\Utils\Context::get('key','default');        # 取出指定值
Hyperf\Utils\Context::has('key');                # 是否存在 返回bool
Hyperf\Utils\Context::override('key','value');   # 重写,取出key,更改后写回

HTTP 路由

标准路由

Router::get('/index', 'IndexController::hello');
Router::post('/index', 'IndexController@hello');
Router::put('/index', [IndexController::class,'index']);
# 支持多种方式。没有 ANY可用
Router::addRoute(['PATCH', 'DELETE','HEADER','OPTIONS'], '/index', 'IndexController@index');

config/routes.php

路由组

Router::addGroup('/user/',function (){
    Router::get('index','UserController@index');
});

为路由分配不同的 server

不指定 server 时,默认使用名为 httpserver


Router::addServer('http_2',function (){

    Router::addGroup('/index', function (){
        Router::get('/index',[IndexController::class,'index']);
    });
    Router::get('/index', [IndexController::class,'index']);
});

注解路由

待续

控制器

通过控制器处理HTTP请求,需要绑定路由
与请求相关的数据要储存到协程上下文(Context)内,不可存储在类属性内
请求和响应对象实际上是代理类,数据最终也是通过协程上下文(Context)获取

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;

class UserController
{
    public function index(RequestInterface $request, ResponseInterface $response)
    {
       
    }
}

创建控制器

php bin/hyperf.php gen:controller UserController

请求

使用上基本与laravel一致
Router::get('/index/{id:\d+}', 'controller');

# 依赖注入 请求对象、参数
use Hyperf\HttpServer\Contract\RequestInterface;

public function info(RequestInterface $request, int $id)
{
    #    使用方法获取参数
    $id = $request->route('id', 'default');
    #    获取请求路径
    $uri = $request->path();
    #    获取URL
    $url = $request->url();
    #    判断路径 支持通配
    $request->is('user/*'):bool
    #    带参数URL
    $url = $request->fullUrl();
    #    获取请求方法
    $url = $request->getMethod();
    #    判断请求方法
    $request->isMethod('post'):bool
    #    获取输入
    $all = $request->all();
    $name = $request->input('name', 'default');
    $name = $request->input('products.0.name'); // 数组、json数组
    #    从查询字符串获取输入 ??没理解
    $name = $request->query('name', 'default');
    #    判断值
    $request->has('name'):bool
    $request->has(['name', 'email']):bool
    #    获取cookie
    $request->getCookieParams();    // 所有cookie
    $name = $request->cookie('name', 'default');
}

文件

$file = $request->file('photo');
$request->hasFile('photo'):bool
//    验证文件是否有效
$request->file('photo')->isValid();
//    文件临时路径
$path = $request->file('photo')->getPath();    
//    原始文件后缀名
$extension = $request->file('photo')->getExtension();
//    保存上传文件    
$file->moveTo('/savePath/newFileName.Extension');    
//    是否移动成功
$file->isMoved():bool

响应

public function json(ResponseInterface $response)
{
        
    return $response->json(array);
    return $response->xml(array);
    return $response->raw(string);
    return $response->redirect('/anotherUrl',302,'http or https');
    return $response->withCookie($cookie)->withContent('Hello Hyperf.');
    return $response->download(file, saveFileName);
}

数据库

创建模型

php bin/hyperf.php gen:model table_name

中间件

在标准路由的 options 中配置

OPTIONS = ['middleware' => [ JwtMiddleware::class ]];

Router::get(PATH,FALLBACK,OPTIONS);
Router::addGroup(PREFIX,FALLBACK,OPTIONS);
Router::addRoute(FUNCTION,PATH,FALLBACK,OPTIONS);

注解

/**
 * 单个
 * @Middleware(FooMiddleware::class)
 */

/**
 * 多个
 * @Middlewares({
 *     @Middleware(FooMiddleware::class),
 *     @Middleware(BarMiddleware::class)
 * })
 */